网络问题导致 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 天前
clklog地域分析中的地名中英文对照问题解决
kubernetes
杰克逊的日记1 天前
如何在不影响业务的情况下对K8S集群升级
云原生·容器·kubernetes
逻极1 天前
Kubernetes 从入门到精通:云原生容器编排
kubernetes·k8s·服务发现·容器编排
zhangfeng11331 天前
国家超算中心K8s 容器服务,新版容器和老版本的一些坑
云原生·容器·kubernetes
开发者联盟league2 天前
使用k8s安装Sonarqube
云原生·容器·kubernetes
运维老郭2 天前
Kubernetes 二进制部署完全指南:从零搭建生产级HA集群
运维·云原生·kubernetes
成为你的宁宁2 天前
【K8S黑盒监控实践:Probe配置、Prometheus验证与Grafana可视化】
kubernetes·grafana·prometheus
成为你的宁宁2 天前
【Prometheus Operator监控K8S Nginx】
nginx·kubernetes·prometheus
宇明一不急2 天前
k8s headless svc
云原生·容器·kubernetes