2、CNI架构总览:CNI插件生命周期、容器运行时与kubelet的交互流程、CNI配置文件的加载机制

本章从架构视角拆解CNI的核心运行机制。理解CNI插件的生命周期、kubelet与容器运行时的交互流程,以及配置文件的加载顺序,是掌握Kubernetes网络准入策略的基础。我们将这三部分串联成一个完整的闭环,帮助你在故障排查和插件开发时建立清晰的全局视图。

2.1 CNI插件生命周期:从安装到销毁

CNI插件的生命周期由Kubernetes集群的Pod生命周期驱动。每个Pod的创建、删除都对应CNI插件的ADD和DEL操作。插件本身是独立的二进制可执行文件,不驻留后台进程。

阶段 触发条件 CNI操作 典型行为
安装 集群初始化或节点加入 无(静态部署) 将插件二进制放入 /opt/cni/bin/
配置 kubelet启动或配置变更 无(读取配置) 加载 /etc/cni/net.d/ 下的配置文件
ADD Pod调度到节点并创建网络命名空间 ADD 分配IP、创建veth对、配置路由、调用ipam
CHECK Pod运行中(可选,由配置决定) CHECK 验证网络配置是否与预期一致
DEL Pod删除或节点驱逐 DEL 清理网络资源、释放IP、删除veth
VERSION CNI插件版本探测 VERSION 返回插件支持的CNI规范版本
关键认知:CNI插件是“无状态”的。每次ADD/DEL调用都基于标准输入(stdin)传入的JSON配置和容器ID,插件不维护持久化连接。这意味着插件必须能够从集群状态(如etcd或CRI状态)中重建上下文。

2.2 容器运行时与kubelet的交互流程

kubelet是CNI调用的发起者,但它不直接调用CNI插件。完整的交互链涉及三个角色:kubelet → CRI运行时 → CNI插件。理解这个链条是定位网络问题的关键。

  1. kubelet 通过CRI接口(如containerd或CRI-O)请求创建Pod沙箱(Sandbox)。
  2. CRI运行时 创建Pod的网络命名空间(netns),然后调用内部的CNI代理(如containerd的cni plugin或CRI-O的conmon)。
  3. CNI代理 读取CNI配置文件,确定要调用的插件链,然后依次执行每个插件的ADD命令。
  4. 每个CNI插件通过标准输入接收容器ID、网络命名空间路径、接口名称等参数,执行网络配置后返回结果。
  5. 结果逐级返回:CNI插件 → CNI代理 → CRI运行时 → kubelet。kubelet将结果(如IP地址)更新到Pod状态中。
常见误区:很多人认为kubelet直接调用CNI插件。实际上,kubelet只与CRI运行时通信,CRI运行时内部再调用CNI。这意味着CRI运行时的CNI集成代码(如containerd的cni.New)是故障排查的第一站。
kubelet ↔ CRI运行时 ↔ CNI插件 交互流程 kubelet CRI运行时 (containerd/CRI-O) CNI代理 (内置cni调用) CNI插件A (bridge) CNI插件B (ipam) CNI插件C (portmap) 交互说明: 1. kubelet通过CRI接口请求创建Pod 2. CRI运行时创建netns并调用CNI 3. CNI代理按配置链顺序执行插件 4. 结果逐级返回至kubelet

2.3 CNI配置文件的加载机制

CNI配置文件的加载是决定“使用哪个插件”和“如何配置”的关键步骤。kubelet(或CRI运行时)在启动时会扫描配置目录,按照特定规则选择并加载配置。

2.3.1 配置目录与文件命名规则

  • 默认配置目录:/etc/cni/net.d/
  • 文件扩展名:只加载 .conf、.conflist、.json 后缀的文件。
  • 选择规则:按文件名字典序排序,选择第一个合法文件。如果该文件是 .conflist(插件列表),则直接使用;如果是 .conf(单个插件),则将其包装为只有一个插件的列表。
  • 优先级控制:通常使用数字前缀控制优先级,例如 10-calico.conflist 优先于 99-flannel.conf。
配置链(Plugin Chain)概念:一个 .conflist 文件可以定义多个插件按顺序执行。例如:先执行 bridge 创建网桥,再执行 ipam 分配IP,最后执行 portmap 配置端口映射。每个插件的输出作为下一个插件的输入上下文。

2.3.2 配置加载流程

  1. 扫描目录:CRI运行时的CNI代理扫描 /etc/cni/net.d/,过滤出合法文件。
  2. 排序选择:按文件名字典序排序,选择第一个文件。
  3. 解析配置:如果是 .conflist,直接解析为插件列表;如果是 .conf,解析单个插件配置并包装成列表。
  4. 解析插件路径:根据配置中的 type 字段,在 /opt/cni/bin/ 中查找对应的二进制文件。
  5. 缓存配置:部分CRI运行时(如containerd)会缓存解析后的配置,避免每次Pod创建都重新扫描文件系统。
  6. 执行插件链:当Pod创建时,按顺序执行每个插件的 ADD 命令;Pod删除时,按逆序执行 DEL 命令。
CNI配置文件加载与插件链执行流程 /etc/cni/net.d/ 10-calico.conflist 99-flannel.conf 00-multus.conf 字典序排序 → 选第一个 解析为插件列表 按序执行ADD /opt/cni/bin/ bridge → ipam → portmap (按配置链顺序) 关键规则: • 只加载 .conf/.conflist/.json • 字典序选第一个合法文件 • .conflist 支持多插件链 • DEL 按逆序执行

2.4 关键交互时序总结

将上述三个部分整合,可以得到一个完整的CNI交互时序。以下表格总结了从Pod创建到网络就绪的关键步骤及涉及的角色:

步骤 发起者 接收者 动作 关键数据
1 kubelet CRI运行时 RunPodSandbox Pod名称、命名空间、UID
2 CRI运行时 CNI代理 加载配置文件 从 /etc/cni/net.d/ 读取
3 CNI代理 CNI插件链 执行ADD 容器ID、netns路径、ifname
4 CNI插件 内核/网络栈 创建veth、分配IP IP地址、路由表、MAC地址
5 CNI插件 CNI代理 返回结果 JSON格式:IP、DNS、路由
6 CRI运行时 kubelet 返回Sandbox状态 PodIP、网络就绪标志
架构启示:CNI的“无守护进程”设计使得插件升级非常安全——只需替换 /opt/cni/bin/ 下的二进制文件,下次Pod创建时自动使用新版本。但这也意味着插件必须快速执行(通常要求在100ms内完成),否则会阻塞Pod调度。