网络问题导致 Pod Pending

ContainerCreating 虚占云厂商 Pod IP 配额,导致 Pod Pending

目录

  • [1. 背景](#1. 背景 "#1-%E8%83%8C%E6%99%AF")
  • [2. 现象](#2. 现象 "#2-%E7%8E%B0%E8%B1%A1")
  • [3. 排查过程](#3. 排查过程 "#3-%E6%8E%92%E6%9F%A5%E8%BF%87%E7%A8%8B")
  • [4. 关键证据](#4. 关键证据 "#4-%E5%85%B3%E9%94%AE%E8%AF%81%E6%8D%AE")
  • [5. 根因分析](#5. 根因分析 "#5-%E6%A0%B9%E5%9B%A0%E5%88%86%E6%9E%90")
  • [6. 恢复动作](#6. 恢复动作 "#6-%E6%81%A2%E5%A4%8D%E5%8A%A8%E4%BD%9C")
  • [7. 预防和沉淀](#7. 预防和沉淀 "#7-%E9%A2%84%E9%98%B2%E5%92%8C%E6%B2%89%E6%B7%80")
  • [8. 排查命令汇总](#8. 排查命令汇总 "#8-%E6%8E%92%E6%9F%A5%E5%91%BD%E4%BB%A4%E6%B1%87%E6%80%BB")
  • [9. 适用场景](#9. 适用场景 "#9-%E9%80%82%E7%94%A8%E5%9C%BA%E6%99%AF")

1. 背景

这次问题发生在一套云厂商 Kubernetes 服务集群里。业务 Pod 需要调度到带有指定标签的节点上,Pod 网络依赖云厂商网络插件分配 Pod IP。

现场涉及的组件主要是:

text 复制代码
kube-scheduler
kubelet
cloud-network-agent / CNI
cloud.example.com/pod-ip 扩展资源
业务 Deployment / Pod
候选节点 nodeAffinity

业务侧看到的是 Pod 长时间 Pending,新副本拉不起来。对 GPU 或计算类业务来说,这类问题的影响很直接:任务没有真正进到节点,GPU 算力也不会被业务使用。

表面看是 Insufficient cloud.example.com/pod-ip,像是节点 Pod IP 已经用完了。但这次不能只按"IP 总量不足"处理。材料里更关键的一点是:候选节点上存在长时间 ContainerCreating 的 Pod。这些 Pod 在调度器侧已经占用了 cloud.example.com/pod-ip 的 request,但 CNI 侧还没有真正分配出 Pod IP。

也就是调度器已经记账,CNI 还没发 IP,中间形成了 IP 配额虚占。

2. 现象

业务 Pod 一直处于 Pending

bash 复制代码
kubectl get pods -n <namespace> -l <label-selector> -o wide

describe pod 里能看到调度失败:

text 复制代码
Warning  FailedScheduling  default-scheduler  0/209 nodes are available:
1 node(s) didn't match pod anti-affinity rules,
195 node(s) didn't match Pod's node affinity/selector,
2 Insufficient cloud.example.com/pod-ip,
3 node(s) had untolerated taint {env: system-service},
5 node(s) were unschedulable.
preemption: 0/209 nodes are available:
2 Insufficient cloud.example.com/pod-ip,
206 Preemption is not helpful for scheduling.

这里不要只盯着 0/209 nodes are available。真正和本次问题相关的是:

text 复制代码
2 Insufficient cloud.example.com/pod-ip

继续看候选节点,Allocated resourcescloud.example.com/pod-ip 显示已经满了:

text 复制代码
Resource              Requests  Limits
cloud.example.com/pod-ip   16        16

但节点上同时存在卡在 ContainerCreating 的 Pod:

text 复制代码
NAMESPACE   NAME          READY   STATUS              RESTARTS   AGE
xxx         pod-abc-xxx   0/1     ContainerCreating   0          30m

这个组合比较关键:

text 复制代码
Pending Pod 报 Insufficient cloud.example.com/pod-ip
节点 cloud.example.com/pod-ip Requests = Allocatable
同一节点存在长时间 ContainerCreating Pod

如果只看前两条,很容易判断成 IP 真满了。加上第三条以后,方向就要转成"是否有 Pod 虚占了调度器侧的 IP 配额"。

3. 排查过程

3.1 先确认调度失败原因

先看 Pending Pod 的事件:

bash 复制代码
kubectl describe pod <pending-pod-name> -n <namespace>

这一步不是为了泛泛看 Events,而是要确认调度器拒绝它的具体原因。调度失败可能是 nodeAffinity、taint、资源不足、反亲和,也可能是 IP 扩展资源不足。

这次 Events 里明确出现:

text 复制代码
Insufficient cloud.example.com/pod-ip

说明调度器认为候选节点上的 cloud.example.com/pod-ip 资源已经不够。

3.2 找到真正的候选节点

不能直接全局看所有节点。业务 Pod 通常有 nodeAffinity、nodeSelector 或反亲和限制。先把候选节点范围缩小:

bash 复制代码
kubectl get deploy <deploy-name> -n <namespace> \
  -o jsonpath='{.spec.template.spec.affinity}' | python3 -m json.tool

如果 affinity 里要求某个标签,比如 workload-type=model=true,再按标签查节点:

bash 复制代码
kubectl get nodes -l workload-type=model -o wide

这样做的原因很简单:全集群可能还有很多节点有 IP,但调度器只会在满足业务约束的节点里挑。真正要看的,是这些候选节点的 IP 资源。

3.3 看候选节点的云厂商 Pod IP 记账

对候选节点执行:

bash 复制代码
kubectl describe node <node-name>

重点看 Allocated resources

text 复制代码
Resource              Requests  Limits
cloud.example.com/pod-ip   16        16

如果 Requests < Allocatable,说明调度器侧还没记满,Pending 的原因就不该只归到 IP 满载上。

如果 Requests = Allocatable,说明调度器侧已经认为 IP 配额满了。此时继续查是不是有 Pod 卡在创建阶段,导致 request 已经计入,但真实 IP 还没分配。

3.4 查节点上是否有长时间 ContainerCreating Pod

bash 复制代码
kubectl get pods -A --field-selector spec.nodeName=<node-name>

重点看 STATUSAGE

text 复制代码
NAMESPACE   NAME          READY   STATUS              RESTARTS   AGE
xxx         pod-abc-xxx   0/1     ContainerCreating   0          30m

ContainerCreating 不是一定有问题。刚创建几秒或一两分钟很正常。但如果已经卡了较长时间,尤其超过 5 分钟,还一直没有 Pod IP,就要怀疑它占着调度器资源账本,却没有走完 CNI 分配阶段。

3.5 看 ContainerCreating 卡在哪

继续 describe 这个卡住的 Pod:

bash 复制代码
kubectl describe pod <containercreating-pod-name> -n <namespace>

这里要找的是它为什么没有进入 Running。材料里列出的常见方向有几类:

报错关键字 更可能的问题 处理方向
hostPath ... no such file 节点本地目录不存在 补齐目录后删除 Pod 重建
ImagePullBackOff 镜像拉取失败 检查镜像地址、权限或凭证后重建
MountVolume failed PVC、ConfigMap 或 Secret 挂载失败 先修复存储或配置资源
rpc error containerd 或运行时异常 检查运行时状态,必要时恢复 containerd 后重建 Pod

这一步的目的不是把 Pending Pod 修好,而是找到哪个已经被调度到节点的 Pod 卡在创建流程里,导致 cloud.example.com/pod-ip 被调度器提前占用。

3.6 区分调度器记账和 CNI 真实分配

cloud.example.com/pod-ip 对调度器来说是一个扩展资源 request。它统计的是 Pod spec 里声明的数字:

yaml 复制代码
resources:
  requests:
    cloud.example.com/pod-ip: 1

这个 1 不是一个真实的 10.x.x.x Pod IP。它只是告诉调度器:这个 Pod 要占用 1 个云厂商 Pod IP 资源配额。

真实 IP 是后面 kubelet 调用 CNI 插件时才分配的。时序大概是:

text 复制代码
Pod 被调度到节点
  -> scheduler 把 requests.cloud.example.com/pod-ip=1 计入节点已分配资源
  -> kubelet 开始创建 Pod
  -> CNI 分配真实 Pod IP
  -> Pod 网络就绪

如果 Pod 卡在 ContainerCreating,就可能停在中间:

text 复制代码
scheduler 已经记账
CNI 还没真正分配 IP
Pod 长时间没有进入 Running
新的 Pod 因调度器账本已满而 Pending

这个差异是这次问题的关键。

4. 关键证据

4.1 Pending Pod 的调度失败事件

text 复制代码
Warning  FailedScheduling  default-scheduler  0/209 nodes are available:
2 Insufficient cloud.example.com/pod-ip
preemption: 0/209 nodes are available:
2 Insufficient cloud.example.com/pod-ip,
206 Preemption is not helpful for scheduling.

这说明调度器在候选节点上判断 cloud.example.com/pod-ip 不够。

4.2 节点扩展资源显示满载

bash 复制代码
kubectl describe node <node-name>

关键输出:

text 复制代码
Allocated resources:
Resource              Requests  Limits
cloud.example.com/pod-ip   16        16

Requests = Allocatable,所以调度器账本里这个节点已经没有可用云厂商 Pod IP 配额。

4.3 同一节点有长时间 ContainerCreating Pod

bash 复制代码
kubectl get pods -A --field-selector spec.nodeName=<node-name>

关键输出:

text 复制代码
NAMESPACE   NAME          READY   STATUS              RESTARTS   AGE
xxx         pod-abc-xxx   0/1     ContainerCreating   0          30m

这个 Pod 已经被调度到节点,所以它的 requests.cloud.example.com/pod-ip 已经被 scheduler 计入。但它还卡在创建阶段,真实 IP 是否已经分配要继续看 Pod 状态和 CNI 侧情况。

4.4 Pod spec 里的 IP request 只是资源声明

yaml 复制代码
resources:
  requests:
    cloud.example.com/pod-ip: 1

这个字段是调度器统计用的扩展资源,不等于 Pod 已经拿到了真实 IP。

5. 根因分析

直接原因

直接原因是候选节点上的 cloud.example.com/pod-ip 在调度器账本里已经被占满,新的业务 Pod 无法通过调度。

但这个"满"不是简单的真实 IP 全部分配完。现场存在长时间 ContainerCreating 的 Pod,它们已经被 scheduler 计入 cloud.example.com/pod-ip request,却没有完成后续创建流程。

链路可以写成:

text 复制代码
已有 Pod 被调度到节点
  -> requests.cloud.example.com/pod-ip 被 scheduler 计入
  -> Pod 卡在 ContainerCreating
  -> CNI 真实 IP 分配或后续创建流程没有完成
  -> 节点调度器侧 IP 配额显示满载
  -> 新 Pod 调度失败,报 Insufficient cloud.example.com/pod-ip

触发条件

触发条件是节点上有 Pod 长时间停在 ContainerCreating。从材料看,可能原因包括:

text 复制代码
hostPath 目录不存在
镜像拉取失败
PVC / ConfigMap 挂载失败
containerd 或运行时 rpc error

这些问题本身不一定都是 CNI 问题,但都会让 Pod 卡在创建流程里。只要 Pod 已经调度到节点,它的扩展资源 request 就会被 scheduler 计入。

为什么删除或重建可以恢复

删除卡住的 ContainerCreating Pod 后,调度器会重新计算节点上的资源占用。原来被这个 Pod 占住的 cloud.example.com/pod-ip request 会释放。

如果底层卡住原因已经处理掉,比如目录补齐、镜像凭证修复、挂载恢复、containerd 恢复,那么重建后的 Pod 能正常走完 CNI 分配和容器创建流程,新 Pending Pod 也有机会重新调度成功。

但如果只删除 Pod,不处理它卡住的真实原因,后续重建出来的 Pod 仍可能继续卡在 ContainerCreating,问题会反复出现。

6. 恢复动作

临时恢复

优先处理长时间 ContainerCreating 的 Pod。删除前要确认它是否由 Deployment、Job、StatefulSet 等控制器托管,确认删除后可以自动重建:

bash 复制代码
kubectl delete pod <containercreating-pod-name> -n <namespace>

删除后观察 Pending Pod 是否重新调度:

bash 复制代码
kubectl get pods -n <namespace> -w

预期是卡住 Pod 释放 request 后,原本 Pending 的业务 Pod 在 1 到 2 分钟内重新进入调度流程。

如果业务允许,也可以临时缩副本,先消除 Pending:

bash 复制代码
kubectl scale deploy <deploy-name> -n <namespace> --replicas=<n>

这个动作只适合降载,不是根治。

最终修复

最终要修的是导致 Pod 卡在 ContainerCreating 的原因。

如果是 hostPath 目录不存在,要在目标节点补齐目录,再删除 Pod 重建。

如果是镜像拉取失败,要修镜像地址、镜像仓库权限或凭证。

如果是挂载失败,要先恢复 PVC、ConfigMap、Secret 等依赖资源。

如果是 containerd 或运行时异常,要先确认运行时状态,再决定是否重启 containerd 或迁移业务。

另外,如果候选节点本身 IP 配额长期偏紧,可以扩充候选节点。比如找一台 IP 有余量、taint 也满足要求的节点,再打上业务需要的标签:

bash 复制代码
kubectl label node <target-node> workload-type=model

这个动作要结合业务调度策略评估,不能只为了让 Pod 调度成功就随便扩大节点范围。

7. 预防和沉淀

这类问题要沉淀成两个方向:发现虚占,减少虚占。

可以落地的动作:

text 复制代码
1. 监控 Pending Pod 的 FailedScheduling 事件,单独提取 Insufficient cloud.example.com/pod-ip;
2. 监控节点上长时间 ContainerCreating 的 Pod,比如超过 5 分钟告警;
3. 定期对比候选节点 cloud.example.com/pod-ip Requests 和 Allocatable;
4. 对业务 nodeAffinity 覆盖的节点池做 IP 配额容量检查;
5. 对 hostPath、本地目录、镜像凭证、PVC/ConfigMap 等创建前置条件做发布前校验;
6. 把 ContainerCreating 清理动作 SOP 化,删除前确认控制器归属,避免误删孤立 Pod;
7. 对高并发拉起的任务,提前评估节点 Pod IP、CPU、内存、GPU 是否同时满足。

还要注意一点:Insufficient cloud.example.com/pod-ip 不是天然等于真实 IP 地址全部耗尽。它首先表示调度器看到的扩展资源不足。是否真实耗尽,要结合 CNI 分配状态和节点上卡住的 Pod 一起判断。

8. 排查命令汇总

查看 Pending Pod:

bash 复制代码
kubectl get pods -n <namespace> -l <label-selector> -o wide

作用:确认业务 Pod 是否处于 Pending,以及是否已经绑定节点。

预期结果:问题 Pod 没有节点,状态长时间为 Pending

查看调度失败事件:

bash 复制代码
kubectl describe pod <pending-pod-name> -n <namespace>

作用:确认 Pending 的直接原因。

预期结果:Events 中出现 Insufficient cloud.example.com/pod-ip

查看 Deployment affinity:

bash 复制代码
kubectl get deploy <deploy-name> -n <namespace> \
  -o jsonpath='{.spec.template.spec.affinity}' | python3 -m json.tool

作用:确认业务 Pod 只能调度到哪些节点,避免看错节点池。

预期结果:能看到 nodeAffinity、podAffinity 或 podAntiAffinity 规则。

按标签列出候选节点:

bash 复制代码
kubectl get nodes -l workload-type=model -o wide

作用:列出符合业务调度约束的节点。

预期结果:输出候选节点列表,后续只对这些节点检查 IP 配额。

查看节点资源记账:

bash 复制代码
kubectl describe node <node-name>

作用:查看 Allocated resourcescloud.example.com/pod-ip 的 Requests 和 Allocatable。

预期结果:如果 Requests = Allocatable,说明调度器侧 IP 配额已经满载。

查看指定节点上的 Pod:

bash 复制代码
kubectl get pods -A --field-selector spec.nodeName=<node-name>

作用:检查节点上是否存在长时间 ContainerCreating 的 Pod。

预期结果:如果有 ContainerCreating 且 AGE 较长,继续 describe 这个 Pod。

查看卡住 Pod 的创建失败原因:

bash 复制代码
kubectl describe pod <containercreating-pod-name> -n <namespace>

作用:确认它卡在 hostPath、镜像拉取、挂载还是运行时异常。

预期结果:Events 中能看到具体失败关键字,比如 hostPath ... no such fileImagePullBackOffMountVolume failedrpc error

列出节点上声明了云厂商 Pod IP request 的 Pod:

bash 复制代码
kubectl get pods -A --field-selector spec.nodeName=<node-name> -o json | \
python3 /tmp/check_ip.py

作用:确认哪些 Pod 在 spec 里声明了 cloud.example.com/pod-ip request。

预期结果:输出 namespace、Pod 名称和对应的 cloud.example.com/pod-ip request 值。

删除长时间 ContainerCreating 的 Pod:

bash 复制代码
kubectl delete pod <containercreating-pod-name> -n <namespace>

作用:释放该 Pod 在调度器账本里的资源占用,让 Pending Pod 有机会重新调度。

预期结果:如果 Pod 有控制器托管,会自动重建;原 Pending Pod 可能在短时间内重新调度成功。

观察业务 Pod 状态变化:

bash 复制代码
kubectl get pods -n <namespace> -w

作用:验证恢复动作是否生效。

预期结果:Pending Pod 进入 ContainerCreating,随后进入 Running 或暴露新的创建失败原因。

临时缩副本:

bash 复制代码
kubectl scale deploy <deploy-name> -n <namespace> --replicas=<n>

作用:业务允许时降低同时占用的 IP request 数,先消除 Pending。

预期结果:Deployment 副本数下降,Pending Pod 减少。

扩充候选节点标签:

bash 复制代码
kubectl label node <target-node> workload-type=model

作用:把有 IP 余量且符合条件的节点纳入候选范围。

预期结果:新节点进入业务调度候选集合,但需要先确认 taint、资源和业务隔离要求。

9. 适用场景

这篇排查适用于下面这类场景:

text 复制代码
1. Pod 长时间 Pending;
2. describe pod 出现 Insufficient cloud.example.com/pod-ip;
3. 候选节点 cloud.example.com/pod-ip Requests = Allocatable;
4. 节点上存在长时间 ContainerCreating 的 Pod;
5. 怀疑调度器侧 IP request 和 CNI 真实 IP 分配状态不一致。

不适用于所有 IP 不足问题。

如果候选节点上没有长时间 ContainerCreating Pod,或者 CNI 已经真实分配了全部 Pod IP,那就要按真实 IP 池耗尽、节点 IP 配额不足、节点池容量不足、网络插件异常继续查。

相关推荐
蜀道山老天师1 天前
K8s 数据存储全解析:从 EmptyDir 到 PV/PVC
云原生·容器·kubernetes
创世宇图1 天前
【Python工程化实战】Kubernetes 中 Python 应用的优雅启停与健康检查:零停机滚动更新实战
python·云原生·kubernetes·优雅停机
小二·1 天前
Docker+K8s生产级部署实战:从0到1打造高可用微服务集群
docker·微服务·kubernetes
用户723429355332 天前
训练任务从提交到运行:完整链路和排障地图
aiops
用户723429355332 天前
Kubernetes 基础对象:Pod、Job、Deployment、Service 等
aiops
析数塔4 天前
AI 时代测试开发新范式:从用例验证到 Agent 评测体系
agent·测试·aiops
SRETalk5 天前
如何使用 AI 解答开源项目的问题,其实只需要一句话
aiops·categraf
SRETalk6 天前
夜莺开源监控如何使用 Docker 部署,有哪些注意事项?
aiops·夜莺监控
运维开发故事8 天前
基于 Arthas 的多集群在线诊断系统设计与实现
kubernetes