深入理解 Kubernetes CNI (容器网络接口)

深入理解 Kubernetes CNI (容器网络接口)

在 Kubernetes (K8s) 集群中,网络是连接各个组件和应用的基础设施。为了实现灵活、可插拔的网络解决方案,Kubernetes 采用了容器网络接口(Container Network Interface, CNI)规范。CNI 是 K8s 网络模型得以实现的关键,它定义了一套标准接口,用于容器运行时动态地配置容器的网络。理解 CNI 的工作原理对于掌握 K8s 网络至关重要。

一、Kubernetes 网络模型的核心原则

在深入 CNI 之前,我们首先需要理解 Kubernetes 对集群网络的基本要求。其网络模型旨在提供一个扁平、易于理解和使用的网络环境,核心原则如下:

  1. 所有 Pod 能够不通过 NAT 就能相互访问:

    • 含义: 集群内任何 Pod 都可以直接使用其他 Pod 的 IP 地址进行通信,无需网络地址转换。这极大地简化了服务发现和应用间通信。
    • 实现: 通过 CNI 插件为每个 Pod 分配集群内唯一的 IP 地址,并确保这些 IP 地址在集群范围内是可路由的。底层网络插件(如 Flannel, Calico)利用 Overlay 网络(如 VXLAN)或路由技术(如 BGP)来实现跨节点的 Pod 间通信。
  2. 所有节点能够不通过 NAT 就能相互访问:

    • 含义: 集群中的物理或虚拟节点之间可以直接通信。这对于 K8s 控制平面组件(如 API Server 与 Kubelet)的正常工作至关重要。
    • 实现: 通常要求所有节点处于同一二层网络或通过路由、VPN 等方式确保节点间的 IP 连通性。
  3. 容器内看见的 IP 地址和外部组件看到的容器 IP 是一样的:

    • 含义: Pod 内的应用看到的自身 IP 地址,与集群中其他 Pod 或节点看到的该 Pod 的 IP 地址是相同的。这避免了地址转换带来的复杂性。
    • 实现: CNI 插件在 Pod 的网络命名空间内配置的 IP 地址即为其在集群网络中的真实 IP。

二、CNI 规范与接口

CNI 本身并不是一个具体的网络实现,而是一套规范接口标准。它由 Cloud Native Computing Foundation (CNCF) 维护。

  • 目的: 将容器运行时的网络设置逻辑与具体的网络实现解耦。容器运行时(如 containerd, CRI-O,或 Kubelet 内置的 Docker shim)只需按照 CNI 规范调用相应的插件,即可完成容器的网络配置,而无需关心底层使用的是 Flannel、Calico 还是其他网络方案。
  • 接口形式: CNI 插件通常是可执行文件(二进制文件或脚本)。容器运行时通过执行这些文件,并通过环境变量传递参数(如容器 ID、网络命名空间路径、网络配置等),通过标准输入(stdin)传递 JSON 格式的网络配置,并通过标准输出(stdout)获取执行结果(如分配的 IP 地址信息)。

三、CNI 插件类型

CNI 插件生态丰富,通常可以分为几类:

  • 主接口插件 (Interface Plugin): 负责创建具体的网络设备。
    • bridge: 创建 Linux 网桥,并将容器连接上去。
    • ipvlan/macvlan: 使用 L2/L3 层虚拟化技术将容器直接连接到主机的物理接口。
    • ptp: 创建 Veth Pair 设备对。
    • 特定网络方案插件:如 calico, flannel, weave-net 等插件,它们实现了更复杂的网络功能(如网络策略、路由分发等),但底层可能也会用到上述基础设备类型。
  • IPAM 插件 (IP Address Management): 负责 IP 地址的分配和管理。
    • host-local: 使用预先配置的地址段在本地节点上为容器分配 IP。
    • dhcp: 通过向 DHCP 服务器请求来获取 IP 地址。
    • calico-ipam, whereabouts: 更高级的 IPAM 插件,支持跨节点协调、IP 池管理等。
  • Meta 插件 / Chained 插件: 用于增强或修改现有网络配置,通常在主插件之后链式调用。
    • portmap: 基于 iptables 实现主机与容器间的端口映射(类似 docker run -p)。
    • bandwidth: 使用 Linux TC (Traffic Control) 对容器进行带宽限制。
    • firewall: 使用 iptables/firewalld 为容器设置防火墙规则。
    • tuning: 调整网络接口的 sysctl 参数。

四、CNI 工作流程

