Pod 一直 Pending:从调度失败到资源碎片的排查路径

举报
yd_282015185 发表于 2026/11/04 08:53:31 2026/11/04
【摘要】 Pending 很少是容量不够,多半是账算错了。资源碎片、污点未容忍、亲和性无解、PVC 卡绑定这四种原因表现完全一样,处理方式却相反,本文给出一句话区分法、九条命令清单和一份决策表。

## 一、Pending 不是一个问题,是六种问题


`kubectl get pod` 看到 `Pending`,很多人第一反应是「节点不够了,加机器」。但在实际排查中,Pending 至少有六个互不相关的原因,加机器只能解决其中两个:


1. **资源不够**:所有节点的可分配资源都小于 Pod 的 requests

2. **资源碎片**:总量够,但没有单个节点塞得下(最容易被误判成「资源不够」)

3. **亲和性冲突**:nodeSelector / nodeAffinity / podAntiAffinity 约束无解

4. **污点未容忍**:节点带 taint,Pod 没配对应的 toleration

5. **存储未就绪**:PVC 处于 Pending,Pod 卡在 VolumeBinding

6. **镜像或配额**:ResourceQuota 拦截,或镜像拉不下来(后者其实是 ImagePullBackOff,但初次创建时也会短暂 Pending)


判断顺序很关键。有一句话能省掉大半时间:**Pending 的 Pod 一定有调度事件,而且事件里写明了失败原因,只是默认没显示出来**。


## 二、第一步永远是 describe,但要看 Events 的最后几行


```

kubectl describe pod <pod> -n <ns>

```


重点看两处:


**Conditions** 里的 `PodScheduled`:


```

Conditions:

  Type Status

  PodScheduled False

```


`PodScheduled=False` 说明问题在调度阶段,后面五类原因都可能。如果 `PodScheduled=True` 而 Pod 仍然 Pending,那问题在拉镜像或挂载卷,不在调度。


**Events** 的最后几行,会直接给出原因,常见几种:


```

0/12 nodes are available: 12 Insufficient cpu.

0/12 nodes are available: 12 node(s) had taint {node-role.kubernetes.io/master: }, that the pod didn't tolerate.

0/12 nodes are available: 3 node(s) didn't match Pod's node affinity/selector, 9 Insufficient memory.

0/12 nodes are available: 12 node(s) didn't find available persistent volumes to bind.

0/12 nodes are available: 12 node(s) exceed max volume count.

```


这行文字已经把原因说清楚了。如果 Events 是空的,说明调度器还没处理到这个 Pod,等 10 秒再看,或者检查 `kube-scheduler` 是否健康。


再看调度器自己记录的原因:


```

kubectl get events -n <ns> --field-selector involvedObject.name=<pod> --sort-by=.lastTimestamp

```


`--sort-by` 很重要,因为 `describe` 只显示最后几条,早期的事件可能被挤掉。


## 三、资源不够 vs 资源碎片:最容易误判的一对


这两者的表现完全一样(`Insufficient cpu` / `Insufficient memory`),但处理方式相反。区分方法是对比两个数字。


**看节点可分配量:**


```

kubectl describe node <node> | grep -A 8 "Allocated resources"

```


输出:


```

Allocated resources:

  Resource Requests Limits

  -------- -------- ------

  cpu 7800m (97%) 12000m

  memory 28Gi (87%) 40Gi

```


**再看 Pod 自己的 requests:**


```

kubectl get pod <pod> -o jsonpath='{.spec.containers[*].resources.requests}'

```


如果节点显示 CPU 97%、剩 240m,而 Pod 要 500m,那就是**碎片**——节点有剩余,但不够这一个 Pod。如果所有节点剩余都小于 500m,才是真的**总量不够**。


处理方式完全不同:


- **碎片**:调整 Pod 的 requests(如果实际用量远小于 requests,说明申请值虚高)、打散已有负载、或者用 `descheduler` 做重平衡。加机器也能解决,但成本最高。

- **总量不够**:扩容节点,或者降低整体 requests。


**一个高频问题:requests 虚高。** 很多团队建 Pod 时直接抄 `requests: cpu 2`,实际用量 200m。结果集群 CPU 利用率只有 20%,却整天报 `Insufficient cpu`。查实际用量:


```

kubectl top pod -n <ns> --containers

```


