ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Linux nf_conntrack调优:容量、buckets与超时排查

Linux nf_conntrack调优:容量、buckets与超时排查

凌晨两点被电话叫起来的那种事,做过几年运维的人大概都碰过。监控页面上业务端口还活着,但新连接的成功率掉到七成,内核日志里一行行刷着nf_conntrack: table full, dropping packet。把业务进程重启一下能好十分钟,然后继续掉。真正的原因跟业务代码一点关系都没有——Netfilter 的连接跟踪表被打满了。而围绕这张表的所有参数,nf_conntrack_max、nf_conntrack_buckets、各种 timeout,恰恰是 Linux Netfilter 内核参数调优里最容易被"抄一份网上模板"糊弄过去的一块。

这篇东西想聊的就是这块。我不打算给你一份可以直接粘贴的sysctl.conf,因为那种模板九成的内容对你的业务没有意义,剩下那一成还可能因为场景不匹配把问题搞得更糟。我想讲清楚的是:这些数字背后的账是怎么算的,什么情况下该动哪个、动了之后会付出什么代价、出了问题怎么顺着链路查下去。读者画像大概是这样:手上有几台跑 NAT、网关、反向代理或者高并发短连接的机器,被连接数、丢包、CPU 软中断中的某一项折磨过,想在调参这件事上从"抄"变成"算"。如果你只是想找几条命令,这篇文章可能会让你觉得啰嗦;如果你想以后不再被同一类问题反复咬,那接着往下看。

1. 调参之前先看清:Netfilter 在数据包路径上到底插了几道手

很多人对 Netfilter 的印象停留在"iptables 就是防火墙",于是调参时脑子里只有规则条数、表链顺序这些概念。但真正吃掉你内存和 CPU 的,往往不是规则匹配,而是连接跟踪。这两件事在数据包路径上是先后关系,而且前者的开销远大于后者——不清这一点,后面所有的参数都会调错方向。

1.1 五个 hook 点对应的是五类完全不同的开销

Netfilter 在协议栈里埋了五个挂载点:PREROUTING、INPUT、FORWARD、OUTPUT、POSTROUTING。这四个表 filter、nat、mangle、raw 分布在这些点上,但开销性质完全不同。raw表在最前面,作用是"跳过连接跟踪",一旦命中NOTRACK,后面的 conntrack 就不干活了;mangle改包头的字段,涉及校验和重算;nat只在连接的第一个包上做决策,后续包靠 conntrack 里存好的映射直接转换;filter是纯匹配,规则多的时候走的是逐条比对,但现代内核有哈希化的匹配优化,规则上千条也未必是瓶颈。

真正的分水岭在于:只要数据包没有被raw表放行跳过,它就必须先进 conntrack 建一条记录。这意味着哪怕你一条 filter 规则都没写,一张空表,只要机器在转发流量,conntrack 表就在长。很多人第一次遇到table full的时候一脸茫然,因为他压根没配过任何防火墙规则——就是这个原因。

1.2 conntrack 是"先记账后过滤",账单在 filter 之前就产生了

用生活化的说法:conntrack 像小区门口的访客登记本,filter 像保安的判断。访客要先登记,保安才决定放不放行。登记本写满了,后面的人连被判断的机会都没有,直接在门口被拦下——这就是table full, dropping packet的本质,包在 conntrack 阶段就被丢了,你的 filter 规则根本没被执行。

所以从调优角度,你面对的是两层资源:记录条目的数量(内存)和查表的效率(CPU)。前者由nf_conntrack_max控制,后者由哈希桶的数量nf_conntrack_buckets控制,两者之间还有一个隐含的耦合关系,后面会细讲。而超时参数决定了每条记录在表里躺多久,等于间接控制了表的"周转率"。这三个维度构成一个三角,只动一角,另外两角就会反过来咬你。

1.3 我习惯先画一张流量图再改任何数字

