一、现象:Pod 卡在 Pending 不动了
K8s Pod Pending 是集群里最高频的调度类故障:你写好 Deployment 一发布,kubectl get pods 里那个 Pod 状态永远是 Pending,READY 是 0/1,迟迟起不来。它既不是镜像拉不到(那是 ImagePullBackOff),也不是进程崩溃(那是 CrashLoopBackOff),而是调度器压根没给它找到合适的节点。本文用一条标准排查链路,把资源不足、污点、亲和性、PVC 未绑定等根因一次性定位清楚。
二、调度器到底在干什么
Pod 创建后先进入调度队列,kube-scheduler 对每个 Pod 做两件事:过滤(Filter)掉不满足硬性条件的节点,打分(Score)在剩下的节点里挑最优选。只要没有节点能通过过滤,Pod 就会一直 Pending,并在 Events 里留下 “0/3 nodes are available” 之类的提示。
2.1 调度流程三步走
- 监听未调度 Pod,加入优先级队列;
- Filter 阶段:资源、污点、亲和性、端口、PVC 等硬约束逐一排除;
- Score 阶段:LeastAllocated、镜像 locality、拓扑散列等选出目标节点,绑定(Bind)后 Pod 才进入 Running。
2.2 调度器内置了哪些插件
理解插件能帮你预判 Pending 原因。Filter 阶段常用插件有 NodeUnschedulable(节点被封锁)、PodFitsResources(资源请求)、PodToleratesNodeTaints(污点)、NodeAffinity、VolumeRestrictions(PVC 拓扑)、PodTopologySpread(打散)。Score 阶段常用 LeastAllocated(剩余越多分越高)、ImageLocality(镜像越近分越高)、InterPodAffinity、NodeResourcesFit。只要任一 Filter 全节点失手,Pod 就 Pending,错误信息里通常会点名是哪个插件拒绝的。
# 查看当前调度器启用的插件配置(kube-scheduler 的 KubeSchedulerConfiguration)
kubectl -n kube-system get configmap scheduler-config -o yaml | grep -A20 plugins
三、最常见 6 类根因(对照表)
| 根因 | 典型 Events 提示 | 解法 |
|---|---|---|
| 节点资源不足 | Insufficient cpu / memory | 降 requests、扩节点、清理碎片 |
| 污点无容忍 | node(s) had untolerated taint | 加 tolerations 或去污点 |
| nodeSelector/亲和性不匹配 | node(s) didn’t match Pod’s node affinity | 修正 label 或亲和规则 |
| PVC 未绑定 | persistentvolumeclaim “x” not found/bound | 先建 StorageClass/PVC |
| 没有 Ready 节点 | 0/N nodes are available: 0 ready | 排查节点 NotReady 原因 |
| GPU/扩展资源缺失 | Insufficient nvidia.com/gpu | 安装 Device Plugin 或换节点 |
四、标准排查流程
记住一句话:先看 Events,再看节点,最后看约束。下面这套命令在任意 K8s 发行版(包括 k3s 轻量级集群)都通用。
4.1 第一步:看 Pod 的 Events
90% 的 Pending 原因直接写在 Events 里,这是第一现场:
kubectl get pods -o wide
kubectl describe pod my-app-7d9c8b5f4-abcde
# 重点看末尾 Events:
# Warning FailedScheduling 0/3 nodes are available:
# 1 node(s) had untolerated taint, 2 Insufficient memory
4.2 第二步:看节点剩余资源
Pod 的 requests 是调度依据(不是 limits)。用下面命令看每个节点已分配与可分配量:
kubectl get nodes
kubectl describe node node-1 | sed -n '/Allocated resources/,/Events/p'
# 关注 Allocated resources 里 cpu / memory 的 Requests 占比
# 若 Requests 已接近 Allocatable,新 Pod 必 Pending
4.3 第三步:看污点与容忍
节点打了污点(Taint),Pod 没有对应容忍(Toleration)就会被过滤掉:
kubectl get nodes -o custom-columns=NAME:.metadata.name,TAINTS:.spec.taints
# 输出示例:
# NAME TAINTS
# master [map[effect:NoSchedule key:node-role.kubernetes.io/master]]
4.4 第四步:PVC 是否绑定
用了持久化卷却没提前建 PVC/StorageClass,Pod 会卡在 WaitForFirstConsumer:
kubectl get pvc
kubectl describe pvc data-pvc
# 状态必须是 Bound;Pending 说明 StorageClass 未就绪或后端存储异常
五、实战案例:资源不足 + 污点双重夹击
某次发布,Deployment 要 2 核 4G,但三台 worker 各剩不到 1 核;同时 master 打了 NoSchedule 污点,Pod 又不能调度上去。Events 同时报 “Insufficient cpu” 和 “had untolerated taint”。修复是给 Pod 加容忍,并把 requests 降到节点能承受的范围:
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app
spec:
replicas: 1
template:
spec:
containers:
- name: app
image: my-registry/app:1.4
resources:
requests:
cpu: "500m" # 从 2 核降到 500m,节点才放得下
memory: "1Gi"
limits:
cpu: "1"
memory: "2Gi"
tolerations: # 容忍 master 的 NoSchedule 污点
- key: "node-role.kubernetes.io/master"
operator: "Exists"
effect: "NoSchedule"
六、亲和性与污点配置速查
想精确控制 Pod 落在哪类节点,用 nodeAffinity;想排斥某类 Pod,用 taint。两者是正交的两套机制:
# nodeSelector:最简单但只能精确匹配
nodeSelector:
disktype: ssd
# nodeAffinity:支持软硬亲和(preferred/required)
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: gpu
operator: In
values: ["true"]
# 给节点打污点
kubectl taint nodes node-2 dedicated=gpu:NoSchedule
# 给 Pod 加容忍
tolerations:
- key: "dedicated"
operator: "Equal"
value: "gpu"
effect: "NoSchedule"
七、生产排查清单
| 检查项 | 命令 | 期望 |
|---|---|---|
| Pod Events | kubectl describe pod | 无 FailedScheduling |
| 节点 Ready | kubectl get nodes | STATUS=Ready |
| 资源余量 | describe node 看 Requests | 有空余 cpu/mem |
| 污点容忍 | get nodes -o custom-columns | Pod 已配 tolerations |
| PVC 状态 | kubectl get pvc | Bound |
| 调度器健康 | kubectl get pods -n kube-system | grep scheduler | Running 且无重启 |
八、如何预防:调度画像与 descheduler
与其每次救火,不如把调度做得可预期。两件事值得做:给所有工作负载显式设置 requests/limits(避免资源黑洞),用 Prometheus 监控节点分配率 在资源见底前告警;对长期碎片化的节点,用 descheduler 的 LowNodeUtilization 策略自动腾挪。配合 K8s 入门里讲的 Pod/Deployment 模型,你会对调度失败有更直觉的判断。
# 实时看全集群事件,按时间排序,Pending 根因往往就在最上面
kubectl get events --sort-by=.lastTimestamp --all-namespaces
如果 Pod 最终是 OOM 而非 Pending,那是另一类问题,可参考 K8s Pod OOMKilled 排查 对症处理。
十、优先级与抢占:你的 Pod 为何被插队
集群开了 Pod PriorityClass 后,高优先级 Pod 在资源紧张时可以抢占(Preemption)低优先级 Pod:调度器会驱逐低优 Pod 腾出节点。但若连低优 Pod 都没得驱逐,或腾出来的资源仍不够,新 Pod 照样 Pending,Events 里会出现 “preemption: 0/3 nodes even with removing lower priority pods”. 排查这类 Pending 要两步:先看 Pod 的 priorityClassName 与 value,确认它到底排第几;再用 kubectl get priorityclass 列出全集群优先级,判断是不是被更高优的常驻负载(如系统组件、监控 Agent)长期占满了节点。经验法则是:业务 Pod 别都设成最高优先级,否则会互相抢占、谁都跑不稳。
kubectl get priorityclass
kubectl get pod my-app-7d9c8b5f4-abcde -o jsonpath='{.spec.priority}{"\n"}'
十一、根治思路:自动扩缩容与调度画像
单次救火只能解一时,长期要把”Pending”消灭在架构层。第一招是集群自动扩缩容:节点资源见底时由 Karpenter 或 Cluster Autoscaler 自动加节点,Pod 自然能落下,配合 k3s 这类轻量集群 也能跑同样的 HPA/CA 逻辑。第二招是调度画像:给每个 Deployment 设准 requests,避免”资源声明虚高”把节点撑死;对批处理类负载用 priorityClassName 区分等级。第三招是容量预警:用 Prometheus 监控节点分配率,在 Requests 超过 70% 时就告警扩容,而不是等 Pod 红了才处理。三招叠加,绝大多数 Pending 会在发生前被化解。
十二、三个最容易踩的误区
排了上百次 Pending,有三个坑最高频。误区一:把 limits 当 requests。很多人只配 limits 不配 requests,以为”最多用 2 核”就够,结果调度器按 0 请求来排,一堆 Pod 被塞上同一节点,运行时互相争抢、频繁驱逐。记住:调度只看 requests,运行才看 limits。误区二:只在 master 上能跑就以为没问题。master 默认带 NoSchedule 污点,临时加了容忍能跑,但生产把业务放 master 是禁忌,正确做法是给 worker 加节点而不是污染控制面。误区三:Pending 就盲目重启 Pod。重启解决不了调度问题,只会让计数+1;先 describe 看 Events,定位是资源、污点还是存储,对症下药才是正道。
九、小结
K8s Pod Pending 的本质是”调度器找不到合格节点”。排查顺序永远是 Events → 节点资源 → 污点/亲和 → PVC。把 requests 设准、容忍配对、存储先就绪,绝大多数 Pending 都能在五分钟内定位。把本文的清单固化进发布前检查,就能让 Pod 从 Pending 直接跑到 Running。




