跳到主要内容

K8S 生产排查台账:Pod 卡在 ContainerCreating (FailedCreatePodSandbox 通用排查指南)

01 前置理论与核心机制​

1. PodSandbox 的生命周期与 CNI 交互机制​

  • 基础架构容器:Pod 在 K8S 中以多容器共享环境形式存在。在拉起业务容器前,容器运行时(CRI,如 containerd)会优先调用底层的 pause 镜像拉起基础架构容器,初始化并持有专用的 Linux Namespace(Network、IPC、UTS 等)。
  • CNI 调用标准流程:
    1. CRI 接收到 Kubelet 的 RunPodSandbox RPC 请求。
    2. CRI 在宿主机创建 Network Namespace。
    3. CRI 按照标准 CNI 规范,解析配置并同步调用网络插件二进制(执行 ADD 动作)。
    4. CNI 插件分配 IP、配置网卡对(veth pair)、设置宿主机与容器内的路由规则。
    5. CNI 将网络配置结果(IP、DNS、路由)以标准 JSON 返回给 CRI,沙箱创建完成。
  • 报错本质:FailedCreatePodSandbox 代表在上述调用链中,系统命名空间创建、CRI 内部流程或 CNI 插件的 ADD 执行失败,导致沙箱未就绪,业务容器因此无法进入拉取镜像与启动流程。

2. Node Ready 状态与网络健康的判定边界​

  • 节点状态上报链路:kubelet 标记节点为 Ready,核心考量是节点的系统基础健康状况(心跳上报、未触碰 DiskPressure/MemoryPressure/PIDPressure 驱逐线,以及 CRI 守护进程处于连接可用状态)。
  • 为什么 CNI 异常时节点通常仍维持 Ready:
    • 在主流 CRI 架构下,节点的网络就绪判定(NetworkReady)主要由底层的 CRI 插件通过检查网络配置目录(如 /etc/cni/net.d/)是否存在合法配置,向 Kubelet 进行状态同步与汇报。
    • 在常规心跳周期中,运行时与 Kubelet 通常不会主动调用 CNI 二进制去发起真实的端到端网络分配探测。
    • 这种解耦设计意味着“静态配置存在”并不等同于“具备动态网络分配能力”;真实的沙箱网络分配只有在 Pod 被实际调度并触发 RunPodSandbox 时才会验证。

02 故障排查与恢复闭环 SOP​

1. 现场止血与范围界定​

# 1. 发现单机批量异常时,优先封锁节点,防止新任务继续落盘扩散影响
kubectl cordon <target-node>

# 2. 查看目标 Pod 事件及报错堆栈
kubectl describe pod <pod-name> -n <namespace>

# 3. 检查异常 Pod 的宿主机分布,判断是单机故障还是集群全局故障
kubectl get pods -A --field-selector spec.nodeName=<target-node>
  • 全局故障:集群多个节点均报 FailedCreatePodSandbox。排查重心:CNI 控制面组件(如 kube-system 下的 DaemonSet/Operator)、IPAM 全局地址池耗尽、APIServer 证书或集群鉴权凭证异常。
  • 单机故障:故障仅集中在特定节点。排查重心:宿主机 CRI 运行环境、本地 CNI 二进制/配置文件、节点 IPBlock 配额、本地系统资源限制。

2. 节点现场排查流程​

单机 FailedCreatePodSandbox
│
├─ 1. 提取精准底层报错 (containerd / kubelet)
├─ 2. 检查 CNI 本机守护容器与路由建立状态
├─ 3. 检查 IPAM 资源池与虚拟网络接口
├─ 4. 核验 CNI 配置加载规则及二进制完整性与权限
└─ 5. 检查宿主机内核限制 (Inode / file-max / inotify)

步骤一:提取精准错误日志​

Kubelet 事件多为顶层概括,深层堆栈需在目标宿主机调取 CRI 运行日志:

# 1. 优先查看 containerd 最近 10 分钟内包含 CNI 报错的上下文(重点关注 error 级别)
journalctl -u containerd --since "10 minutes ago" --no-pager | grep -C 5 -i "cni"

