问题 @

最近遇到了一个比较奇葩的bug,属于网络问题,涉及到一些内核参数以及conntrack表,抽空配合客户排查了两三天才找到问题的原因,于是打算写篇文章记录一下排查经验。

客户反应产品页面偶现打不开,接口总是超时,但过一段时间突然又自己恢复了。我觉得很奇怪,由于我们产品是漏洞扫描产品,扫描过程中会对外发大量请求,并且我是负责开发扫描器的,第一个想法肯定是扫描器是不是出bug把客户机器资源吃没了。因为以前也出现过内存泄漏、高io wait的场景,但分析了机器的CPU、内存、负载、IO发现整体都不高,那就需要深度排查了,然后就有了如下我发现的现象。

  • 容器内 ping​ 目标地址时,前 3-5 个包必丢,随后恢复正常;但 arping 正常。
  • nginx​日志正常,并不是nginx服务本身的问题导致的接口超时
  • 服务日志报错全部都是慢SQL,有些表完全没数据也会有长达10s的查询时间。
  • 通过ss​或者netstat没有发现大量的tcp连接
  • 通过127本地连接mysql执行SQL执行时间正常,但通过docker网络ip连接mysql执行则响应时间长达数秒或超时。

排查到当前这一步,第一想法肯定是怀疑是docker网络转发出了问题,导致容器间的网络通信非常慢。于是我删除了原来的网桥,然后重启docker服务,重建网桥,结果还是一样,问题并没有解决。后来经过几番周折,总算在nf_conntrack上找到了线索。

原因 @

通过sysctl net.netfilter.nf_conntrack_count​查看conntrack表,发现其数量已经堆积到26w多,已经到了nf_conntrack_max​值,也就是conntrack的上限。

然后手动调大了conntrack的上限到100w,sysctl -w net.netfilter.nf_conntrack_max=1000000后,果然接口正常了,也没有再超时了。原因可以归纳如下:

  • 连接表溢出: 内核 nf_conntrack​ 模块用于记录 NAT 和状态防火墙的连接信息。当高频短连接产生大量记录并达到 max 上限时,内核会丢弃新的网络包。

  • 默认参数: Linux 内核默认 established​ 连接超时时间为 5天,对于漏洞扫描等场景极易堆积。

方案 @

编辑 /etc/sysctl.conf,针对高并发扫描场景优化:

# 提高天花板(根据内存情况建议 100w)
net.netfilter.nf_conntrack_max = 1048576

# 缩短存活时间,加快回收速度
net.netfilter.nf_conntrack_tcp_timeout_established = 1800
net.netfilter.nf_conntrack_tcp_timeout_time_wait = 30
net.netfilter.nf_conntrack_tcp_timeout_close_wait = 30
net.netfilter.nf_conntrack_tcp_timeout_fin_wait = 30

# 缩短 UDP 存活时间(针对端口扫描)
net.netfilter.nf_conntrack_udp_timeout = 10
net.netfilter.nf_conntrack_udp_timeout_stream = 20

执行 sysctl -p 生效。

结论 @

服务器的Conntrack表的数据量明显有问题,服务的网络请求并不高,在非扫描期间正常也就300-500,即使是扫描期间最多也就5000-10000,而这26万的堆积明显是有问题的。经过cat /proc/net/nf_conntrack​的各种统计,发现其大量conntrack的泄漏数据集中在某一个网段,该网段后可能挂了防火墙,导致分手的FIN包可能被防火墙拦截,于是conntrack表堆积了这条记录,只能等到nf_conntrack_tcp_timeout_established的到期时间(5天)后才会进行清理。于是让客户先排除该网段扫描,后来果然没有再出现问题,最后这个问题总算是解决了,也算是又学到了一些新东西吧。