在生产或家庭实验室里运行 Kubernetes 时,偶尔会在 Kubelet 日志中看到类似于下面的错误信息: StopPodSandbox from runtime service failed: rpc error: code = Unknown desc = failed to destroy network for sandbox "055b221e44a28ce8d9120f771d5e1ef201f2457ce49c58999a0147104cca2708": failed to get network "cbr0" cached result: decoding version from network config: unexpected end of JSON input 这个错误看起来吓人,但有时并不会立刻导致可见的服务中断,因此许多工程师容易忽略它。然而长期积累会导致日志淤积、自动回收失败或在极端情况下触发容器生命周期管理问题。本文基于真实故障调查经过,逐步讲解如何定位、复现、修复该类错误,并提供预防与监控建议,便于工程实践中快速响应与长期防护。 故障背景与环境说明 案例环境是运行在 Proxmox 上的 Homelab 集群,操作系统采用 Talos Linux,集群由单控制面节点和五个工作节点组成。网络插件使用 Flannel(默认配置),容器运行时使用 containerd。集群中还使用了 SMB 类型的 CSI 驱动挂载 NAS 卷,这可能在故障触发时起到间接作用。
出现频率上,Kubelet 日志中大量重复的相同错误每小时可达数百至上千条,但实际受影响的 sandbox ID 数量较少,说明问题集中在少数失败的沙箱实例上而非全局网络故障。 错误含义快速拆解 错误信息中包含的重要线索有两点:一是无法销毁 sandbox 的网络,二是网络缓存读取出现 JSON 解码失败,提示 "unexpected end of JSON input"。结合 containerd 与 CNI 的实现逻辑,可以推断问题与 CNI 在本地存储的网络结果缓存有关。CNI 插件通常在 /var/lib/cni/results 目录下为每个网络接口写入一个 JSON 文件,后续用于回收与确认清理工作。如果这些缓存文件为空或内容损坏,CNI 在尝试解析时就会报类似的解码错误,从而导致 StopPodSandbox 等 RPC 操作失败。 如何在节点上安全地排查 Talos Linux 默认不提供交互式 shell,因此常规的 SSH 登录不可行。
可用的方法是通过 kubectl debug 针对节点启动一个调试容器,借由节点文件系统挂载来进行离线排查。示例命令如下(示例中使用了包含常用容器工具的镜像): kubectl debug -it --image raesene/alpine-containertools --env CONTAINER_RUNTIME_ENDPOINT=unix:///host/run/containerd/containerd.sock node/talos-192-168-1-15 -- sh 进入到调试容器后,借助 crictl 可以查看 containerd 中的 pod(指 sandbox)与容器状态。常见排查命令包括: crictl pods crictl inspectp --output=json <sandbox-id> crictl ps crictl inspect <container-id> crictl rmp --force <sandbox-id> 在复现错误时,使用 crictl rmp 强制移除 sandbox 常常会得到与 Kubelet 日志相同的错误输出,从而确认问题并非仅出现在 Kubelet 层,而是 containerd 与 CNI 交互过程中出现异常。 定位根因:CNI 结果缓存为空文件 关键发现来自于检查 /var/lib/cni/results 目录的文件状态。该目录下每个网络接口会对应一个以网络名称与 sandbox ID 命名的文件,例如 cbr0-<sandbox>-eth0。正常情况下这些文件包含 JSON 格式的网络配置信息,而在故障节点上可以发现其中某些文件大小为 0 字节,完全为空。
举例输出如下(简化示意): /var/lib/cni/results cbr0-01d576...-eth0 1647 cbr0-055b22...-eth0 0 <-- 空文件 cbr0-22548...-eth0 1608 当 containerd 或 CNI 试图读取该空文件并解析 JSON 时,会触发 "unexpected end of JSON input" 的解析错误,导致无法完成网络销毁步骤,进而报出 StopPodSandbox 或 RemovePodSandbox 相关的 RPC 错误。 修复方法:安全删除损坏的缓存文件 修复相对简单且直接:删除对应 sandbox 的缓存文件即可。删除后再次尝试使用 crictl rmp 或让 Kubelet 进行清理,sandbox 通常能成功移除,相关错误也随之消失。示例命令如下: rm -f /host/var/lib/cni/results/cbr0-055b221e44a28ce8d9120f771d5e1ef201f2457ce49c58999a0147104cca2708-eth0 crictl rmp 055b221e44a28ce8d9120f771d5e1ef201f2457ce49c58999a0147104cca2708 删除后的反馈应显示 sandbox 被成功移除。 为何会产生空文件 对空文件产生的直接原因不容易从单次日志中判定,可能性包括节点突发断电导致文件写入中断、底层存储(例如 NAS)短暂崩溃使得写入失败、或 CNI 在极端并发/异常退出情况下未能完成原子写入。由于该类故障通常发生在底层 I/O 出问题的时间窗口,定位到具体触发点需要结合节点系统日志、存储端日志和 CSI 驱动的运行状态才能进一步判断。
如何防止再次发生 根本性预防方法分为两条:减少底层异常导致的写入中断,以及在应用层增加检测和恢复机制。 在基础设施层面,保证节点电源和存储可用性是最直接的做法。对 NAS 等网络存储启用健壮的监控和自动重试策略,避免短时间内退化导致写入损坏。 在 Kubernetes 层面,可以通过两类手段提升可观测性和自动恢复能力。第一类是监控 containerd 的 RPC 错误以便及时告警;第二类是定期扫描 /var/lib/cni/results 目录并对异常文件(例如 0 字节或非 JSON 内容)进行清理或上报。 启用并采集 containerd 指标 要在监控系统中区分此类问题,containerd 本身的指标非常有价值。
以 Talos Linux 为例,containerd 的 metrics 默认未开启,需要通过机器配置增加 metrics 监听。配置示例为在 containerd 的配置片段中添加 metrics 地址,例如监听 11234 端口。完成配置后,监控端需将每个节点上的该端点纳入抓取。 一旦采集到 containerd 指标,关键的 PromQL 查询可以帮助识别 sandbox 启动或销毁失败的情况。示例的 PromQL 查询包括识别 RunPodSandbox 失败和 StopPodSandbox/RemovePodSandbox 失败: sum by(grpc_code, instance) (rate(grpc_server_handled_total{job=\"containerd\", grpc_code!=\"OK\", grpc_method=\"RunPodSandbox\"}[5m])) > 0 sum by(grpc_code, grpc_method, instance) (rate(grpc_server_handled_total{job=\"containerd\", grpc_code!=\"OK\", grpc_method=~\"StopPodSandbox|RemovePodSandbox\"}[5m])) > 0 此外,可以编写更复杂的查询来发现 containerd 中存在但 Kubernetes 不再管理的容器,从而检测残留容器或沙箱: ( container_pids_current{namespace=\"k8s.io\"} unless on (container_id) label_replace(kube_pod_container_info, \"container_id\", \"$1\", \"container_id\", \"containerd://(.+)\") unless on (container_id) label_replace(kube_pod_init_container_info, \"container_id\", \"$1\", \"container_id\", \"containerd://(.+)\") ) unless on (container_id) (container_memory_swap_limit_bytes > 0) 将这些查询作为告警规则可以在问题刚出现时发出通知,避免在日志堆积后才被发现。 在 Talos 上启用 containerd metrics 与抓取配置 在 Talos 的 machine 配置中加入对 containerd 的 metrics 启用段是非常简单的操作。
配置好后,需在监控采集端(如 vmagent、Prometheus)为每个节点配置抓取路径,例如通过 kubernetes API 将每个节点的 11234 端口代理到采集器上。抓取配置要考虑证书与 token 认证,以确保安全抓取。 告警设计建议 对于容器运行时相关的告警,建议对短时的偶发错误先采用较宽容的触发条件,例如要求持续 5 分钟再触发告警;对于残留容器、长时间无法移除的 sandbox 则可以设置更长的宽限期(如 15 分钟),以避免频繁骚扰。 告警信息应携带足够上下文,例如发生的节点、grpc_code、grpc_method 以及最近出现的 sandbox ID,方便值班工程师快速定位并进入节点进行排查。 日常运维建议与自动化 将 /var/lib/cni/results 的健康状态纳入节点自检流程可以显著降低类似问题的干扰风险。可编写小型守护脚本或 Kubernetes DaemonSet 定期扫描该目录,对异常文件进行归档并发送事件。
事件触发后可自动尝试安全清理空文件并通知运维人员。 在集群级别,建议将容器存储与网络存储的异常作为整体 SLO 的一个子项监控。CSI 卷相关的短暂失效、网络抖动或主机层面异常都可能间接触发 CNI 写入失败,统一纳入巡检可以更早发现潜在问题。 总结与关键要点回顾 面对 Kubelet 日志中频繁出现的 "StopPodSandbox from runtime service failed" 错误,不要急于将其视为不可知的内核错误。通过合理的排查手段可以快速定位到 CNI 的本地缓存文件异常,例如 /var/lib/cni/results 目录下出现 0 字节或损坏的 JSON 文件。利用 kubectl debug 进入节点、用 crictl 观察 containerd 状态并直接删除异常缓存文件,通常可以即时恢复正常。
长期防护上,启用并采集 containerd 指标、为关键 RPC 错误添加告警、定期检查 CNI 结果目录以及提升底层存储与节点可用性,是避免该问题重复发生的有效策略。结合这些手段,工程师能够在未来遇到类似问题时快速定位与恢复,同时将可见性和自动化纳入日常运维流程,从根本上降低类似故障对集群稳定性的影响。 如需进一步的操作命令示例、PromQL 规则模板或 Talos 配置片段,可按需提供具体环境信息以便给出更精确的配置示例与构建步骤。祝排查顺利,集群稳定运行。 。