CNI 插件的调用和执行遵循一套标准流程:

  1. 配置加载:

    • 容器运行时(在 K8s 中通常是 Kubelet 通过 CRI 接口调用 containerd/CRI-O)查找 CNI 配置文件。默认目录是 /etc/cni/net.d/
    • 配置文件是 JSON 格式,后缀可以是 .conf (单个网络配置) 或 .conflist (插件链配置)。
    • 运行时按文件名的字母顺序加载第一个配置文件作为默认网络配置。.conflist 文件允许定义一个 plugins 数组,指定按顺序调用的多个 CNI 插件。
  2. 插件执行 (ADD/DEL 操作):

    • 容器创建 (ADD): 当 Pod 启动时,容器运行时为 Pod 创建网络命名空间后,根据加载的配置,调用相应的 CNI 插件(可执行文件,默认在 /opt/cni/bin/ 目录下查找)。
      • 运行时设置必要的环境变量(如 CNI_COMMAND=ADD, CNI_CONTAINERID, CNI_NETNS, CNI_IFNAME, CNI_PATH 等)。
      • 运行时将 JSON 网络配置通过标准输入传递给插件。
      • 插件执行网络设置:创建网络接口(如 veth pair)、将其一端移入容器网络命名空间、调用 IPAM 插件分配 IP、配置 IP 地址和路由、设置 DNS 等。
      • 如果使用 .conflist,前一个插件的执行结果(JSON 格式,包含 IP 配置等)会通过标准输入传递给下一个插件(作为 prevResult 字段)。
      • 插件将执行结果(成功时包含分配的 IP/MAC、DNS 等信息)以 JSON 格式输出到标准输出
    • 容器销毁 (DEL): 当 Pod 删除时,运行时调用 CNI 插件执行清理操作。
      • 环境变量 CNI_COMMAND=DEL
      • 插件负责释放 IP 地址、删除网络接口、清理相关规则等。
      • 对于插件链 (.conflist),DEL 操作必须按照 ADD 操作的相反顺序执行。
  3. 关键参数:

    • --cni-bin-dir: Kubelet (或容器运行时) 查找 CNI 插件可执行文件的目录。
    • --cni-conf-dir: Kubelet (或容器运行时) 查找 CNI 网络配置文件的目录。

示例配置 (/etc/cni/net.d/10-mycni.conflist):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// filepath: /etc/cni/net.d/10-mycni.conflist
{
"cniVersion": "0.4.0", // 注意 CNI 版本
"name": "mycni-network", // 网络名称
"plugins": [
{
"type": "bridge", // 第一个插件:创建网桥并连接容器
"bridge": "cni0", // 网桥设备名
"isGateway": true, // 在此网桥上设置默认路由
"ipMasq": true, // 对离开此网络的流量进行 SNAT
"ipam": {
"type": "host-local", // IPAM 插件类型
"subnet": "10.244.0.0/16", // 可分配的子网
"routes": [
{ "dst": "0.0.0.0/0" } // 添加默认路由指向网桥 IP
]
}
},
{
"type": "portmap", // 第二个插件:处理端口映射
"capabilities": {"portMappings": true}
},
{
"type": "tuning", // 第三个插件:调整 sysctl 参数
"sysctl": {
"net.core.somaxconn": "511"
}
}
]
}

这个配置定义了一个名为 “mycni-network” 的网络,它按顺序执行三个插件:bridge (负责核心网络连接和 IP 分配), portmap (处理端口映射), 和 tuning (调整内核参数)。

五、CNI 插件设计要点 (深入解析)

CNI 规范对容器运行时和插件的行为提出了一些关键要求,以确保互操作性和健壮性:

  1. 网络命名空间先行: 运行时必须在调用任何 CNI 插件之前,为容器创建好一个新的、隔离的网络命名空间。插件的工作是在这个命名空间内进行的。
  2. 配置决定插件: 运行时负责读取 CNI 配置文件,确定需要为当前容器调用哪些插件以及它们的执行顺序。
  3. JSON 配置: 网络配置采用标准化的 JSON 格式,易于读写和解析。
  4. 插件链顺序执行: 对于 .conflistADD 操作必须按 plugins 数组顺序执行,DEL 操作必须按相反顺序执行。前一个插件的 ADD 结果作为后一个插件的输入。
  5. 生命周期管理 (ADD/DEL 对称性与幂等性):
    • 一个成功的 ADD 最终应对应一个 DEL
    • DEL 操作必须是幂等的。即多次调用 DEL 应能正确处理(第一次清理资源,后续调用发现已清理则直接成功返回),以应对运行时或节点故障恢复场景。
  6. 并发控制:
    • 运行时不能对同一个容器(由 ContainerID 标识)并行执行 CNI 操作。
    • 运行时可以对不同的容器并行执行 CNI 操作。
  7. 唯一标识与状态管理:
    • ContainerID 是容器的唯一标识。
    • 需要存储状态的插件(如 IPAM)应使用网络名称、ContainerID 和网络接口名 (IfName) 组成的复合键来索引状态信息。
  8. 禁止重复 ADD: 运行时不能对同一个容器、同一个网络、同一个接口连续调用两次 ADD(在没有 DEL 的情况下)。

