系列导航
本系列从 Pod 网络连通入手,逐步展开到 K8s 网络全景。
① 概念 → ② Flannel → ③ Calico → ④ 流量路径 → ⑤ Cilium → ⑥ 对比 → ⑦ 排障 ‖ ⑧ 开发 → ⑨ 多网卡 → ⑩ AI 演进
| 顺序 | 文章 | 定位 |
|---|---|---|
| ① | 概念与入门 | 基础——主机网络、Docker 网络、CNI 标准、方案分类树 |
| ② | Flannel 详解 | CNI 实现——Overlay 封装(UDP/VXLAN/HostGW)与抓包 |
| ③ | Calico 详解 | CNI 实现——三层路由(BGP/IPIP)+ NetworkPolicy |
| ④ | 流量路径全解析 | 全貌——Pod/Service/Ingress/Egress,揭示 CNI 的边界 |
| ⑤ | Cilium 详解 | 超越 CNI——eBPF 统一 Pod + Service + L7 + Hubble |
| ⑥ | 插件对比与选型 | 选型——5 插件横向对比 + 决策树 |
| ⑦ | 排障思路与常用命令 | 运维——工具链 + 场景排查 + 性能 |
| — | 核心路径 ↑ | 扩展展望 ↓ |
| ⑧ | 本篇 - CNI 插件开发指南 | 扩展——基于 CNI 规范开发自定义插件 |
| ⑨ | 多网卡方案详解 | 进阶——Multus + SR-IOV/ipvlan 多网口实战 |
| ⑩ | AI 时代网络演进 | 展望——GPU 网络、eBPF 加速、未来方向 |
当社区插件不满足需求(如特定硬件、定制安全策略、私有云网络集成),需要自行开发 CNI 插件。本文说明 CNI 插件在 K8S 中的位置、调用链、开发步骤和注意事项。
1. CNI 插件在 K8S 中的位置
CNI 插件是 Kubelet 与 Pod 网络之间的中间层。Pod 创建流程中网络部分的调用链如下:
Kubelet → CRI 实现(dockershim / containerd-CRI)
↓
读取 /etc/cni/net.d/*.conf(CNI 配置文件)
↓
设置 CNI 环境变量(CNI_COMMAND、CNI_IFNAME 等)
↓
通过 stdin 传入 Network Configuration(JSON)
↓
调用 /opt/cni/bin/<plugin>(CNI 插件可执行文件)
↓
插件执行 ADD → 进入 Pod NS → 创建 veth → 分配 IP → 配置路由
↓
通过 stdout 返回结果 JSON → Kubelet 获取 Pod IP1.1 调用来源:CRI 实现
Kubelet 不直接调用 CNI 插件。网络操作由 CRI(Container Runtime Interface)实现完成:
| 容器运行时 | CRI 实现 | CNI 调用位置 |
|---|---|---|
| Docker | dockershim(Kubelet 内置,1.24 已移除) | SetUpPod / TearDownPod |
| containerd | 内置 CRI 插件 | CRI 内部直接调用 CNI |
| CRI-O | 内置 CRI 实现 | CRI 内部直接调用 CNI |
dockershim 调用 CNI 的关键方法是 SetUpPod:先调 Docker API 创建并启动 Infra 容器,然后立即调用 CNI 插件为该 Infra 容器的 Network Namespace 配置网络栈。
1.2 环境变量
CNI 插件运行时,CRI 实现会设置以下环境变量:
| 环境变量 | 含义 | 示例值 |
|---|---|---|
CNI_COMMAND | 操作类型 | ADD / DEL / CHECK / VERSION |
CNI_CONTAINERID | 容器 ID(Pod Sandbox ID) | a1b2c3d4e5f6... |
CNI_NETNS | Pod 的 Network Namespace 路径 | /proc/<pid>/ns/net |
CNI_IFNAME | 容器内网卡名(默认 eth0) | eth0 |
CNI_ARGS | 扩展参数(Key-Value 格式) | IgnoreUnknown=1;K8S_POD_NAMESPACE=default |
CNI_PATH | CNI 插件可执行文件搜索路径 | /opt/cni/bin |
1.3 Network Configuration(stdin)
CRI 实现读取 /etc/cni/net.d/ 下的 JSON 配置文件,通过**标准输入(stdin)**传给 CNI 插件:
| |
2. CNI 插件分层架构
CNI 标准将可执行文件分为三类,部署在 /opt/cni/bin/ 下:
2.1 三类插件
| 类别 | 用途 | 典型二进制文件 | 说明 |
|---|---|---|---|
| Main 插件 | 创建具体网络设备 | bridge、ipvlan、macvlan、ptp、loopback、vlan | 负责创建网桥/Veth/ipvlan,把容器接入网络 |
| IPAM 插件 | IP 地址管理 | host-local、dhcp、static | 负责分配、回收 IP 地址 |
| Meta 插件 | 附加功能 | portmap、bandwidth、tuning、flannel | 端口映射、限流、参数调优、委托调用 |
2.2 调用链
一个完整的 CNI 配置可以组合多个插件,按顺序链式调用:
CNI 配置文件(plugins 列表) → 依次执行 [0] flannel(补充配置、调用 delegate) → stdout → 管道 [1] portmap(配端口映射 iptables 规则) → stdout → 管道 [2] bandwidth(配 TBF 限流队列) → stdout → 最终结果
Kubernetes 只支持按字母顺序加载第一个 CNI 配置文件。但单个配置文件内可以通过 plugins 字段定义多个插件协作。
2.3 Delegate 模式
Flannel CNI 插件的典型做法:自己不做具体网络操作,而是调用其他 CNI 内置插件(如 bridge):
| |
Flannel CNI 插件从 /run/flannel/subnet.env 读子网信息,补完 ipam 字段,再 fork 执行 /opt/cni/bin/bridge,参数通过 stdin 传递。
自己开发 CNI 插件时,如果只是做轻量封装(如增加自定义安全策略),可以用 Delegate 模式。如果是完全新的网络方案,则需要直接实现网络设备创建逻辑。
3. 开发关键步骤
3.1 实现 CNI 接口
CNI 规范要求实现 4 个命令:
| 命令 | 含义 | 触发时机 | 必须实现 |
|---|---|---|---|
ADD | 将容器加入网络 | Pod 创建 | 是 |
DEL | 将容器从网络移除 | Pod 销毁 | 是 |
CHECK | 检查容器网络是否正常 | 周期性健康检查(CNI v0.4.0+) | 否 |
VERSION | 返回支持的 CNI 版本 | 初始化检查 | 是 |
3.2 Go 实现骨架(使用 containernetworking/cni 库)
| |
3.3 关键操作明细
| 步骤 | 操作 | 命令/API 对应 | 注意事项 |
|---|---|---|---|
| 创建网桥 | 宿主机上创建 CNI 网桥 | ip link add cni0 type bridge | 首次创建,后续复用;需处理已存在的情况(幂等) |
| 创建 veth pair | 创建一对虚拟网卡 | ip link add eth0 type veth peer name vethXXX | 在宿主机 NS 创建,再将 container 端移入 Pod NS |
| 移动网卡到 Pod NS | 将 veth 一端移入容器 | ip link set eth0 netns <pid> | 必须在宿主机 NS 执行,移入后宿主机不可见 |
| 分配 IP | 调 IPAM 插件或自研分配 | /opt/cni/bin/host-local | 需要加锁防并发,保证集群唯一 |
| 配置 Pod 内路由 | 设置默认网关 | ip route add default via <gw> | 在 Pod NS 内执行 |
| 配置宿主机路由 | 宿主机添加 Pod 子网路由 | ip route add <podSubnet> dev <veth> | 使宿主机知道如何到达 Pod |
| 配置 iptables | SNAT / 端口映射 | iptables -t nat -A POSTROUTING... | 通常是 meta 插件(portmap)的工作 |
| 返回结果 | stdout 输出 JSON | 包含 IP、网关、DNS、路由 | 必须符合 CNI Result 规范 |
3.4 ADD 操作完整流程
1. CNI bridge 插件检查宿主机 cni0 网桥是否存在 → 不存在则创建 2. 通过 CNI_NETNS 路径进入 Pod Network Namespace 3. 在宿主机 NS 创建 veth pair(eth0 → vethXXX) 4. 将 eth0 端移入 Pod NS 5. 在 Pod NS 内启动 eth0、分配 IP、配置默认路由 6. 将 vethXXX 端挂载到宿主机 cni0 网桥 7. 调 IPAM 插件分配 IP(host-local:从预配置子网选未使用 IP) 8. 配置宿主机路由表(指向新 Pod 子网的路由) 9. 向 stdout 返回结果 JSON
3.5 DEL 操作
| |
4. 注意事项
| # | 问题 | 说明 | 处理方式 |
|---|---|---|---|
| 1 | 并发安全 | Kubelet 可能并发创建多个 Pod | IP 分配、路由修改必须加文件锁或原子操作 |
| 2 | 幂等性 | ADD 可能被重复调用 | 多次 ADD 同一容器应返回相同结果,不应报错 |
| 3 | 清理完整性 | DEL 遗漏资源会累积 | 网卡、路由、iptables 规则、IPAM 租约全部清理 |
| 4 | 错误回滚 | 中间步骤失败 | ADD 中任何步骤失败都要回滚已创建的资源 |
| 5 | 版本兼容 | CNI 规范多版本并存 | 0.3.1 / 0.4.0 / 1.0.0,注意 K8S 版本对应关系 |
| 6 | 热路径性能 | Pod 创建速度直接影响调度 | IPAM 尽量本地缓存,避免每次请求外部服务 |
| 7 | NS 路径失效 | Pod 销毁后 /proc/<pid>/ns/net 不存在 | DEL 时不应假设 NS 仍有效;缓存必要信息到文件 |
| 8 | MTU 匹配 | Overlay 隧道增加封装头 | Pod MTU = 宿主机 MTU - 隧道开销(VXLAN=50B,IPIP=20B) |
| 9 | CNI_PATH | 部分发行版不设 CNI_PATH | 默认 /opt/cni/bin,不要依赖环境变量,代码中 fallback |
5. 开发调试工具
| 工具 | 用途 | 示例 |
|---|---|---|
| cnitool | 官方命令行工具,不依赖 K8S 独立测试 CNI 插件 | CNI_PATH=/opt/cni/bin cnitool add mynet /var/run/netns/test |
| nsenter | 进入 Pod Network Namespace 调试 | nsenter -t <pid> -n ip addr |
| tcpdump | 抓包验证隧道封装、路由是否正确 | tcpdump -i cni0 -nn host 10.244.1.3 |
| iproute2 | Linux 网络栈调试 | ip link、ip route、ip neigh、bridge fdb |
| iptables | NAT/策略规则验证 | iptables -t nat -L -n -v、iptables-save |
| skel 库内置 debug | Go CNI 库自带调试输出 | 设置环境变量 CNI_DEBUG=1 开启详细日志 |
| /var/log/cni 日志 | CNI 插件 stderr 输出 | 所有 stderr 被 Kubelet 记录,可设 "log_level": "debug" |
5.1 用 cnitool 独立测试(不依赖 K8S)
| |