适用场景
业务运行在 Linux 主机、Docker 或 Kubernetes 节点上,经过 NAT、iptables/nftables 防火墙或四层负载均衡转发。某个时段开始出现 API 偶发超时、DNS 请求失败、容器无法访问外部服务,但主机 CPU、内存和带宽看起来都正常;重启网络或重启节点后短暂恢复。
本文以 Linux 连接跟踪(conntrack)表耗尽为例,给出从确认、归因到安全扩容和长期治理的完整步骤。适合 Ubuntu、Debian、CentOS、Rocky Linux 等使用 netfilter 的系统。
现象描述
一次典型故障中,应用侧出现大量 context deadline exceeded、i/o timeout,但 TCP 建连没有普遍变慢。节点内核日志出现如下内容:
nf_conntrack: nf_conntrack: table full, dropping packet
此时现象通常有三个特点:
- 已建立的长连接大多还能继续传输,新建连接或 UDP 请求更容易失败。
- 故障随流量高峰出现、在低峰自行缓解,因此容易被误判为外部依赖抖动。
- 节点上的容器、Pod 共同受影响,因为它们共享宿主机的 conntrack 表(网络命名空间和内核配置不同会有例外)。
conntrack 会为经过跟踪的连接保存五元组、状态和超时信息。NAT、状态防火墙规则、Kubernetes Service 转发都依赖它。表满时,内核无法为新报文创建状态项,随后丢包。
先确认是不是 conntrack 问题
不要先重启节点。先在故障节点保留现场,并用以下命令同时查看当前用量、上限和内核告警:
# 当前已分配的连接跟踪项
cat /proc/sys/net/netfilter/nf_conntrack_count
# 最大允许项数
cat /proc/sys/net/netfilter/nf_conntrack_max
# 查看本次启动以来的 table full 告警
sudo journalctl -k --since '2 hours ago' | grep -i conntrack
# 如果安装了 conntrack-tools,按状态抽样查看
sudo conntrack -L -o extended | head -n 20
sudo conntrack -S
将 nf_conntrack_count 与 nf_conntrack_max 相除即可得到使用率。达到 80% 就应告警;若接近 100% 且日志包含 table full,基本可以确认根因。conntrack -S 中的 insert_failed、drop 持续增长,也说明新条目无法插入。
若没有 conntrack 命令,不要临时依赖它清表;/proc/sys/net/netfilter/ 中的两个数和内核日志已经足以完成第一轮判断。
定位:到底是什么连接占满了表
1. 先按协议和状态统计
在高峰期抽样,优先关注短生命周期 UDP、持续增长的 SYN_SENT,以及异常多的 TIME_WAIT/ESTABLISHED:
sudo conntrack -L -o extended \
| awk '{for (i=1;i<=NF;i++) if ($i ~ /^(tcp|udp|icmp)$/) print $i}' \
| sort | uniq -c | sort -nr
sudo conntrack -L -p udp | wc -l
sudo conntrack -L -p tcp --state SYN_SENT | wc -l
sudo conntrack -L -p tcp --state ESTABLISHED | wc -l
第一条命令的输出反映协议占比。若 UDP 数量在故障前陡增,常见来源是 DNS 重试、监控探针、服务发现或 UDP 放大流量;若 SYN_SENT 异常多,则检查不可达上游、错误路由或外部扫描。
2. 用五元组聚合找出来源
以下命令从 conntrack 输出中提取源地址和目的地址,快速找出数量最多的通信对:
sudo conntrack -L -o extended \
| awk '{
for (i=1;i<=NF;i++) {
if ($i ~ /^src=/) {split($i,a,"="); src=a[2]}
if ($i ~ /^dst=/) {split($i,a,"="); dst=a[2]}
}
if (src != "" && dst != "") print src " -> " dst
}' \
| sort | uniq -c | sort -nr | head -n 30
需要注意:NAT 连接常包含正反两个 src=、dst= 字段,简单解析用于排查方向和热点即可。要精确确认某条业务流,应结合 conntrack -L -p tcp --dport 443、应用访问日志、CNI 流量日志和负载均衡日志交叉验证。
3. 关联节点和应用指标
建议采集以下指标并放在同一张 Grafana 面板:
nf_conntrack_count
nf_conntrack_max
node_network_receive_drop_total
应用请求量、超时率、DNS 失败率
若连接跟踪使用率先升高,随后出现丢包与请求超时,因果链就很清楚。Kubernetes 环境还要比较各节点的使用率,避免只扩容一台异常节点而忽略流量倾斜。
修复方案:先止血,再做有依据的扩容
临时止血
先修正明显的流量异常:
- 为访问不可达上游的客户端设置指数退避和连接超时,防止无限重试制造更多半连接。
- 合并高频 DNS 查询,启用本地缓存,并排查错误的搜索域或循环解析。
- 将健康检查频率与超时设置为合理值,避免每个容器每秒建立多个短连接。
- 对异常来源做限速或阻断。iptables 环境可针对入口做速率限制;在 Kubernetes 中优先通过 NetworkPolicy、入口网关或应用限流实施。
不要在生产高峰期执行 conntrack -F。它会删除所有状态项,使现有 NAT 和防火墙连接同时失效,影响范围往往比原故障更大。
安全调整表容量
确认内存余量和流量模型后,可先临时扩大上限验证效果:
# 示例:将上限调至 524288;数值需按节点内存和压测结果确定
sudo sysctl -w net.netfilter.nf_conntrack_max=524288
# 验证新值
sysctl net.netfilter.nf_conntrack_max
cat /proc/sys/net/netfilter/nf_conntrack_count
长期配置写入独立文件,避免修改发行版默认配置:
sudo tee /etc/sysctl.d/90-conntrack-capacity.conf >/dev/null <<'EOF'
net.netfilter.nf_conntrack_max = 524288
EOF
sudo sysctl --system
上限不是越大越好。每个表项会消耗内核内存,实际开销与内核版本、扩展字段有关。扩容前应在同规格节点压测,并观察 Slab/SUnreclaim、可用内存和延迟。对 Kubernetes 节点,还应将配置纳入镜像、Ansible 或 DaemonSet 管理,确保节点替换后不回退。
一次定位示例
某 API 节点在 11:20 出现 5% 超时。现场数据显示:
nf_conntrack_count = 261742
nf_conntrack_max = 262144
kernel log = table full, dropping packet
协议统计发现 UDP 占 78%,聚合后绝大多数来自业务容器到两台 DNS 地址。应用日志显示某次配置变更将一个不存在的域名写入服务发现列表,客户端以很短的超时持续重试。修正配置、为解析失败增加退避后,连接数回落至 9 万。随后依据压测将上限从 262144 调至 524288,并设置 70% 预警、85% 紧急告警。故障未再复现。
这个案例说明:扩容只解决容量余量,真正的修复是消除异常连接的生产者。
预防措施
- 对
nf_conntrack_count / nf_conntrack_max建立 70%、85%、95% 分级告警,并把内核table full日志纳入日志告警。 - 在容量评审中估算峰值并发连接、NAT 比例、UDP 查询量和重试放大倍数;为发布和突发流量预留余量。
- 应用统一配置连接复用、合理超时、有限重试和抖动退避,避免故障时形成连接风暴。
- 对 DNS、健康检查、监控采集等短连接来源单独监控 QPS 与失败率。
- 节点发生
table full后,保留 conntrack 状态抽样、内核日志和应用错误时间线,形成可复用的复盘模板。
总结
conntrack 表满的本质是共享的内核状态容量被异常或峰值连接耗尽。排查时先用计数、上限和内核日志确认,再按协议、状态与五元组找到连接来源;治理上先削减异常流量,再基于内存和压测扩容。把使用率、丢包和应用超时放在同一面板,才能在用户感知前发现风险。
Discussion
评论