在真的动手改/proc/sys/net/netfilter/下的东西之前,我会先在纸上写清楚四件事:这台机器的角色是什么(转发网关、NAT 出口、反向代理、还是纯终端服务器);流量的连接形态是什么(长连接还是短连接,新建速率大概多少,平均持续多久);有没有经过二次 NAT 或者容器网络;内存总共有多少、可以给内核网络栈分配多少。

这四件事决定了后面每一个参数的取值逻辑。举个对比例子:一台做七层反向代理的机器,并发连接几十万但新建速率不高,nf_conntrack_max要按并发数给足,超时可以放宽;一台做 NAT 出口的机器,新建连接每秒上万,但单条连接活得短,这时候max反而不是重点,buckets和time_wait超时才是关键。同一个nf_conntrack_max = 1048576放在这两台机器上,一台绰绰有余,一台可能还是会打满。参数没有绝对的好坏,只有跟流量形态配不配。

2. nf_conntrack_max 不该拍脑袋:把"同时在线连接数"算成内存账

nf_conntrack_max是所有相关文章里出现频率最高的一个数,也是最容易被随手写成1048576或者655360的一个数。我见过有人把它设成 4194304,机器内存 8G,然后抱怨内核动不动 OOM。这个参数的本质是"允许同时存在的记录条数上限",它的约束条件只有一个——内存。

2.1 一条 conntrack 记录的真实体积怎么量

不同内核版本、不同编译选项、是否开启扩展(如记账、时间戳、标签)都会影响单条记录的大小,网上流传的"约 300 字节"只是一个粗略参考。想拿到自己机器上的真实值,最直接的办法是查 slab:

grep -E '^nf_conntrack\s' /proc/slabinfo

输出里的第五列是objsize,即单个对象占用的字节数。我在几台不同发行版上看到过的值分布在 280 到 400 之间,开了nf_conntrack_acct之后会明显变大。拿到这个值,估算公式就很朴素了:

内存上限 ≈ nf_conntrack_max × objsize

比如objsize是 320 字节,max设成 1048576,理论上限就是约 320MB。注意这里是上限,不是常驻占用——内核是按需分配记录的,表里没有那么多条目就不会占那么多内存。但你必须按上限来规划,因为流量洪峰来的时候它会真的长到那么大。

2.2 用并发峰值而不是 QPS 推 max

一个常见的错误算法是"我的 QPS 是 5 万,所以 max 设 5 万"。这是把速率和存量搞混了。conntrack 表里躺的是当前存活的连接,不是每秒新建的连接。正确做法是找出业务的历史并发峰值,再留出余量:

目标 max = 并发连接峰值 × 1.5 ~ 2

余量为什么给这么多?因为超时时间的存在意味着连接关闭后记录还会残留一段时间(time_wait、fin_wait、close各有各的残留时长),实际表里的条目数总是大于业务视角看到的活跃连接数。另外突发流量、扫描流量、健康检查流量都会额外占用条目,这些在你的业务监控里通常是看不见的。

如果拿不到准确的并发数据,退一步的办法是观察/proc/sys/net/netfilter/nf_conntrack_count的历史曲线。这个文件是只读的,反映当前条目数。采集它一段时间,取 P99 或者最大值,再乘 1.5,比拍脑袋靠谱得多。

2.3 buckets 与 max 的比例,以及哈希冲突的代价

这是被忽略得最厉害的一环。conntrack 内部是一张哈希表,buckets就是桶的数量。每次查表、插入都要先算哈希落到某个桶,再在桶内的链表里线性比对。桶太少会发生什么?大量记录挤在少数几个桶里,链表变长,查找从 O(1) 退化成近似 O(n),表现出来就是ksoftirqdCPU 飙高、conntrack -S里search_restart计数上涨、网络延迟抖动。

