跨节点容器通信的大多数丢包和性能劣化,根因往往不是网络带宽或CPU,而是宿主机与容器网卡之间MTU不一致引发的分片问题把宿主机MTU改成1450通常就能解决一大半。
容器网络里MTU为什么总在“打架”
容器跑在宿主机上,但网络链路却要穿过好几层设备,你的Pod内网卡、宿主机物理网卡、交换机、对端服务器物理网卡,任何一层的MTU数值不统一,就会出问题。
Kubernetes集群中常见的CNI插件,比如Flannel VXLAN模式、Calico VXLAN或IPIP封装,都会在原始数据包外面再套一层新头部,以VXLAN为例,额外增加的封装开销是50字节,如果宿主机物理网卡MTU保持默认的1500,VXLAN封装后的数据包总大小就会变成“原始数据+50字节”,直接超出1500的限制。
行业共识认为,使用VXLAN或IPIP封装时,容器网卡的MTU应该设置为宿主机MTU减去50字节,多数情况下也就是1450,但很多刚上手容器网络的运维同学,常常忘记调整这一层,Pod内的MTU依然是1500。
这会造成一个具体场景:集群里两个节点上的Pod互相访问,应用层发了一个接近1500字节的包,容器网卡放行,到了宿主机要VXLAN封装时发现超了,只能分片,分片动作本身消耗CPU,更麻烦的是,分片后的数据包如果遇到防火墙丢弃分片包或某些网络设备禁止分片,就直接丢包。
容器网络丢包是什么原因:先查MTU再看封装
排查跨节点容器通信问题,我的建议是先把MTU检查放到“嫌疑名单”前三位,尤其是消息体稍大、频繁超时的场景。
用实际场景描述问题
假设你有一个Kubernetes集群,两个工作节点各跑着一个Nginx Pod,测试从节点A的Pod curl节点B的Pod的Service地址,小请求正常,大请求超时或者响应极慢,抓包后发现,宿主机物理网卡上有大量ICMP Fragment Needed消息。
这个现象基本就把矛头指向MTU不一致了,宿主机觉得包太大,要分片,但上游设备(通常在云环境里)又回了ICMP错误告诉它“不能分片”,TCP连接会反复重传,表现就是慢,慢到怀疑人生。
一步步定位MTU问题
以下是可操作的具体排查路径:
- 进入Pod后查看容器网卡MTU:
kubectl exec -it <pod-name> -- ip link show eth0 - 登录宿主机查看物理网卡MTU:
ip link show <物理网卡名>或ethtool <物理网卡名> | grep -i mtu - 查看CNI配置里给的MTU默认值:
kubectl get cm -n kube-system kube-flannel-cfg -o yaml(Flannel项目)或查阅Calico的ippool配置
对比三层MTU值,如果出现容器1500、宿主机1500、但隧道接口1450这种“两头宽中间窄”的情况,问题就定位了。
用ping命令验证
可以直接用带DF标志的大包ping测试,这比抓包快得多:
- 从Pod内ping对端Pod IP,指定不分片并设置包大小1450字节:
ping -M do -s 1450 <对端Pod IP>(1450字节payload加28字节ICMP头,总大小1478) - 然后再试1500字节:
ping -M do -s 1472 <对端Pod IP>(1472+28=1500) - 如果1452字节的payload能通、1472字节的payload不通,说明链路MTU瓶颈就在1500以内
这个操作路径简洁有效,能快速验证是不是MTU问题。
Kubernetes MTU不一致怎么解决:按CNI插件分别处理
分片是妥协,不是解决方案,真正解决问题是统一各层MTU,下面按常见CNI插件给出具体做法。
Flannel VXLAN模式
Flannel会创建名为flannel.1的VTEP接口,默认MTU是1475(1500-25,因为VXLAN在Flannel里用UDP封装多了50字节,但Flannel实际设置的默认值因版本而异),建议做法是修改Flannel的ConfigMap:
- 编辑ConfigMap:
kubectl -n kube-system edit cm kube-flannel-cfg - 在net-conf.json里增加或修改
"VNI": 1和"MTU": 1450字段 - 修改后逐个重启节点上的flanneld Pod,让新配置生效
如果物理网络本身支持更大的MTU(比如9000的巨型帧),可以按同样逻辑设置Host MTU 9000,容器MTU 8950。
Calico VXLAN或IPIP模式
Calico的配置更分散一点,VXLAN模式下需要修改IPPool的MTU字段:
- 查看现有IPPool:
calicoctl get ippool -o yaml - 在spec下修改
mtu: 1450 -
修改后需要滚动重启使用该IPPool的节点上的calico-node Pod
对于Calico IPIP模式,同一个IPPool里同样有mtu字段可改,另外还需要注意Calico默认创建的隧道接口名是tunl0或vxlan.calico,这些接口本身的MTU也要跟随设置。
云环境特殊场景
在公有云K8s集群里,宿主机所在的VPC网络MTU通常是1500,但部分云厂商由于隧道叠加内部网络,实际可用MTU可能只有1450或更少,据行业从业者反馈,遇到云上节点MTU不一致导致的Pod通信问题后,标准处理路径是:
- 确认云厂商文档里说明的VPC内最大MTU统计显示,多数主流云厂商在VXLAN叠加网络下推荐容器MTU设置为1450
- 按云厂商建议调整CNI配置文件里的MTU
- 验证调整后节点间大包通信恢复正常
分片是真兜底还是真坑
当MTU问题暂时没法改配置时,分片工作会由宿主机内核完成,它会将超过MTU限制的数据包切成片段,到对端再重组,听着挺无感,但分片机制对容器网络有显著副作用:
- 性能开销:分片和重组消耗CPU资源,对高频大包交互的场景影响明显
- NAT穿透问题:SNAT/DNAT场景下,分片包的端口信息只存在于第一个分片里,后续分片匹配不到NAT规则会被丢弃
- 防火墙拦截:不少安全组规则会默认丢弃分片包,尤其是非第一个分片的包
更隐蔽的是路径MTU发现(PMTUD)失效,现代Linux默认是开启PMTUD的,通过ICMP消息协商路径上能通过的最大报文,但云环境中,很多网络设备会过滤ICMP类型3代码4的报文(即“需要分片但DF标志已设置”的通知),PMTUD一失效,TCP连接就直接悬挂在黑洞里,这也解释了很多场景下大包全丢、小包正常的原因。
在容器网络里,分片本质上是不得已的兜底,不是长期方案,能通过改MTU根治的问题,不要靠分片硬扛。
数据面验证:改完怎么确认
MTU调整完了,不能光看配置,要实际跑数据验证。
验证操作清单
- 在Pod内对跨节点Pod发起大包长ping(带DF标志):
ping -M do -s 1400 <对端Pod IP>连续几十个包
- 观察宿主机物理网卡的错误计数器有没有Fragment-related错误:
ip -s link show <物理网卡> - 用curl或wget跨节点传输一个5MB以上的文件,对比传输耗时与节点间小包往返时延是否正常
- 跟踪路由:
traceroute -T -p 80 <对端Pod IP>看中间有没有跳数异常或超时
验证时长至少覆盖数分钟,有条件的话放在业务低峰期做,因为MTU问题往往是间歇性出现的,持续观察才不容易漏掉。
验证完还不行怎么办
操作都做完了还是有分片或丢包,把排查视角拉大:
- 检查宿主机iptables规则中有没有限制分片包的规则
- 检查NodePort或Service转发链路中,kube-proxy的转发模式对分片包的处理是否会引入额外问题
- 检查物理网络两端交换机的端口MTU设置是否与宿主机一致
常见问题解答
为什么把MTU设成1450而不是其他数字?
因为大多数物理网络MTU是1500,VXLAN封装头部是50字节(外部UDP+IP+MAC+VXLAN),1500减50等于1450,这个减法逻辑在所有隧道网络里都通用,封装开销是多少就减多少,Flannel VXLAN的50字节与Calico VXLAN相同,IPIP则是多20字节所以常见值是1480,如果不确定封装开销,可以看CNI插件官方文档的MTU计算章节。
Calico和Flannel的MTU设置在哪里改?
Flannel在kube-flannel-cfg这个ConfigMap的net-conf.json里改MTU字段,Calico在IPPool资源定义的mtu字段改,两者都改完后要滚动重启节点上的网络插件Pod才能使新值生效,公有云托管的K8s集群(比如ACK、TKE、EKS)则通常提供了集群级别的MTU预设,改起来会更方便一些。
分片包对跨节点容器通信性能影响有多大?
影响幅度取决于分片比例和频率,多数情况下,分片包占比越高,传输效率下降越明显,CPU占用也会增大,由于NAT场景下分片包的处理异常复杂,不少云厂商支持直接丢弃分片包来换取安全防护能力,别指望分片能救一切,老老实实把MTU调对才是根本,分片在防火墙、NAT等中间设备参与较多的链路里,往往是最先被牺牲的对象。
首发原创文章,作者:王坚,如若转载,请注明出处:https://idctop.com/article/642495.html