# 2. 若 containerd 无明确报错,查看 kubelet 针对该 Pod 的沙箱调用记录
journalctl -u kubelet --since "10 minutes ago" --no-pager | grep -i "FailedCreatePodSandbox"

步骤二:检查 CNI 节点守护进程​

# 查看本机 CNI 容器状态
crictl ps -a | grep -E 'calico|flannel|cilium'

# 查看 CNI 容器日志
crictl logs <cni-container-id> --tail 100
  • 排查重点:容器是否存在频繁重启(CrashLoopBackOff)、OOMKilled、与集群控制面的通信超时。

步骤三:排查 IPAM(IP 分配状态)​

# Calico 环境下在管理节点检查各节点 IPBlock 分配水线
calicoctl ipam show --show-blocks

# 检查节点虚拟网络设备状态
ip link show
  • 排查重点:
    • 该节点的专属 IPBlock 是否已经无空闲 IP 可供借调;
    • 本地核心虚拟网卡(如 tunl0、vxlan.calico、flannel.1)是否处于 DOWN 状态。

步骤四:检查 CNI 配置与插件文件​

# 检查配置文件目录
ls -lah /etc/cni/net.d/

# 检查二进制目录文件属性
ls -lah /opt/cni/bin/
  • 排查重点:
    • 配置文件选择机制:运行时通常按照配置目录的规则(多为字典序)筛选首个生效配置,但具体行为需结合实际 CRI 配置(如 containerd 的 conf_dir、cni_default_network 等)确认。排查时应重点检查目录中是否存在历史遗留的备份文件或干扰文件(如 .bak、00-old.conf);
    • 二进制损坏与权限:检查核心文件大小(如 Calico 二进制通常在数十 MB,若显示仅几百字节则为下载截断或损坏),并确认具备可执行权限(-rwxr-xr-x)。

步骤五:排查宿主机内核与系统资源限制​

# 检查磁盘与 Inode 水位
df -h
df -i

# 检查系统句柄与监听上限
sysctl fs.file-nr
sysctl fs.inotify.max_user_watches

3. 根因修复与恢复规范​

[高危操作警示]

  • kubectl delete pod --force --grace-period=0:此命令会跳过容器优雅停机流程,强制从 etcd 抹除对象。仅在底层网络确认修复、且原 Pod 确实处于死锁无法自行退出时使用。对包含本地卷挂载的有状态应用(StatefulSet),严禁盲目强制删除,否则可能引发底层卷锁无法释放或脑裂。如果在 IPAM 未彻底释放前强制删 Pod,并在同节点同名拉起,极易导致 CNI 分配的虚拟网卡(veth)残留在宿主机,引发同名网卡冲突报错。
  • calicoctl ipam release:手动回收 IP 必须明确确认该 IP 的宿主容器已在系统层面彻底销毁。若误回收正在使用的 Pod IP,将导致多容器网络地址冲突并引发大面积业务异常。
  • 直接替换 CNI 二进制文件:在生产环境手动替换 /opt/cni/bin/ 属于应急止血动作,操作前必须备份旧文件;事后必须核对该节点 CNI DaemonSet 的注入规范,防止 DaemonSet 重启后重新覆盖引发故障复现。
# 1. 针对二进制文件损坏的应急修复(以 Calico 为例)
cp /opt/cni/bin/calico /opt/cni/bin/calico.bak.$(date +%F)
scp /opt/cni/bin/calico root@<target-node>:/opt/cni/bin/calico
chmod 755 /opt/cni/bin/calico

# 2. 故障恢复与验证规范:
# 优先等待原 Pod 自动重试;若 Pod 处于长期指数退避中,可安全清理无状态实例以触发重新拉起
kubectl delete pod <pod-name> -n <namespace>

# 3. 实时跟踪新 Pod 的调度与网络分配
kubectl get pod <pod-name> -n <namespace> -o wide -w

# 4. 确认节点业务与网络链路完全恢复后,再解除调度隔离
kubectl uncordon <target-node>

03 真实生产报错日志案例库​

