UDP 模式之所以能工作,依赖于一个关键内核特性:TUN 设备(虚拟隧道网络接口)。TUN 是 Linux 内核提供的一种虚拟网络设备,它与物理网卡的区别在于:物理网卡的”另一端”是网线,而 TUN 设备的”另一端”是一个用户态进程的文件描述符——用户态进程可以从 TUN 设备读取内核送来的原始 IP 数据包,也可以向 TUN 设备写入 IP 数据包,内核会把它当作真实收到的网络包处理。
核心概念
TUN(三层隧道)和 TAP(二层隧道)是 Linux 两种不同的虚拟网络设备。TUN 工作在 L3(IP 层),用户态读写的是 IP 数据包;TAP 工作在 L2(以太网层),读写的是以太网帧。Flannel UDP 模式使用 TUN 设备,因为它处理的是 IP 数据包的封包/解包。OpenVPN 也使用 TUN 设备实现 VPN 隧道。
2.2 UDP 模式的数据包路径
设有两个节点:
Node A:IP 192.168.1.10,Pod A:IP 10.244.0.2
Node B:IP 192.168.1.11,Pod B:IP 10.244.1.3
Pod A 向 Pod B 发送一个数据包,在 UDP 模式下经历以下步骤:
步骤 1:Pod A 发出数据包
Pod A 的 eth0(10.244.0.2)发出目标 IP 为 10.244.1.3 的数据包。根据 Pod A 内的路由表,默认路由走 cni0 bridge(10.244.0.1)。数据包经过 veth pair,到达 Node A 的 cni0。
步骤 2:Node A 内核路由查找
Node A 的路由表中有一条由 flanneld 写入的路由:10.244.1.0/24 via flannel0 dev flannel0(flannel0 是 TUN 设备)。内核将数据包交给 TUN 设备 flannel0。
步骤 3:flanneld 从 TUN 读取原始包
由于 flannel0 是 TUN 设备,“另一端”的 flanneld 用户态进程通过 read(fd) 读取到这个原始 IP 数据包(src=10.244.0.2, dst=10.244.1.3)。
步骤 4:flanneld 封包为 UDP
flanneld 查询路由表:目标 10.244.1.3 属于 Node B 管理的子网 10.244.1.0/24,Node B 的公网 IP 是 192.168.1.11。flanneld 将原始 IP 包作为 UDP payload,封装为:
VXLAN(Virtual eXtensible Local Area Network,虚拟可扩展局域网)是 IETF RFC 7348 定义的网络隧道协议。它诞生于数据中心网络虚拟化的需求:在物理三层(L3)网络之上,构建虚拟的二层(L2)网络,让 VM 可以跨机架、跨数据中心”看起来”在同一个局域网内。
这种集中式感知 + 分散式配置的模式,是 Flannel VXLAN 模式能够工作的核心机制。如果 flanneld DaemonSet 某个节点上的 Pod 崩溃,该节点将无法感知新节点的加入,可能导致与新节点 Pod 的通信中断。
# 排查 VXLAN 网络问题的常用命令# 1. 查看 flannel.1 设备ip -d link show flannel.1# 2. 查看 VTEP ARP 表(哪个 VTEP IP 对应哪个 MAC)ip neigh show dev flannel.1# 3. 查看 VTEP FDB 表(哪个 MAC 在哪个物理 IP 上)bridge fdb show dev flannel.1# 4. 查看路由表中 flannel 相关路由ip route show | grep flannel# 5. 验证某个远端节点的 FDB 记录是否正确bridge fdb show dev flannel.1 | grep <远端节点物理IP>
3.5 MTU 的问题
VXLAN 封包会增加包头开销:
VXLAN header: 8 bytes
UDP header: 8 bytes
IP header: 20 bytes
Ethernet header: 14 bytes
合计:50 bytes overhead
如果底层网络的 MTU 是 1500 bytes,那么 Flannel VXLAN 模式的有效 MTU 是 1450 bytes(1500 - 50)。这就是 /run/flannel/subnet.env 中 FLANNEL_MTU=1450 的来源。
如果 Pod 发出的 IP 包超过 1450 bytes 但没有正确设置 MTU,会发生IP 分片(IP Fragmentation)——大包被分成多个小包,在目标端重组,这是严重的性能杀手。更糟糕的情况是,如果中间路由器设置了 Don't Fragment 位,大包会被直接丢弃,导致神秘的连接超时故障(数据量小时没问题,数据量大时连接中断)。
生产避坑
Flannel 会通过 FLANNEL_MTU 配置节点和 Pod 的 MTU,但这个机制并不总是可靠的。生产中建议显式在节点的 Docker/containerd 配置中设置 MTU:
Host-GW(Host Gateway,主机网关)模式是 Flannel 性能最高的模式,它完全放弃了隧道封包,改用纯 IP 路由来实现跨节点通信。
核心思路:每个节点本身就是其他节点 Pod 子网的”网关”——当 Node A 想要把包发到 Node B 管理的 10.244.1.0/24,只需在 Node A 的路由表上加一条:10.244.1.0/24 via 192.168.1.11 dev eth0(即,通过物理网卡 eth0,把包直接发给 Node B 的物理 IP)。数据包到达 Node B 后,Node B 根据本地路由表转发到对应 Pod。
这个方案的精妙之处在于:数据包不需要任何封包/解包,内外层 IP 只有一层(Pod IP 就是最外层 IP),完全等同于原生 IP 路由。
10.244.0.0/24 dev cni0 proto kernel scope link src 10.244.0.1 # 本节点 Pod 子网,直连
10.244.1.0/24 via 192.168.1.11 dev eth0 # Node B 的 Pod 子网
10.244.2.0/24 via 192.168.1.12 dev eth0 # Node C 的 Pod 子网
整个过程没有任何封包,数据包的 IP header 始终是 src=10.244.0.2, dst=10.244.1.3,完全满足 Kubernetes 的”无 NAT”约束。
4.3 Host-GW 模式的关键限制
Host-GW 要求所有节点必须在同一个二层网络(L2 域,即同一子网)。
原因是:Host-GW 路由表中的下一跳是其他节点的物理 IP(如 192.168.1.11),而 Linux 路由只能将数据包发往与本机直接连通的下一跳——即同一二层网络内的地址。如果 Node A(192.168.1.10)和 Node B(10.0.0.11)在不同的子网,via 10.0.0.11 这条路由就无法直接生效,因为 Node A 的 ARP 找不到 10.0.0.11(不在同一以太网段)。
这个限制在以下场景中成为阻碍:
云厂商多可用区部署:不同可用区的节点通常在不同的 VPC 子网,天然是不同 L2 域
混合云/跨数据中心集群:节点必然跨越多个 L3 网络
反之,在以下场景中,Host-GW 是完美方案:
裸金属集群:所有节点接同一台 ToR 交换机,同一 VLAN
同一云厂商同一子网的节点:满足二层互通条件
4.4 三种模式的性能对比与选型
graph LR
classDef udp fill:#ff5555,stroke:#ff5555,color:#f8f8f2
classDef vxlan fill:#ffb86c,stroke:#ffb86c,color:#282a36
classDef hostgw fill:#50fa7b,stroke:#50fa7b,color:#282a36
A["UDP模式<br/>~2-3 Gbps<br/>用户态封包"]
B["VXLAN模式<br/>~7-8 Gbps<br/>内核态封包"]
C["Host-GW模式<br/>~9+ Gbps<br/>原生路由"]
A -->|"改进: 封包下沉内核"| B
B -->|"改进: 取消封包"| C
class A udp
class B vxlan
class C hostgw
维度
UDP 模式
VXLAN 模式
Host-GW 模式
数据面
用户态(TUN + flanneld)
内核(VTEP 设备)
内核(纯路由)
封包 overhead
较大(UDP 封包)
中等(VXLAN 50B)
无
MTU 损耗
~50 bytes
~50 bytes
0
跨子网支持
✅
✅
❌(需要 L2 互通)
CPU 使用
最高(用户态处理)
中等
最低
延迟
最高
中等
最低
适用场景
兼容性最高,性能不敏感
主流选择,云环境通用
裸金属、同 L2 域节点
第 5 章 Flannel 的 iptables 规则与 IP Masquerade
5.1 出集群流量的 NAT 问题
前面讨论的都是 Pod 间通信。当 Pod 需要访问集群外部网络(如公网 API、外部数据库)时,存在一个关键问题:Pod IP(10.244.x.x)是集群内部的私有地址,出了集群没有任何意义——外部网络不知道如何把响应包路由回来。
解决办法是 IP Masquerade(SNAT,源地址转换):当 Pod 的数据包经过节点网卡出集群时,将源 IP 从 Pod IP 替换为节点的公网 IP。这样外部网络看到的请求来自节点的公网 IP,响应也会发到节点,节点再通过 conntrack 找到对应的 Pod,将响应包的目标 IP 改回 Pod IP(DNAT)。
什么是 NetworkPolicy? 它是 Kubernetes 定义的网络访问控制策略,允许用户声明”Pod A 只能接受来自 Pod B 的 TCP 80 端口流量”。如果没有 NetworkPolicy,集群中所有 Pod 之间默认是全互通的——任何 Pod 可以访问任何其他 Pod 的任意端口,这在多租户或安全敏感场景中是不可接受的。