六、CNI 与主机网络及 Kube-proxy 的集成

CNI 插件不仅要配置 Pod 网络,还必须确保 Pod 能够与 Kubernetes 的服务发现机制(主要是 ServiceKube-proxy)正确集成。

  1. 基础要求:lo 插件

    • 所有 Pod 网络命名空间都需要一个启动的 lo (loopback) 接口 (127.0.0.1)。标准的 CNI lo 插件负责实现这一点。
  2. 与 Kube-proxy (iptables/IPVS 模式) 的协同

    • Kube-proxy 通过在宿主机上设置 iptablesIPVS 规则来实现 Service 的负载均衡和转发。
    • 关键挑战: CNI 插件必须确保从 Pod 发出、目标是 Service ClusterIP 的流量,以及从外部访问 NodePortLoadBalancer 并需要转发到 Pod 的流量,能够被宿主机上的 Kube-proxy 规则所处理。
  3. 场景一:使用 Linux 网桥的 CNI 插件 (如 bridge 插件, Flannel VXLAN)

    • 问题: 当 Pod 通过 veth pair 连接到宿主机上的 Linux 网桥时,如果源 Pod 和目标 Pod(或 Service 的后端 Pod)位于同一个节点且连接到同一个网桥,它们之间的流量可能仅在二层(网桥层面)转发,绕过宿主机的三层 IP 栈,从而错过 iptables/IPVS 规则的处理。这会导致 Service 访问失败。
    • 解决方案: 启用内核参数 net.bridge.bridge-nf-call-iptables=1
      • 该参数(位于 /proc/sys/net/bridge/bridge-nf-call-iptables)强制让通过 Linux 网桥的数据包也经过宿主机的 iptables 链(如 PREROUTING, FORWARD, OUTPUT)。
      • 这样,即使是桥接流量,也能被 Kube-proxynat 表或 filter 表中设置的规则匹配和处理。
      • 通常也需要设置 net.bridge.bridge-nf-call-ip6tables=1 (IPv6) 和检查 br_netfilter 内核模块是否加载。
    • 图示: (此图展示了 veth pair 连接到 cni0 网桥的典型结构)
  4. 场景二:不使用 Linux 网桥的 CNI 插件 (如 Calico BGP 模式, OVS-based 插件)

    • 机制: 这类插件通常不依赖 Linux 网桥,而是直接在宿主机上配置路由规则,或者使用像 Open vSwitch (OVS) 这样的高级虚拟交换机。
      • 路由方式 (如 Calico BGP): CNI 插件为每个 Pod 的 veth 接口配置 /32 路由,并通过 BGP 或直接写入内核路由表,确保宿主机知道如何将流量路由到目标 Pod(无论在本机还是其他节点)。由于流量必须经过宿主机的 IP 路由层,它自然会经过 iptables/IPVS 的处理点。关键在于 CNI 插件必须正确配置路由。
      • OVS 方式: 基于 OVS 的插件需要配置 OVS 流表,确保 Pod 流量能被正确导向宿主机网络栈(以应用 iptables/IPVS 规则)或直接转发。
    • 结论: 在这些场景下,通常不需要设置 bridge-nf-call-iptables,因为流量路径本身就包含了 IP 层处理。重点在于 CNI 插件自身的路由或流表配置是否正确。
    • 图示: (此图可能示意了另一种网络连接方式,例如直接路由或通过其他虚拟交换技术)

七、总结

CNI 是 Kubernetes 网络生态系统的基石。它通过一套简洁的规范,实现了网络配置的标准化和插件化,极大地增强了 Kubernetes 的灵活性和可扩展性。无论是选择 Flannel、Calico、Weave Net 还是其他网络方案,它们都遵循 CNI 规范与 Kubernetes 集成。理解 CNI 的工作原理、插件类型、设计要点以及它如何与 Kube-proxy 等组件协同工作,对于诊断网络问题、选择合适的网络方案以及深入理解 Kubernetes 本身都至关重要。


深入理解 Kubernetes CNI (容器网络接口)
https://mfzzf.github.io/2025/03/25/kubernetes-CNI/
作者
Mzzf
发布于
2025年3月25日
许可协议