Featured image of post Pod Pending 排查:不是所有 Pending 都是资源不够

Pod Pending 排查:不是所有 Pending 都是资源不够

记录 Kubernetes Pod Pending 的常见原因:资源 requests、nodeSelector、nodeAffinity、taints、PVC、拓扑分布和 cordon。

Pod Pending 很容易被一句“资源不够”带过去。但实际排查下来,Pending 只是说明 Pod 还没有被成功调度到节点上,原因可能很多。

我现在遇到 Pending,基本不会先猜,先看 Events。

我会把 Pending 当成“调度器没有找到合适节点”的结果,而不是根因。这个视角很重要,因为资源不足只是其中一种,更多时候是自己写的约束互相打架。

第一步:describe Pod

1
2
3
kubectl get pod -n default

kubectl describe pod -n default api-server-xxx

重点看最后的 Events,特别是 FailedScheduling

也可以直接按时间看事件:

1
2
3
4
5
kubectl get events -n default --sort-by=.lastTimestamp

kubectl get events -n default \
  --field-selector involvedObject.kind=Pod,involvedObject.name=api-server-xxx \
  --sort-by=.lastTimestamp

如果看到类似:

1
2
Warning  FailedScheduling  default-scheduler  0/3 nodes are available:
3 Insufficient cpu.

那确实是资源问题。但如果看到的是 affinity、taint、PVC、topology spread,就要换方向查。

我会先把 Events 里的原因按类型拆一下:

Event 关键词优先看什么
Insufficient cpu/memoryrequests、节点 allocatable、是否有过大副本
node affinity/selectornodeSelector、nodeAffinity、节点 label
taintnode taints、Pod tolerations
pod anti-affinity副本数、topologyKey、已有 Pod 分布
unbound immediate PersistentVolumeClaimsPVC、StorageClass、PV、provisioner
topology spreadmaxSkew、topology label、whenUnsatisfiable

CPU 或内存 request 太大

比如这个 Pod 请求了 4 核 CPU:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
apiVersion: v1
kind: Pod
metadata:
  name: big-request
spec:
  containers:
    - name: app
      image: nginx:1.27
      resources:
        requests:
          cpu: "4"
          memory: 4Gi

如果集群里没有任何一个节点能提供这么多可分配资源,它就会 Pending。

1
2
3
4
5
kubectl describe pod big-request

kubectl describe node node-1

kubectl top node

要注意,调度看的是 request,不是当前真实使用量。一个节点 top 看起来还有空闲,不代表它还能接收新的大 request Pod。

nodeSelector 或 nodeAffinity 匹配不到节点

比如 Pod 要求只能跑到 disk=ssd 的节点:

1
2
3
spec:
  nodeSelector:
    disk: ssd

但节点根本没有这个 label:

1
kubectl get nodes -L disk

那就会调度失败。

nodeAffinity 也是一样:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
spec:
  affinity:
    nodeAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
        nodeSelectorTerms:
          - matchExpressions:
              - key: node-pool
                operator: In
                values:
                  - online

如果是 required,匹配不到就不会调度。preferred 只是偏好,不会单独导致 Pending。

节点有 taint,但 Pod 没有 toleration

节点可以通过 taint 排斥 Pod。

1
kubectl taint nodes node-1 dedicated=gpu:NoSchedule

没有对应 toleration 的 Pod 不会被调度到这个节点。

查看节点 taint:

1
kubectl describe node node-1 | grep -i taints

给 Pod 加 toleration:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
apiVersion: v1
kind: Pod
metadata:
  name: tolerate-gpu-node
spec:
  tolerations:
    - key: "dedicated"
      operator: "Equal"
      value: "gpu"
      effect: "NoSchedule"
  containers:
    - name: app
      image: nginx:1.27

还有一类自动 taint 也很常见,比如节点出现 DiskPressureMemoryPressureNotReady 时,控制面会给节点加对应 taint。

PVC 没有绑定

有些 Pod Pending 不是因为 CPU,也不是因为 affinity,而是 PVC 还没有 Bound。

1
2
kubectl get pvc -n default
kubectl describe pvc data -n default

如果 PVC 一直 Pending,常见方向是:

  • 没有默认 StorageClass。
  • PVC 指定的 storageClassName 不存在。
  • 请求容量太大,没有匹配的 PV。
  • 动态 provisioner 异常。
  • local PV 带 nodeAffinity,限制了可调度节点。

PVC 不 Bound 时,Pod 可能一直等着,看起来像调度问题,但根因在存储。

存储这里还有个细节:有些 StorageClass 使用 WaitForFirstConsumer,PVC 不会立刻 Bound,而是等 Pod 调度时一起考虑拓扑。这个场景下 PVC Pending 不一定是坏事,要结合 StorageClass 的 volumeBindingMode 看。

anti-affinity 或 topologySpreadConstraints 太硬

例如强制同一个应用不能在同一个节点:

1
2
3
4
5
6
7
affinity:
  podAntiAffinity:
    requiredDuringSchedulingIgnoredDuringExecution:
      - labelSelector:
          matchLabels:
            app: api-server
        topologyKey: kubernetes.io/hostname

如果只有 2 个节点,却要 3 个副本,第 3 个副本就没有位置。

topologySpreadConstraints 也类似:

1
2
3
4
5
6
7
topologySpreadConstraints:
  - maxSkew: 1
    topologyKey: topology.kubernetes.io/zone
    whenUnsatisfiable: DoNotSchedule
    labelSelector:
      matchLabels:
        app: api-server

如果只是希望尽量分散,不想因为分布不均导致 Pending,可以考虑把 DoNotSchedule 改成 ScheduleAnyway,或者放宽 maxSkew

一个固定排查顺序

我一般按这个顺序来:

1
2
3
4
5
6
7
kubectl describe pod <pod> -n <ns>
kubectl get events -n <ns> --sort-by=.lastTimestamp
kubectl get nodes -o wide
kubectl get nodes --show-labels
kubectl describe node <node>
kubectl get pvc -n <ns>
kubectl describe pvc <pvc> -n <ns>

如果 Events 里已经明确写了 FailedScheduling 的原因,就优先相信 Events。Pending 不是一个根因,它只是结果。真正要找的是调度器为什么找不到合适节点。

如果排查到最后发现多个原因同时存在,我会先处理“硬约束”。比如先解决 PVC、taint、required nodeAffinity,再看资源和 preferred 规则。软约束通常不是 Pending 的直接原因,硬约束才是。

参考资料

记录工程实践和排障过程。
使用 Hugo 构建
主题 StackJimmy 设计