Skip to content

Pod 卡在 ContainerCreating

01 前置理论与核心机制

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

  • 基础架构容器:Pod 在 K8S 中以多容器共享环境形式存在。在拉起业务容器前,容器运行时(CRI,如 containerd)会优先调用底层的 pause 镜像拉起基础架构容器,初始化并持有专用的 Linux Namespace(Network、IPC、UTS 等)。
  • CNI 调用标准流程:
  • CRI 接收到 Kubelet 的 RunPodSandbox RPC 请求。
  • CRI 在宿主机创建 Network Namespace。
  • CRI 按照标准 CNI 规范,解析配置并同步调用网络插件二进制(执行 ADD 动作)。
  • CNI 插件分配 IP、配置网卡对(veth pair)、设置宿主机与容器内的路由规则。
  • 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

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

# 3. 检查异常 Pod 的宿主机分布,判断是单机故障还是集群全局故障
kubectl get pods -A --field-selector spec.nodeName=
  • 全局故障:集群多个节点均报 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  --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@:/opt/cni/bin/calico
chmod 755 /opt/cni/bin/calico

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

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

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

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 无法在本地池内申请新租约。
  • 处置流程:
  • 检查是否存在僵死容器未注销租约:calicoctl ipam check(若是使用 host-local 的插件如 Flannel,需检查宿主机 /var/lib/cni/networks/<network-name>/ 目录);
  • 若确认有残留未释放租约,在业务停止状态下执行安全释放:calicoctl ipam release --ip=<IP>(host-local 直接删除对应 IP 文件);
  • 扩容该节点可借调的 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 参数挂载。
  • 处置流程:
  • 修复权限:chmod 755 /opt/cni/bin/calico;
  • 核查挂载参数: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 插件解析。
  • 处置流程:
  • 清理或移惊非生效的配置文件:mv /etc/cni/net.d/00-flannel.conf /tmp/;
  • 确保目录下仅留存唯一的有效主配置(如 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。
  • 处置流程:
  • 检查 CNI 容器状态:crictl ps -a | grep calico-node;
  • 提取崩溃日志并重启守护进程:crictl restart 。

案例 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 默认地址网络不通(如被墙),或私有库凭证过期,沙箱将直接卡死在创建阶段。
  • 处置流程:
  • 检查 containerd 配置:cat /etc/containerd/config.toml | grep sandbox_image;
  • 替换为可用镜像源(如 registry.aliyuncs.com/google_containers/pause:3.6);
  • 重启运行时生效: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 restart04IP 资源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 +x07系统限制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 /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}"