Kubernetes Pod一直处于Pending状态怎么办?资源调度与事件分析

在 Kubernetes 里创建了 Deployment,kubectl get pods 盯着看,状态一直卡在 Pending,几分钟过去也不变成 Running。这种情况比 CrashLoopBackOff 更让人摸不着头脑,因为容器根本没启动,也就没有应用日志可看——问题出在调度阶段,而不是运行阶段。

Pod 的 Pending 本质上意味着「调度器没能为它找到一个合适的节点」。Kubernetes 的调度是两道关:先做过滤(Filter),排除不满足条件的节点;再做打分(Score),在剩下的节点里挑最优的。任何一道关上所有候选节点都被淘汰,Pod 就会一直 Pending。常见原因包括资源不足、节点选择器/亲和性不匹配、污点与容忍度冲突、PVC 未绑定、镜像过大等。

下面按「先看事件、再查资源、后看亲和与存储」的思路,把 Pod Pending 的排查方法讲清楚,配上真实命令和 YAML 片段,让你能快速定位到是哪一类约束把调度卡住了。

一、第一步永远是看 Events

kubectl describe pod 会输出事件列表,几乎一半以上的 Pending 问题,答案就写在 Events 里:

kubectl describe pod  -n <命名空间>

重点看最后几行的 Events 段落,找 Warning 类型。几个高频提示及其含义:

事件提示 含义 根因方向
0/3 nodes are available: 3 Insufficient cpu 节点 CPU 不足 资源请求过大
0/3 nodes available: 3 node(s) had taint 节点有污点且 Pod 未容忍 污点/容忍度
0/3 nodes available: 3 didn't match node selector 标签不匹配 nodeSelector/亲和性
pod has unbound immediate PersistentVolumeClaims PVC 未绑定 存储问题
no nodes available to schedule pods 没有可调度节点 节点 NotReady
FailedScheduling 综合调度失败 看后面的具体原因

也可以直接看集群事件:

kubectl get events -n <命名空间> --sort-by='.lastTimestamp'

二、资源不足:最常见的一类

调度器判断「够不够」依据的是 Pod 的 requests(不是 limits)。如果一个 Pod 请求 4 核 CPU、8GB 内存,而所有节点剩余可分配资源都不足,就会一直 Pending。

查看节点资源与已分配情况:

kubectl describe node <节点名> | grep -A8 "Allocated resources"

输出示例:

Allocated resources:
  Resource           Requests      Limits
  cpu                3800m (95%)   5000m
  memory             14Gi (87%)    16Gi

如果 Requests 已经逼近 100%,就说明该节点几乎没法再塞入新的 Pod 了。

查看各节点整体使用率(需要 metrics-server):

kubectl top nodes
kubectl top pods -n <命名空间>

如果没部署 metrics-server,可以看节点容量与已分配:

kubectl get nodes -o custom-columns=\
NAME:.metadata.name,\
CPU:.status.capacity.cpu,\
MEM:.status.capacity.memory

解决方向:
- 降低 Pod 的 requests(前提是确实用不了那么多);
- 给集群扩容节点;
- 清理闲置 Pod 释放资源;
- 检查是否 requests 设得虚高(很多团队照抄模板,把 requests 设成 4 核却只用到 0.2 核)。

三、节点选择器、亲和性与污点

nodeSelector

如果 Pod 指定了 nodeSelector,而没有任何节点带这个标签,就会 Pending:

spec:
  nodeSelector:
    disktype: ssd

排查节点标签:

kubectl get nodes --show-labels

给节点补标签:

kubectl label node <节点名> disktype=ssd

节点亲和性(nodeAffinity)

requiredDuringSchedulingIgnoredDuringExecution 是硬约束,不满足就不调度。查看 Pod 的亲和性配置,再对照节点标签是否匹配。用 In、Exists 等操作符时要小心大小写和拼写。

污点与容忍度(Taint/Toleration)

污点让节点「排斥」Pod,只有带对应容忍度的 Pod 才能调度上去。查看节点污点:

