目录
Please enable Javascript to view the contents

K8s网络-Flannel详解

 ·  ☕ 16 分钟

系列导航

本系列从 Pod 网络连通入手,逐步展开到 K8s 网络全景。

① 概念 → ② Flannel → ③ Calico → ④ 流量路径 → ⑤ Cilium → ⑥ 对比 → ⑦ 排障 ‖ ⑧ 开发 → ⑨ 多网卡 → ⑩ AI 演进

顺序文章定位
概念与入门基础——主机网络、Docker 网络、CNI 标准、方案分类树
本篇 - Flannel 详解CNI 实现——Overlay(UDP/VXLAN)+ Underlay(HostGW)与抓包
Calico 详解CNI 实现——Overlay(VXLAN/IPIP)+ Underlay(BGP)+ NetworkPolicy
流量路径全解析全貌——Pod/Service/Ingress/Egress,揭示 CNI 的边界
Cilium 详解(超CNI)超 CNI——eBPF 数据平面(Service + NetworkPolicy)+ CNI 连通(VXLAN/BGP)
插件对比与选型选型——5 插件横向对比 + 决策树
排障思路与常用命令运维——工具链 + 场景排查 + 性能
核心路径 ↑扩展展望 ↓
CNI 插件开发指南扩展——基于 CNI 规范开发自定义插件
多网卡方案详解进阶——Multus + SR-IOV/ipvlan 多网口实战
AI 时代网络演进展望——GPU 网络、eBPF 加速、未来方向

Flannel 由 CoreOS 开发,是 k8s 最成熟的开源 CNI 插件之一,通过三层网络把各节点的 Pod 子网连成一个跨节点的内部网络,每个节点分配一个子网。

1. Flannel架构

1.1 组件

flanneld —— 每个节点上的核心进程,作为 DaemonSet 部署,负责子网划分、子网租赁管理,使用 Etcd 或 K8S API 存储状态。

cni0 —— Linux 网桥,由 bridge CNI 插件创建,对应 Docker 单机环境的 docker0。每个 Pod 的 veth pair 对端插入 cni0,降级为网桥端口。同节点 Pod 间通信走 CAM 表二层转发。

后端(Backend) —— 负责跨节点报文的具体转发方式。Flannel 提供三种后端:UDP(用户态 Overlay)、VXLAN(内核态 Overlay)、Host-GW(路由模式)。

1.2 原理

每个节点从全局子网池中租赁一个子网段(如 10.244.1.0/24),节点内 Pod 从该段分配 IP。跨节点 Pod 通信时,转发路径为:

Pod eth0 → veth pair → cni0 网桥 → 宿主机路由 → 后端封装/转发 → 对端宿主机 → cni0 → veth pair → 目标 Pod

与 Calico 的关键区别在数据平面:Flannel 的 veth pair 对端必须插入 cni0 网桥——先经二层 CAM 转发(网桥层),再进宿主机路由表做三层转发(路由层),即「网桥+路由」两层。Calico 的 veth 对端不插网桥、直接对接宿主机路由表,是「纯路由」单层结构(Calico 篇展开)。

注意:Calico 的所有模式都不用网桥——包括 VXLAN 模式,它用 per-pod 路由 + VTEP 设备(vxlan.calico)做 Overlay,而不是把 veth 插进网桥。「用了 Overlay 就一定有网桥」是常见误区,详见 Calico 篇。

1.3 IPAM:子网租赁与 Pod IP 分配

Flannel 的 IP 管理分两层,对应「谁决定子网」和「谁分配具体 IP」:

