最近发现Tracker所在的BuyVM服务器意外重启,于是调查重启原因时发现系统日志中持续出现大量的如下提示信息。
July 23 08:29:57 localhost kernel: [345379.839795] nf_conntrack: nf_conntrack: table full, dropping packet
July 23 08:29:57 localhost kernel: [345379.841751] nf_conntrack: nf_conntrack: table full, dropping packet
July 23 08:29:57 localhost kernel: [345379.848711] nf_conntrack: nf_conntrack: table full, dropping packet
July 23 08:29:57 localhost kernel: [345379.851790] nf_conntrack: nf_conntrack: table full, dropping packet
July 23 08:29:57 localhost kernel: [345379.864515] nf_conntrack: nf_conntrack: table full, dropping packet
才注意到TCP会话数超出了系统配置的最大值,然后开始丢包。由于Tracker服务器目前服务全网请求量确实很大,内核netfilter模块conntrack默认参数配置过低,导致新连接被drop掉。
查阅了资料后,发现nf_conntrack模块在Linux Kernel 2.6.15 被引入,同时支持IPV4和IPV6,取代只支持IPV4的ip_connktrack,主要用来跟踪连接的状态,以供其他模块使用,例如用于NAT和连接状态记录。
nf_conntrack用一个哈希表记录已建立的连接,包括其他机器到本机、本机到其他机器、本机内部的连接,如果连接进来比释放得快,把这个哈希表塞满了,新连接的数据包会被drop掉,此时netfilter变成了一个黑洞,导致拒绝服务,就像上面看到的结果。
和 nf_conntrack 相关的内核参数可以用下面的命令查看
sysctl -a | grep conntrack
nf_conntrack_buckets 使用情况查看
grep conntrack /proc/slabinfo
前四个数字分别为:当前活动对象数、可用对象总数、每个对象的大小(字节)、包含至少1个活动对象的分页数。
查看nf_conntrack表最大连接数
cat /proc/sys/net/netfilter/nf_conntrack_max
查看nf_conntrack_buckets哈希表最大大小
cat /proc/sys/net/netfilter/nf_conntrack_buckets
此时会发现两个值几乎相近(当然最大连接数必定略小于哈希表最大大小,毕竟多出来的都被drop了)。
那既然最大连接数过小,那么调大就好了。
注意一下 net.netfilter.nf_conntrack_max 不是越高越好,通常根据内存大小进行设置。一般来说网上通用的计算公式如下:
nf_conntrack_max计算公式(64位)
CONNTRACK_MAX = RAMSIZE (inbytes)/16384/2
例如你的机器是一个64GB 64bit的系统,那么最合适的值是
CONNTRACK_MAX = 64*1024*1024*1024/16384/2 = 2097152
nf_conntrack_buckets决定存储conntrack条目的哈希表大小,默认值是nf_conntrack_max的1/4,延续这种计算方式:BUCKETS = CONNTRACK_MAX/4,如64G内存可以设置524288,此时哈希表也可修改一下。
但是我算了下结果,貌似和我当前的默认值一样,因此索性把值提了一倍。
修改方法如下:
执行以下命令,使配置即时生效。(注意这里的两个值要根据实际修改一下)
sysctl -w net.netfilter.nf_conntrack_max=2097152
修改哈希表大小
echo 524288 > /sys/module/nf_conntrack/parameters/hashsize
执行以下命令可确保重启后配置仍然生效。
echo "net.netfilter.nf_conntrack_max = 2097152" >> /etc/sysctl.conf
可以顺便修改一下tcp_timeout_established,默认值长达五天之久。
echo "net.netfilter.nf_conntrack_tcp_timeout_established = 3600" >> /etc/sysctl.conf
修改后连接就不会被drop了。实际上如果云服务厂商有提供安全组、防火墙等功能,可以直接使用外部防火墙,此时可以直接撤掉机器内部的防火墙,也就没有这个问题了。亦或者如果不需要使用到涉及到状态链路相关或者NAT相关的应用,也可以直接设置不加载这个模块的。