内核的默认策略在不同版本里不太一样,早期是按max/4推算,后来有版本改成max/2甚至更大。但"默认值"在max被手工调大之后就不会自动跟着变了——这是关键的坑:你把max从 65536 调到 1048576,buckets可能还停在 16384,平均每条链路上挂着 64 条记录。

我的经验比例是buckets ≈ max / 4,在短连接高新建场景下会调到max / 2。桶本身是哈希表头数组,每个桶占一个指针(8 字节),buckets = 262144时表头内存才 2MB,可以放心加。真正限制你的是缓存局部性——桶太多会让哈希表本身撑出 CPU 缓存,所以也不是越大越好,max/2到max/4这个区间基本够用。

2.4 一份可以直接抄的容量配置

假设一台 32G 内存的 NAT 出口机,objsize实测 320,业务并发峰值 30 万,新建速率较高:

参数取值依据
nf_conntrack_max78643230 万 × 2 余量,内存上限约 240MB
nf_conntrack_buckets196608max/4,表头约 1.5MB
nf_conntrack_tcp_timeout_established1200出口 NAT 场景,配合下游保活
nf_conntrack_tcp_timeout_time_wait30短连接多,压缩残留
nf_conntrack_udp_timeout30保持默认即可

这张表里的每一个数字都有推导过程,换一台机器就要重算,不能照抄。尤其是established那一项,下面单独说。

3. 超时时间是藏在幕后的第二个容量开关

把max调大是最容易想到的办法,但它有一个前提:你得有足够的内存。在内存吃紧或者业务不允许扩内存的场景下,动超时参数往往性价比更高。原因很简单——表里的条目数约等于"新建速率 × 平均存活时长"。速率不变的前提下,把存活时长砍一半,表就小一半。

3.1 五天的 established 默认值,谁在用谁吃亏

nf_conntrack_tcp_timeout_established的默认值是 432000 秒,也就是 5 天。这个数字的历史原因很朴素:TCP 连接理论上可以无限期存活,干脆给一个足够大的值保证不误杀。但放到今天的业务场景里,它带来的后果是——一条早就断掉的连接,可能还会在 conntrack 表里占着位置好几天。

为什么断掉的连接还在?因为 TCP 是双向的,如果一端异常断电、网络闪断、或者中间设备静默丢包,另一端可能收不到 FIN 或 RST,连接就处于一个"双方都不动"的状态。业务层面有超时机制会自己清理,但 conntrack 不知道,它只会在超时到期时回收。5 天的默认值意味着这类僵尸条目会持续堆积。

我的取值习惯是:短连接为主的场景(HTTP API、数据库连接池短连)设 600 到 1800 秒;长连接场景(WebSocket、消息推送、数据库长连接)设 3600 秒左右,同时确保应用层有心跳保活。关键不是这个数字多大,而是它必须大于应用层心跳间隔的若干倍。如果应用每 300 秒发一次心跳,conntrack 超时设成 200 秒,那每条连接都会被误判回收,后续包会被标记为INVALID直接丢掉,症状是"连接看起来没断,但一发包就报错"。

3.2 time_wait、udp、generic 该分别怎么砍

不同协议状态的超时应该分开看待,一刀切会出问题:

  • nf_conntrack_tcp_timeout_time_wait(默认 120 秒):高并发短连接场景下这是最值得砍的一项。TIME_WAIT 状态的记录已经没有数据要传了,纯粹在等待端口复用窗口,压到 30 秒甚至更短一般不会有副作用,只要你的ip_local_port_range足够宽。
  • nf_conntrack_udp_timeout(默认 30 秒):UDP 无连接,靠这个超时判断"流结束"。DNS、监控上报这类场景 30 秒合理;如果是 QUIC 或者游戏类长 UDP 流,要注意nf_conntrack_udp_timeout_stream,它管的是"看到双向包之后"的超时,默认在 120 到 180 秒之间,不同内核版本有差异。
  • nf_conntrack_generic_timeout(默认 600 秒):兜底值,管 ICMP 之外的杂项协议。除非你跑 SCTP 之类的,一般不用管。
  • nf_conntrack_icmp_timeout(默认 30 秒):ping 洪水场景下会被大量占用,正常业务保持默认即可。

