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插件。理解这个链条是定位网络问题的关键。
- kubelet 通过CRI接口(如containerd或CRI-O)请求创建Pod沙箱(Sandbox)。
- CRI运行时 创建Pod的网络命名空间(netns),然后调用内部的CNI代理(如containerd的cni plugin或CRI-O的conmon)。
- CNI代理 读取CNI配置文件,确定要调用的插件链,然后依次执行每个插件的
ADD命令。 - 每个CNI插件通过标准输入接收容器ID、网络命名空间路径、接口名称等参数,执行网络配置后返回结果。
- 结果逐级返回:CNI插件 → CNI代理 → CRI运行时 → kubelet。kubelet将结果(如IP地址)更新到Pod状态中。
常见误区:很多人认为kubelet直接调用CNI插件。实际上,kubelet只与CRI运行时通信,CRI运行时内部再调用CNI。这意味着CRI运行时的CNI集成代码(如containerd的
cni.New)是故障排查的第一站。
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 配置加载流程
- 扫描目录:CRI运行时的CNI代理扫描
/etc/cni/net.d/,过滤出合法文件。 - 排序选择:按文件名字典序排序,选择第一个文件。
- 解析配置:如果是
.conflist,直接解析为插件列表;如果是.conf,解析单个插件配置并包装成列表。 - 解析插件路径:根据配置中的
type字段,在/opt/cni/bin/中查找对应的二进制文件。 - 缓存配置:部分CRI运行时(如containerd)会缓存解析后的配置,避免每次Pod创建都重新扫描文件系统。
- 执行插件链:当Pod创建时,按顺序执行每个插件的
ADD命令;Pod删除时,按逆序执行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调度。