案例 1:IPAM 地址池耗尽​

  • containerd 抓取日志:
    failed to run network programming: [failed to allocate for range 0: no IP addresses available in range set: 10.244.3.64-10.244.3.127]
  • 现场分析:节点绑定的 CIDR Block(如 /26)所有可用 IP 均已分发完毕,CNI 无法在本地池内申请新租约。
  • 处置流程:
    1. 检查是否存在僵死容器未注销租约:calicoctl ipam check(若是使用 host-local 的插件如 Flannel,需检查宿主机 /var/lib/cni/networks/<network-name>/ 目录);
    2. 若确认有残留未释放租约,在业务停止状态下执行安全释放:calicoctl ipam release --ip=<IP>(host-local 直接删除对应 IP 文件);
    3. 扩容该节点可借调的 Block 配额或调低节点的 maxPods 上限。

案例 2:CNI 二进制执行权限丢失​

  • containerd 抓取日志:
    failed to setup network for sandbox "...": fork/exec /opt/cni/bin/calico: permission denied
  • 现场分析:运维分发、备份同步或挂载参数异常,导致 /opt/cni/bin/calico 执行权限(x)丢失,或宿主机文件系统被以 noexec 参数挂载。
  • 处置流程:
    1. 修复权限:chmod 755 /opt/cni/bin/calico;
    2. 核查挂载参数:mount | grep /opt,若存在 noexec 需调整为 exec。

案例 3:配置文件干扰导致解析异常​

  • kubelet / containerd 抓取日志:
    error parsing CNI configuration: plugin type "flannel" failed: open /run/flannel/subnet.env: no such file or directory
  • 现场分析:集群已全面切换为 Calico,但运维在 /etc/cni/net.d/ 下备份了旧文件 00-flannel.conf。CRI 在加载配置时优先匹配到了历史配置,导致新 Pod 仍被丢给缺失环境的 Flannel 插件解析。
  • 处置流程:
    1. 清理或移惊非生效的配置文件:mv /etc/cni/net.d/00-flannel.conf /tmp/;
    2. 确保目录下仅留存唯一的有效主配置(如 10-calico.conflist)。

案例 4:CNI 本地守护进程崩溃或 Socket 无响应​

  • containerd 抓取日志:
    rpc error: code = Unavailable desc = connection error: desc = "transport: Error while dialing dial unix /var/run/calico/cni.sock: connect: connection refused"
  • 现场分析:Calico CNI 插件在执行分配时需要与本地运行的 cni.sock 通信,而宿主机上的 calico-node 容器已挂死或发生 OOM。
  • 处置流程:
    1. 检查 CNI 容器状态:crictl ps -a | grep calico-node;
    2. 提取崩溃日志并重启守护进程:crictl restart <calico-node-container-id>。