砍超时之前务必确认一件事:这些连接在超时被回收之后,如果还有包过来会怎样。答:会被判定为新连接,重新走一遍建链流程。对于 TCP 来说,如果对方还在旧连接上发包,这个包不满足新连接的握手条件,会被标记INVALID。如果你的防火墙规则里有DROP INVALID(通常都有),包就被丢了。这就是下面要说的反例。

3.3 砍超时会踩到的两个正确性反例

第一个反例是跨设备超时不一致。内部 conntrack 设了 3600 秒,但上游有一个运营商的 NAT 设备只保留 300 秒。当上游静默丢弃了映射关系,内部还认为连接有效,于是继续往上发——包出去了,回不来。表现出来就是"连接不断开也不通",客户端一直卡着等超时。这类问题的定位非常费劲,因为你在本机抓包看到的都是"我发了,没回"。解决思路是让 conntrack 超时小于或等于链路上最短的那个设备超时,靠应用层保活来维持连接。

第二个反例是负载均衡器后面的长连接。后端服务的 conntrack 超时被压到 600 秒,而前端 LB 到后端用的是长连接,LB 侧的空闲超时是 900 秒。结果就是后端先回收,LB 再次复用这条连接发包时被丢,用户看到偶发的 502。这类问题概率低、复现难,但一旦发生就是持续性故障。调超时之前,一定要把整条链路上所有设备的空闲超时列出来,取最小值作为上限。

4. 表满之后:一条从告警到根因的完整排查链路

参数调完了不等于问题解决了。真出故障的时候,光知道该改哪个参数没用,得能快速判断"这次是容量不够,还是条目泄漏,还是别的什么在伪装成表满"。这一节把我自己走过很多次的排查路径完整写一遍。

4.1 先分清是"容量不够"还是"条目泄漏"

第一步永远是看当前值和上限:

cat /proc/sys/net/netfilter/nf_conntrack_count cat /proc/sys/net/netfilter/nf_conntrack_max

如果count贴着max上下浮动,说明容量确实不够。但这里有个细节:内核在count达到上限时不会立刻丢弃,它会先尝试从当前 CPU 的哈希桶里挑一个"没有确认的、最早建立的"条目踢掉,这就是early_drop。所以真实的表满可能表现为count略低于max,但early_drop和drop计数在涨。

第二步看增长的形态。如果count是随业务流量同步起伏的,那是容量问题,加max或者砍超时。如果count是台阶式单调上涨、业务闲时也不回落,那大概率是条目没有被正确回收——查的方向就变成"哪类连接在堆积"。用下面的命令按协议和状态分组看:

conntrack -L -o extended 2>/dev/null | awk '{print $1, $4}' | sort | uniq -c | sort -rn | head -20

输出里如果某一类(比如某个特定端口的TIME_WAIT)占了绝大多数,问题就定位到具体业务了。

4.2 conntrack -S 的四个计数器各自在说什么

conntrack -S输出的是 per-CPU 的统计,几个关键字段的含义值得记牢:

字段含义涨了说明什么
insert成功插入的记录数正常增长,反映新建速率
insert_failed插入失败数与drop基本同步,表满的直接证据
early_drop提前回收的条目数表接近上限,开始踢老记录
drop最终丢弃的包数已经影响到业务
search_restart查找冲突重启次数偏大说明哈希冲突严重,桶太少
invalid被判无效的包数超时设置与业务不匹配,或状态跟踪异常

search_restart这个字段容易被忽略,但它是buckets配置是否合理的直接指标。如果它随着流量增长线性上升,而count并不高,那就是桶太少导致的链表过长,这时候调max完全没用,得调buckets。

4.3 三种常见的假象,别被它们带偏

