记一次令人窒息的“性能优化”:别把无知当极客

上午十点半,阳光刚好打在机房外围办公室的玻璃上。正逢早高峰流量拉升的阶段,我正盯着Grafana上的大盘,手里这杯美式还没喝到一半,监控告警群直接炸了。
核心交易链路的Redis集群,P99延迟曲线像一根突然勃起的中指,直插云霄。接着就是铺天盖地的 5xx 报错,网关层的Timeout日志刷得终端根本看不清。
第一反应:Redis挂了?
切到终端,kubectl get pods -n data-layer,所有Redis Pod状态全是 RunningREADY 也是 1/1。
查看 CPU 和内存,风平浪静,连个OOM的影子都没有。
我顺手找了个应用Pod进去,直接 ping Redis的Pod IP,通的。
再用 nc -vz 6379,秒连。
但只要用K8s的 Service (ClusterIP) 去连,nc -vz 6379,直接卡死,直到超时。
Pod网络正常,Service网络瘫痪。而且只针对这一个Redis Service瘫痪。
排查到这里,我脑子里大概有底了。Kube-proxy的规则出问题了,或者底层的网络栈被动了手脚。就在我准备拉取宿主机的 iptables 规则时,群里一个刚入职没半年的“资深”DevOps同事冒泡了:
“我刚才通过Ansible推了一个内核网络优化脚本,会不会跟这个有关系?我看网上说这样能大幅降低高并发下的网络延迟。”
听到这句话,我眼皮跳了一下。直接让他把推的脚本发来看看。
不看不知道,一看血压直接飙到180。脚本里赫然躺着这么两行:

iptables -t raw -A PREROUTING -p tcp --dport 6379 -j NOTRACK
iptables -t raw -A OUTPUT -p tcp --sport 6379 -j NOTRACK

我深吸了一口气,强压着顺着网线过去砸他键盘的冲动。
这是一个极其经典的“只知其一不知其二”的愚蠢操作。
这位同事大概是看了某篇不知哪年哪月的“高并发调优指南”,知道Linux内核的 nf_conntrack(连接跟踪)表在高并发下容易被打满,导致 nf_conntrack: table full, dropping packet 的丢包报错,并且连接跟踪确实会消耗一点点CPU。所以他觉得,既然Redis是高频调用的内网服务,直接在 raw 表把它 Bypass 掉(NOTRACK),不就能榨干最后一点性能了吗?
听起来很极客,对吧?但他根本没搞懂Kubernetes的网络基石是什么。
让我用最底层的逻辑来拆解一下,为什么在这个技术点上犯错是不可原谅的。
在Linux的Netfilter数据包处理流水线中,生命周期是这样的:
raw 表 -> Connection Tracking -> mangle 表 -> nat 表 -> 路由决策 -> filter
Kubernetes的Service(这里指ClusterIP)是基于DNAT(目标地址转换)实现的。不管你底层是 iptables 模式还是 IPVS 模式,Kube-proxy都需要拦截发往ClusterIP的数据包,并将其目标IP修改为后端真实的Pod IP。
这个动作,发生在哪里?发生在 nat 表。
重点来了:Linux内核的 nat 表是强依赖于连接跟踪(conntrack)的。
nat 表只处理一条连接的第一个数据包(状态为 NEW)。一旦第一个包完成了地址转换,内核会在 conntrack 表里记录下这层映射关系。后续属于这条连接的数据包,甚至回包,都会直接根据 conntrack 里的记录进行自动转换,根本不会再去走一遍 nat 表的规则。
当你自作聪明地在第一关 raw 表里加上了 -j NOTRACK
数据包带着免检金牌大摇大摆地绕过了连接跟踪机制。
紧接着,它来到了 nat 表。nat 表一看:这包没有被跟踪?那对不起,我没法处理。
于是,这个数据包直接跳过了Service IP到Pod IP的DNAT转换环节。
结果是什么?
应用端发往 10.96.x.x (ClusterIP) 的SYN包,带着原始的目的地址进入了底层的网络插件(Calico/Flannel),路由器一看,这个IP根本不在我的Pod网段路由表里,直接黑洞丢弃。
应用端永远等不到ACK,直到连接超时,业务全盘崩溃。
你在K8s环境里,把Kube-proxy赖以生存的底座给抽了,还美其名曰“性能优化”。这就好比嫌汽车发动机发热,所以把冷却液全抽干一样荒谬。
我切到生产机,两行命令把这该死的规则删了:

iptables -t raw -D PREROUTING -p tcp --dport 6379 -j NOTRACK
iptables -t raw -D OUTPUT -p tcp --sport 6379 -j NOTRACK

回车敲下的瞬间,监控大盘上的延迟曲线断崖式下跌,5xx报错清零,早高峰的流量重新平稳涌入。
在这个行业干了三十年,我见过无数华丽的故障,大多源于对底层原理的无知和对网文盲目的崇拜。
技术结论:
在Kubernetes集群或任何重度依赖NAT/SNAT/DNAT的网络架构中,严禁对业务端口使用 NOTRACK
如果你真的遇到了 conntrack 瓶颈:
1. 请去调大 net.netfilter.nf_conntrack_max 并配合增加相应的哈希表大小 (hashsize)。
2. 缩短TIME_WAIT的跟踪超时时间 (net.netfilter.nf_conntrack_tcp_timeout_time_wait)。
3. 如果真的到了千万级并发连接,不要试图通过魔改内核网络栈来硬抗,去重构你的应用架构,引入本地缓存或者连接池复用。
永远记住:架构是一个精密的齿轮组,任何一行底层的参数修改,都必须建立在对整个数据流转路径的绝对掌控之上。没有这种敬畏心,你敲下的每一个回车,都是一颗定时炸弹。