你有没有遇到过这种画面——
新 Pod 创建了,kubectl get pod一看,Init:0/1卡了十分钟死活不动。kubectl describe pod甩出来一句networkPlugin cni failed to set up pod network,再去kubectl get nodes,好家伙,某个节点直接NotReady了。
然后你重启 kubelet、重启 containerd、删 Pod 重建……折腾两小时,发现根因是/etc/cni/net.d/下躺着一个版本对不上的配置文件。
这事儿我踩过不止一次。所以今天把 CNI 的底裤彻底扒干净——它到底干什么、怎么工作、哪些命令真能救命、哪些坑能让你加班到凌晨三点。
速查急救包:5条命令先别慌
Pod 网络出问题的时候,别急着删集群。按这个顺序来:
# 1. 先看 CNI 配置文件是否存在、版本是否正确(80%的根因在这) ls -la /etc/cni/net.d/ && cat /etc/cni/net.d/*.conf* # 2. 确认插件二进制文件已安装 ls /opt/cni/bin/ # 3. 看 CNI Pod 是否正常运行 kubectl get pods -n kube-system | grep -E 'calico|cilium|flannel' # 4. 看节点 NotReady 的具体原因 kubectl describe node <node-name> | grep -A 5 Conditions # 5. 在节点上看 kubelet 日志中的 CNI 错误 journalctl -u kubelet -n 100 --no-pager | grep -i cni📌 我先看配置文件再看日志,这个顺序能省你半小时。kubelet 日志里的报错往往是配置文件问题的结果,不是原因。
CNI到底是个啥?别被官方定义绕晕了
CNI,全称 Container Network Interface,本质是一个规范和一组插件。它定义了容器运行时(containerd、CRI-O)怎么调用网络插件来给 Pod 配置网络。
Kubernetes 自己不实现Pod 网络。它把这件事完全委托给了 CNI 插件。你的集群里没装 CNI 插件,节点就是NotReady,Pod 连 IP 都拿不到。
从 Kubernetes 1.24 开始,CNI 的管理彻底从 kubelet 剥离了。容器运行时(containerd/CRI-O)才是真正调用 CNI 插件的那一方。
调用链:谁在什么时候调 CNI?
整个流程大概是这样:
API Server → Kubelet → CRI(容器运行时)→ CNI 插件
注意:kubelet 调的是CRI 接口,不是直接调 CNI 二进制。当你创建一个 Pod 时,kubelet 通过 CRI 告诉 containerd“给我建个沙箱”,containerd 的 CRI 插件再调用/opt/cni/bin/下的 CNI 二进制文件,传入环境变量和/etc/cni/net.d/里的配置文件,插件完成网络配置后返回结果。
💡关键点:CNI 配置目录默认是/etc/cni/net.d/,插件二进制默认在/opt/cni/bin/。系统会对目录下的合法配置文件名进行字典排序,取第一个合法配置作为 default 网络平面的配置。而且每隔 5 秒会自动扫描配置目录并重新加载更新——这个 5 秒扫描间隔在 iSulad 的 CRI 实现中有明确记载,containerd 和 CRI-O 的具体行为可能存在差异,建议以你使用的运行时的官方文档为准。但无论如何,改完配置文件后通常几秒内就会生效,不用重启 kubelet 或 containerd。我以前不知道这个机制的时候,改完配置傻乎乎地重启 kubelet,现在想想真是白折腾。
Pod 跨节点通信:数据包到底经历了什么?
以最常见的Flannel VXLAN 模式为例(其他插件的实现细节不同),当 Pod A(节点1)访问 Pod B(节点2)时,数据包在节点内部的路径是这样的:
- Pod A 内的
eth0实际是veth pair的一端,另一端挂在节点的cni0网桥上 - IP 包通过 veth pair 到达
cni0网桥 - 网桥根据路由表将包路由到本机的
flannel.1(VTEP 设备) - VTEP 设备将原始 IP 包整个封装进 VXLAN 帧,外层是节点间 UDP 通信
- 物理网络传输到节点2 的
eth0 - 节点2 内核识别 VXLAN 头,拆包后交给本机
flannel.1设备 - 根据路由表发往
cni0网桥,最终通过 veth pair 到达 Pod B
整个过程对 Pod 完全透明,Pod 只知道对方的 Pod IP。
场景 | 数据包处理方式 | 性能开销 |
同节点 Pod 间 |
| 几乎为零 |
跨节点 Pod 间(Overlay) | VXLAN 封装/解封装 | 约 10%-20% |
跨节点 Pod 间(BGP/Underlay) | 路由表直接转发 | 接近原生 |
Pod 访问外部 | SNAT 后走节点网络栈 | 轻微 |
理解了 CNI 的调用机制之后,下一个问题自然是——市面上这么多插件,到底该选哪个?
主流 CNI 插件怎么选?
2026 年了,生产环境基本就三个选项。别纠结,按需求对号入座。
Flannel:简单是真简单,功能也是真少
部署就一条命令kubectl apply -f kube-flannel.yml,默认 VXLAN 模式,资源占用低,适合测试环境和小集群。
但注意——基础 Flannel 插件不支持 NetworkPolicy 的策略执行(Talos 从 v1.13 开始可通过配置让 Flannel 支持 NetworkPolicy,属于平台特定支持)。NetworkPolicy 对象在 Flannel 集群上能创建,但流量不会被实际阻断。如果你的业务涉及多租户隔离或者合规要求,Flannel 直接出局。通过 Canal(Calico + Flannel)组合方案可以解决:Calico 负责策略执行,Flannel 仅负责节点间网络通信。
Calico:企业级稳如老狗
默认 BGP 模式(Underlay),性能接近原生网络,NetworkPolicy 支持完善(L3/L4),大规模集群验证充分。当前最新稳定版为 v3.32.2(约 2026-08-30 发布,以官方 GitHub Release 页面为准),v3.31.7 仍在维护中。
我推荐大部分生产集群优先考虑 Calico——它在金融、医疗这些对网络策略要求严格的行业里跑了很多年,不是营销话术。
⚠️ 但 Calico 的 BGP 模式需要底层网络支持 BGP 协议。如果你的机房交换机没开 BGP,那就只能退回 IPIP 模式或者换插件。
Cilium:性能天花板,但别盲目上
基于 eBPF,性能最强,支持 L7 NetworkPolicy,内置 Hubble 可观测性。当前最新稳定版为 v1.20.1(2026-08-18 发布),v1.19.7 和 v1.18.13 也在维护中。注意 v1.17 已于 2026-07-29 EOL,不再接收安全更新。
但 Cilium 的学习曲线是真的陡。我见过团队照着文档装完就跑,结果默认 Pod CIDR 和宿主机网段冲突,整个节点的网络直接瘫了。
维度 | Flannel | Calico | Cilium |
部署难度 | 极低 | 中等 | 较高 |
NetworkPolicy | ❌(Canal 方案中由 Calico 执行策略) | ✅ L3/L4 | ✅ L3/L7 |
性能 | 一般 | 高 | 极高 |
适用规模 | ≤100节点 | 企业级 | 大规模/高性能 |
故障排查:别猜,按这个顺序来
网络问题的排查原则就一条:从底层往上查。操作系统 → 容器运行时 → kubelet → CNI → 工作负载。
场景一:节点 NotReady,报 “cni plugin not initialized”
这是最常见的报错。kubectl describe node看到:
KubeletNotReady container runtime network not ready: NetworkReady=false reason:NetworkPluginNotReady message:Network plugin returns error: cni plugin not initialized排查思路见速查急救包步骤1-2。如果这两步都正常,再查 CNI DaemonSet 是否在每个节点都有 Pod 在跑。还可以用crictl info查看容器运行时报告的 CNI 状态。
场景二:Pod 启动时报 “Incompatible CNI versions”
根据 Kubernetes 官方文档,containerd v1.6.0 到 v1.6.3 存在一个已知问题:当 CNI 插件没有升级,并且/或者 CNI 配置文件中没有声明 CNI 配置版本时,containerd 日志会出现以下错误:
incompatible CNI versions; config is "1.0.0", plugin supports ["0.1.0" "0.2.0" "0.3.0" "0.3.1" "0.4.0"]containerd 团队官方声明:“这些问题在 containerd v1.6.4 中得到了解决”。该版本包含了两项针对 CNI 和 SELinux 的修复。
注意触发条件:如果你用的是 containerd v1.6.0-1.6.3,但 CNI 配置文件中正确声明了cniVersion,不一定会触发这个问题。所以排查时先确认配置文件中到底有没有版本声明,不要一上来就怪 containerd 版本。
解决方案:升级 containerd 到 v1.6.4+,同时确保/etc/cni/net.d/下配置文件的cniVersion字段与插件实际支持的版本匹配。
场景三:Pod 能跑但删不掉,报 “Failed to destroy network for sandbox”
StopPodSandbox for "b" failed error="failed to destroy network for sandbox ... invalid version \"\": the version is empty"根因是 CNI 配置文件中缺少版本声明。Pod 能创建但销毁时 CNI 无法正确清理网络命名空间。
解决方案:在 CNI 配置文件中补上"cniVersion"字段。然后强制删除卡住的 Pod 重建。
场景四:Calico IPAM 地址池耗尽
现象是 Pod 一直 Pending,kubectl describe pod看到failed to allocate IP相关事件。
在 Calico 节点上执行calicoctl ipam check --show-problem-ips看 IP 分配情况。--show-problem-ips标志用于打印泄露或分配异常的 IP,不带这个标志只能看到一行摘要。这个命令从 calicoctl v3.18+ 开始可用,在 Calico v3.25+ 中趋于稳定。
⚠️IPAM 输出格式可能因 calicoctl 版本而异,尤其在 Calico v3.32 引入 native v3 CRDs 之后,不同补丁版本的实际输出字段可能略有差异。注意 native v3 CRDs 在 v3.32 中为 tech preview 状态,生产环境建议先使用默认的 aggregated API server 模式。重点关注leaked IPs、leaked handles 和 missing handles这几类问题,但以你实际版本命令的输出为准。
如果泄露 IP 数量较多,可以用calicoctl ipam check -o report.json生成报告文件,再用calicoctl ipam release --from-report report.json批量释放泄露的 IP 和 handles。报告文件的 JSON 格式可能因 calicoctl 版本而异,建议先用cat report.json | jq .查看结构,确认字段后再写解析脚本。使用前建议查阅所用 Calico 版本的官方calicoctl参考文档,确认--from-report标志的可用性。
生产环境建议直接使用与 Calico 集群同版本的 calicoctl 二进制,如果版本不一致,需要加--allow-version-mismatch标志。如果是地址池真的不够了,需要调整 IPPool 的 CIDR 范围——具体操作可参考后续 CNI 安全切换章节的准备阶段。
CNI 插件安全切换:滚动节点替换实操指南
这是一个独立于故障排查的高危操作,弄不好整个集群网络全断。不能在同一节点上同时运行两个独立的 CNI 插件,除非使用受支持的 CNI chaining 模式。
滚动节点替换方案
Cilium 官方文档推荐通过双 Overlay 混合模式进行迁移,新旧 CNI 使用不同的 Pod CIDR 和封装端口,Linux 路由表负责分离流量。
具体步骤:
- 准备阶段:审计当前网络配置,备份集群状态,确认新 CNI 使用的 Pod CIDR 和封装端口与现有 CNI 不同
- 并行部署:新增一个节点池,装新 CNI,打上标签。在新节点上验证跨 CNI 的 Pod 连通性——这步最容易出问题,取决于 PodCIDR 分配、路由、封装和防火墙规则是否兼容
- 逐个迁移:
kubectl cordon旧节点,kubectl drain --ignore-daemonsets把工作负载迁到新节点 - 清理:确认业务正常后,移除旧节点,清理旧 CNI 组件和
/etc/cni/net.d/下的残留配置
⚠️如果跨 CNI 连通性验证不通过,不要继续迁移。需要先解决 PodCIDR 分配和路由兼容性问题。如果 PodCIDR 无法调整,可能需要在新旧 CNI 之间建立显式的路由规则,或者使用 CNI chaining 模式过渡,再或者考虑在维护窗口内进行全集群切换。整个迁移周期可能长达数周,别想着一个晚上搞定。
⚠️ 迁移完成后,务必重新启用 NetworkPolicy 执行
Cilium 官方迁移文档明确说明:迁移期间 Cilium 的 NetworkPolicy 和 CiliumNetworkPolicy 执行将被禁用,否则来自非 Cilium Pod 的流量可能会被错误丢弃。
策略被禁用的条件是这组配置参数同时生效:EnablePolicy=never、DisableCiliumEndpointCRD=true、EnableK8sNetworkPolicy=false、EnableCiliumNetworkPolicy=false、EnableCiliumClusterwideNetworkPolicy=false、IdentityAllocationMode=crd。
重新启用时,Cilium 官方给出了两步流程,避免流量中断:
- 如果集群中还没有 NetworkPolicy:先启用策略配置并重启 cilium-agent,再创建 NetworkPolicy
- 如果集群中已经有 NetworkPolicy:先将
DisableCiliumEndpointCRD设为 false 并重启 cilium-agent,再依次启用EnablePolicy、EnableK8sNetworkPolicy、EnableCiliumNetworkPolicy等相关开关。千万不要在改DisableCiliumEndpointCRD之前就动EnablePolicy或其他策略开关——因为 cilium-agent 的策略执行系统依赖 Cilium Endpoint CRD 来对远程节点上的 Pod 执行策略,如果策略和 CEP CRD 同时启用,cilium-agent 滚动更新期间远程节点的 CEP 可能还没创建好,导致跨节点流量被意外丢弃。
📌 Cilium 官方也提供了迁移模式的详细文档,通过 label 控制哪些节点使用 Cilium 作为主 CNI,可以更精细地控制迁移节奏。
验证方法:确认你的 CNI 真的工作了
配置完 CNI 后,别只看 Pod 是不是 Running。跑一遍完整验证:
# 1. 检查所有 CNI Pod 状态(期望全部 Running) kubectl get pods -n kube-system -o wide # 2. 节点状态(期望全部 Ready) kubectl get nodes # 3. 部署测试 Pod 验证跨节点通信 kubectl run test-a --image=busybox --restart=Never -- sleep 3600 kubectl run test-b --image=busybox --restart=Never -- sleep 3600 # 4. 获取两个 Pod 的 IP,从 A ping B kubectl get pods -o wide | grep test- kubectl exec test-a -- ping -c 3 <test-b-ip> # 5. 如果是 Calico,检查节点路由 kubectl exec -n kube-system <calico-node-pod> -- calicoctl node status⚠️ 如果步骤4超时但步骤3正常,大概率是跨节点路由没配好,优先检查底层网络是否允许 VXLAN(UDP 8472)或 BGP 流量通过。
彩蛋:两个能让你少加班的隐藏细节
💡Cilium 默认 Pod CIDR 就是10.0.0.0/8,会直接搞瘫宿主机网络
这不是“某个子段可能撞上”的问题,而是整个10.0.0.0/8都是冲突区域。Cilium 默认从10.0.0.0/8中给每个节点分配 /24 子网。如果你的节点网段在10.0.0.0/8里(很多企业内网就是这样),Cilium 分配的前几个 /24 会直接和宿主机网络冲突。一旦路由规则下发到宿主机,节点网络会完全中断——SSH 断连、API Server 失联,你只能从控制台救回来。
GitHub issue #42383 记录了这个问题,报告者使用的是 Cilium v1.18.3,节点网络在10.0.0.0/22,Cilium 默认分配的前几个 /24 与宿主机网段碰撞。
⚠️别假设你用的 Cilium 版本一定不受影响。GitHub issue 中未明确验证 v1.20 是否修复了这个问题,安装前直接检查你当前版本文档中clusterPoolIPv4PodCIDRList的默认值——只要默认值是10.0.0.0/8,而你的节点网段也在该范围内,就有冲突风险。
安装 Cilium 之前,一定先确认 Pod CIDR 和节点网段不重叠,然后在安装时显式指定clusterPoolIPv4PodCIDRList,例如--set ipam.operator.clusterPoolIPv4PodCIDRList="{10.244.0.0/16}"。
⚠️配置文件扫描间隔是 5 秒,但“字典序陷阱”仍然存在
CNI 每隔 5 秒扫描/etc/cni/net.d/目录,按字典序取第一个合法配置。如果有05-cilium.conflist和10-flannel.conflist同时存在,Cilium 优先。但如果你新装了 Cilium 后 Flannel 的配置文件没清理干净,手动改了 Cilium 配置以为会生效,实际上可能还在加载别的。执行ls -la /etc/cni/net.d/确认只有一个目标配置文件,多余的直接mv到备份目录。这个操作我做过不下五次。
写在最后
CNI 这个东西,装的时候五分钟,出问题的时候能查五小时。核心记住三件事:
- CNI 是规范不是实现,调用方是容器运行时不是 kubelet
- 排查网络问题从底层往上走:配置文件 → 插件状态 → kubelet 日志 → 数据包路径
- 选型比调优重要,Flannel 够用就别上 Cilium,Calico 能满足就别折腾 eBPF
最后的最后——装 Cilium 之前先检查10.0.0.0/8和你的节点网段是否冲突,这个坑一旦踩了,不是重启能解决的。
你在 CNI 上踩过什么坑?欢迎评论区聊聊,也把这篇文章分享给团队里正在被 K8s 网络折磨的兄弟。