假象一:容器里看到的 count 很小,宿主机却满了。较新的内核支持 conntrack 按网络命名空间隔离,容器里读到的/proc/sys/net/netfilter/nf_conntrack_count是容器自己的表,宿主机完全是另一套。排查容器网络问题时,必须在宿主机命名空间里看,或者用nsenter进入正确的 netns。这一条坑过我不止一次。

假象二:max调大之后问题依旧。常见原因是max只改在了运行时,没写进持久化配置,机器一重启就回到默认值;或者改的时候写错了层级(比如写到/etc/sysctl.conf但系统实际读的是/etc/sysctl.d/目录且顺序被覆盖)。还有一种情况是max确实改了,但buckets没跟上,于是包不再被丢,但 CPU 软中断先爆了。

假象三:业务侧显示连接数很低,误判为没有并发。业务侧统计的通常是"活跃请求数"或者"已完成握手的连接数",而 conntrack 统计的是"所有被跟踪过的四元组",包含大量半开连接、健康检查、扫描流量。两者数量级可能差好几倍,不要拿业务指标直接推 conntrack 容量。

4.4 处置顺序:先止血再改配置

真在线上遇到表满,我的顺序是这样的:

  1. 止血:临时把nf_conntrack_max顶上去(内存允许的前提下),先让业务恢复。命令是sysctl -w net.netfilter.nf_conntrack_max=xxx,立即生效。
  2. 观察:确认drop停止增长、count稳定在新的水位。这时候不要急着继续调,先看十分钟。
  3. 定位:按 4.1 的方法查是哪类连接在堆积。如果是泄漏,光调大max只是把故障时间往后推。
  4. 修正:根据定位结果调整超时或者业务侧连接管理。
  5. 固化:把变更写进持久化配置,并补上监控项——nf_conntrack_count / nf_conntrack_max的比值超过 0.7 就该告警,不要等它满了才发现。

5. 和 conntrack 绑在一起的几个参数,抄错了比不调更糟

除了容量和超时这两大类,还有几个参数经常出现在各种"内核优化模板"里,但它们的行为并不像名字看起来那么无害。用错了会把一类原本能正常工作的流量直接打挂。

5.1 tcp_loose 与 be_liberal:放宽判定换来的代价

nf_conntrack_tcp_loose默认是 1,含义是"如果收到的包不满足 TCP 状态机的合法转移,也允许为它建立跟踪记录"。这个默认值对非对称路由、抓包工具、以及那些连接中途才经过本机的场景很友好。设成 0 之后,只有看到完整的 SYN 握手才会建记录,能省一点资源,但代价是所有"半路经过"的连接都会被判无效。

nf_conntrack_tcp_be_liberal默认是 0,设成 1 之后会放宽窗口检查——窗口外的包不再被标记INVALID。这个参数在遇到老式设备乱发窗口外报文时能救急,但它同时会削弱 conntrack 对异常包的识别能力,等于你把一道安全检查关掉了。我的建议是除非有明确的兼容性需求,否则保持默认。

还有一个nf_conntrack_tcp_max_retrans(默认 3),控制一条连接最多容忍多少次重传,超过就认为失效并提前回收。在高丢包链路上,这个值设太小会导致连接被误杀,设太大又会让僵死连接占着位置。3 是一个经验平衡点,一般不用动。

5.2 helper 自动加载与 events 的隐性开销

nf_conntrack_helper从较新内核开始默认关闭,这是个好变化。早期默认开启时,任何一个连到 21 端口的 FTP 流量都会触发内核自动加载 FTP helper,而这个 helper 会解析应用层内容、动态开洞。这不仅带来开销,还是一个不小的安全隐患——攻击者可以构造畸形报文触发解析漏洞。如果你的机器上还有老系统,确认一下这个参数:

sysctl net.netfilter.nf_conntrack_helper

值为 1 的话,考虑关掉,改用 iptables 的CT --helper显式指定,只对必要的流量开启。

