nf_conntrack: table full 排障:看 drop 列,别只看 count/max
应用侧的错误不会指向 conntrack
IBM 支持页记录的形态很典型:注册在虚拟机上的服务报 I/O timeout、connection refused,Consul agent 日志先打出来的是这些:
Mar 21 11:20:13 host consul: [WARN] agent: Check socket connection failed: error="dial tcp 10.14.51.43:25922: i/o timeout"
Mar 21 11:26:58 host consul: [WARN] agent: Check socket connection failed: error="dial tcp 10.14.51.43:30692: connect: connection refused"
真正的线索在系统日志里,时间比应用报错还早:
Mar 21 10:40:57 host kernel: nf_conntrack: table full, dropping packet
包在 netfilter 层被丢掉,应用既收不到 RST 也收不到 SYN-ACK,表现出来就是 dial tcp ...: i/o timeout;走到对端或中间设备回 RST 时,则变成 connect: connection refused。应用侧没有任何途径报出 conntrack 状态,所以看到成片 timeout 时,先执行 dmesg -T | grep -i conntrack,核对两者时间。
先确认 conntrack 到底在不在
不是所有发行版都需要 conntrack。conntrack 条目只有在 conntrack 处于激活状态时才会出现在 /proc 树里;模块没跑起来时 nf_conntrack_max 不生效,表也不会满。先查一遍:
sysctl net.netfilter.nf_conntrack_max net.netfilter.nf_conntrack_count
如果这两项键不存在,或者 /proc/sys/net/netfilter/ 下没有 nf_conntrack_max,说明这条线索不成立:dmesg 里那条 table full 可能来自别的 netns,也可能是历史记录(dmesg 带时间戳,先看时间)。只有当 count 接近 max 时,扩容才看起来合理;但 count 只是当前的水位,不能当作判断依据。
判据:drop、early_drop、insert_failed
/proc/net/stat/nf_conntrack 第一行是表头,列顺序固定:
entries searched found new invalid ignore delete delete_list insert insert_failed drop early_drop icmp_error expect_new expect_create expect_delete search_restart
关键几列:
entries:当前表内条目数,与nf_conntrack_count一致new:开机以来新建的条目数insert:插入次数insert_failed:插入失败次数drop:因表满被丢弃的新连接包数,大于 0 说明表曾经被打爆过early_drop:内核主动踢掉快过期或未确认条目、为新连接腾位的次数
三者里 drop 最直接,它记录的就是 table full 那一刻丢掉的包。early_drop > 0 说明内核已经进过“腾位”逻辑,表在压力下运行过。insert_failed 说明插入这一步失败过。
这些是开机以来的累计计数器,只增不减。单次快照只能回答“有没有发生过”;要判断“现在还在不在发生”,需要采两次做差。文件按 CPU 分行,数值以十六进制打印,直接 grep 出来的数字不能按十进制比较,看它是否从 0 变成非 0、以及两次之间是否还在增长即可。
反过来,count/max 的使用率为什么不够用:kernel 文档写明 nf_conntrack_max 默认等于 nf_conntrack_buckets,而每条 entry 在哈希表中会出现两次(原始方向一次,反向地址的回复方向一次),所以默认配置打满时平均链长是 2 而不是 1。这意味着:
- 你看到的“100%”只是同一个数除以自己;
- 表满丢的是新连接包,瞬时突发就足以触发,等你去查时一批条目已超时释放,count 掉下来了,
drop计数还留着; - count 长期贴着 max,只要链长可接受,未必一直在丢。
正确的顺序是:dmesg 确认 table full → /proc/net/stat/nf_conntrack 的 drop/early_drop 确认丢包确实发生、当前是否还在涨 → 最后才看 count/max 决定扩到多少。
表满时被丢的是谁
2011 年 NFWS 的讨论(Jesper Brouer,The missing conntrack garbage collector)把机制讲得很清楚:表有固定上限,超过就直接丢新连接,并在 syslog 打印 nf_conntrack: table full, dropping packet;表满时代码调用 early_drop(),只杀掉“未确认”的连接(没有双向流量),因此已建立连接不会被踢,被丢的是新连接。
这正好对应观察到的现象:老的长连接还在跑,新建连接全超时。也解释了为什么高峰时去看 count 会卡在 max 附近——已建立连接占着条目不释放,新连接根本没有位置。
扩容时的几个边界
max 默认等于 buckets。 kernel 文档:buckets 若未在加载模块时指定,默认由总内存除以 16384 算出,不会少于 1024、不会多于 262144;nf_conntrack_max 默认被设为 nf_conntrack_buckets。“调 max”和“调 buckets”是两件事。
只加 max 不加 hashsize,只是把链拉长。 哈希链变长后,查找退化成链表扫描。2011 那份材料举的例子是:12GB 内存、kernel 3.0.2,nf_conntrack version 0.5.0(16384 buckets,65536 max),条目约 304 字节,65536 × 304 ≈ 20MB。内存占用并不大,但 65536 这个上限对短连接业务偏低。经验比例是 hashsize ≈ nf_conntrack_max / 4;64 位上一个 bucket 就是一个指针(8 字节)。
buckets 只在初始 netns 可写。 nf_conntrack_buckets 这个 sysctl 只在初始 net namespace 可写;那份材料里也提到 /proc/sys/net/netfilter/nf_conntrack_buckets 实际是只读的,真正能改进来的口子是 /sys/module/nf_conntrack/parameters/hashsize,或者加载模块时传参。
sysctl -p 会报 cannot stat。 启动早期 nf_conntrack 尚未加载时,/proc/sys/net/netfilter/nf_conntrack_max 不存在,sysctl -p 会报 cannot stat /proc/sys/net/netfilter/nf_conntrack_max,这一行设置不生效。先把模块或规则加载起来(modprobe nf_conntrack,或有规则引用触发加载),再 apply 一次并读回确认。
宿主机上看不到容器的表
conntrack 表按 netns 独立。容器、pod 有自己的 netns,就有各自的表和上限;宿主机上 sysctl 读到的 count 是初始 netns 的。dmesg 是主机内核打的,容器里打满时宿主机 count 可能很干净,只能靠 dmesg 时间点去对。要定位得进目标 netns:
nsenter -t
-n sysctl net.netfilter.nf_conntrack_count net.netfilter.nf_conntrack_max
conntrack -L 需要 CAP_NET_ADMIN。
先止血,再找源
止血按内存定上限,同时把 hashsize 抬上去,避免只把链拉长。IBM 支持页给的经验公式是 CONNTRACK_MAX = RAMSIZE(bytes) / 16384 / (x/32),x 为指针位数;64 位 8GB 内存算出 262144。条目实际大小可以从 slab 看:
awk '$1=="nf_conntrack"{print $4}' /proc/slabinfo
量级在 200~400 字节,属于不可换出的内核内存。expect 表的上限是 nf_conntrack_expect_max,默认 nf_conntrack_buckets / 64,扩容时一起看。
另一条路是收超时。默认值偏大,先读实际值再决定是否动:
sysctl -a | grep conntrack | grep timeout
找源则是回答“谁在造这么多条目”:conntrack -L 看分布(conntrack -L -p tcp --state ESTABLISHED、conntrack -L -s 1.2.3.4),node_exporter 暴露的 nf_conntrack_* 指标接进监控看趋势,再配合 ss 看 TIME_WAIT 堆积、健康检查频率、连接池是否复用、是否某个下游或 DNS 在大量短连接。
持久化
max 和 buckets 写 /etc/sysctl.d/,hashsize 走 /etc/modprobe.d/。下面数值仅作示意,要按自己机器的内存和连接量算:
# /etc/sysctl.d/99-conntrack.conf(示意值)
net.netfilter.nf_conntrack_max = 262144
# /etc/modprobe.d/nf_conntrack.conf(示意值)
options nf_conntrack hashsize=65536
hashsize 这一项进不了 sysctl,只能靠加载参数或直接写 /sys/module/nf_conntrack/parameters/hashsize。
参考
- kernel 文档 Netfilter Conntrack Sysfs variables:
- IBM 支持页 I/O timeout error caused by error nf_conntrack: table full:
- Jesper Brouer, NFWS2011 The missing conntrack garbage collector: