新节点池 CNI MTU 配置漂移叠加 ICMP 被过滤,造成“小请求正常、大响应卡死”的跨节点黑洞。
某业务集群新增一个节点池后,部分服务出现间歇性超时。典型表现是:小请求正常,返回体较大时卡住;跨节点调用 P99 飙升,入口出现 504;同一个节点上的 Pod 互访完全正常。TLS 握手有时也会卡在证书传输阶段,看起来像应用或证书问题。
排查时发现几个互相矛盾的信号:DNS 解析正常,TCP 端口可以连通,ping 小包也通;但 curl -v 在 TLS 握手或响应体传输阶段停住,ss -ti 显示大量重传。受影响的服务只分布在部分节点上,而且故障方向不对称:由新节点池中的 Pod 发出的报文容易失败,由旧节点池中的 Pod 发出的报文基本正常。
在发送端抓包能看到大包已经发出,接收端却收不到;同时链路上没有出现对应的 ICMP “fragmentation needed”。这几个现象组合在一起,已经很像路径 MTU 发现被黑洞化。
| 时间 | 事件 |
|---|---|
| T+0 | 新节点池上线约十分钟后,跨节点接口超时告警 |
| T+5m | 应用日志只有 upstream timeout,先按应用和下游依赖排查 |
| T+10m | DNS、Ingress、Service 均正常,同节点 Pod 互访正常,怀疑点收窄到跨节点链路 |
| T+16m | 用 ping -M do -s 逐级测试,1472 字节失败、1400 字节成功 |
| T+22m | 两端抓包确认大包在接收端缺失,且没有 ICMP fragmentation needed |
| T+30m | 对比两批节点的 CNI MTU,发现新池为 1500、旧池为 1450 |
| T+38m | 修正新池 CNI MTU,并在隧道层增加 MSS clamp |
| T+50m | 大包跨节点恢复,504 消失 |
| T+70m | 全量节点核对 MTU,重启相关 Pod 使 veth 生效 |
| T+2h | 增加节点启动自检和跨节点大包探测 |
新节点池承载了约四分之一的服务副本,跨节点大包流量在约五十分钟内不稳定。小于路径 MTU 的请求和健康检查大多正常,因此流量仍会被负载均衡打到这些 Pod 上,形成“健康检查通过、真实请求失败”的错配。入口 504、客户端重试和下游超时叠加,放大了故障观感。
同节点访问和旧节点池之间的访问不受影响,导致故障看起来像随机发生,定位成本很高。没有持久化数据丢失,但部分请求可能被重试多次,需要确认业务幂等性;长连接和 TLS 会话恢复也出现了额外失败。
最初的判断是应用超时或 DNS 问题。理由是只有部分请求失败,日志里是 upstream timeout,而且 DNS 解析、端口连通性都正常。ping 小包成功进一步强化了“网络没问题”的结论。
真正推翻这个判断的证据有四条:
ping -M do -s 1472 失败、-s 1400 成功。 这直接把可用路径 MTU 卡在 1428 到 1499 之间(约 1450),和 VXLAN 封装开销吻合。另一个干扰项是告警前后有 CNI Pod 重启。团队一度把它当成根因,但重启后问题依旧,而且 CNI Pod 重启本身可能是因为无法连接 apiserver,属于伴随现象。
直接原因是新节点池的 CNI MTU 配置漂移。旧节点池按 VXLAN 封装把 Pod 网络 MTU 设为 1450,新节点池的模板却使用了 1500。底层网卡 MTU 是 1500,新节点上的 Pod 发出 1500 字节报文,经 VXLAN 封装后约为 1550 字节,超过底层链路可承载的大小,被静默丢弃。
路径 MTU 发现依赖 ICMP type 3 code 4(fragmentation needed)通知发送端减小报文,但安全组和边界防火墙没有放行这类 ICMP,形成了 PMTUD 黑洞。发送端收不到反馈,TCP 继续按 1500 MTU 对应的 MSS(1460)重传,表现为“连接建立成功、传输大包时卡死”。
促成条件包括:
tcp_mtu_probing,ICMP 被过滤后缺少后备探测手段。ping 和端口探测,没有做跨节点大包传输测试;小包探测对 MTU 问题天然不敏感。需要强调:MTU 不是“越大越好”。覆盖网络必须按封装方式扣除开销,VXLAN 通常扣 50 字节,IP-in-IP 扣 20 字节,WireGuard 等加密方式还要更多;底层网络如果启用了 jumbo frame,两端也必须一起调整。
先确认路径 MTU,再改配置。以下命令在 Pod 内执行;如果镜像没有 ping,可以用临时调试容器:
# 从新节点池的 Pod 测旧节点池的 Pod
kubectl exec -it <pod-new> -- ping -M do -s 1472 -c 3 <pod-old-ip>
kubectl exec -it <pod-new> -- ping -M do -s 1400 -c 3 <pod-old-ip>
kubectl exec -it <pod-new> -- tracepath <pod-old-ip>
# 查看 Pod 网卡 MTU
kubectl exec -it <pod-new> -- ip link show eth0
kubectl exec -it <pod-old> -- ip link show eth0
在节点上核对隧道和 CNI 配置:
ip -d link show vxlan.calico
ip -d link show cilium_vxlan
ip -d link show flannel.1
cat /run/flannel/subnet.env
# 按所用 CNI 选择对应命令
kubectl -n kube-system get cm cilium-config -o jsonpath='{.data.mtu}'
calicoctl get felixconfiguration default -o yaml | grep -i mtu
修复时把所有节点池的 CNI MTU 统一为“底层 MTU 减去封装开销”。以 1500 底层 + VXLAN 为例,Pod MTU 设为 1450;如果底层是 9000,也要保证所有节点、交换机和云网络路径一致。修改 CNI 配置后,重启对应节点上的 CNI Pod,并确认 veth 和隧道接口的 MTU 都已更新;已有 Pod 可能需要重建才能拿到新的 veth MTU。
在隧道层增加 MSS clamp 作为兜底,范围限定在 Pod 网段,避免影响其他流量:
sudo iptables -t mangle -A FORWARD -s <pod-cidr> \
-p tcp --tcp-flags SYN,RST SYN -j TCPMSS --set-mss 1400
同时放行必要的 ICMP,至少允许 type 3 code 4 到达发送端;安全组、边界防火墙和网络策略都要检查。临时可以在受影响节点开启 TCP MTU 探测:
# tcp_mtu_probing 是 per-netns 的;节点上设置不会影响 Pod
sudo nsenter -t <pod-pid> -n sysctl -w net.ipv4.tcp_mtu_probing=1
这只是缓解手段,不能替代正确的 MTU 配置。验证时要同时测试两个方向的大包,而不是只从一个方向 ping:
kubectl exec -it <pod-new> -- curl -sS -o /dev/null -w '%{http_code} %{size_download}\n' \
http://<service-old>/large-response
kubectl exec -it <pod-old> -- curl -sS -o /dev/null -w '%{http_code} %{size_download}\n' \
http://<service-new>/large-response
确认 504 消失、重传率回落、两个方向的 MTU 探测都通过后,再逐步恢复流量。
tcp_mtu_probing=1 作为兜底(节点级 sysctl 不作用于 Pod),同时保留正确的 MTU 配置。合上页面,先回答,再展开对照。
小程序版
微信扫码,随时查命令