系列导航
本系列从 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 加速、未来方向 |
重要
本文是系列地基,回答一个问题:K8S 为什么需要 CNI。
脉络按两条线索展开,在 跨主机 Pod 为什么不通 处汇合:
- 自底向上——找出问题:主机网络 → Docker 单机网络 → 跨主机 Pod 不通 → CNI 补了什么
- 自顶向下——给出方案:K8S 网络假设 → IPAM + 路由转发 → Overlay / 路由方案分类树
先补齐 Linux 和 Docker 网络地基,再看跨主机问题为什么必须引入 CNI,最后给出方案分类全貌。后续文章按分类树的枝叶逐一深入。
1. 主机网络基础
理解 K8S 网络之前,需要先搞清楚一台 Linux 主机是怎么收发包的。无论物理机、虚拟机还是容器,都遵循同一套协议栈逻辑。
2.1 路由表:内核如何决定下一跳
为网卡配置 IP 地址和子网掩码后,内核自动生成两类路由:
| 路由类型 | 生成条件 | 目标网段 | 网关 | 含义 |
|---|---|---|---|---|
| 直连路由 | 网卡配置了 IP/掩码 | 该接口所在子网 | 0.0.0.0 | 同子网直接发送,不需要网关 |
| 默认路由 | 配置了默认网关 | 0.0.0.0/0 | 指定的网关 IP | 不在子网的目标IP,交给网关 |
发包时内核查路由表,按最长前缀匹配决定下一跳:
同子网通信 → 匹配直连路由 → 下一跳 = 目标 IP 本身 跨子网通信 → 匹配默认路由 → 下一跳 = 网关 IP
2.2 ARP:IP 如何找到 MAC
路由决策只确定了「下一跳 IP」,但二层封帧需要的是「下一跳 MAC」。ARP 协议负责这个映射:
| 场景 | 下一跳 IP | ARP 请求目标 |
|---|---|---|
| 同子网 | 目标主机 IP | 直接找目标主机的 MAC |
| 跨子网 | 网关 IP | 找网关的 MAC |
流程:
查 `ARP 缓存`(`ip neigh` 或 `arp -n`) ├─ 命中 → 直接封装 `二层帧`(目标 MAC = 下一跳 MAC,目标 IP 不变) └─ 未命中 → 广播 `ARP请求` → 收到应答后学习 MAC → 封装帧发送
同子网:下一跳就是
目标IP,ARP 查目标 MAC。
跨子网:下一跳就是网关,ARP 查网关 MAC。目标 IP 首尾不变。
2.3 二层交换与跨网段转发
帧离开源主机后,经过的设备分两类:
| 设备类型 | 工作层 | 行为 |
|---|---|---|
| 交换机 / Linux 网桥 | 二层 | 只看目标 MAC,查 CAM 表转发端口,不解封 IP 头 |
| 路由器 | 三层 | 剥离二层头,查目标 IP,查路由表决定下一跳,重新封装二层头 |
二层设备的转发规则:
| 目标 MAC 类型 | 行为 |
|---|---|
| 广播/多播 | 泛洪到所有端口 |
| 单播且 MAC 在 CAM 表中 | 从对应端口转发 |
| 单播但 CAM 未命中 | 泛洪(未知单播) |
跨网段转发时,数据包经过多个路由器,目标 IP 自始至终不变,每跳的源/目标 MAC 都在变:
源主机 → 路由器1 → 路由器2 → ... → 目标主机 目标IP = 10.0.2.55(始终不变) 目标MAC = 路由器1.MAC → 路由器2.MAC → ... → 目标主机.MAC(每跳变化)
2.4 主机网络小结
发包流程: 1. 查路由表 → 确定下一跳 IP(同网段 = 目标IP / 跨网段 = 网关IP) 2. 查 ARP 缓存 → 确定下一跳 MAC(同网段 = 目标MAC / 跨网段 = 网关MAC) 3. 封装二层帧(目标 MAC = 下一跳 MAC,目标 IP 不变)→ 从网卡发出 4. 二层设备按 MAC 转发 → 到达下一跳 5. 若需跨网段,路由器重复步骤 1-4
网卡、网桥 和 路由。2. Docker 单机容器网络
Docker 没有发明新协议,完全基于 Linux 内核已有的网络能力:
Docker容器网络 = Linux NetworkNamespace + veth pair + 网桥 + 自动生成的标准路由配置 + 宿主机NAT规则
3.1 网络栈与 network namespace
网络栈是进程发起和响应网络请求的基本环境,包括:网卡、回环设备、路由表、iptables 规则。
Linux 通过 network namespace 实现网络栈隔离。每个容器拥有独立的 namespace,里面有自己的网卡、路由表、iptables 规则,与宿主机和其他容器互不干扰。
3.2 Veth Pair:连接不同命名空间的网线
Veth Pair 是一对虚拟网卡,从一端写入的数据包会直接出现在另一端,即使两端在不同的 namespace 中。因此常被用来连接不同 NS:
容器 NS 宿主机 NS ┌──────────┐ ┌──────────┐ │ eth0 │◄──veth pair──►│ veth001 │ └──────────┘ └──────────┘
3.3 网桥 docker0
docker0 是一个 Linux 网桥,根据目标 MAC 将数据包转发到自身不同的端口上。每个容器启动时,其 veth 对端会插入 docker0。
关键点: veth 网卡一旦插入网桥,就从独立网卡降级为网桥的一个端口,不再调用协议栈处理数据包,转发决策完全由网桥接管。
| |
3.4 容器内的路由表
容器启动后,eth0 配置 IP(如 172.17.0.2/16),内核自动生成两条路由,与 §2 主机网络的路由规则一致:
目标网络 网关 掩码 Flags 网卡 default 172.17.0.1 0.0.0.0 UG eth0 ← 默认路由 172.17.0.0 0.0.0.0 255.255.0.0 U eth0 ← 直连路由
- 直连路由(
172.17.0.0/16):同网段流量走 eth0,目标 MAC 从 ARP 缓存查 - 默认路由(
default):其他流量走 eth0,下一跳是 docker0 的 IP(172.17.0.1),目标 MAC = docker0 的 MAC
3.5 容器 A ping 容器 B(同主机)
假设容器 A(172.17.0.2)ping 容器 B(172.17.0.3),两台容器在同一台宿主机上。
容器A eth0 ──vethA── docker0 ──vethB── 容器B eth0 172.17.0.2 172.17.0.3
完整流程:
1. 容器A 查路由表 目标 172.17.0.3 ∈ 172.17.0.0/16 → 匹配直连路由 下一跳 = 172.17.0.3(目标本身),走 eth0 2. 容器A 查 ARP 缓存:172.17.0.3 → 未命中 → 从 eth0 发送 ARP 广播:"谁是 172.17.0.3?" 3. ARP 广播经 vethA 到达 docker0 网桥 → ARP 是广播帧 → docker0 泛洪到所有端口(vethA, vethB, ...) 4. 容器B 的 eth0(通过 vethB)收到 ARP 广播 → 目标 IP == 自身 IP → 回复 ARP 应答(MAC_B) 5. 容器A 获得容器B 的 MAC → 封装二层帧 目标 MAC = MAC_B,目标 IP = 172.17.0.3 6. 数据包经 eth0 → vethA → docker0 → docker0 查 CAM 表:MAC_B → 端口 vethB → 转发到 vethB 7. 数据包经 vethB → eth0 进入容器B → 容器B 协议栈处理 → 回复 pong
整个过程依赖:容器路由规则 → VethPair → 网桥 CAM 转发 → VethPair → 目标容器。
3.6 docker0 的双重角色与报文分岔
docker0 既是二层网桥,也是三层接口:
| 角色 | 触发条件 | 功能 | 特性 |
|---|---|---|---|
| 二层网桥 | 目标 MAC ≠ docker0 自身 MAC | 查 CAM 表,按 MAC 转发到对应的 veth端口 | 与网桥docker0绑定的veth网卡会自动降级成端口,失去报文解析、转发的能力 |
| 三层接口 | 目标 MAC = docker0 自身 MAC | 上交宿主机协议栈(路由、iptables) | docker0需要具备IP和MAC |
报文分岔逻辑:
数据包到达 docker0
├─ 目标 MAC == docker0 自身 MAC
│ → 上交宿主机 IP 层 → 走路由、iptables、协议栈处理
│
├─ 目标 MAC == 某端口 MAC
│ → 查 CAM 表 → 转发到对应端口(不进宿主机 IP 层)
│
└─ 目标 MAC == 广播/多播/CAM 未命中的单播
→ 泛洪到所有端口举例:容器访问外网
容器内 ping baidu.com,目标不在 172.17.0.0/16 网段,匹配默认路由:
1. 容器查路由表 → 匹配 默认路由 → 下一跳 = 172.17.0.1(docker0 的 IP) 2. ARP 解析 → 获取 docker0 的 MAC 3. 数据包目标 MAC = docker0.MAC → docker0 判定目标是自身 → 上交宿主机协议栈 4. 宿主机 iptables NAT(SNAT)→ 源 IP 替换为宿主机 IP 5. 从宿主机 eth0 发出 → 走宿主机路由 → 出外网
3.7 容器网络与 Linux 协议栈的关系
所有转发决策均遵循内核固有的路由/ARP/二层交换逻辑。容器内外协议栈行为完全一致,差别只在于容器使用的是虚拟设备(veth、网桥)而非物理设备。
3. K8S 网络模型假设
K8S 不对具体网络实现做限制,只强制约定 4 条网络假设:
| 序号 | 假设 | 说明 |
|---|---|---|
| 1 | Pod 间直接通信 | 任意两个 Pod 可以直接用对方 Pod IP 通信,不需要 NAT |
| 2 | 节点与 Pod 间直接通信 | 节点和 Pod 可以直接通信,不需要 NAT |
| 3 | Pod 所见 IP = 外部所见 IP | Pod 内部感知到的 IP 地址,与其他 Pod 或节点看到的 IP 完全一致 |
| 4 | Service 抽象 | 集群内可通过 Service IP 访问 Pod,Service 做负载均衡和 DNS 解析 |
这 4 条假设的核心是:
这避免了传统虚拟化网络中 “网络地址转换 NAT” 带来的复杂性,为服务发现、负载均衡、可观测性提供了统一基础。
4. 跨主机容器通信问题:Docker bridge 为什么不够
4.1 单机能解决什么
- 同主机,容器间通信:
容器.路由表 + VethPair + docker0网桥(veth降级为端口 + CAM根据mac查端口)
- 容器 访问 其他宿主机或外网:
容器.路由表(匹配默认路由,下一跳 = docker0 IP) → VethPair → docker0 三层设备(目标 MAC = docker0.MAC,上交宿主机协议栈) → 宿主机.路由表(根据目标 IP 决定从哪个网卡出) → 宿主机对应网卡发出
4.2 跨主机容器通信为什么失败
当容器 A(节点1,172.17.0.2)要访问容器 C(节点2,172.17.0.5)时:
节点1 节点2 容器A (172.17.0.2) 容器C (172.17.0.5) │ eth0 │ eth0 │ vethA │ vethC docker0 (172.17.0.1) docker0 (172.17.0.1) │ eth0 │ eth0 └────────────────────── 物理网络 ─────────┘
问题出在三个层面:
| 问题 | 说明 |
|---|---|
| 容器路由表不感知外部拓扑 | 容器A 查路由表,172.17.0.5 匹配直连路由,认为在 eth0 直连可达。实际上容器C 在另一台物理机上 |
| ARP 无法跨主机 | 容器A 发 ARP 广播找 172.17.0.5 的 MAC,广播只到达 docker0 和同节点的其他容器,永远到不了节点2 |
| 两个 docker0 网桥互相不感知 | 每台宿主机有独立的 docker0,各自维护独立的 CAM 表,彼此不知道对方有哪些端口 |
根本原因:
容器看到的直连路由是基于
所有容器都在同一个 docker0 下 的假设,这个假设在跨节点时崩塌。4.3 跨主机需要什么能力
要打通跨主机容器通信,必须额外补充三个能力:
| 能力 | 说明 | 谁负责 |
|---|---|---|
| 全局 IPAM | 两个节点的容器子网不能冲突,需要统一的 IP 分配 | CNI 插件 |
| 跨节点路由信息 | 节点1 需要知道「172.17.0.5 在节点2」 | CNI 插件 |
| 数据包送达机制 | 跨节点时通过封装(Overlay)或直接路由 | CNI 插件 |
这就是 CNI 要做的事情——在 Docker 单机网络的基础上,补齐跨主机能力。
5. CNI 标准与调用链
5.1 什么是 CNI
CNI (Container Network Interface) 是 CNCF 定义的容器网络接口标准。它规定了:
- Kubelet 调用网络插件的接口格式
- 插件必须实现的生命周期操作:ADD、DEL、CHECK、VERSION
- 插件返回结果的数据结构
CNI 解决的是 “Pod 网络怎么配置” 的标准化问题,让不同网络方案可以按同样的接口接入 K8S,而不需要修改 K8S 核心代码。
CNI 不管的事: Service 抽象(
kube-proxy负责)、Ingress/Egress(Ingress Controller负责)、网络策略(NetworkPolicy实现负责)、可观测性。Cilium 这类"超 CNI"方案把这些全部整合进一个内核级数据路径。
5.2 CNI 解决两件事
CNI 标准本质上只解决两件事:
- IPAM(IP 地址管理)——给 Pod 分什么 IP、怎么避免冲突
- 连通性(路由转发)——包怎么从源 Pod 送到目的 Pod
所有 CNI 插件的能力都可以归到这两个维度。后续文章中 Flannel、Calico 的各种后端模式,都是沿「连通性」这条线展开的不同实现:
CNI 解决的两件事
├── IPAM:给 Pod 分 IP
│ ├── host-local(静态子网分配)
│ ├── calico-ipam(动态分配)
│ └── dhcp
│
└── 连通性:怎么送达
├── Overlay — 构造特殊通路
│ ├── UDP(应用层封装,用户态,性能差,仅演示)
│ ├── VXLAN(内核态 L2-in-UDP,生产通用)
│ └── IPIP(内核态 IP-in-IP,纯三层封装)
└── Underlay — 维护节点路由
├── HostGW(静态路由,要求二层互通)
└── BGP(动态路由,三层互通)本篇只讲分类逻辑和每类的核心思想。具体封装细节、路由表长什么样、抓包验证,留给 Flannel 详解 和 Calico 详解 展开。
5.3 K8S 中的 CNI 调用链
用户创建 Pod
↓
API Server 写入 Etcd
↓
Scheduler 调度到某节点
↓
Kubelet 监听到本节点 Pod 创建
↓
读取 /etc/cni/net.d/ 目录下的 CNI 配置文件
↓
执行配置中指定的 CNI 插件二进制文件(在 /opt/cni/bin/)
↓
CNI 插件进入 Pod 网络命名空间
↓
创建 veth 网卡对 → 分配 IP → 配置路由 → 设置 iptables
↓
返回 Pod IP 等信息给 Kubelet
↓
Pod 启动关键配置位置:
- CNI 配置文件:
/etc/cni/net.d/*.conf- CNI 二进制文件:
/opt/cni/bin/- IPAM 存储:etcd(Flannel)或 CRD(Calico)
6. 跨节点通信的两条路
跨主机 Pod 通信的所有方案,最终都落在「连通性」维度的两个分支上。
6.1 Overlay:构造特殊通路
Overlay 的核心理念:用隧道封装在物理网络之上构建叠加网络。 底层网络不知道 Pod IP,只看到封装后的宿主机间流量。属于"构造特殊通路"的方式。
层次由内层报文决定,不看底层隧道:
| 方案 | 报文结构 | 层次 | 特点 | 场景 |
|---|---|---|---|---|
| UDP | HostIP|UDP|内层IP|Data | L3 | 用户态封装,28B 开销,性能最差 | 仅演示 |
| VXLAN | HostIP|UDP|VXLAN|内层MAC|内层IP|Data | L2 | 内核态 VTEP 封装,~50B 开销,生产通用 | 公有云、虚拟化环境 |
| IPIP | HostIP|IPIP|内层IP|Data | L3 | 内核态封装,~4B 开销,更轻量但只支持 IP 单播 | 三层互通 |
底层隧道都是 UDP(L4),但层次由 内层报文 决定——内层含 MAC 头就是 L2,只有 IP 就是 L3。
6.2 Underlay:维护节点路由
路由模式的核心理念:不做封装,直接维护宿主机路由表。 跨节点流量按标准 IP 路由转发。
| 方案 | 路由方式 | 要求 | 典型实现 |
|---|---|---|---|
| HostGW | 静态路由,下一跳 = 目标节点 IP | 所有节点必须在同一个 L2 广播域 | Flannel-HostGW |
| BGP | 动态路由协议,每个节点宣告自己的 Pod 子网 | 机房间允许 BGP 协议 | Calico-BGP |
6.3 分类决策
两类方案的选择本质是 通用性 vs 性能 的权衡:
| 维度 | Overlay(构造通路) | Underlay(维护路由) |
|---|---|---|
| 通用性 | 高,适配任何底层网络 | 低,要求 L2 互通或支持 BGP |
| 性能 | 有 10-20% 封装开销 | 接近裸机网络 |
| 典型场景 | 公有云、虚拟化环境 | 自建 IDC 机房 |
具体各方案的包流向、抓包分析、配置示例,详见 Flannel 详解 和 Calico 详解。多插件横向对比和选型决策,见 插件对比与选型。