记一次早高峰的TCP黑洞:当“安全合规”遇上网络文盲

今天上午10点15分,正值业务早高峰,监控大盘上的QPS曲线还在往上爬。我手里的咖啡还没喝完,Prometheus的报警就响了:好几个核心微服务的接口响应时间P99直接飙到了5秒以上,甚至开始大面积出现 504 Gateway Timeoutcontext deadline exceeded
我看了一眼底层资源面板,CPU使用率不到30%,内存充足,磁盘IO安静得像死海。再切到K8S集群层面的监控,Pod没有发生驱逐,OOM指标也是0。
不是资源瓶颈。直觉告诉我,底层网络出事了。
我挑了一台报错最密集的K8S Worker节点,直接SSH登上去。进入报错微服务的网络命名空间,用 curl 测试下游接口。现象很诡异:请求小载荷的 /health 接口,瞬间返回;但请求带有几KB大载荷的业务接口,直接挂起(Hang死),直到超时。
小包能通,大包必死。这是教科书般的MTU(最大传输单元)不匹配导致的TCP黑洞现象。
我随手敲了一行抓包命令:

tcpdump -i any -nn -S -v host 10.244.15.12

屏幕上疯狂滚动的输出印证了我的猜想。TCP三次握手(SYN, SYN-ACK, ACK)极其顺畅,但随后推送业务数据的大包(长度1514)发出去之后,全都没有收到ACK响应,接着就是无止境的 TCP Retransmission
我退回宿主机,看了一眼路由和网卡配置:

ip link show | grep -E "mtu|tunl"

当前K8S集群用的是Calico的IPIP模式。因为宿主机的物理网卡 eth0 MTU是默认的1500,IPIP封装需要额外增加20字节的外部IP头,所以Calico自动创建的隧道网卡 tunl0 的MTU应该是1480。
但离谱的事情出现了:我在那些业务Pod里用 ip link 查看,容器内 eth0(veth pair的一端)的MTU居然被硬生生改成了 1500!
更离谱的是,即使Pod的MTU被错误地设置成了1500,当这个1500字节的包被路由到宿主机的 tunl0(1480)时,由于TCP包默认带有 DF(Don’t Fragment,不可分片)标志,Linux内核协议栈理应丢弃这个包,并向发送方(Pod)回复一个 ICMP Type 3 Code 4(Fragmentation Needed)的消息。发送方收到这个ICMP消息后,会触发PMTUD(路径MTU发现)机制,自动调小后续发送包的MSS(最大报文段长度),问题也就自动修复了。
可是,这个ICMP回包去哪了?
我调出宿主机的iptables规则,看到了让我血压瞬间拉满的一幕:

iptables -nvL FORWARD | grep icmp

赫然存在一条:

 120M  10G DROP       icmp --  *      *       0.0.0.0/0            0.0.0.0/0

所有的ICMP包,在FORWARD链上被无差别、全局DROP了。数据包高达10GB,说明它已经在这里默默干掉无数个原本能拯救网络的ICMP消息。
我把昨天刚入职不久的那个高级运维拉到了屏幕前。
经过五分钟的对峙,破案了。
昨天下午临近下班,这位老兄收到了一份所谓“安全合规扫描工具”生成的报告,提示集群节点响应了ICMP Timestamp Request,有极低风险的信息泄露可能。为了图省事,他直接写了个Ansible Playbook,在所有节点的FORWARD链和INPUT链上加了全局DROP ICMP的规则。
这还没完。今天早上9点,有开发反映内部文件上传接口变慢(其实就是因为丢ICMP导致重传)。这位天才不仅没有排查防火墙,反而去百度了一篇不知道是2008年还是2010年的“网络调优”博客,认为是容器MTU太小导致分包损耗性能,于是又推了个DaemonSet,用 nsenter 暴力把所有运行中Pod的网卡MTU刷成了1500。
完美。他用两次愚蠢的操作,亲手打造了一个坚不可摧的TCP黑洞。
第一步,修改Pod MTU为1500,导致超出 tunl0 1480 的限制,触发内核由于DF位存在的强制丢包。
第二步,全局DROP ICMP,彻底瞎了TCP协议栈的眼睛,把PMTUD机制连根拔起,让发送方永远等不到那句“你需要分片”的通知,只能在超时和重传中耗死连接。
我冷着脸,用两行命令收拾了这个烂摊子:

# 删掉那条极其业余的防火墙规则
iptables -D FORWARD -p icmp -j DROP
# 恢复正确的防探测规则(如果真要过合规,也是精细控制类型)
iptables -A FORWARD -p icmp --icmp-type timestamp-request -j DROP

并让他立刻停掉那个暴力刷MTU的DaemonSet,重启关联的Pod恢复Calico分配的默认1480 MTU。两分钟后,监控大盘上的报错断崖式下跌,QPS重回巅峰,早高峰的危机解除了。
在这个行业干了这么多年,我对各种诡异的Kernel Bug都能一笑而过,但唯独对这种缺乏底层敬畏心的操作无法容忍。
TCP/IP协议栈是一个精密咬合的齿轮组。在没有彻底搞懂IPIP隧道封装的20字节开销(Outer MAC + Outer IP)与内层数据包边界的关系之前,去碰网卡MTU无异于蒙眼走钢丝。而在现代数据中心网络里,把ICMP视作洪水猛兽并无脑DROP,不仅是对RFC 1191(PMTUD)的无知,更是对Linux内核网络设计的侮辱。
记住,网络安全不是靠把协议栈的嘴堵上建立的。搞不懂底层数据包的流转路径,敲下的每一行 DROP,最终都会化作系统崩溃时流下的泪。