`requests` 应该贴近 P95 实际用量,而不是「怕不够就往上写」。这也是为什么生产上应该配 VPA(垂直自动伸缩)或至少定期 review。


## 四、污点与容忍:节点明明空着却调度不上去


现象:节点 CPU 5%、内存 10%,Pod 就是上不去。


```

kubectl describe node <node> | grep -i taint

```


输出:


```

Taints: node-role.kubernetes.io/control-plane:NoSchedule

```


节点上打了污点,Pod 没有对应的 toleration。三种处理:


1. 给 Pod 加 toleration(适合确实要跑在专用节点上的场景)

2. 去掉节点污点(仅适合自建集群,托管集群的 master 污点不要动)

3. 用 nodeSelector 避开这些节点(治标)


新集群最容易踩的一类:**云厂商的托管节点池默认带污点**,比如 GPU 节点带 `nvidia.com/gpu=present:NoSchedule`,普通 Pod 上不去,而 GPU 业务 Pod 因为忘了加 toleration 也上不去,两边都卡着。


## 五、亲和性:约束写错时最难查


三种约束的作用域不同,容易混:


| 约束 | 作用对象 | 典型误用 |

|---|---|---|

| `nodeSelector` | 节点标签,精确匹配 | 标签键写错,永远不匹配 |

| `nodeAffinity` | 节点标签,支持 In/NotIn/Exists | `requiredDuringScheduling` 写太死 |

| `podAffinity` / `podAntiAffinity` | 其他 Pod 的拓扑分布 | `topologyKey` 写错导致无解 |


排查手段是直接查节点标签是否真的存在:


```

kubectl get nodes --show-labels | grep <你的标签键>

```


很多人以为 `kubernetes.io/hostname` 之类的标签一定有,或者以为云厂商的 `node.kubernetes.io/instance-type` 是 `ecs.c6.xlarge` 而实际是别的值。**标签值必须逐字核对**。


另一个高频坑是 `podAntiAffinity` 配 `requiredDuringSchedulingIgnoredDuringExecution` + `topologyKey: kubernetes.io/hostname`,同时副本数大于节点数。这种配置在节点数不足时必然有 Pod Pending,而且事件里只会说 `node(s) didn't match pod anti-affinity rules`,不会告诉你是「副本数超过了节点数」。这种情况下应该改用 `preferredDuringScheduling`,或者先把副本数压到节点数以内。


## 六、PVC 未绑定:Pod 卡在 VolumeBinding


事件里出现这句就该查存储:


```

pod has unbound immediate PersistentVolumeClaims

```


或者:


```

node(s) didn't find available persistent volumes to bind

```


三个方向:


**1. StorageClass 是否存在、是否可动态供给**


```

kubectl get sc

kubectl get pvc -n <ns>

```


PVC 的 `STATUS` 如果是 `Pending`,说明没有 PV 能匹配。看它的 Events:


```

kubectl describe pvc <pvc> -n <ns>

```


**2. `accessModes` 不匹配**


要 `ReadWriteMany` 但 StorageClass 只支持 `ReadWriteOnce`,就会永远 Pending。云厂商的块存储通常只支持 RWO,要 RWX 得用文件存储(NFS/CephFS 类)。


**3. 节点可用区约束**


云上 PV 是绑定可用区的。如果 Pod 被 nodeAffinity 限定在 A 区,而 PV 在 B 区,就会出现「PV 有、节点有,但绑不上」。事件里会写 `node(s) had volume node affinity conflict`,这一句很容易被忽略掉,因为它看起来像节点亲和性问题,实际是存储的拓扑约束。


## 七、批量排查:一次看清全部 Pending


单个 Pod 看 describe 还行,一次几十个就得用聚合手段。


**按原因分组:**


```bash

kubectl get pods -A --field-selector=status.phase=Pending -o wide

```


**直接读调度器事件,按消息归类:**


```bash

kubectl get events -A \

  --field-selector reason=FailedScheduling \

  --sort-by=.lastTimestamp \

  -o jsonpath='{range .items[*]}{.message}{"\n"}{end}' \

  | sed 's/[0-9]\+ node(s)/N node(s)/' \

  | sort | uniq -c | sort -rn

```


这条命令的价值在于:把「12 nodes are available: 12 Insufficient cpu」这类消息归一化后统计,一眼就能看出是资源问题占多数,还是亲和性问题占多数。比逐个 describe 快一个量级。


