一句话总结
CNI(容器网络接口)是 Kubernetes 把"配置 Pod 网络"标准化外包出去的接口: 它只规定"插件必须能给 Pod 配好网络, 能回收", 至于怎么配(Overlay 还是路由, 要不要策略)由选哪个插件决定.
理解 CNI, 就理解了为什么 Flannel, Calico, Cilium 能被随意替换.
前置回顾
第 01, 02 篇我们手动搭了 namespace, veth, 网桥, 也讲了跨节点的 Overlay 和路由两条路线, 但一直没回答: 这些东西在 Pod 创建的瞬间是谁自动配好的? Flannel, Calico 到底是什么?
本篇揭开--它们都是 CNI 插件, 而 CNI 正是本系列核心主线"K8s 定义接口, 实现外包给插件"在网络侧的体现.
为什么 K8s 自己不实现网络
先问一个根本问题: Kubernetes 既然这么强大, 为什么不干脆内置一套网络实现, 非要搞个接口外包出去?
因为"Pod 怎么联网"的需求千差万别:
- 自建小集群图省事 → 想要 Flannel 这种无脑 Overlay
- 大规模性能敏感 → 想要 Calico 的 BGP 路由
- 要 L7 安全和可观测 → 想要 Cilium 的 eBPF
- 跑在 AWS 上 → 想用 VPC 原生 IP
- 还有各种私有云方案...
如果 K8s 内置死一种实现, 就无法适配所有这些场景.
所以 Kubernetes 做了一个克制而聪明的决定: 自己只定义"Pod 网络该满足什么"(第 01 篇的四条规则), 把"具体怎么实现"抽象成一个标准接口, 谁都可以来实现. 这就是 CNI(Container Network Interface).
这是'机制与策略分离'的典范
K8s 定义机制(接口: "必须能给 Pod 配网, 能清理"), 插件提供策略(实现: "我用 VXLAN / 我用 BGP / 我用 eBPF"). 这种分离让 Kubernetes 的核心保持精简稳定, 而网络生态可以百花齐放, 自由竞争与替换.
存储侧的 CSI(第 07 篇)是一模一样的设计--这种对称不是巧合, 而是云原生贯穿始终的设计哲学.
CNI 到底是什么: 一个朴素到惊人的接口
CNI 的规范简单得超乎想象. 它本质上就是约定: 一个 CNI 插件是一个可执行程序, 接收标准化的输入(容器的 namespace, 网络配置), 完成网络配置, 返回结果. 核心就两个操作:
| 操作 | 什么时候调用 | 插件要做什么 |
|---|---|---|
ADD |
Pod 创建时 | 给 Pod 建网络: 创建/配置网卡(veth), 分配 IP, 配路由--就是第 01-02 篇那些手动操作的自动版 |
DEL |
Pod 销毁时 | 清理网络: 回收 IP, 删除网卡等 |
CNI 像电源插座的国家标准: 标准只规定"插孔的形状, 电压"(接口), 不规定"电从核电站来还是太阳能来"(实现).
任何符合标准的电器(插件)都能插上用, 想换供电方案, 换个插座背后的东西就行, 电器(K8s)完全无感.
CNI 是怎么被调用的
把第 01-02 篇和 CNI 接通, 看一次 Pod 创建时网络是怎么自动配好的. 这里有个常被讲错的细节: 真正创建网络命名空间, 调用 CNI 的不是 kubelet, 而是容器运行时(containerd / CRI-O):
|
所以第 01 篇说的"这些 veth, 网桥不用手动配", 真相就是: 容器运行时在起 Pod 沙箱时调用了 CNI 插件, 插件自动完成了手动做的一切. 而 kubelet 只是经 CRI(下文)发起"启动 Pod"的指令, 它和运行时都不懂网络细节--具体怎么联网, 全是 CNI 插件的事. 这就是接口外包的威力: 每一层只管自己那一环, 网络实现被干净地隔离在插件里.
为什么是运行时而不是 kubelet 调 CNI
这正好印证本篇的 CRI/CNI/CSI 三件套: kubelet 不直接干运行时的活, 它通过 CRI 把"创建 Pod 沙箱"交给容器运行时; 沙箱(含 network namespace)由运行时创建, 所以"给这个 namespace 配网络(调 CNI)"自然也由运行时发起.
kubelet 站在更高层只做编排决策, 把运行时, 网络, 存储分别外包给 CRI, CNI, CSI--层层解耦, 各司其职.(早期 dockershim 时代链路略有不同, 现代 K8s 统一为"运行时经 CRI 被调用, 再调 CNI".)
CNI 插件通常以 DaemonSet 形式跑在每个节点
装一个 CNI(如 kubectl apply Calico 的 manifest), 它会以 DaemonSet(每节点一个 Pod, Kubernetes 笔记里的工作负载)的形式部署: 在每个节点上放好 CNI 可执行文件, 跑好需要的守护进程(如 Calico 的 BGP 程序, Flannel 的 flanneld).
节点上的容器运行时就能调用它了. 所以"装 CNI"=在每个节点铺好那个能响应 ADD/DEL 的插件程序.
三大主流插件: 一条光谱
有了 CNI 接口, 市面上出现了几十种插件. 把最主流的三个排在一条"从简单到强大"的光谱上, 就能快速建立选型直觉. 它们恰好对应第 02 篇的技术路线:
Flannel: 最简单, 够用就好
- 路线: 默认 VXLAN Overlay(第 02 篇路线一)
- 特点: 配置极简, 无脑能用, 几乎零运维. 专注"把 Pod 网络跑通"这一件事
- 短板: 不支持 NetworkPolicy(第 05 篇), 即没有网络安全隔离能力; 有封装性能损耗
- 适合: 学习, 开发, 中小集群, 不需要网络策略的场景
Calico: 性能与策略兼备
- 路线: 默认 BGP 路由(第 02 篇路线二, 无封装), 也支持 IPIP/VXLAN 兜底
- 特点: 性能好(无封装), 成熟稳定, 完整支持 NetworkPolicy, 生产环境广泛使用
- 短板: BGP 配置门槛比 Flannel 高; 策略基于 iptables(大规模时有性能话题, 见第 04 篇)
- 适合: 生产集群, 性能敏感, 需要网络策略
Cilium: 基于 eBPF 的下一代
- 路线: 基于 eBPF(第 04 篇专讲), 数据平面在内核里高效执行
- 特点: 高性能, 可替代 kube-proxy, 支持 L7 层策略(到 HTTP 方法/路径级别), 自带 Hubble 可观测性
- 短板: 概念新, 门槛最高, 对内核版本有要求
- 适合: 追求性能与安全可观测, 愿意拥抱新技术的现代集群
| 维度 | Flannel | Calico | Cilium |
|---|---|---|---|
| 跨节点路线 | VXLAN 封装 | BGP 路由(可封装兜底) | eBPF(可路由/封装) |
| NetworkPolicy | ❌ 不支持 | ✅ 支持(L3/4) | ✅ 支持(到 L7) |
| 性能 | 一般(封装) | 好(无封装) | 很好(eBPF) |
| 复杂度 / 门槛 | 最低 | 中 | 较高 |
| 一句话 | 简单够用 | 生产主力 | 前沿强大 |
选型一句话
学习/小集群/图省事 → Flannel; 生产, 要性能和网络策略 → Calico; 追求极致性能 + L7 安全 + 可观测, 能接受新技术 → Cilium.
正因为有 CNI 这个统一接口, 这三者理论上可以替换(实际换 CNI 需重建 Pod 网络, 有成本, 但应用层无感)--这就是"接口外包"带来的自由. 当下的趋势是 Cilium 势头很猛, 第 04 篇会讲清它凭什么.
快速回顾
- K8s 不自己实现网络, 因为联网需求千差万别; 它定义 CNI 接口, 把实现外包给可插拔插件--机制/策略分离
- CNI 接口朴素: 插件是个可执行程序, 核心两操作--
ADD(Pod 创建时配网络),DEL(销毁时清理) - 调用链: kubelet 起 Pod → 建 namespace → 调 CNI 的 ADD → 插件自动完成第 01-02 篇那些手动操作; CNI 插件通常以 DaemonSet 铺在每个节点
- 三大插件光谱: Flannel(VXLAN, 简单, 无策略) < Calico(BGP, 生产主力, 支持 L3/4 策略) < Cilium(eBPF, L7 策略+可观测, 前沿)
- 选型: 图省事 → Flannel; 生产+策略 → Calico; 极致性能+L7+可观测 → Cilium
CNI 和 CRI, CSI 是什么关系? 名字好像.
它们是 Kubernetes 的"三大可插拔接口", 同一种设计哲学的三个方向: CRI(Container Runtime Interface)外包容器运行时(用 containerd 还是 CRI-O), CNI 外包网络(本篇), CSI(Container Storage Interface)外包存储(第 07 篇).
记忆法: R=Runtime 运行, N=Network 网络, S=Storage 存储. K8s 把这三块最容易有分歧, 最需要适配不同环境的能力, 统统做成了标准接口. 看懂一个, 另两个触类旁通.
能同时装多个 CNI 插件吗?
主网络插件(负责给 Pod 配主网卡的那个)通常只能有一个--两个都想管 Pod 的主网络会打架. 但 CNI 支持链式调用(chaining) 和多网卡方案(如 Multus): Multus 本身作为"元插件", 能让一个 Pod 拥有多块网卡, 分别由不同 CNI 负责(常见于电信, NFV 等需要多网络平面的场景).
常规业务用单个主 CNI 即可, 不必折腾多 CNI.
动手练习
- 查看节点 CNI 配置: 在一个跑着的 K8s 节点上, 看看
/etc/cni/net.d/目录里的配置文件和/opt/cni/bin/里的插件可执行文件--这就是容器运行时调用 CNI 的依据 - 确认 CNI DaemonSet: 用
kubectl get pods -n kube-system找到 CNI 插件 Pod(如 calico-node, cilium, kube-flannel), 确认它是 DaemonSet, 每个节点一个 - 对比不同 CNI 集群: 分别用 kind 创建一个默认 CNI 的集群和一个指定 Calico 的集群, 对比两者跨节点流量(抓包)和路由表有何不同(印证第 02 篇两条路线)
- 验证策略能力差异: 在装了 Flannel 的集群上写一个 NetworkPolicy, 观察它不生效(Flannel 不支持); 换到 Calico/Cilium 再试, 确认生效--亲身体会插件能力差异
- 实际选型判断: 对照团队的实际情况, 用本篇的选型逻辑判断: 该用(或正在用)哪个 CNI? 理由是什么?