案例 5:基础架构容器 (Pause) 镜像拉取失败​

  • containerd 抓取日志:
    failed to get sandbox image "k8s.gcr.io/pause:3.6": failed to resolve reference "k8s.gcr.io/pause:3.6": failed to do request
  • 现场分析:CNI 网络分配前,CRI 必须先拉起 Pause 容器初始化命名空间。如果 sandbox_image 默认地址网络不通(如被墙),或私有库凭证过期,沙箱将直接卡死在创建阶段。
  • 处置流程:
    1. 检查 containerd 配置:cat /etc/containerd/config.toml | grep sandbox_image;
    2. 替换为可用镜像源(如 [registry.aliyuncs.com/google_containers/pause:3.6](https://registry.aliyuncs.com/google_containers/pause:3.6));
    3. 重启运行时生效:systemctl restart containerd。

04 生产排障速查表(FailedCreatePodSandbox)​

遇到故障请按序单向排查:

顺序排查层级执行命令正常基线异常处置动作
01止血隔离kubectl cordon <node>节点进入 SchedulingDisabled优先防止新 Pod 继续调度至故障机
02深层日志journalctl -u containerd -n 100 --no-pager | grep -i cni无持续输出的 error 堆栈根据捕获的具体日志对号入座(见案例库)
03守护容器crictl ps -a | grep -E 'calico|flannel|cilium'状态维持 Running,无反复重启检查 OOMKilled,执行 crictl restart
04IP 资源calicoctl ipam show --show-blocks对应节点 IP 占用低于 100%释放孤儿租约 / 扩充 CIDR Block 借调
05配置目录ls /etc/cni/net.d/仅存在唯一的当前网络插件有效配置文件移除 .bak、历史废弃配置等干扰文件
06二进制ls -lah /opt/cni/bin/<plugin>权限为 755,文件体积通常数十 MB重新同步完整文件并加权:chmod +x
07系统限制df -i && sysctl fs.inotify.max_user_watchesInode 水位正常,句柄未耗尽清理磁盘残余文件,调整内核参数上限
08恢复验证修复根因后等待或重建 Pod,观察 IP 分配新拉起 Pod 成功获得 IP 并进入 Running确认网络与业务正常后,最后执行 kubectl uncordon

05 面试应答参考话术​

面试官问:“生产中遇到 Pod 卡在 ContainerCreating,且报错是 FailedCreatePodSandbox,你应该怎么排查与应对?”

面试者可以这样回答:

如果生产环境遇到 Pod 一直卡在 ContainerCreating,同时 Events 里面出现 FailedCreatePodSandbox,我一般不会先去看业务容器,而是先确认是不是 Pod Sandbox 创建失败了。

我会先执行 kubectl describe pod 看 Events,确认具体报错,然后看这个 Pod 是不是集中落在同一个 Node 上。如果发现同一个 Node 上有多个 Pod 都出现类似问题,我会先把这个 Node cordon 掉,避免继续往这个节点调度新的 Pod。

接下来我会直接登录这个 Node 看 containerd 和 kubelet 的日志。因为 FailedCreatePodSandbox 往往只是 kubelet 暴露出来的表层错误,真正的原因通常能在 containerd 或 CNI 的报错里看到。

如果日志指向 CNI,我会继续检查这个节点上的 CNI 组件,比如 calico-node、flannel 有没有重启、OOM 或异常退出。然后再看 IPAM 有没有耗尽,同时检查 /etc/cni/net.d/ 的配置是不是正常,/opt/cni/bin/ 下的 CNI 二进制有没有缺失、损坏或者没有执行权限。

比如如果日志明确提示 IP 地址分配失败,我就重点检查 IPAM;如果提示 permission denied,我就检查 CNI 二进制的执行权限;如果提示配置解析失败,我就重点检查 /etc/cni/net.d/ 里面实际生效的配置。如果遇到并非网络层报错,我也会去排查 CRI 配置里的 Pause 镜像是否拉取超时。

找到具体原因以后,我会先修复根因,而不是直接反复删 Pod。修复完成后先观察 kubelet 是否能够自动重试。如果 Pod 长时间还是卡住,再根据业务情况删除并让它重新创建,同时确认新的 Pod 能正常拿到 IP 并进入 Running。

最后我会确认这个 Node 上的网络和 Pod 都恢复正常,再执行 kubectl uncordon 恢复调度。

如果是生产环境,我还会继续做一次复盘,比如增加 FailedCreatePodSandbox 的告警、IPAM 使用率告警,以及检查 CNI 二进制和配置的统一分发机制,避免同类问题再次发生。


面试官追问:“你遇到这种问题,通常重点看哪些地方?”

面试者可以这样回答:

我一般就看四个方向:CNI 组件、IPAM、CNI 配置和 CNI 二进制。

先看日志确定方向,再针对具体错误去查。比如 IP 不够就查 IPAM,权限问题就查二进制,配置解析失败就查 /etc/cni/net.d/,如果 CNI 组件本身异常,就先处理 calico-node 或其他 CNI DaemonSet。

所以我的思路不是看到 FailedCreatePodSandbox 就直接删 Pod,而是先判断是节点问题、CNI 问题、IPAM 问题还是配置/二进制问题,找到根因之后再恢复业务。


06 面试高频追问与应答预案​

追问 1:既然 CNI 异常,为什么 Node 依然能维持 Ready 状态?​

  • 回答要点:
    • Kubelet 对 Node Ready 的判断基于心跳与资源压力条件(磁盘、内存、PID)。
    • 针对网络状态,主流 CRI 运行时通常仅基于网络配置目录的存在性进行基础健康上报。
    • 运行时与 Kubelet 均不会在常规心跳中主动调用 CNI 二进制发起端到端的沙箱网络预分配探测,因此无法提前感知底层二进制损坏或动态 IP 耗尽等运行时异常。

追问 2:如果定位到错误是 IP 耗尽,应该怎么处理?​

  • 回答要点:
    • 应急手段:使用 calicoctl ipam check 查找是否存在由于 Pod 异常退出但 IP 租约未释放的泄漏资源,通过 calicoctl ipam release 手动回收。
    • 架构优化:如果单节点 Pod 密度过大,需调整节点 IPPool 分配的 Block 大小(如从 /26 扩容),或在调度层通过 maxPods 参数限制单节点承载上限。

追问 3:在离线环境下,如何快速验证 CNI 插件是否能正常工作?​

  • 回答要点:
    • 使用官方提供的 cnitool 工具,手动模拟 CRI 调用 CNI 插件的 ADD 和 DEL 行为,脱离 Kubernetes 控制平面直接验证底层网络配置是否畅通。
    • 执行命令示例:
      # 1. 声明 CNI 环境变量
      export CNI_PATH=/opt/cni/bin
      export NETCONFPATH=/etc/cni/net.d

      # 2. 创建临时测试 Network Namespace
      ip netns add test-cni-ns

      # 3. 调用 CNI 执行 ADD 操作(以加载 calico 为例)
      cnitool add calico /var/run/netns/test-cni-ns

      # 4. 验证分配结果并清理测试环境
      ip netns exec test-cni-ns ip addr
      cnitool del calico /var/run/netns/test-cni-ns
      ip netns del test-cni-ns

07 一键检查脚本(check_cni_health.sh)​

该脚本用于在单机出现网络沙箱异常时,快速收集宿主机运行状态。脚本仅执行读取与状态比对操作,不修改系统配置、不重启进程。

#!/usr/bin/env bash
# ==============================================================================
# 脚本名称: check_cni_health.sh
# 功能描述: 节点出现 FailedCreatePodSandbox 时的基础状态排查
# 使用方式: 在目标节点以 root 权限执行(用于读取日志及内核参数)
# ==============================================================================

set -o pipefail

# 终端输出样式
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[0;33m'
BLUE='\033[0;34m'
PLAIN='\033[0m'

echo -e "${BLUE}================================================================${PLAIN}"
echo -e "${BLUE} 节点 CNI 环境状态检查 ${PLAIN}"
echo -e "${BLUE}================================================================${PLAIN}"

# 1. CRI / Containerd 日志排查
echo -e "\n${YELLOW}[1/5] 查看运行时日志 (最近 10 分钟)...${PLAIN}"
if command -v journalctl &>/dev/null; then
ERR_LOGS=$(journalctl -u containerd --since "10 minutes ago" --no-pager 2>/dev/null | grep -iE 'cni.*(error|failed|deadline)' | tail -n 3)
if [ -n "$ERR_LOGS" ]; then
echo -e "${RED}[WARN] containerd 日志中存在 CNI 相关报错:${PLAIN}"
echo "$ERR_LOGS"
else
echo -e "${GREEN}[OK] containerd 最近 10 分钟未检索到明显 CNI 错误。${PLAIN}"
fi
else
echo -e "${YELLOW}[INFO] 未检测到 journalctl,请手动检查运行时日志。${PLAIN}"
fi

# 2. CNI 配置目录检查 (/etc/cni/net.d/)
echo -e "\n${YELLOW}[2/5] 检查 CNI 配置目录 (/etc/cni/net.d/)...${PLAIN}"
CNI_NET_DIR="/etc/cni/net.d"
if [ -d "$CNI_NET_DIR" ]; then
CONF_COUNT=$(ls -1 "$CNI_NET_DIR" 2>/dev/null | wc -l)
if [ "$CONF_COUNT" -eq 0 ]; then
echo -e "${RED}[WARN] $CNI_NET_DIR 目录为空。${PLAIN}"
else
echo -e "${GREEN}[OK] 目录存在配置文件(共 ${CONF_COUNT} 个)。${PLAIN}"
BAK_FILES=$(ls -1 "$CNI_NET_DIR" 2>/dev/null | grep -E '\.(bak|old|orig|backup)$' || true)
if [ -n "$BAK_FILES" ]; then
echo -e "${RED}[WARN] 存在备份或临时配置文件,建议确认加载顺序:${PLAIN}"
echo "$BAK_FILES"
fi
fi
else
echo -e "${RED}[WARN] 目录 $CNI_NET_DIR 不存在。${PLAIN}"
fi

# 3. CNI 二进制文件检查 (/opt/cni/bin/)
echo -e "\n${YELLOW}[3/5] 检查 CNI 二进制文件 (/opt/cni/bin/)...${PLAIN}"
CNI_BIN_DIR="/opt/cni/bin"
if [ -d "$CNI_BIN_DIR" ]; then
COMMON_PLUGINS=("calico" "flannel" "bridge" "host-local" "loopback" "portmap")
FOUND_COUNT=0
for plugin in "${COMMON_PLUGINS[@]}"; do
P_PATH="$CNI_BIN_DIR/$plugin"
if [ -f "$P_PATH" ]; then
FOUND_COUNT=$((FOUND_COUNT + 1))
if [ ! -x "$P_PATH" ]; then
echo -e "${RED}[WARN] 插件 $plugin 缺少执行权限 (+x)。${PLAIN}"
fi
FILE_SIZE=$(wc -c < "$P_PATH" 2>/dev/null || echo 0)
if [ "$FILE_SIZE" -lt 1024 ]; then
echo -e "${RED}[WARN] 插件 $plugin 文件体积偏小 (${FILE_SIZE} 字节),需确认完整性。${PLAIN}"
fi
fi
done
if [ "$FOUND_COUNT" -gt 0 ]; then
echo -e "${GREEN}[OK] 检测到 ${FOUND_COUNT} 个常用 CNI 插件文件。${PLAIN}"
else
echo -e "${YELLOW}[INFO] 未匹配到常见开源插件,若为云厂商专用插件请手动核对。${PLAIN}"
fi
else
echo -e "${RED}[WARN] 目录 $CNI_BIN_DIR 不存在。${PLAIN}"
fi

# 4. 本地 CNI 容器状态
echo -e "\n${YELLOW}[4/5] 检查本地 CNI 容器运行状态...${PLAIN}"
if command -v crictl &>/dev/null; then
CNI_PODS=$(crictl ps 2>/dev/null | grep -iE 'calico-node|flannel|cilium|weave' || true)
if [ -n "$CNI_PODS" ]; then
echo -e "${GREEN}[OK] 检测到正在运行的 CNI 容器:${PLAIN}"
echo "$CNI_PODS"
else
echo -e "${YELLOW}[INFO] 未检索到常见 CNI 容器,可能作为宿主机进程运行或名称不包含关键字。${PLAIN}"
fi
else
echo -e "${YELLOW}[INFO] 未安装或未配置 crictl,跳过容器状态检测。${PLAIN}"
fi

# 5. 宿主机内核与系统资源
echo -e "\n${YELLOW}[5/5] 检查宿主机关键资源指标...${PLAIN}"
INODE_USAGE=$(df -i / 2>/dev/null | awk '+$5>0 {print $5}' | sed 's/%//' | head -n 1)
if [ -n "$INODE_USAGE" ] && [ "$INODE_USAGE" -gt 90 ]; then
echo -e "${RED}[WARN] 根分区 Inode 使用率偏高: ${INODE_USAGE}%。${PLAIN}"
else
echo -e "${GREEN}[OK] 根分区 Inode 使用率正常 (${INODE_USAGE}%)。${PLAIN}"
fi

MAX_WATCHES=$(sysctl -n fs.inotify.max_user_watches 2>/dev/null || echo 0)
if [ "$MAX_WATCHES" -lt 65536 ]; then
echo -e "${YELLOW}[WARN] 当前 inotify 监听上限偏低 (${MAX_WATCHES}),建议结合负载评估是否调整。${PLAIN}"
else
echo -e "${GREEN}[OK] 内核 inotify 上限正常 (${MAX_WATCHES})。${PLAIN}"
fi

echo -e "\n${BLUE}================================================================${PLAIN}"
echo -e "${BLUE}检查完成。如存在标记为 WARN 的项目,请参考排查台账对应章节处理。${PLAIN}"
echo -e "${BLUE}================================================================${PLAIN}"

完整内容已隐藏

请联系作者或在线获取阅读密码。