**看整体资源缺口:**


```bash

kubectl describe nodes | grep -E "^Name:|cpu |memory " | head -40

```


或者用 `kubectl-top` 之类的工具看集群维度的 requests 总和与 allocatable 的比值。这个比值超过 85% 时,碎片导致的 Pending 会开始频繁出现。


## 八、requests 与 limits 设错的连锁反应


调度只看 `requests`,不看 `limits`,也不看实际用量。这个设计导致一个常见矛盾:


- `requests` 设小 → 调度容易通过 → 但运行时被限流(CPU throttling)或 OOMKilled

- `requests` 设大 → 运行稳定 → 但集群「看起来很满」,实际利用率很低


正确的做法是分开设:CPU 的 requests 按 P95 用量(可压缩资源,超了只是变慢),memory 的 requests 按接近 limits(不可压缩资源,超了就被杀)。


```

resources:

  requests:

    cpu: 200m

    memory: 1Gi

  limits:

    cpu: 1000m

    memory: 1Gi

```


CPU 的 limit 给到 requests 的 3~5 倍是合理的(应对突发),memory 的 limit 等于 requests 最稳(保证不被 OOM 杀)。


这样配置的好处很直接:调度按 200m 算,一台 4 核节点能放 20 个 Pod;运行时它们可以各自突发到 1 核。集群利用率上去了,Pending 也少了。


## 九、一份可直接抄的排查清单


```bash

# 1. 是不是调度阶段

kubectl describe pod <pod> -n <ns> | grep -A 3 "Conditions:"


# 2. 直接看失败原因

kubectl get events -n <ns> --field-selector involvedObject.name=<pod> --sort-by=.lastTimestamp


# 3. 节点可分配余量

kubectl describe node <node> | grep -A 8 "Allocated resources"


# 4. Pod 要多少

kubectl get pod <pod> -o jsonpath='{.spec.containers[*].resources.requests}'


# 5. 节点污点

kubectl describe node <node> | grep -i taint


# 6. 节点标签(核对亲和性)

kubectl get nodes --show-labels


# 7. PVC 状态

kubectl get pvc -n <ns>


# 8. 全部 Pending 按原因聚合

kubectl get events -A --field-selector reason=FailedScheduling \

  -o jsonpath='{range .items[*]}{.message}{"\n"}{end}' | sort | uniq -c | sort -rn


# 9. 实际用量(判断 requests 是否虚高)

kubectl top pod -n <ns> --containers

```


第 2 步和第 8 步是最省时间的两个:一个看单个,一个看全局。


## 十、决策表


| 事件消息 | 判断 | 处理 |

|---|---|---|

| `Insufficient cpu/memory`,节点余量 < 需求 | 碎片 | 降 requests、重平衡,或扩节点 |

| `Insufficient cpu/memory`,所有节点余量都很小 | 总量不够 | 扩容 |

| `had taint ... that the pod didn't tolerate` | 污点 | 加 toleration 或去污点 |

| `didn't match Pod's node affinity/selector` | 标签不匹配 | 逐字核对节点标签 |

| `didn't match pod anti-affinity rules` | 反亲和无解 | 改 preferred,或减副本 |

| `unbound immediate PersistentVolumeClaims` | PVC 未绑定 | 查 SC / accessModes |

| `didn't find available persistent volumes` | 无可用 PV | 查 SC 是否动态供给 |

| `had volume node affinity conflict` | PV 与节点跨可用区 | 让 PV 与节点同区 |

| `exceed max volume count` | 节点挂盘数超限 | 换节点或减少 PVC |

| `PodScheduled=True` 但仍 Pending | 不在调度阶段 | 查镜像拉取与卷挂载 |

| 长期 pending 且事件无变化 | 调度器异常 | 查 `kube-scheduler` 日志与健康 |


## 收尾


Pending 的排查有个很朴素的判断顺序:**先确认是不是调度问题(看 PodScheduled),再看事件里的那句话,然后对照上面那张表**。


真正花时间的不是定位,而是定位之后的取舍:requests 该不该降、反亲和该不该改、是加节点还是加密度。这些决定没有标准答案,但只要 requests 贴近实际用量、CPU 与 memory 分开策略,绝大多数 Pending 根本不会出现。


一句话总结:**Pending 很少是「容量不够」,多半是「账算错了」。**


【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。