这篇有点过时了,但因为发现有人转载不注明出处,不好原地更新……
最新版本看我博客,时不时更新:http://keithmo.me/post/2018/08/25/conntrack-tuning/
论坛上的新版本:https://testerhome.com/topics/15824可以不用往下看了。
背景:之前线上出性能问题了,我们都不知道nf_conntrack
的存在,精力都放在 nginx-应用-db-第三方服务这块
结果被这东西的默认参数坑了,请求连 nginx 都到不了……
恶补了几天资料 + 拿线上其中一台机测了 n 轮,总结了一些心得放出来抛砖引玉
(服务器用的阿里云主机,CentOS 7.3,似乎不管内存多少阿里云都把 conntrack_max 设成 65536)
CentOS 服务器,负载正常,但请求大量超时,服务器/应用访问日志看不到相关请求记录。
在dmesg
或/var/log/messages
看到大量以下记录:
kernel: nf_conntrack: table full, dropping packet.
服务器访问量大,内核 netfilter 模块 conntrack 相关参数配置不合理,导致新连接被 drop 掉。
nf_conntrack
模块在 kernel 2.6.15(2006-01-03 发布)被引入,支持 ipv4 和 ipv6,取代只支持 ipv4 的ip_connktrack
,用于跟踪连接的状态,供其他模块使用。
最常见的使用场景是 iptables 的 nat
和 state
模块:
nat
根据转发规则修改 IP 包的源/目标地址,靠nf_conntrack
的记录才能让返回的包能路由到发请求的机器。state
直接用 nf_conntrack
记录的连接状态(NEW
/ESTABLISHED
/RELATED
/INVALID
)来匹配防火墙过滤规则。nf_conntrack
用 1 个哈希表记录已建立的连接,包括其他机器到本机、本机到其他机器、本机到本机(例如 ping 127.0.0.1
也会被跟踪)。
如果连接进来比释放的快,把哈希表塞满了,新连接的数据包会被丢掉,此时 netfilter 变成了一个黑洞,导致拒绝服务。 这发生在 3 层(网络层),应用程序毫无办法。
各发行版区别:
netfilter 相关的内核参数:
sudo sysctl -a | grep conntrack
# 只看超时相关参数(超时时间 = 连接在哈希表里保留的时间)
sudo sysctl -a | grep conntrack | grep timeout
netfilter 模块加载时的 bucket 和 max 配置:
sudo dmesg | grep conntrack
# 找类似这样的记录:
# nf_conntrack version 0.5.0 (16384 buckets, 65536 max)
哈希表使用情况:
grep conntrack /proc/slabinfo
# 前4个数字分别为:
# 当前活动对象数、可用对象总数、每个对象的大小(字节)、包含至少1个活动对象的分页数
当前跟踪的连接数:
sudo sysctl net.netfilter.nf_conntrack_count
# 或 cat /proc/net/nf_conntrack | wc -l
跟踪的每个连接的详情:
cat /proc/net/nf_conntrack
# 统计里面的TCP连接的各状态和条数
cat /proc/net/nf_conntrack | awk '/^.*tcp.*$/ {count[$6]++} END {for(state in count) print state, count[state]}'
# 记录数最多的10个ip
cat /proc/net/nf_conntrack | awk '{print $7}' | cut -d "=" -f 2 | sort | uniq -c | sort -nr | head -n 10
# 记录格式:
# 网络层协议名、网络层协议编号、传输层协议名、传输层协议编号、记录失效前剩余秒数、连接状态(不是所有协议都有)
# 之后都是key=value或flag格式,1行里最多2个同名key(如 src 和 dst),第1次出现的来自请求,第2次出现的来自响应
# flag:
# [ASSURED] 请求和响应都有流量
# [UNREPLIED] 没收到响应,哈希表满的时候这些连接先扔掉
stackoverflow - details of /proc/net/ip_conntrack / nf_conntrack
# 哈希表里的实时连接跟踪数(只读)
net.netfilter.nf_conntrack_count
# 值跟 /proc/net/nf_conntrack 的行数一致
有说法是这数字持续超过 nf_conntrack_max
的 20% 就该考虑调高上限了。
# 哈希表大小(只读)(64位系统、8G内存默认 65536,16G翻倍,如此类推)
net.netfilter.nf_conntrack_buckets
# 最大跟踪连接数,默认 nf_conntrack_buckets * 4
net.netfilter.nf_conntrack_max
net.nf_conntrack_max
# 跟踪的连接用哈希表存储,每个桶(bucket)里都是1个链表,默认长度为4
# 默认值参考以下公式:(使用内存的 1/16384)
# CONNTRACK_MAX = RAMSIZE (in bytes) / 16384 / (ARCH / 32)
# (ARCH为你机器CPU的架构,64或32)
# HASHSIZE = CONNTRACK_MAX / 4
# (N年前是除8,这数字就是每个桶里的链表长度)
现在凡是有那么点用户量的服务器跟踪 20 万以上连接很正常,真按系统默认值也勉强能用,但阿里云似乎设了特别保守的默认值,bucket 为 16384,max 为 65536,这是倒退回了 07-11 年 CentOS 5-6 的时代。
# netfilter的哈希表存储在内核空间,这部分内存不能swap
# 操作系统为了兼容32位,默认值往往比较保守:
# 在32位Linux下,内核空间的虚拟地址空间最多 1G,通常能用的只有前 896M
# 1条跟踪记录约 300 字节,给netfilter分配太多地址空间可能会导致其他内核进程不够分配,因此当年默认最多 65535 条,占 20多MB
# 64位系统内核空间最多能用虚拟地址空间的一半(128TB),只需要关心物理内存使用多少就行了
# 内存占用参考以下公式:
# size_of_mem_used_by_conntrack (in bytes) = CONNTRACK_MAX * sizeof(struct ip_conntrack) + HASHSIZE * sizeof(struct list_head)
# sizeof(struct ip_conntrack) 在不同架构、内核版本、编译选项下不一样,192~352字节之间,可以按 352 算
# sizeof(struct list_head) = 2 * size_of_a_pointer(32位系统是4字节,64位是8字节)
# 在64位下,当CONNTRACK_MAX为 1048576,HASHSIZE 为 262144 时,最多占350多MB
# 对现在的机器来说毫无压力
推荐 bucket 至少 262144,max 至少 1048576,不够再继续加
缩短超时时间可以让 netfilter 更快地把跟踪的记录从哈希表里移除。
调优的基本思路是先看 /proc/net/nf_conntrack
,哪种协议哪种状态的连接最多,改小对应的超时参数
。
注意要充分测试,确保不影响业务。
# 通常挥手的状态都不怎么重要,连接都关了,没必要继续跟踪那么久:
net.netfilter.nf_conntrack_tcp_timeout_fin_wait # 默认 120 秒
net.netfilter.nf_conntrack_tcp_timeout_time_wait # 默认 120 秒
# 主动方的最后1个状态,默认2MSL
net.netfilter.nf_conntrack_tcp_timeout_close_wait # 默认 60 秒
# CLOSE_WAIT是被动方收到FIN发ACK,然后会转到LAST_ACK发FIN,除非程序写得有问题,正常来说这状态持续时间很短。
# (我们服务器 nf_conntrack文件里 time_wait 占了99%
# 把time_wait超时改成 30 秒后,nf_conntrack_count下降超过一半)
net.netfilter.nf_conntrack_tcp_timeout_established # 默认 432000 秒(5天)
# 理论上不用这么长,不小于 net.ipv4.tcp_keepalive_time 就行了
# (我们调了看不出效果)
net.netfilter.nf_conntrack_generic_timeout # 默认 600 秒(10分钟)
# 通用超时设置,作用于4层(传输层)未知或不支持的协议
# (基本不会碰到这种连接,同样调了看不出效果)
#net.netfilter.nf_conntrack_tcp_timeout_max_retrans # 默认 300 秒
#net.netfilter.nf_conntrack_tcp_timeout_unacknowledged # 默认 300 秒
如果不能关掉防火墙,基本思路就是上面说的,调大nf_conntrack_buckets
和nf_conntrack_max
,调小超时时间。
除了有关联的参数,尽量一次只改一处,记下默认值,效果不明显或更差就还原。
# net.netfilter.nf_conntrack_buckets 不能直接改(报错)
# 需要修改模块的设置:
echo 262144 > /sys/module/nf_conntrack/parameters/hashsize
# 如果不是root:
echo 262144 | sudo tee /sys/module/nf_conntrack/parameters/hashsize
# 再查看,bucket已经变成设置的大小
sudo sysctl net.netfilter.nf_conntrack_buckets
# max设为桶的4倍
sudo sysctl -w net.netfilter.nf_conntrack_max=1048576
suod sysctl -w net.nf_conntrack_max=1048576
sudo sysctl -w net.netfilter.nf_conntrack_tcp_timeout_fin_wait=30
sudo sysctl -w net.netfilter.nf_conntrack_tcp_timeout_time_wait=30
sudo sysctl -w net.netfilter.nf_conntrack_tcp_timeout_close_wait=15
sudo sysctl -w net.netfilter.nf_conntrack_tcp_timeout_established=300
用sysctl -w
或echo xxx > /pro/sys/net/netfilter/xxx
做的修改在重启后会失效。
如果测试过没问题,可以编辑/etc/sysctl.d/
下的配置文件(旧系统是/etc/sysctl.conf
),系统启动时会加载里面的设置。
sudo vim /etc/sysctl.d/90-conntrack.conf
# 格式:<参数>=<值>,等号两边可以空格,支持 # 注释
net.netfilter.nf_conntrack_max=1048576
net.nf_conntrack_max=1048576
net.netfilter.nf_conntrack_tcp_timeout_fin_wait=30
net.netfilter.nf_conntrack_tcp_timeout_time_wait=30
net.netfilter.nf_conntrack_tcp_timeout_close_wait=15
net.netfilter.nf_conntrack_tcp_timeout_established=300
# 如果要马上应用配置文件里的设置:
sudo sysctl -p /etc/sysctl.d/90-conntrack.conf
# 不传配置文件路径默认加载 /etc/sysctl.conf
对不直接暴露在公网、没有用到 NAT 转发的服务器来说,关闭 Linux 防火墙是最简单也是最佳的办法。
通常防火墙一关,sysctl -a
里就没有 netfilter 相关的参数了。如果有例外,照上面调整。
# CentOS 7.x
sudo systemctl stop firewalld
sudo systemctl disable firewalld
# CentOS 6.x
sudo service iptables stop
# 网上有些文章说关了iptables之后,用 iptables -L -n 之类查看规则也会导致nf_conntrack重新加载,实测并不会
sudo chkconfig --del iptables
【注意】以下是网上有些文章提到的解决方法,其实不好用,只记录下来作备忘。
对需要防火墙的机器,可以在iptables
设置NOTRACK
规则,减少要跟踪的连接数
【注意】设置成不跟踪的连接无法拿到状态,可能会导致 keep-alive 用不了:
我们改之前 ESTAB 的连接 1000 多,改之后超过 1.5w,响应时间几十秒,前端基本连不上。
# 查看所有规则
sudo iptables-save
# 这个必须插在第1条,凡是不跟踪的肯定是你想放行的
sudo iptables -I INPUT 1 -m state --state UNTRACKED -j ACCEPT
# 设置成不跟踪的连接无法拿到状态,包含状态(-m state --state)的规则统统失效
# iptables处理规则的顺序是从上到下,如果这条加的位置不对,可能导致请求无法通过防火墙
# 不跟踪 127.0.0.1
sudo iptables -t raw -A PREROUTING -i lo -j NOTRACK
sudo iptables -t raw -A OUTPUT -o lo -j NOTRACK
# 保存规则(否则重启服务后失效)
sudo service iptables save
# 其实就是把 iptables-save 的内容存到 /etc/sysconfig/iptables
# 假如Nginx和应用部署在同一台机子上,增加这规则的收益极为明显
# Nginx连各种upstream使得连接数起码翻了倍,不跟踪本地连接一下干掉一大半
# (其他条件不变,修改前后 nf_conntrack_count:30k+ -> 2.9k+ ,下降 90%!)
# sudo iptables -t raw -A PREROUTING -p tcp -m multiport --dports 80,443 -j NOTRACK
# sudo iptables -t raw -A OUTPUT -p tcp -m multiport --sports 80,443 -j NOTRACK
# (实测少跟踪 7% 左右的链接)
说明:
-t raw
会加载 iptable_raw
模块(kernel 2.6+ 都有)
raw
表基本就干一件事,通过-j NOTRACK
给不需要被连接跟踪的包打标记(UNTRACKED
状态),告诉nf_conntrack
不要跟踪连接
raw
的优先级大于 filter
,mangle
,nat
,包含 PREROUTING
(针对进入本机的包)和 OUTPUT
(针对从本机出去的包)链
缺点:不好维护,服务器对外端口较多或有变化时,容易改出问题
只要iptables
还有规则用到nat
和state
模块,就不适合关掉 netfilter,否则这些规则会失效。
例如这条默认规则(通常写在第 1 条或很靠前的位置):
-A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
表示放行已经建立的连接,不再往下匹配其他规则(第一次建立时已经全部检查过关了)。关掉 netfilter 会拿不到状态,导致每个请求都要从头到尾检查一次,影响性能。
因此如果 iptables 不能关,最好不要禁用 netfilter。
如果确实需要禁用:
# 查找相关模块
sudo lsmod | egrep "ip_table|iptable|nat|conntrack"
# 查看iptables规则
sudo iptables-save
# 把带 -t nat 、-m state 的规则都干掉
# 或删掉 /etc/sysconfig/iptables 里相应内容
# 编辑 iptables 配置文件
sudo vim /etc/sysconfig/iptables-config
# 找到 IPTABLES_MODULES ,删掉跟conntrack有关的模块(如果有)
# 停掉iptables
sudo service iptables stop
sudo chkconfig --del iptables
# 移除相关模块(如果有)
sudo rmmod iptable_nat
sudo rmmod ip6table_nat
sudo rmmod nf_defrag_ipv4
sudo rmmod nf_defrag_ipv6
sudo rmmod nf_nat
sudo rmmod nf_nat_ipv4
sudo rmmod nf_nat_ipv6
sudo rmmod nf_conntrack
sudo rmmod nf_conntrack_ipv4
sudo rmmod nf_conntrack_ipv6
sudo rmmod xt_conntrack
# 需要再开就 sudo modprobe <name>
缺点:如果环境/配置文件不是完全由你掌控或没有很好的管理,容易出问题
官方参数说明(等于什么都没说……)
Linux 连接跟踪源码分析 & 源码目录
wikipedia - Netfilter#Connection_Tracking
stackexchange - nf_conntrack: table full, dropping packet
Caveats about Linux connection tracking and high traffic servers, 2014-02
解决恶心的 Nf_conntrack: Table Full 问题, 2014
nf_conntrack: table full, dropping packet. 终结篇, 2015(还不错,然而并不是终结)
kernel nf_conntrack: table full, dropping packet 解决办法, 2012
解决 nf_conntrack: table full, dropping packet 的几种思路, 2012
通过 modprobe 彻底禁用 netfilter, 2012
nf_conntrack: table full, dropping packet on Nessus server, 2014
nf_conntrack: table full, dropping packet — A solution for CentOS Dedicated Servers, 2015
Resolving “nf_conntrack: table full, dropping packet.” flood message in dmesg Linux kernel log, 2012(***)
how do I disable the nf_conntrack kernel module in CentOS 5.3 without recompiling the kernel, 2009
nf_conntrack 的前身 ip_conntrack 的相关资料(CentOS 5、6):
不要盲目增加 ip_conntrack_max-理解 Linux 内核内存, 2011-12
ip_conntrack table full dropping packet 解决方案, 2013-08
一次由 ip_conntrack 跟踪连接库满导致的大量丢包现象排除, 2009-12
关于 ip_conntrack 的几点认识, 2007-11
CentOS ip_conntrack: table full, dropping packet 的解决方法ip_conntrack 的作用, 2014-01
linux 内核 netfilter 之 ip_conntrack 模块的作用 -- 抽象总结, 2010-06
其他