kubectl describe node <节点名> | grep -A3 Taints

常见污点如 node-role.kubernetes.io/control-plane:NoSchedule(主节点默认不打业务 Pod)。给 Pod 加容忍度:

spec:
  tolerations:
    - key: "key1"
      operator: "Equal"
      value: "value1"
      effect: "NoSchedule"

需要提醒的是,容忍度只是「允许」调度到有该污点的节点,不等于「一定」会去。如果同时有 nodeAffinity 限定,两者要同时满足。

四、存储问题:PVC 未绑定

如果 Pod 挂载了 PersistentVolumeClaim,而该 PVC 处于 Pending,Pod 就会一直等,事件里会出现 pod has unbound immediate PersistentVolumeClaims。

kubectl get pvc -n <命名空间>
kubectl describe pvc  -n <命名空间>

PVC Pending 的常见原因:
- 没有可用的 PV 与之匹配(静态供给场景);
- StorageClass 不存在或 provisioner 异常(动态供给场景);
- 存储容量不足或访问模式(accessModes)不匹配。

排查 StorageClass 是否正常:

kubectl get storageclass
kubectl describe storageclass <名称>

关于 PV/PVC 的完整配置,后面另有一篇专门讲,这里只强调一点:存储没准备好,Pod 一定 Pending,而且事件里会明确指向 PVC,看到这个提示就直接去查存储,不用在资源上绕圈。

五、节点状态异常与不可调度

有时候不是资源不够,而是节点本身有问题。检查:

kubectl get nodes

如果节点是 NotReady,或带着 SchedulingDisabled,调度器就不会往里放 Pod。kubectl cordon 会把节点标记为不可调度:

# 取消不可调度标记
kubectl uncordon <节点名>

节点 NotReady 常见原因是 kubelet 挂了、网络插件异常、磁盘压力(DiskPressure)、内存压力(MemoryPressure)。查看节点状况:

kubectl describe node <节点名> | grep -A6 Conditions

如果看到 MemoryPressure: True 或 DiskPressure: True,需要先解决节点本身的资源压力。

六、一张表定位 Pending 根因

排查维度 查看命令 典型结论
事件 kubectl describe pod 直接给出失败原因
资源 describe node 的 Allocated requests 超节点余量
标签 get nodes --show-labels nodeSelector 无匹配
污点 describe node 的 Taints 缺少 toleration
存储 get pvc PVC 未 Bound
节点 get nodes 节点 NotReady/封锁
亲和 get pod -o yaml 硬亲和不满足

七、实操:一个完整的排查流程

假设有个 Pod 一直 Pending,按下面顺序走:

# 1. 看 Pod 概况与所在节点
kubectl get pod  -o wide

# 2. 看事件,找 FailedScheduling 的具体理由
kubectl describe pod  | sed -n '/Events/,$p'

# 3. 如果是资源不足,看节点余量
kubectl describe node <节点名> | grep -A8 "Allocated resources"

# 4. 如果是选择器不匹配,看节点标签
kubectl get nodes --show-labels

# 5. 如果涉及存储,看 PVC 状态
kubectl get pvc -A | grep -v Bound

# 6. 看调度器自身是否正常(多调度器或自定义调度器场景)
kubectl get pods -n kube-system | grep scheduler

如果调度器日志需要看:

kubectl logs -n kube-system  --tail=100

经验补充:一个上了 500 个节点的集群,调度决策通常在毫秒到几十毫秒级,如果 Pod 长时间不调度,几乎可以锁定是约束条件冲突,而不是调度器「慢」。

八、写 YAML 时的预防习惯

  1. requests 要基于真实监控数据,不要抄模板虚高;
  2. 尽量少用硬亲和(required),能软(preferred)就软,给调度器留余地;
  3. 给关键业务加 PodDisruptionBudget,避免节点维护时被批量驱逐;
  4. 集群留出 15% 到 20% 的缓冲资源,应对突发扩容和节点故障;
  5. 用 kubectl get pod -o yaml 定期 review 线上 Pod 的资源声明,防止长期漂移。