nf_conntrack_events默认是 1,作用是向用户态发送连接事件,供 conntrackd 之类的工具做状态同步。如果你没有用状态同步工具,这个参数等于白白产生 netlink 消息开销。关掉它能省一点,但在高并发场景下省下的量其实有限,属于优化边际收益较小的那一类。真正收益大的是nf_conntrack_acct和nf_conntrack_timestamp——这两个默认都是 0,如果被人打开过(有些监控方案会要求打开),每条记录都会额外带计数器或时间戳,objsize会明显变大。调max之前先确认这两个是关的,否则你的内存估算会偏。

5.3 端口范围、backlog 与 conntrack 的耦合

这几个参数严格说不属于 netfilter,但和连接跟踪的表现强相关,经常被一起调:

net.ipv4.ip_local_port_range默认是32768 60999,约 2.8 万个可用端口。做 NAT 出口的机器,每个目标地址最多只能有这么多并发连接。如果你的nf_conntrack_max设到了上百万,但端口范围没扩,那瓶颈根本不在 conntrack,而在端口耗尽——症状是cannot assign requested address。扩到10240 65000能到 5.4 万个。

net.core.somaxconn和net.ipv4.tcp_max_syn_backlog管的是 accept 队列和半连接队列,和 conntrack 是两套东西。但要注意:半连接队列里的 SYN_RECV 连接也会在 conntrack 里占记录。所以 SYN 洪水攻击时,你会同时看到半连接队列满和 conntrack 表满两个现象。这时候单纯调max是治标,真正的解法是在上游或者本机做 SYN 限速。

net.ipv4.tcp_tw_reuse值得单独提一句。它允许把 TIME_WAIT 状态的端口复用于新的出站连接,能缓解端口耗尽。但复用的前提是时间戳满足条件,在 NAT 场景下如果两端时间戳行为不一致,可能引发连接异常。我的做法是稳态下不开,只在高新建速率且端口确实不够时才开,并且开之前先确认tcp_timestamps是启用的。

6. 参数怎么落地:模块参数、sysctl 与不停机变更

前面讲的全是"该设成多少",这一节讲"怎么设才不会在重启后失效"。这是我见过的最多的低级失误来源——改了、生效了、跑了三个月,一次重启回到解放前。

6.1 hashsize 为什么不能只写在 sysctl.conf 里

nf_conntrack_max和各类 timeout 都是 sysctl 接口,写进/etc/sysctl.d/下的配置文件即可持久化。但buckets(也就是 hashsize)在不同内核版本里的暴露方式不一样:老一些的版本只能通过模块参数设置,路径是/sys/module/nf_conntrack/parameters/hashsize,运行时可以写,但重启后需要重新写;较新的内核提供了net.netfilter.nf_conntrack_buckets这个 sysctl 键,就可以和其他参数一起管理了。

先确认你的内核支持哪种:

sysctl net.netfilter.nf_conntrack_buckets

如果返回cannot stat,那就走模块参数路线,写一个/etc/modprobe.d/nf-conntrack.conf:

options nf_conntrack hashsize=196608

这里有个前提:nf_conntrack必须是模块而不是编译进内核的。检查方法是lsmod | grep nf_conntrack,如果没有输出但功能正常工作,那多半是内建的,模块参数就不生效了,只能在引导参数里加nf_conntrack.hashsize=196608。

6.2 一套带灰度与回滚的变更流程

内核网络参数的变更有个特点:绝大部分可以热改、立即生效、不需要重启。这既是好事也是坏事——好处是快,坏处是出错也快,而且影响面是全机器的。

我的流程固定为四步。第一步,先在一台非核心机器上验证,改完观察至少一个业务高峰。第二步,确认持久化配置文件写好,并且放在正确的位置——注意/etc/sysctl.d/下的文件按文件名字典序加载,后面的覆盖前面的,所以自定义文件建议用99-前缀,避免被发行版自带的配置覆盖。第三步,用sysctl --system验证一遍加载过程没有报错,特别留意 "No such file or directory" 这类提示,它通常意味着nf_conntrack模块在 sysctl 执行时还没加载。第四步,准备回滚脚本,把改之前的原始值记录下来。

