☰
别再乱装CNI了!K8s网络频繁抽风的元凶,大多数人没查对这个位置
2026/9/29 20:10:13 网站建设 项目流程

你有没有遇到过这种画面——

新 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)时,数据包在节点内部的路径是这样的:

  1. Pod A 内的eth0实际是veth pair的一端,另一端挂在节点的cni0网桥上
  2. IP 包通过 veth pair 到达cni0网桥
  3. 网桥根据路由表将包路由到本机的flannel.1(VTEP 设备)
  4. VTEP 设备将原始 IP 包整个封装进 VXLAN 帧,外层是节点间 UDP 通信
  5. 物理网络传输到节点2 的eth0
  6. 节点2 内核识别 VXLAN 头,拆包后交给本机flannel.1设备
  7. 根据路由表发往cni0网桥,最终通过 veth pair 到达 Pod B

整个过程对 Pod 完全透明,Pod 只知道对方的 Pod IP。

场景

数据包处理方式

性能开销

同节点 Pod 间

cni0网桥本地转发

几乎为零

跨节点 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 路由表负责分离流量。

具体步骤:

  1. 准备阶段:审计当前网络配置,备份集群状态,确认新 CNI 使用的 Pod CIDR 和封装端口与现有 CNI 不同
  2. 并行部署:新增一个节点池,装新 CNI,打上标签。在新节点上验证跨 CNI 的 Pod 连通性——这步最容易出问题,取决于 PodCIDR 分配、路由、封装和防火墙规则是否兼容
  3. 逐个迁移:kubectl cordon旧节点,kubectl drain --ignore-daemonsets把工作负载迁到新节点
  4. 清理:确认业务正常后,移除旧节点,清理旧 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 网络折磨的兄弟。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询