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 resources 里 cloud.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>
重点看 STATUS 和 AGE:
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 resources 中 cloud.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 file、ImagePullBackOff、MountVolume failed 或 rpc 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 配额不足、节点池容量不足、网络插件异常继续查。