第一层:子网租赁(flanneld

flanneld 从全局子网池(Network 配置项,默认 10.244.0.0/16)为每个节点租赁一个子网段(如 10.244.1.0/24),租约写入 Etcd 或 K8S API,节点下线后释放归还池子。

第二层:Pod IP 分配(host-local CNI 插件)

节点内分配 Pod IP 的是 CNI 标准的 host-local IPAM 插件。flanneld 把本节点租赁到的子网写进 CNI 配置,host-local 读取后从子网中按序分配 IP(10.244.1.210.244.1.3…),并把已分配 IP 记录在本地目录(/var/lib/cni/networks/<network>)。

两者分工:flanneld 管「哪个节点用哪个子网」,host-local 管「子网里哪个 IP 给哪个 Pod」。

2. Flannel后端模式

Flannel 解决的核心问题:跨节点 Pod 发送 IP 包时,原始 Pod 报文如何到达对端节点? 三种后端给出三种答案:

模式分类通路封装位置性能前提条件
UDPOverlay(L3)flanneld 进程间 UDP 通信 + TUN 设备 flannel0用户态最差(3次状态切换)
VXLANOverlay(L2)内核态 VTEP (flannel.1) + VXLAN 隧道 + FDB 转发内核态良好(~50字节开销)内核支持 VXLAN
Host-GWUnderlay(路由)静态路由,flanneld 直接修改宿主机路由表无封装最高(裸机性能)所有节点 L2 互通

Flannel 的三种后端按转发方式分为两类:Overlay 封装(UDP、VXLAN)和 Underlay 路由(Host-GW)。

2.1 Overlay: UDP 模式

通路:flanneld 进程间 UDP 通信 + TUN 设备 flannel0——各节点 flanneld 组成 Overlay 网络,用户态封包解包,实现最简单,性能最差。

封装结构:|HostMAC|HostIP|UDP|内层IP|Data|,IP 头(20B)+ UDP 头(8B)= 28B 开销。

2.1.1 组件概念

flannel0:TUN 设备

flannel0 是一个 TUN 设备(三层虚拟网络设备),在内核和应用程序(flanneld 进程)之间传递 IP 包:

方向流向行为
宿主机 → flannel0内核态 → 用户态内核态 IP 包交给创建该设备的应用程序(flanneld 进程)
flanneld → flannel0用户态 → 内核态flanneld 进程写入的 IP 包,出现在宿主机网络栈,按路由表流转

2.1.2 封装流程

五段流程

1
S.容器 → S.cni0 → S.flanneld → D.flanneld → D.cni0 → D.容器

1. S.容器 → S.cni0

默认路由 → veth pair → cni0 三层接口上交宿主机协议栈

1.1 容器内,默认路由,即 cni0 的 IP == 网关 IP,通过容器 eth0 发出,目标 IP = 目的容器 IP,目标 MAC = cni0.MAC
1.2 容器 eth0 → veth-pair → cni0 端口 vethxx
1.3 cni0 发现目标 MAC == 自身 MAC,发往 S.宿主机网络栈。

2. S.cni0 → S.flanneld 进程

宿主机路由匹配 Overlay 网段 → flannel0 TUN(内核→用户)→ flanneld 进程,内核剥 L2 头

2.1 到达 S.宿主机网络栈后,根据路由(flanneld 维护),目标 IP(目的容器 IP) ∈ Overlay 网段,发往 flannel0 设备(TUN);
2.2 根据 flannel0 设备的 TUN 特性,IP 包进入 flanneld 进程(注意,内核此时会剥离原始报文的 L2 头,将 IP 报文发给 flannel0)。

3. S.flanneld → D.flanneld

查 Etcd 获取目标宿主机 IP:8285 → UDP 封装(负载=原始 L3-L7)→ 经宿主机网络发往 D.flanneld

3.1 S.flanneld 进程,根据目的容器 IP 结合 Etcd 中节点-子网对应关系,获得目的宿主机 IP,目的端口 = 8285

外层包头:根据宿主机网络 L2-L4 封包,完全按宿主机网络两个 UDP 进程通信进行封包。

UDP 负载:容器网络原始报文的 L3-L7(因为 TUN 设备直接走三层,内核将报文发送 TUN 设备时,会剥离 L2 头)。

3.2 经宿主机网络,到达目的宿主机 UDP 进程——D.flanneld 进程。(注意:进入目标主机内核后,会重新 ARP 缓存或 ARP 请求,根据目标 IP 取得目标 MAC,再封装二层头。此行为属于步骤 4.3。)

4. D.flanneld → D.cni0

内核剥外层 L2/L3/L4 → flanneld 得原始 L3 报文 → 写回 flannel0(用户→内核)→ 路由 /24 dev cni0 → 出设备是网桥,内核 ARP 封 L2 头(src=cni0.MAC,dst=容器.MAC)→ cni0 CAM 转发

4.1 D.flanneld 进程收到数据时,D.宿主机网络栈已剥离 L2-L4 协议头,得到原始报文(L3 层报文,目标 IP = 目的容器 IP。跟 S.flanneld 收到的一模一样);
4.2 D.flanneld 进程,将 IP 包发往 D.flannel0,IP 报文进入 D.宿主机网络栈;
4.3 D.宿主机网络栈,根据路由(flanneld 维护),目标 IP(目的容器 IP) ∈ 当前节点子网,出设备 = D.cni0。

cni0 是一个网桥(二层设备),要通过它发送,必须封装以太网帧头。D.宿主机网络栈封装 L2 帧头,通过 ARP 请求或 ARP 缓存获取目的容器 MAC,并封装。

5. D.cni0 → D.容器

cni0 CAM 查端口 → veth pair → 目标容器 eth0

5.1 cni0,根据目的容器 MAC,查 CAM 表,转发至指定端口 veth;
5.2 端口 veth → veth-pair → 进入目的容器 eth0

+---------------------------------------+                                +-------------------------------------+
| S.Host (192.168.1.1)                  |                                | D.Host (192.168.1.2)                |
|                                       |                                |                                     |
|   [S.Container] eth0  10.244.1.2      |                                |   [D.Container] eth0  10.244.2.3    |
|     [1] route: default->cni0.1     |                                |     [17] veth pair -> recv pkt      |
|         ARP:   D.MAC = cni0.MAC    |                                |                                     |
|         (user->kernel, 1st switch)    |                                |                                     |
|     [2] veth pair                     |            [2] [16]            |     [16] veth pair                  |
|     container.eth0 <-> cni0.port   |                                |     cni0 port <-> container eth0 |
|                                       |                                |                                     |
|   [S.cni0]  IP:10.244.1.1          |                                |   [D.cni0]  IP:10.244.2.1        |
|               CAM: port <-> MAC       |                                |               CAM: port <-> MAC     |
|     [3] D.MAC==self -> L3 iface       |            [3]  [15]           |     [15] CAM: MAC -> port veth      |
|     TO: S.Host net stack              |                                |                                     |
|                                       |                                |                                     |
|   [S.Host net stack]                  |                                |   [D.Host net stack]                |
|     [4] route: 10.244.2.0/24          |            [4]  [14]           |     [14] route: 10.244.2.3          |
|         -> flannel0 (flanneld preset) |                                |          in subnet -> cni0       |
|         kernel strips L2 hdr          |                                |       cni0 L2 bridge, add L2     |
|                                       |                                |       ARP get Container MAC         |
|                                       |                                |                                     |
|   [S.flannel0 TUN]  L3 tunnel         |                                |   [D.flannel0 TUN]  L3 tunnel       |
|     [5] kernel->user (2nd switch)     |            [5]  [13]           |     [13] user->kernel               |
|         IP pkt -> flanneld process    |                                |          IP pkt -> D.Host net stack |
|                                       |                                |                                     |
|   [S.flanneld] user space             |                                |   [D.flanneld] user space           |
|     [6] etcd: 10.244.2.3              |            [6]  [12]           |     [12] write orig IP -> flannel0  |
|            -> 192.168.1.2:8285        |                                |          -> kernel net stack        |
|      encap UDP payload=L3-L7          |                                |                                     |
|                                       |                                |                                     |
|   [UDP pkt] [7] user->kernel (3rd)    |            [7]  [11]           |   [D.Host kernel]                   |
|            Src 192.168.1.1:rand       |                                |     [11] strip L2-L4 -> UDP 8285    |
|            Dst 192.168.1.2:8285       |                                |          -> D.flanneld              |
|                                       |                                |       orig IP == S.flanneld pkt     |
|                                       |                                |                                     |
|   [S.Host net stack]                  |                                |   [D.Host eth0]                     |
|     [8] add L2 hdr: ARP next-hop      |            [8]  [10]           |     [10] recv L2 frame              |
|                                       |                                |                                     |
|                                       |            [9]  [10]           |                                     |
|                                       |                                |                                     |
|                                       | routers only see outer headers |                                     |
|   [S.Host eth0]                       |                                |                                     |
|     [9] send L2 frame                 |               [9]              |                                     |
|                                       |                                |                                     |
+---------------------------------------+                                +-------------------------------------+

步骤说明

编号位置规则 / 动作
[1]S.Container eth0路由决策: 查路由表,目标 10.244.2.3 不在本机子网 → 匹配默认路由,下一跳 = cni0 IP(10.244.1.1)。发起 ARP 获取 cni0 MAC,封装 L2 帧(目标 MAC = cni0.MAC,目标 IP = 10.244.2.3)。第一次状态切换:用户态→内核态。
[2]veth pair容器 eth0 → veth pair → cni0 网桥上的端口 vethxx。veth 插入网桥后降级为端口,只收不发,由 cni0 决定转发。
[3]S.cni0目标 MAC 判断: 收到帧 → 目标 MAC = 自身 MAC → cni0 充当三层接口,上交 S.Host 协议栈处理。
[4]S.Host net stack路由决策: 查路由表(flanneld 预置),目标 10.244.2.3 ∈ Overlay 网段 → 出设备 = flannel0内核剥 L2 头,将 IP 包发往 flannel0
[5]S.flannel0 (TUN)TUN 设备特性: 内核态 IP 包 → 交给创建该设备的应用程序(flanneld 进程)。第二次状态切换:内核态→用户态。
[6]S.flanneld查 Etcd: 10.244.2.3 → D.Host 192.168.1.2,D.Port 8285UDP 封装: UDP 负载 = 原始报文的 L3-L7(因为 TUN 设备直接走三层,内核发 TUN 时已剥 L2)。外层按宿主机网络两个 UDP 进程通信封 L3-L4 头(源 IP=192.168.1.1,目标 IP=192.168.1.2,目标端口=8285)。
[7]encapsulated状态切换: flanneld 进程 → S.Host eth0。第三次状态切换:用户态→内核态。
[8]S.Host net stack封 L2 头: 查路由表/ARP,确定下一跳 MAC(同子网=对端宿主机 MAC,跨子网=网关 MAC)→ 封装以太网帧头。
[9]S.Host eth0L2 帧从 eth0 发出,进入物理网络。
[10]Physical Network标准 IP 路由转发。中间设备只看外层包头(源/目标 MAC 和 IP),不知道内层 Pod IP。
[11]Physical Network帧到达 D.Host 192.168.1.2eth0
[12]D.Host kernel拆包: 内核依次剥 L2 头(目标 MAC = 自身)、L3 头(目标 IP = 自身)→ L4 UDP 层 → 目标端口 8285 → 交给 D.flanneld 进程。此时网络栈已剥离 1-4 层协议头,得到的原始报文跟 S.flanneld 收到的一模一样。
[13]D.flanneld将原始 IP 包写入 flannel0 → IP 报文进入 D.Host 网络栈。
[14]D.flannel0状态切换:用户态→内核态。 IP 包按 D.Host 路由表流转。
[15]D.Host net stack路由决策: 目标 10.244.2.3 ∈ 本机 Pod 子网 (10.244.2.0/24) → 出设备 = cni0cni0 是二层网桥,须先封装 L2 头 → 查 ARP 缓存(或发 ARP 请求)获取 D.Container MAC → 封装以太网帧头(源 MAC=cni0.MAC,目标 MAC=D.Container MAC)。
[16]D.cni0CAM 转发: 查 CAM 表,目标 MAC → 对应端口 veth → 从该端口发出。
[17]veth pairveth pair → 进入 D.Container eth0。容器协议栈逐层解包,收到完整数据。

2.1.3 性能分析

为什么性能差?三次内核态↔用户态切换

发送端:
  第一次:容器进程 → 容器 eth0               用户态 → 内核态
          之后 eth0→vethxx→cni0→flannel0 一直在内核态
  第二次:flannel0 → flanneld 进程            内核态 → 用户态  ← TUN 特性
  第三次:flanneld 进程 → 宿主机 eth0         用户态 → 内核态
UDP 模式仅用于演示原理,生产环境绝对不要用。

2.2 Overlay: VXLAN 模式

通路:内核态 VTEP 设备 flannel.1 + VXLAN 隧道 + FDB 转发表,封装/解包全在内核中完成。

封包格式:|HostMAC|HostIP|UDP|VXLAN|内层MAC|内层IP|Data|,总计 50B 开销(外层 IP 头 20 + UDP 头 8 + VXLAN 头 8 + 内层 MAC 14)。

VXLAN (Virtual Extensible LAN) 是 Linux 内核原生支持的 Overlay 协议,采用内核态标准协议,所有封装/解包在内核中完成,实际传输速度比 UDP 模式快许多。内核态 VTEP 设备 flannel.1 使用 UDP 端口 8472(VXLAN)做封装/解封,对端节点用随机端口连接。

VNI (VXLAN Network Identifier): Flannel 默认使用 VNI 1。同一 VNI 内的节点属于同一个 L2 广播域。

2.2.1 组件概念

flannel.1 = VTEP

flannel.1 是一个 VTEP(VXLAN Tunnel End Point)设备,工作在内核态,负责 VXLAN 封装和解封。

与普通网桥(cni0)的区别:

特性普通网桥(cni0)VTEP(flannel.1)
转发表CAM 表(端口 ↔ MAC)FDB 表(远端 VTEP MAC ↔ 远端宿主机 IP)
是否记录 IP是,FDB 记录远端宿主机 IP
封装能力无封装,纯二层转发内核态 VXLAN 封装/解封

三层表

flanneld 进程在每个节点预置三类表项,构成完整的数据平面:

内容作用查看命令
路由表远端子网 → flannel.1,网关 = 远端 VTEP IP将跨节点 Pod 流量引向 VTEP 设备ip route
ARP 表远端 VTEP IP → 远端 VTEP MACVTEP 间邻居发现,回答"对端 VTEP 的 MAC 是什么"ip neigh show dev flannel.1
FDB 表远端 VTEP MAC → 远端宿主机物理 IP回答"对端 VTEP 在哪台宿主机上"bridge fdb show dev flannel.1
1
2
3
4
# 示例:查看 FDB 表
bridge fdb show dev flannel.1 | grep <目标VTEP.MAC>
# <目标VTEP.MAC> dev flannel.1 dst 10.168.0.3 self permanent
# 含义:发往该 VTEP MAC 的二层帧,通过 flannel.1 设备发出,发往物理 IP=10.168.0.3 的节点
关键改进: 封装/解封装全在内核态完成(通过 flannel.1 虚拟网卡),无需像 UDP 模式那样经过用户态 flanneld 进程,性能显著优于 UDP 方案。

2.2.2 封装流程

完整封装流程

以源容器 → 目的容器为例:

步骤1:源容器发出 IP 包
  S.容器 eth0 → veth pair → S.宿主机 cni0 网桥
  → 路由表匹配:D.容器IP ∈ 远端子网

步骤2:路由引向 VTEP
  路由规则(flanneld 预置):
  10.244.2.0/24 via 10.244.2.0 dev flannel.1 onlink
  → IP 包发往 S.flannel.1,下一跳 = 远端 VTEP IP

步骤3:VTEP 内层二层封装
  flannel.1 查 ARP 表:远端 VTEP IP → 远端 VTEP MAC
  封装内层以太网帧头:S.VTEP.MAC + D.VTEP.MAC

步骤4:VTEP 封 VXLAN 头
  添加 VNI = 1,形成 VXLAN 报文

步骤5:VTEP 封外层 UDP+IP 头
  查 FDB 表:D.VTEP.MAC → D.宿主机.IP
  源 IP = S.宿主机.IP, 目标 IP = D.宿主机.IP
  目标端口 = 8472

步骤6:宿主机二层发出
  查路由表/ARP,封装外层以太网帧(D.宿主机.MAC),从 eth0 发出

步骤7:目的宿主机解封装
  D.宿主机 eth0 → 内核依次解外层 MAC/IP/UDP
  → 识别 VXLAN 端口 8472 → 根据 VNI=1 交给 flannel.1

步骤8:VTEP 剥离内层头
  flannel.1 剥离 VTEP 内层 MAC 头、VXLAN 头,还原原始 IP 包

步骤9:路由到目标容器
  查路由表,目标 IP ∈ 本机 Pod 子网 → 走 cni0
  → ARP 获取容器 MAC → 封装二层头 → cni0 CAM 转发
  → veth pair → D.容器 eth0
VXLAN 协议只定义了数据封装与 FDB 转发机制。Flannel 作为控制平面,负责利用 Etcd 同步信息,并具体化为宿主机上的路由、FDB、ARP 表项。

via 是路由表里「下一跳」的意思——它不替换目的 IP,只决定本跳把包交给谁

10.244.2.0/24 via 192.168.1.2 dev eth0 处理目的 IP = 10.244.2.8 的包为例:

  1. 最长前缀匹配 → 命中该路由 → 出接口 eth0,下一跳 = 192.168.1.2
  2. 确定「发给谁」:有 via → 发给 via IP(192.168.1.2),不是目的 IP
  3. ARP 查 192.168.1.2 的 MAC(不是 10.244.2.8 的 MAC)
  4. 封 L2 帧:dst MAC = 192.168.1.2 的 MAC,src MAC = eth0 的 MAC
  5. L3 头原封不动:dst IP 仍是 10.244.2.8
路由类型下一跳 = 谁ARP 谁例子
直连(无 via)目的 IP 本身(on-link)目的 IP10.244.2.0/24 dev cni0 → ARP 10.244.2.8
via 路由via 后面的 IPvia IP10.244.2.0/24 via 192.168.1.2 → ARP 192.168.1.2

IP 路由 = 每跳重封 L2 帧,IP 头永不动。 改 IP 只发生在 NAT(SNAT/DNAT),纯路由从不改 IP。「下一跳」是 IP 路由可扩展的根本:每台设备只需知道下一跳,不需要知道完整路径。

VXLAN 里这条 via 10.244.2.0 dev flannel.1 onlink 是特例:via 用的是一个占位 IP,配合 onlink + proxy ARP,让内核在 flannel.1 上 ARP 得到的是远端 VTEP 的 MAC(见上面步骤3)。但「ARP 下一跳、不改 IP」的原则完全一致。

2.2.3 抓包验证

抓包验证

如下面一条抓包数据所示——源 192.168.1.215 节点的 pod [10.42.0.0] 访问目的 192.168.1.216 节点的 pod [10.42.1.7] 的 443 端口,抓源主机 enp0s3 设备的 UDP 包:

1
2
3
192.168.1.215.38381 > 192.168.1.216.8472: [bad udp cksum 0x11bc -> 0x6334!] OTV, flags [I] (0x08), overlay 0, instance 1
f2:60:ca:96:7f:1f > 4a:02:36:26:f9:c1, ethertype IPv4 (0x0800), length 74: (tos 0x0, ttl 64, id 34781, offset 0, flags [DF], proto TCP (6), length 60)
10.42.0.0.10384 > 10.42.1.7.443: Flags [S], cksum 0x1589 (incorrect -> 0x6701), seq 1841703998, win 64240, options [mss 1460,sackOK,TS val 3365946307 ecr 0,nop,wscale 7], length 0
  • 192.168.1.215.38381 > 192.168.1.216.8472:外层 UDP 包,源端口 38381,目标端口 8472(VXLAN 协议端口)
  • f2:60:ca:96:7f:1f > 4a:02:36:26:f9:c1:外层以太网帧,源/目标 MAC 为两台宿主机 enp0s3 网卡的 MAC
  • 10.42.0.0.10384 > 10.42.1.7.443:内层 TCP 包,这是 Pod 之间真实的通信——源 IP 10.42.0.0,目标 IP 10.42.1.7

注:抓包里的 OTV 是 tcpdump 的误识别——VXLAN 与 Cisco OTV 都默认用 UDP 8472 端口,老版本 tcpdump 会把 8472 上的 VXLAN 报文显示成 OTV,实际封装是 VXLAN。

2.3 Underlay: Host-GW 模式

通路:宿主机路由表 + cni0 网桥——flanneld 将 Pod 子网路由写入宿主机路由表,跨节点无任何封装,性能等于裸机网络。

2.3.1 组件概念

与前两种 Overlay 模式不同,Host-GW 采用主机路由方案——去掉的是隧道封装(没有 flannel.1、没有 flannel0),改走宿主机普通三层路由。既然跨节点不通是因为节点间缺少 Pod 子网的路由信息,而 Flannel 知道这些信息,就直接把它刷到宿主机路由表上。

注意:cni0 网桥依然存在。 veth 对端仍然插在 cni0 上,同节点 Pod 通信仍走网桥二层转发(CAM 表),与 Overlay 模式完全一致。Host-GW 删掉的只是跨节点的隧道设备,网桥和容器接入方式都没变。

跨节点通信靠路由:flanneld 在每台宿主机路由表维护「其他节点 Pod 子网 → 下一跳 = 目标节点主机 IP」的静态路由(如 10.244.2.0/24 via 192.168.1.2)。包到达目的节点后,由目的节点通过本机 cni0 直连路由进入网桥、再转给容器(完整路径见 2.3.3)。

Host-GW 没有 Overlay 的装包解包操作,完全是普通网络路由机制,性能接近裸机网络。

然而,Flannel 只能修改宿主机的路由表,一旦节点间隔了其他三层设备(如路由器),该设备没有 Pod 子网的路由,会把包丢弃或错误转发。因此 Host-GW 要求所有节点在同一个 L2 广播域内——下一跳(目标节点)必须 on-link 直连可达。

2.3.2 主要流程

工作原理:
1. 每个节点的 Pod 子网在 Etcd 注册,flanneld 感知全局子网
2. flanneld 在每台宿主机路由表写入到其他节点子网的静态路由,下一跳 = 目标节点主机 IP
3. 同节点通信:Pod → veth → cni0 网桥 → CAM 转发 → 目标 Pod(不进路由表)
4. 跨节点发送:源 Pod → veth → cni0 → 匹配远端 via 路由 → 经 eth0 发往目标节点
5. 目的节点接收:剥 L2 → 匹配本机直连路由 /24 dev cni0 → 进网桥 → CAM 转发 → 目标 Pod
性能最高,等于裸机网络,零封装开销。 代价是要求所有节点必须在同一个 L2 广播域(跨 VLAN、跨机房、云环境无法使用)。

2.3.3 路由验证

假设两个节点:

1
2
节点1:宿主机 IP = 192.168.1.1,Pod 子网 = 10.244.1.0/24
节点2:宿主机 IP = 192.168.1.2,Pod 子网 = 10.244.2.0/24

flanneld 在节点1 的路由表中写入:

1
2
3
[root@node1 ~]# ip route
10.244.1.0/24 dev cni0 proto kernel scope link src 10.244.1.1   # 本机 Pod 子网(直连)
10.244.2.0/24 via 192.168.1.2 dev eth0                          # 远端 Pod 子网 → 节点2

flanneld 在节点2 的路由表中写入:

1
2
3
[root@node2 ~]# ip route
10.244.2.0/24 dev cni0 proto kernel scope link src 10.244.2.1   # 本机 Pod 子网(直连)
10.244.1.0/24 via 192.168.1.1 dev eth0                          # 远端 Pod 子网 → 节点1

节点1 的 Pod(10.244.1.5)访问节点2 的 Pod(10.244.2.8),完整路径如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
发送侧(节点1):
  10.244.1.5 → veth → cni0 → 匹配 10.244.2.0/24 via 192.168.1.2
  → 下一跳 = 192.168.1.2(on-link)→ ARP 拿节点2 eth0 的 MAC
  → 封 L2 帧(dst MAC = 节点2 eth0)→ 经 eth0 发出,IP 不变

接收侧(节点2):
  eth0 收到 → 剥 L2 → 目的 IP = 10.244.2.8
  → 匹配本机直连路由 10.244.2.0/24 dev cni0(无 via,on-link)
  → ARP 拿 10.244.2.8(目的 Pod)的 MAC
  → 封 L2 帧(dst MAC = Pod MAC)→ 交给 cni0 网桥
  → cni0 查 CAM 表(MAC → 端口)→ 转发到对应 veth → 容器

整条路径无任何隧道封装,IP 头从源 Pod 到目的 Pod 全程不变,每跳只换 L2 帧的 MAC(via 机制见 2.2.2 的知识科普)。

同节点通信更简单:10.244.1.5 访问本机 10.244.1.6,包到 cni0 后查 CAM 直接转到同一网桥上的 veth,完全不进路由表。

3. 流量加密

Flannel 的 VXLAN/UDP 封装默认不加密,跨不可信网络(如公网)时报文可被窃听。Flannel 提供两种加密后端:

  • WireGuard: 当前推荐方案,内核态加密,性能开销小。每个节点配置一对密钥,节点间流量在 VXLAN 封装之外再套一层 WireGuard 隧道。
  • IPSec: 早期方案,用 strongSwan 在节点间建立 IPSec 隧道,已标记 deprecated,性能差,不建议新部署。

加密只作用于跨节点流量;同节点 Pod 通信走 cni0 网桥、不离开宿主机,无需加密。

4. 总结

维度UDPVXLANHost-GW
封装开销28B50B0
封装位置用户态内核态无封装
性能最差良好最高
部署要求无特殊要求内核支持 VXLAN节点 L2 互通
适用场景仅演示原理公有云、虚拟化环境,生产通用同 L2 广播域的自建机房
控制方式flanneld 进程转发VTEP 内核隧道路由表

VXLAN 是 Flannel 的默认和推荐模式。UDP 模式仅作演示理解原理,Host-GW 在不跨网段的场景下性能最优。

参考链接

Comparing CNI providers – Flannel

分享

Hex
作者
Hex
CloudNative Developer