你的集群跑了三个月,一切正常。某天上午,有人报告:“kubectl exec 能进 Pod,但 scp 往 Pod 里传文件就卡住。” 你 ping 了一下,通的。curl 一个小接口,也通的。但只要请求体超过某个大小,连接就像被黑洞吞掉——没有报错,没有超时提示,TCP 就是不动了。
你花了两天排查应用层、Service、Ingress、CNI 版本,最后发现:某台节点的物理网卡 MTU 被运维脚本改成了 1480,而 VXLAN overlay 需要 50 字节的封装头,于是 Pod 的实际可用 MTU 只剩 1430。超过这个大小且设置了 DF(Don’t Fragment)标志的包,在中间链路上被静默丢弃——因为中间的防火墙把 ICMP “Fragmentation Needed” 也过滤掉了。
这就是 MTU 问题的典型特征:小包永远正常,大包永远失败,中间没有任何明确的错误信息。
本文的目标:
- 彻底讲清楚 MTU 的底层原理,包括以太网 MTU、Jumbo Frame 和 Path MTU Discovery。
- 拆解 K8s 网络中 MTU 的层层封装计算:物理网卡、VXLAN/Geneve overlay、WireGuard/IPsec 加密。
- 分析 MTU 不匹配的各种症状和根因。
- 给出一套完整的排查方法论和工具链。
- 总结各 CNI 的 MTU 最佳实践配置。
- 动手实验:故意制造 MTU 错误,复现并修复问题。
实验环境:Ubuntu 22.04, kernel 6.5, kind v0.20+ 或两台 VM(同一 L2 子网)。CNI 使用 Flannel 或 Cilium。
一、MTU 基础:从以太网帧说起
什么是 MTU
MTU(Maximum Transmission Unit,最大传输单元)是一个网络接口在单个帧中能承载的最大 IP 数据包大小。注意两个关键细节:
- MTU 度量的是 IP 层 的大小,不包括以太网帧头(14 字节)和 FCS(4 字节)。
- MTU 是一个 每接口 的属性,不是全局的。同一台机器上不同接口可以有不同的 MTU。
标准以太网的 MTU 是 1500 字节,这个值从 1980 年代沿用至今。一个完整的以太网帧结构:
|<-- 14B -->|<------------ MTU (1500B) ------------>|<- 4B ->|
+----------+--------+--------+----------------------+--------+
| Eth Hdr | IP Hdr | TCP Hdr| Payload | FCS |
| dst+src | (20B) | (20B) | (max 1460B) | |
| +type | | | | |
+----------+--------+--------+----------------------+--------+
所以一个标准以太网帧的最大总长度是 14 + 1500 + 4 = 1518 字节。
Jumbo Frame:当 1500 不够用
在数据中心内部,1500 字节的 MTU 意味着大量数据传输时 CPU 需要处理更多的包。Jumbo Frame 把 MTU 提升到 9000 字节(有些设备支持到 9216),显著减少包数量和中断次数,提升吞吐量。
启用 Jumbo Frame 的前提条件:
# 检查网卡是否支持
ethtool -i eth0 | grep -i driver
# 查看当前 MTU
ip link show eth0 | grep mtu
# 设置 Jumbo Frame
ip link set eth0 mtu 9000
# 持久化(Ubuntu netplan 示例)
cat /etc/netplan/01-netcfg.yamlnetwork:
version: 2
ethernets:
eth0:
mtu: 9000
dhcp4: trueJumbo Frame 的硬性要求:整条链路上所有设备(网卡、交换机、路由器)的 MTU 必须一致。任何一个环节的 MTU 小于 9000,Jumbo Frame 就会导致丢包。这在实践中是最大的部署障碍。
Path MTU Discovery(PMTUD)
端到端的网络路径可能经过多个不同 MTU 的链路段。Path MTU(路径 MTU)就是这条路径上所有链路段中最小的 MTU 值。
PMTUD(RFC 1191 / RFC 8201)的工作机制:
- 发送方在 IP 头中设置 DF(Don’t Fragment)标志。
- 如果某个中间路由器发现包大小超过其出接口的 MTU,由于 DF 标志禁止分片,路由器丢弃该包。
- 路由器向发送方返回一个 ICMP “Destination Unreachable, Fragmentation Needed”(Type 3, Code 4)消息,消息中携带该链路的 MTU 值。
- 发送方收到 ICMP 后,缩小发送的包大小,重新尝试。
- 重复以上过程,直到包能顺利通过整条路径。
发送方 路由器 (MTU=1400) 接收方
|------- 1500B, DF=1 ------->| |
| | X 包太大,不能分片 |
|<-- ICMP: need frag, mtu=1400 --| |
| |
|------- 1400B, DF=1 ------------------------------------------>|
| OK! |
这套机制在理想情况下工作得很好。但现实中有一个致命问题——
PMTUD 黑洞
很多防火墙、安全组、网络设备会过滤 ICMP 消息。一旦 ICMP “Fragmentation Needed” 被丢弃,发送方永远不知道路径上有一个更小的 MTU,它会不断重传同样大小的包,包不断被中间设备丢弃——形成一个 PMTUD 黑洞。
黑洞的典型表现:
- TCP 三次握手成功(SYN/SYN-ACK 包很小,不受影响)。
- 小的 HTTP 请求/响应正常。
- 一旦传输较大的数据(文件下载、gRPC streaming、大 JSON 响应),连接就卡住。
netstat看到连接状态是 ESTABLISHED,但发送队列 (Send-Q) 持续增长。
在云环境中这个问题尤其常见:
- AWS 的安全组默认不允许 ICMP。
- GCP 的防火墙需要显式放行 ICMP。
- 跨 VPC、跨 Region 的链路上,中间可能有多层 NAT 和防火墙。
RFC 4821 定义了 Packetization Layer PMTUD(PLPMTUD),通过 TCP 探测而非 ICMP 来发现路径 MTU,可以绕过 ICMP 被过滤的问题。Linux 内核从 5.x 开始支持此特性。
二、K8s 网络中的 MTU 层层封装
Kubernetes 网络的复杂之处在于:Pod 到 Pod 的通信往往要经过多层封装,每一层都会”吃掉”一部分 MTU。如果不仔细计算,很容易出现某一层的 MTU 配置不匹配的问题。
场景一:无 Overlay(BGP 直连 / host-gw)
这是最简单的情况。Calico BGP 模式、Flannel host-gw 模式下,Pod 的流量直接通过路由表转发,不经过任何隧道封装。
Pod veth MTU = 物理网卡 MTU = 1500
没有额外封装开销,Pod 能使用完整的 1500 字节 MTU。但前提是所有节点在同一个 L2 广播域内(host-gw),或者底层网络支持 BGP 路由传播(Calico BGP)。
场景二:VXLAN Overlay
VXLAN 是最常见的 Overlay 方案,Flannel、Calico VXLAN 模式、Cilium VXLAN 模式都使用它。VXLAN 封装会在原始包外面加上:
| 头部 | 大小 |
|---|---|
| Outer IP Header | 20 B |
| Outer UDP Header | 8 B |
| VXLAN Header | 8 B |
| Inner Ethernet Header | 14 B |
| 总 overhead | 50 B |
Pod veth MTU = 物理网卡 MTU - VXLAN overhead
= 1500 - 50
= 1450
如果物理网卡 MTU 是 1500,Pod 的 MTU 应该设为 1450。这也是 Flannel VXLAN 模式的默认值。
验证方法:
# 查看 flannel.1 设备的 MTU
ip link show flannel.1 | grep mtu
# 输出:mtu 1450
# 查看 Pod 内部的 eth0 MTU
kubectl exec -it <pod-name> -- ip link show eth0 | grep mtu
# 输出:mtu 1450场景三:Geneve Overlay
Cilium 默认使用 Geneve 而非 VXLAN。Geneve 的头部比 VXLAN 稍大,因为 Geneve 的基础头部是 8 字节但还包含 8 字节的选项字段:
| 头部 | 大小 |
|---|---|
| Outer IP Header | 20 B |
| Outer UDP Header | 8 B |
| Geneve Header (base + options) | 16 B |
| Inner Ethernet Header | 14 B |
| 总 overhead | 58 B |
Pod veth MTU = 1500 - 58 = 1442
Cilium 默认会自动检测底层 MTU 并减去 Geneve overhead,但如果你手动配置了物理网卡 MTU 但忘记调整 Cilium 配置,就会出问题。
场景四:VXLAN + WireGuard 加密
启用 WireGuard 加密后,在 VXLAN 封装之外还要再加一层 WireGuard 封装:
| 头部 | 大小 |
|---|---|
| Outer IP Header | 20 B |
| Outer UDP Header | 8 B |
| WireGuard Header | 32 B |
| WireGuard overhead | 60 B |
总的封装开销:
Pod veth MTU = 物理网卡 MTU - VXLAN overhead - WireGuard overhead
= 1500 - 50 - 60
= 1390
场景五:VXLAN + IPsec(ESP Tunnel Mode)
IPsec ESP 模式的 overhead 更复杂,取决于加密算法和认证方式。以常用的 AES-GCM-128 为例:
| 头部 | 大小 |
|---|---|
| ESP Header (SPI + Seq) | 8 B |
| IV (Initialization Vector) | 8 B |
| ESP Trailer (Padding + Pad Length + Next Header) | 2-15 B |
| ICV (Integrity Check Value, AES-GCM) | 16 B |
| IPsec ESP overhead | 50-62 B |
加上外层 IP 头:
IPsec Tunnel 模式 overhead = 20(新 IP 头) + 8(SPI+Seq) + 8(IV) + ~7(Trailer 含 padding) + 16(ICV)
≈ 59-73 B(取决于 payload 对齐)
Pod veth MTU = 1500 - 50(VXLAN) - 73(IPsec worst case)
= 1377
各 CNI 的默认 MTU 行为
| CNI | 默认 Overlay 模式 | 默认 MTU | 自动检测 | 备注 |
|---|---|---|---|---|
| Flannel | VXLAN | 1450 | 是(基于物理网卡 MTU - 50) | 写入
/run/flannel/subnet.env |
| Calico (VXLAN) | VXLAN | 1450 | 是 | calico-node
自动计算 |
| Calico (BGP) | 无 | 1500 | 是 | 无 overlay 开销 |
| Cilium (Geneve) | Geneve | 1442 | 是 | 自动检测后减 58 |
| Cilium (VXLAN) | VXLAN | 1450 | 是 | 自动检测后减 50 |
| Cilium (native) | 无 | 1500 | 是 | 无 overlay 开销 |
| Canal | VXLAN | 1450 | 部分 | Flannel 负责 MTU |
| Weave Net | sleeve/fastdp | 1376 | 否 | 需手动配置 |
关键原则:MTU 必须端到端一致。 Pod veth MTU 必须小于等于物理网卡 MTU 减去所有封装层的 overhead。所有节点上的同类型设备 MTU 必须相同。
三、MTU 不匹配的症状与根因
MTU 问题最狡猾的地方在于它不会立即暴露。很多基础连通性测试(ping、简单 curl)因为包很小,完全不会触发 MTU 限制。只有当传输较大数据时问题才会出现。
典型症状清单
症状一:小包正常,大包超时
# 小包(默认 56+28=84 字节)正常
ping -c 3 10.244.1.5
# PING 10.244.1.5: 3 packets transmitted, 3 received, 0% packet loss
# 大包(1472+28=1500 字节,刚好触发 MTU 限制)超时
ping -c 3 -M do -s 1472 10.244.1.5
# PING 10.244.1.5: 3 packets transmitted, 0 received, 100% packet loss这里 -M do 表示设置 DF
标志,-s 1472 表示 payload 大小为 1472
字节,加上 20 字节 IP 头和 8 字节 ICMP 头,总共恰好 1500
字节。
症状二:kubectl exec 正常,但 SCP/大文件传输失败
# exec 正常(控制消息很小)
kubectl exec -it my-pod -- ls /
# 正常返回
# 复制大文件卡住
kubectl cp large-file.tar my-pod:/data/
# 卡在传输阶段,永远不完成原因:kubectl exec 的控制消息(stdin/stdout
的小块数据)远小于 MTU
限制,所以看不出问题。但文件传输会产生接近 MTU
大小的数据包。
症状三:TCP 连接建立成功,数据传输卡死
# curl 小响应正常
curl http://my-service/healthz
# OK
# curl 大响应卡住
curl http://my-service/api/large-response
# 卡住,最终超时TCP 三次握手的 SYN 包只有几十字节,不会触发 MTU 问题。但一旦开始传输数据,大的 TCP 段就会被丢弃。
症状四:gRPC streaming 断流
gRPC 使用 HTTP/2 多路复用,小消息正常但大消息(如 protobuf 编码的大对象)会触发 MTU 限制。表现为:
- 单次 RPC 调用正常(请求和响应较小)。
- Server streaming 或 bidirectional streaming 在传输一段时间后卡死。
- 客户端收到
DEADLINE_EXCEEDED或UNAVAILABLE错误。
六种常见根因
根因一:物理网卡 MTU 被意外修改
运维脚本、cloud-init 配置、DHCP 服务器下发的 MTU 选项,都可能修改物理网卡的 MTU。如果物理网卡 MTU 从 1500 变成了 1400,而 CNI 仍然按 1450 配置 Pod veth MTU,就会出现丢包。
根因二:CNI 配置错误或升级后 MTU 参数丢失
手动修改 CNI 配置时写错了 MTU 值,或者 CNI 升级后配置被重置为默认值。
根因三:混合 Overlay/非 Overlay 节点
集群中一部分节点使用 VXLAN(MTU 1450),另一部分使用 BGP 直连(MTU 1500)。跨这两种节点的通信会出问题。
根因四:启用加密后忘记调整 MTU
开启 WireGuard 或 IPsec 加密后,额外的 60-73 字节 overhead 没有反映到 Pod MTU 中。
根因五:Jumbo Frame 部分启用
节点网卡设置了 MTU 9000,但中间交换机的某个端口仍然是 1500,导致跨交换机的大包被丢弃。
根因六:云环境的隐式 MTU 限制
- AWS EC2 同一 VPC 内默认 MTU 9001,但跨 VPC Peering 或 VPN 只支持 1500。
- Azure 默认 MTU 1500,但 VNet Peering 可能有不同的限制。
- GCP 支持 MTU 1460(因为 GCP 网络内部有自己的封装)。
四、MTU 排查方法论
当你怀疑问题是 MTU 导致的,按照以下步骤系统排查。
第一步:确认症状
用不同大小的包测试连通性:
# 不设 DF,允许分片(作为基线)
ping -c 3 -s 1472 10.244.1.5
# 设置 DF,禁止分片(触发 MTU 限制)
ping -c 3 -M do -s 1472 10.244.1.5
# 逐步缩小包大小,找到临界值
ping -c 3 -M do -s 1400 10.244.1.5
ping -c 3 -M do -s 1300 10.244.1.5如果不设 DF 时正常、设了 DF 后大包丢失,就可以确认是 MTU 问题。
第二步:检查各层 MTU 值
# 1. 物理网卡 MTU
ip link show eth0 | grep mtu
# 2. Overlay 设备 MTU(VXLAN/Geneve)
ip link show flannel.1 | grep mtu # Flannel
ip link show cilium_vxlan | grep mtu # Cilium VXLAN
ip link show cilium_geneve | grep mtu # Cilium Geneve (if exists)
# 3. Pod veth pair MTU(在宿主机上查看)
# 先找到 Pod 对应的 veth
POD_PID=$(crictl inspect $(crictl ps --name <container> -q) | jq .info.pid)
nsenter -t $POD_PID -n ip link show eth0 | grep mtu
# 4. Docker/containerd 的默认 MTU
# 某些情况下 container runtime 会覆盖 CNI 设置的 MTU
cat /etc/docker/daemon.json | jq .mtu把所有值列在一起,检查是否满足关系:
Pod veth MTU <= Overlay 设备 MTU <= 物理网卡 MTU - overlay overhead
第三步:使用 tracepath 发现路径 MTU
tracepath 是比 traceroute
更适合 MTU 排查的工具,它会自动发现每一跳的 MTU:
tracepath -n 10.244.1.5 1?: [LOCALHOST] pmtu 1500
1: 10.244.0.1 0.120ms
1: 10.244.0.1 0.085ms
2: 10.244.1.1 pmtu 1450
2: 10.244.1.5 0.356ms reached
Resume: pmtu 1450 hops 2 back 2
这个输出说明路径上的 PMTU 是 1450——如果 Pod 发送大于 1450 的包(加 DF 标志),就会在第二跳被丢弃。
第四步:tcpdump 抓包分析
重点关注两件事:DF 标志和 ICMP Fragmentation Needed。
# 在物理网卡上抓 ICMP 包
tcpdump -i eth0 -nn icmp
# 在 overlay 设备上抓包,观察 DF 标志
tcpdump -i flannel.1 -nn -v 'ip[6:1] & 0x40 != 0'
# ip[6:1] & 0x40 匹配 DF=1 的包
# 在 Pod 的 network namespace 内抓包
nsenter -t $POD_PID -n tcpdump -i eth0 -nn -c 20如果你看到大包被发出但没有对应的 ICMP “Fragmentation Needed” 返回,说明 ICMP 被中间设备过滤了——这就是 PMTUD 黑洞。
# 在另一个终端触发大包
kubectl exec -it test-pod -- ping -c 1 -M do -s 1472 10.244.1.5
# 观察 tcpdump 输出
# 正常情况应该看到:
# IP 10.244.0.5 > 10.244.1.5: ICMP echo request, id xxx, seq 1, length 1480
# IP 10.244.0.1 > 10.244.0.5: ICMP 10.244.1.5 unreachable - need to frag (mtu 1450)
#
# PMTUD 黑洞情况:
# IP 10.244.0.5 > 10.244.1.5: ICMP echo request, id xxx, seq 1, length 1480
# (没有任何 ICMP 响应)第五步:检查 iptables/nftables 规则
确认没有规则丢弃 ICMP:
# 检查 iptables 规则中是否有 DROP ICMP
iptables -L -n -v | grep -i icmp
iptables -L -n -v -t mangle | grep -i icmp
# 检查 nftables
nft list ruleset | grep -i icmp
# 查看 ICMP 相关的内核统计
cat /proc/net/snmp | grep Icmp五、MTU 计算公式与最佳实践
通用公式
Pod veth MTU = 物理网卡 MTU - Overlay overhead - 加密 overhead
其中:
Overlay overhead:
无 Overlay: 0 B
VXLAN: 50 B (20 IP + 8 UDP + 8 VXLAN + 14 Inner Eth)
Geneve: 58 B (20 IP + 8 UDP + 16 Geneve + 14 Inner Eth)
IP-in-IP: 20 B (20 Outer IP)
加密 overhead:
无加密: 0 B
WireGuard: 60 B (20 IP + 8 UDP + 32 WG Header)
IPsec ESP: 62-73 B (取决于算法和对齐)
常见组合速查表
| 配置组合 | 物理 MTU 1500 | 物理 MTU 9000 |
|---|---|---|
| 无 Overlay | 1500 | 9000 |
| VXLAN | 1450 | 8950 |
| Geneve | 1442 | 8942 |
| IP-in-IP | 1480 | 8980 |
| VXLAN + WireGuard | 1390 | 8890 |
| VXLAN + IPsec | 1377-1388 | 8877-8888 |
| Geneve + WireGuard | 1382 | 8882 |
| Geneve + IPsec | 1369-1380 | 8869-8880 |
Flannel MTU 配置
Flannel 会自动检测物理网卡 MTU 并计算 overlay
overhead,结果写入
/run/flannel/subnet.env。但你也可以手动指定:
{
"Network": "10.244.0.0/16",
"Backend": {
"Type": "vxlan",
"MTU": 1400
}
}通过 ConfigMap 修改:
kubectl edit configmap kube-flannel-cfg -n kube-flannel
# 在 net-conf.json 中添加 "MTU": 1400
# 然后滚动重启 flannel DaemonSet
kubectl rollout restart daemonset kube-flannel-ds -n kube-flannel注意:修改 MTU 后,已有的 Pod 不会自动更新。需要重启 Pod 才能拿到新的 MTU 值。
Calico MTU 配置
Calico 通过 FelixConfiguration 资源控制 MTU:
# 查看当前 MTU 配置
kubectl get felixconfiguration default -o yaml | grep -i mtuapiVersion: projectcalico.org/v3
kind: FelixConfiguration
metadata:
name: default
spec:
mtu: 1450
wireguardMTU: 1390
# 或者使用自动检测
# mtuIfacePattern: "^(en|eth|bond).*"# 修改 MTU
kubectl patch felixconfiguration default --type=merge -p '{"spec":{"mtu":1400}}'Calico 的自动检测逻辑:
- 查找匹配
mtuIfacePattern的物理接口。 - 取这些接口中最小的 MTU 值。
- 根据当前的 Overlay 模式减去对应的 overhead。
- 将计算结果应用到 Pod veth 和隧道设备。
Cilium MTU 配置
Cilium 在 Helm values 或 ConfigMap 中配置 MTU:
# Helm 安装时指定
helm install cilium cilium/cilium \
--set mtu=1450 \
--set tunnel=vxlan
# 或者通过 ConfigMap
kubectl -n kube-system edit configmap cilium-config# cilium-config ConfigMap 中的相关字段
mtu: "1450"
tunnel: vxlan
enable-wireguard: "false"Cilium 的自动检测:
# 查看 Cilium 检测到的 MTU
kubectl -n kube-system exec -it ds/cilium -- cilium-dbg status | grep MTU
# 输出示例:
# KubeProxyReplacement Details:
# MTU: 1450当 mtu 设置为 0
或不设置时,Cilium 自动检测。逻辑如下:
- 检测主网络接口的 MTU。
- 如果使用 Geneve 隧道,减去 58 字节。
- 如果使用 VXLAN 隧道,减去 50 字节。
- 如果启用 WireGuard,再减去 60 字节。
- 如果使用 native routing(无隧道),直接使用物理接口 MTU。
TCP MSS Clamping:兜底方案
当你无法控制整条路径上所有设备的 MTU 时(比如跨云、跨 VPN),可以使用 TCP MSS Clamping 来避免 PMTUD 黑洞:
# 在 FORWARD 链上 clamp MSS
iptables -t mangle -A FORWARD -p tcp --tcp-flags SYN,RST SYN \
-j TCPMSS --clamp-mss-to-pmtu
# 或者指定固定值
iptables -t mangle -A FORWARD -p tcp --tcp-flags SYN,RST SYN \
-j TCPMSS --set-mss 1360MSS Clamping 的原理:在 TCP 三次握手时,修改 SYN 包中的 MSS(Maximum Segment Size)选项,使其不超过路径上最小的 MTU 减去 IP+TCP 头的大小。这样 TCP 层从一开始就不会发送过大的段,避免了后续的分片和 PMTUD 问题。
很多 CNI 插件已经内置了 MSS Clamping:
# Flannel 的 iptables 规则中包含 MSS Clamping
iptables -t mangle -S | grep TCPMSS
# -A FORWARD -o flannel.1 -p tcp -m tcp --tcp-flags SYN,RST SYN -j TCPMSS --clamp-mss-to-pmtu六、动手实验:制造 MTU 故障并修复
本节我们故意在一个 kind 集群中制造 MTU 不匹配,观察症状,然后修复。
环境准备
cat <<'EOF' > kind-mtu-lab.yaml
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
- role: worker
networking:
disableDefaultCNI: false
podSubnet: "10.244.0.0/16"
EOF
kind create cluster --name mtu-lab --config kind-mtu-lab.yaml
# 部署测试 Pod,分别调度到两个节点
kubectl run sender --image=nicolaka/netshoot --restart=Never \
--overrides='{"spec":{"nodeName":"mtu-lab-control-plane"}}' -- sleep infinity
kubectl run receiver --image=nicolaka/netshoot --restart=Never \
--overrides='{"spec":{"nodeName":"mtu-lab-worker"}}' -- sleep infinity
kubectl wait --for=condition=Ready pod/sender pod/receiver --timeout=60s
RECV_IP=$(kubectl get pod receiver -o jsonpath='{.status.podIP}')
echo "Receiver IP: $RECV_IP"验证基线
# 小包正常
kubectl exec sender -- ping -c 3 $RECV_IP
# 大包正常(VXLAN MTU=1450,payload 最大 1450-28=1422)
kubectl exec sender -- ping -c 3 -M do -s 1422 $RECV_IP
# 超过 MTU 的包失败
kubectl exec sender -- ping -c 3 -M do -s 1473 $RECV_IP制造故障
# 把 worker 节点物理网卡 MTU 改小
docker exec mtu-lab-worker ip link set eth0 mtu 1400此时 worker 的物理网卡 MTU 是 1400,但 Pod veth MTU 仍为 1450。大包在到达物理网卡时会超限。
观察症状
# 小包仍然正常
kubectl exec sender -- ping -c 3 $RECV_IP
# 大包失败
kubectl exec sender -- ping -c 3 -M do -s 1422 $RECV_IP
# 超时或丢包
# 找到临界值:1322 + 28 = 1350,加 VXLAN 50B = 1400,刚好等于物理 MTU
kubectl exec sender -- ping -c 3 -M do -s 1322 $RECV_IP抓包验证
# 在 worker 上抓 ICMP
docker exec mtu-lab-worker tcpdump -i eth0 -nn icmp -c 10 &
# 触发大包
kubectl exec sender -- ping -c 3 -M do -s 1422 $RECV_IP
# 看到 "need to frag (mtu 1400)" 说明 PMTUD 工作正常
# 看不到 ICMP 响应则说明存在 PMTUD 黑洞修复
三种修复方案,根据实际场景选择:
# 方案一:恢复物理网卡 MTU(最直接)
docker exec mtu-lab-worker ip link set eth0 mtu 1500
# 方案二:降低 Pod MTU(物理 MTU 无法修改时)
kubectl -n kube-flannel edit configmap kube-flannel-cfg
# net-conf.json 中设置 "MTU": 1350
kubectl -n kube-flannel rollout restart daemonset kube-flannel-ds
kubectl delete pod sender receiver # 重建 Pod 使新 MTU 生效
# 方案三:添加 MSS Clamping(兜底)
docker exec mtu-lab-worker iptables -t mangle -A FORWARD \
-p tcp --tcp-flags SYN,RST SYN -j TCPMSS --clamp-mss-to-pmtu# 验证修复后大包恢复正常
kubectl exec sender -- ping -c 3 -M do -s 1422 $RECV_IP
# 清理
kind delete cluster --name mtu-lab七、Jumbo Frame 在 K8s 中的实践
Jumbo Frame(MTU 9000)把单包容量提升 6 倍,传输 1 GB 数据的包数量从约 700,000 降到约 120,000,在万兆网络下通常能提升 5-15% 的吞吐量。
部署要点:
# 设置所有节点的物理网卡 MTU(需先确认交换机已启用 Jumbo Frame)
ip link set eth0 mtu 9000
# 端到端验证
ping -c 3 -M do -s 8972 <other-node-ip>
# CNI 相应调整
# Flannel VXLAN: MTU = 9000 - 50 = 8950
# Calico VXLAN: MTU = 8950
# Cilium Geneve: MTU = 9000 - 58 = 8942三个陷阱需要警惕:混合 MTU 环境(部分节点 9000、部分 1500)会导致 PMTUD 问题;大多数公有云的跨 AZ/Region 链路不支持 Jumbo Frame;Jumbo Frame 需要更大的内核缓冲区,在内存紧张的节点上可能造成压力。
八、生产环境 MTU 检查清单
部署新集群或修改网络配置后,按此清单逐项检查:
[ ] 1. 所有节点物理网卡 MTU 一致: ip link show eth0 | grep mtu
[ ] 2. CNI 的 MTU 配置正确: 检查 ConfigMap / FelixConfiguration / Cilium values
[ ] 3. Pod veth MTU 等于预期值: kubectl exec <pod> -- ip link show eth0 | grep mtu
[ ] 4. Overlay 设备 MTU 正确: ip link show flannel.1 | grep mtu
[ ] 5. 端到端大包测试通过: ping -M do -s <MTU-28> <远端 Pod IP>
[ ] 6. PMTUD 工作正常: tracepath <远端 Pod IP>
[ ] 7. ICMP 未被过滤: tcpdump -i eth0 icmp + 源节点发大包
[ ] 8. 加密 overhead 已计入 MTU: WireGuard(-60B) / IPsec(-62~73B)
[ ] 9. Jumbo Frame 全链路已启用: 包括交换机端口
[ ] 10. MSS Clamping 规则存在: iptables -t mangle -S | grep TCPMSS
自动化检查脚本:
#!/usr/bin/env bash
set -euo pipefail
echo "=== MTU 一致性检查 ==="
PHY_MTU=$(ip link show eth0 2>/dev/null | grep -oP 'mtu \K[0-9]+' || echo "N/A")
echo "物理网卡 (eth0) MTU: $PHY_MTU"
for dev in flannel.1 cilium_vxlan cilium_geneve vxlan.calico; do
if ip link show "$dev" >/dev/null 2>&1; then
echo "Overlay ($dev) MTU: $(ip link show "$dev" | grep -oP 'mtu \K[0-9]+')"
fi
done
echo ""; echo "=== veth 设备 MTU ==="
ip link show type veth 2>/dev/null | grep -E 'mtu|veth' | paste - - | awk '{print $2, $5}' | sort -u
echo ""; echo "=== MSS Clamping ==="
iptables -t mangle -S 2>/dev/null | grep TCPMSS || echo "未找到 MSS Clamping 规则"九、IPv6 与 MTU
IPv6 有两个与 MTU 直接相关的重要特性。第一,IPv6 要求所有链路的最低 MTU 为 1280 字节(RFC 8200);第二,IPv6 完全移除了路由器分片能力,只有源节点可以分片,因此 IPv6 完全依赖 PMTUD,黑洞的影响更加严重。
# IPv6 PMTUD 测试
ping6 -c 3 -M do -s 1452 fd00::1
# 查看 ICMPv6 Packet Too Big
tcpdump -i eth0 -nn icmp6 and 'ip6[40] == 2'在双栈集群中,VXLAN 的 IPv6 outer header 比 IPv4 大 20 字节(40 vs 20),因此 overhead 从 50 B 增加到 70 B,物理 MTU 1500 时 IPv6 Pod MTU 应设为 1430。双栈环境取两者较小值。
十、总结
MTU 问题是 K8s 网络中最隐蔽、最容易被忽视的故障类型之一。它的核心特征是选择性失败——小包正常、大包丢失,基础连通性测试全部通过,但实际业务流量无法传输。
一张表总结关键要点:
| 维度 | 要点 |
|---|---|
| 标准以太网 MTU | 1500 字节(IP 层) |
| Jumbo Frame MTU | 9000 字节,需全链路支持 |
| VXLAN overhead | 50 字节 |
| Geneve overhead | 58 字节 |
| WireGuard overhead | 60 字节 |
| IPsec ESP overhead | 62-73 字节 |
| 排查首选工具 | ping -M do -s、tracepath、tcpdump |
| 兜底方案 | TCP MSS Clamping |
| 核心原则 | Pod MTU = 物理 MTU - 所有封装层 overhead |
| 最大陷阱 | PMTUD 黑洞(ICMP 被过滤) |
回顾一下我们的公式:
Pod veth MTU = 物理网卡 MTU - Overlay overhead - 加密 overhead
记住这个公式,在每次修改网络配置、启用加密、切换 CNI 模式时重新计算一遍。MTU 差 1 字节就可能导致间歇性故障——而间歇性故障是所有故障中最难排查的。
下一篇我们进入 DSR(Direct Server Return):绕过 SNAT 的回程加速,看看如何通过让回程流量绕过负载均衡器来降低延迟和节省带宽。
附录 A:快速参考命令
# MTU 查看
ip link show eth0 | grep mtu
ip link show flannel.1 | grep mtu
kubectl exec <pod> -- cat /sys/class/net/eth0/mtu
# MTU 测试
ping -c 3 -M do -s 1472 <target-ip> # 标准以太网(不经过 overlay)
ping -c 3 -M do -s 1422 <target-pod-ip> # VXLAN overlay
tracepath -n <target-pod-ip>
# 抓包分析
tcpdump -i eth0 -nn icmp # 抓 ICMP(含 Fragmentation Needed)
tcpdump -i eth0 -nn 'ip[6:1] & 0x40 != 0' # 抓 DF=1 的包
# CNI MTU 配置
kubectl -n kube-flannel get configmap kube-flannel-cfg -o yaml | grep -A5 net-conf
kubectl get felixconfiguration default -o yaml | grep -i mtu
kubectl -n kube-system exec ds/cilium -- cilium-dbg status | grep MTU
# MSS Clamping
iptables -t mangle -S | grep TCPMSS附录 B:各云厂商 MTU 限制速查
| 云厂商 | 同 VPC/VNet MTU | 跨 VPC/VNet MTU | VPN/专线 MTU | 备注 |
|---|---|---|---|---|
| AWS | 9001 | 1500 (VPC Peering) | 1500 | Transit GW 支持 8500 |
| GCP | 1460 | 1460 | 1460 | GCP 内部有封装 |
| Azure | 1500 | 1500 | 1400 | VNet Peering 同 Region 1500 |
| 阿里云 | 1500 | 1500 | 1400 | 经典网络和 VPC 不同 |
附录 C:延伸阅读