九、从过滤到打分:调度器到底怎么想

把 Pending 彻底搞懂,最好理解一下调度器的两步逻辑。

第一步是过滤(Filter),也叫预选。调度器遍历所有节点,逐个检查是否满足 Pod 的硬性约束:资源是否够(按 requests 算)、节点选择器与亲和性是否匹配、污点是否被容忍、端口是否冲突、卷所在可用区是否对得上。任何一条不满足,该节点就被淘汰。如果所有节点都被淘汰,Pod 就 Pending,事件里会打印每个节点被淘汰的原因,这就是为什么 describe 里那句「0/5 nodes are available」后面跟着一串理由——每一种理由前面都带着被淘汰的节点数。

第二步是打分(Score),也叫优选。过滤剩下的候选节点会被按多种策略打分,比如资源均衡(倾向于把 Pod 放到资源更空闲的节点)、亲和性权重、拓扑分散(把副本打散到不同节点)等,得分最高的节点胜出。打分段一般不会导致 Pending,因为它是在「有候选」的前提下做选择。所以排查 Pending,重点永远在过滤段。

理解了这一点,排查就有了明确手势:看 0/N nodes are available 里 N 等于节点总数,说明所有节点都过不了过滤;后面跟着的原因往往不止一条,要一条条对照。如果 N 小于节点总数,说明只有部分节点被过滤,剩下的节点可能因为其他原因(比如镜像拉取失败)而卡住,这时要往节点侧看。

还有两个容易被忽略的细节。一是资源计算用的是 requests 而非实际用量,节点上「已分配」的虚拟资源可能早已超过真实负载,但调度器只认 requests,于是新的 Pod 被拒之门外;二是资源碎片,一个节点虽然总量够,但被切得七零八落,剩不下满足最小请求的连续资源,也会导致 Pending。面对碎片,除了扩容,也可以适当调小 Pod 的 requests 粒度来提升装箱效率。

常见问题

Q1:Pod 一直 Pending,但 describe 里没有明显报错,怎么办?
A:先确认是不是根本没有 Ready 的节点(kubectl get nodes),再看是否所有节点都被 cordon 或带污点。极端情况下是调度器组件异常,检查 kube-scheduler Pod。

Q2:节点上 CPU 还有空余,为什么还报 Insufficient cpu?
A:调度看的是 requests,不是实际使用率。节点上已分配 requests 之和可能已经占满,即使实际用量很低。要么调低其他 Pod 的 requests,要么扩容节点。

Q3:加了大内存节点,Pod 还是 Pending?
A:检查是否还有其他约束没满足,比如 nodeSelector、污点、PVC。大节点若不满足这些硬性条件一样调度不上去。

Q4:PVC 显示 Bound 了,Pod 还是 Pending?
A:看 Pod 事件里是否有其他原因,比如访问模式冲突(ReadWriteOnce 被多节点争用)、镜像体积过大导致节点拉取超时,或者节点亲和与 PVC 所在可用区不一致。

Q5:能不能强制把 Pod 调度到某个节点?
A:可以临时用 nodeName 字段直接指定,但这会绕过调度器的资源检查,风险较高,仅适合紧急排障,事后应删除。推荐的做法是给节点打标签配合 nodeSelector。

总结

  1. Pod Pending 一定是调度阶段的问题,第一件事是 kubectl describe pod 看 Events,多数情况答案就在那里。
  2. 资源类报错看的是 requests 而非实际使用率,kubectl describe node 的 Allocated resources 是关键依据。
  3. 标签、污点、亲和性是三个高频「硬约束」,排查时把它们和节点实际属性逐一对照。
  4. 事件里出现 unbound PVC 就直奔存储,PVC 不 Bound,Pod 永远 Pending。
  5. 预防胜于救火:requests 按实测来设、少用硬亲和、集群留足缓冲资源,能大幅减少 Pending 的发生。