关于模块加载顺序这个坑,再强调一次。很多发行版上nf_conntrack模块是在网络服务启动时才被拉起的,而systemd-sysctl服务执行得更早,于是所有net.netfilter.*的配置在这一刻都会写入失败,日志里一行警告,很容易被忽略。解决办法有两个:一是在/etc/modules-load.d/里显式声明nf_conntrack,保证它在 sysctl 之前加载;二是在 sysctl 配置里用-前缀忽略错误(不推荐,会把真正的问题也吃掉)。我更倾向第一种,因为它是从根上解决顺序问题。

改完之后怎么验证真的生效了?不要只看sysctl -a的输出,那个值可能来自运行时而不是配置文件。正确的验证方式是重启一次(或者在测试机上重启),然后再读一遍,同时检查dmesg里有没有 netfilter 相关的报错。生产环境不方便重启的,至少要把配置文件内容和sysctl --system的输出对一遍。

7. 这几年反复踩到的坑

参数调优这件事,讲原理是一回事,真正让人长记性的是被坑。下面几条都是我自己或者身边同行真实遇到过的,写出来省得你重走一遍。

7.1 只调 max 不调 buckets,CPU 先扛不住

这是最典型的一类。表不再满了,包不再丢了,但机器的ksoftirqd占用从 5% 涨到 40%,网络延迟 P99 翻倍。原因就是前面说的哈希冲突——记录数翻了 16 倍,桶还是原来那些,每条链路上挂着一长串,每次查表都要走一遍链表,还要频繁触发search_restart。调max的时候必须同步检查buckets,这两个是一对,拆开就没有意义。判断方法很简单:看conntrack -S里的search_restart是不是随流量明显增长。

7.2 变更后指标"变好"其实是假象

有一次把tcp_timeout_established从 432000 砍到 600,nf_conntrack_count立刻从 40 万掉到 8 万,所有人都觉得优化成功。结果两天后业务报障:一批长连接业务在空闲十几分钟后首次请求必然超时一次。原因就是 600 秒的超时把那些"应用层认为还活着但链路上没有包"的连接回收了,下一次发包被判INVALID。指标变好不代表业务变好,调超时一定要配合应用层的连接存活时长来看,光看 count 曲线会得出完全错误的结论。

7.3 容器环境里两套视图,排查方向完全跑偏

在容器化环境里排查连接问题,最容易犯的错是在容器内看指标。Conntrack 在新内核里是按 netns 隔离的,容器里那张表通常很小,怎么调都不会满;而真正承载流量的是宿主机的那张表。正确姿势是进宿主机的 netns 看,或者用nsenter -t <pid> -n切进去。另外容器编排平台自己也会管一套 conntrack 相关的配置,改动之前最好确认一下它有没有在启动时覆盖你的设置。

7.4 内存估算漏掉了扩展字段

前面提过,nf_conntrack_acct和nf_conntrack_timestamp打开之后单条记录会变大。我遇到过一台机器max设 200 万,按 320 字节算应该是 640MB,实际峰值吃掉了 1.2G。查下来是某个监控代理把acct打开了,objsize涨到了 600 多。教训是:估算内存不要用网上的通用值,一定要在本机slabinfo里读实际值,而且要在所有功能都开启的状态下读。

最后分享一个小技巧。如果你不想每次都手动算,可以在机器上放一个小小的巡检脚本,定期把count、max、buckets、objsize、drop、search_restart这几个值打印出来,顺便算一下比值。跑上几周你就能摸清这台机器的"正常水位"长什么样,下次出问题的时候,一眼就能看出哪个数字不对劲——这种直觉比任何调优模板都值钱。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进