AIGC标识 Cilium如何处理pause容器netns消失异常

结论

Cilium 处理"pause 容器的 netns 已消失"这类异常的核心思路是:netns 只是配置数据面时临时借用的通道,endpoint 的生杀权不在 netns 手里,而在 kubelet 的 CNI ADD/DEL 事件和 GC 手里。netns 消失不会让 cilium-agent panic,而是按"事件从哪来"走三条容错路径:正常销毁的走幂等 CNI DEL 清理;运行中异常消失的把 endpoint 置为 invalid 等 reconcile;agent 重启恢复失败的把 endpoint 标 stale 等 GC 收尾。

分析版本

  • Cilium:v1.19 / v1.20 官方文档与源码机制(endpoint 生命周期、CNI 容错、GC 这套设计自 v1.10+ 长期稳定,具体路径与参数以实际版本为准)
  • 关键组件:cilium-agent(endpoint 生命周期)、cilium-cni(CNI ADD/DEL)、cilium-operator(孤儿 CiliumEndpoint CRD GC)
  • 关键路径:/var/run/cilium/state/endpoints(endpoint 状态持久化)、DeletionQueueDir(CNI 删除落盘队列)

详细分析

1. 前提:pause 容器 = sandbox = netns 的持有者

K8s 里 pause 容器就是 pod 的 sandbox,由它创建并持有整个 pod 的网络命名空间,业务容器只是 join 进这个 netns。Cilium 和这个 netns 的交道发生在两个时机:

  • 建链时:kubelet 创建 sandbox 后调 CNI ADD,把 netns 路径(通常是 /proc/<pause-pid>/ns/net)传给 cilium-cni,Cilium 在里面建 veth pair、配默认路由、分配 IP、挂 eBPF 程序。
  • 改链时:后续每次 endpoint regeneration(网络策略更新、identity 变化、kubelet 触发的配置变更)都要重新进入这个 netns,往里面推路由、邻居、代理规则。

pause 一死,内核因 netns 引用计数归 0 直接销毁它,上面两种时机要访问的路径全部变成 ENOENT。endpoint 的状态则持久化在 /var/run/cilium/state/endpoints 下。

2. 入口①:kubelet 正常销毁 pod → CNI DEL

最常见的场景:pod 删除、驱逐、节点 drain,kubelet 通过 CRI 检测到 sandbox 退出后调 CNI DEL。此时 runtime 往往已经把 netns 回收了,Cilium 侧要处理的核心问题是:netns 已经打不开了,清理还怎么做?

答案是 CNI DEL 被设计成幂等且容错的:

  • 打开 netns 失败不阻塞清理流程。endpoint 的清理动作——释放 IP 地址、删除 eBPF map 项、删除 host 端 veth、删除 CiliumEndpoint CR——全部不依赖 pod netns 还活着,所以照常执行。
  • 如果 DEL 发生时 cilium-agent 恰好不在线,cilium-cni 会把删除请求落盘到 DeletionQueueDir,agent 恢复上线后重放这批请求,保证删除不丢。这就是为什么 agent 重启后通常能看到队列里有一批待处理的 DEL。

3. 入口②:运行中 netns 突然消失(sandbox 异常,pod 还在)

更棘手的场景:pod 对象和 Cilium endpoint 都还在,但 pause 容器异常死亡、netns 被提前回收。Cilium 自己不会立刻发现,触发点是下一次 regeneration:它要进 netns 配置路由/邻居,netns.GetFromPath 返回 ENOENT,regeneration 失败,endpoint 进入 invalid / disconnected 非 ready 状态,错误记入 endpoint 事件。

这里有两个关键设计:

  • agent 不 panic、不重建 sandbox。重新拉起 pause/sandbox 是 kubelet/CRI 的职责,Cilium 越权去做会形成"双主人",反而制造更多不一致。它只是把 endpoint 标记好,周期 reconcile 持续重试。
  • 自愈靠 kubelet。kubelet 发现 sandbox 异常后会重建 sandbox,进而发起新的 CNI ADD(同一个 pod、更新了 containerID/netns 路径),Cilium 把已有 endpoint 绑定到新 netns,regeneration 成功,endpoint 恢复 Ready;如果 pod 直接被删,则回到入口①。

4. 入口③:cilium-agent 重启后 restore 失败

agent 重启时会从 /var/run/cilium/state 恢复 endpoint,并按持久化记录里的 netns 路径重新打开。如果这期间 pause 已经死了,reopen 失败,该 endpoint restore 失败、标记为 stale,不挂数据面。

这种残留靠两层 GC 兜底:

  • agent 侧 endpoint GC:周期性对照本节点 kubelet 上报的 pod 列表,pod 已经不存在的 endpoint 被删除并释放 IP;
  • operator 侧 GC:cilium-operator 周期性清理集群层面的孤儿 CiliumEndpoint CRD(pod 没了但 CEP 对象还在)。

三条路径汇总结算如下图:

Cilium 处理 pause 容器 netns 消失的三条容错路径

5. 设计原则

把上面三条路径抽象出来,是四条互相支撑的设计原则:

  1. netns 是 lease,不是 source of truth。endpoint 的生命周期由 k8s pod 事件(CNI ADD/DEL)驱动,netns 只是配置数据面时临时借的通道;通道断了不等于 endpoint 没了。
  2. 所有 netns 内操作全部包容错。进 netns 失败不会让 agent 崩溃,而是转成 invalid 状态 + 周期重试,错误可观测。
  3. DEL 幂等 + 落盘队列。即使 netns 已经消失、agent 暂时不在线,清理动作也保证最终完成,不残留 IP 和 eBPF 项。
  4. 不越权重建 sandbox。网络栈的修复权在 kubelet/CRI,Cilium 只负责把数据面跟控制面对齐,避免两个组件各自拉起网络栈造成状态打架。

6. 排查命令

# 列出非 ready 的 endpoint,查看其失败事件
cilium-dbg endpoint list | grep -v ready
cilium-dbg endpoint get <endpoint-id>

# 对照节点上真实存在的 netns 和 pause 进程
ip netns list
ls /proc/*/ns/net

# agent 宕过机时检查被延迟的删除队列(DeletionQueueDir 下的 queued DEL)
ls /var/run/cilium/

典型现象是:endpoint 卡在 invalid、cilium endpoint list 里看到 regeneration 报错;此时先看 kubelet 侧 pod 的 sandbox 状态(kubectl describe pod 里的 Events),多数情况等 kubelet 重建 sandbox 后自动恢复;如果 pod 已删但 endpoint 残留,等下一轮 endpoint GC 或确认队列清空即可。

posted on 2026-10-11 20:26  王景迁  阅读(3)  评论(0)    收藏  举报

导航