土法炼钢兴趣小组的算法知识备份

MTU 调优:1 字节的差距如何搞垮整个集群

目录

你的集群跑了三个月,一切正常。某天上午,有人报告:“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 问题的典型特征:小包永远正常,大包永远失败,中间没有任何明确的错误信息

本文的目标:

  1. 彻底讲清楚 MTU 的底层原理,包括以太网 MTU、Jumbo Frame 和 Path MTU Discovery。
  2. 拆解 K8s 网络中 MTU 的层层封装计算:物理网卡、VXLAN/Geneve overlay、WireGuard/IPsec 加密。
  3. 分析 MTU 不匹配的各种症状和根因。
  4. 给出一套完整的排查方法论和工具链。
  5. 总结各 CNI 的 MTU 最佳实践配置。
  6. 动手实验:故意制造 MTU 错误,复现并修复问题。

实验环境:Ubuntu 22.04, kernel 6.5, kind v0.20+ 或两台 VM(同一 L2 子网)。CNI 使用 Flannel 或 Cilium。


一、MTU 基础:从以太网帧说起

什么是 MTU

MTU(Maximum Transmission Unit,最大传输单元)是一个网络接口在单个帧中能承载的最大 IP 数据包大小。注意两个关键细节:

标准以太网的 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.yaml
network:
  version: 2
  ethernets:
    eth0:
      mtu: 9000
      dhcp4: true

Jumbo Frame 的硬性要求:整条链路上所有设备(网卡、交换机、路由器)的 MTU 必须一致。任何一个环节的 MTU 小于 9000,Jumbo Frame 就会导致丢包。这在实践中是最大的部署障碍。

Path MTU Discovery(PMTUD)

端到端的网络路径可能经过多个不同 MTU 的链路段。Path MTU(路径 MTU)就是这条路径上所有链路段中最小的 MTU 值。

PMTUD(RFC 1191 / RFC 8201)的工作机制:

  1. 发送方在 IP 头中设置 DF(Don’t Fragment)标志。
  2. 如果某个中间路由器发现包大小超过其出接口的 MTU,由于 DF 标志禁止分片,路由器丢弃该包。
  3. 路由器向发送方返回一个 ICMP “Destination Unreachable, Fragmentation Needed”(Type 3, Code 4)消息,消息中携带该链路的 MTU 值。
  4. 发送方收到 ICMP 后,缩小发送的包大小,重新尝试。
  5. 重复以上过程,直到包能顺利通过整条路径。
发送方                    路由器 (MTU=1400)                接收方
  |------- 1500B, DF=1 ------->|                            |
  |                             |  X 包太大,不能分片        |
  |<-- ICMP: need frag, mtu=1400 --|                        |
  |                                                         |
  |------- 1400B, DF=1 ------------------------------------------>|
  |                                                    OK!  |

这套机制在理想情况下工作得很好。但现实中有一个致命问题——

PMTUD 黑洞

很多防火墙、安全组、网络设备会过滤 ICMP 消息。一旦 ICMP “Fragmentation Needed” 被丢弃,发送方永远不知道路径上有一个更小的 MTU,它会不断重传同样大小的包,包不断被中间设备丢弃——形成一个 PMTUD 黑洞

黑洞的典型表现:

在云环境中这个问题尤其常见:

RFC 4821 定义了 Packetization Layer PMTUD(PLPMTUD),通过 TCP 探测而非 ICMP 来发现路径 MTU,可以绕过 ICMP 被过滤的问题。Linux 内核从 5.x 开始支持此特性。


二、K8s 网络中的 MTU 层层封装

Kubernetes 网络的复杂之处在于:Pod 到 Pod 的通信往往要经过多层封装,每一层都会”吃掉”一部分 MTU。如果不仔细计算,很容易出现某一层的 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 限制。表现为:

六种常见根因

根因一:物理网卡 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 限制


四、MTU 排查方法论

当你怀疑问题是 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 mtu
apiVersion: 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 的自动检测逻辑:

  1. 查找匹配 mtuIfacePattern 的物理接口。
  2. 取这些接口中最小的 MTU 值。
  3. 根据当前的 Overlay 模式减去对应的 overhead。
  4. 将计算结果应用到 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 自动检测。逻辑如下:

  1. 检测主网络接口的 MTU。
  2. 如果使用 Geneve 隧道,减去 58 字节。
  3. 如果使用 VXLAN 隧道,减去 50 字节。
  4. 如果启用 WireGuard,再减去 60 字节。
  5. 如果使用 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 1360

MSS 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 -stracepathtcpdump
兜底方案 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:延伸阅读


By .