nf_conntrack詳解

(服務器用的阿里雲主機,CentOS 7.3,彷佛無論內存多少阿里雲都把 conntrack_max 設成 65536)php

症狀
CentOS服務器,負載正常,但請求大量超時,服務器/應用訪問日誌看不到相關請求記錄。前端

在dmesg或/var/log/messages看到大量如下記錄:vim

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 模塊:tcp

nat 根據轉發規則修改IP包的源/目標地址,靠nf_conntrack的記錄才能讓返回的包能路由到發請求的機器。
state 直接用 nf_conntrack 記錄的鏈接狀態(NEW/ESTABLISHED/RELATED/INVALID)來匹配防火牆過濾規則。
iptables性能

nf_conntrack用1個哈希表記錄已創建的鏈接,包括其餘機器到本機、本機到其餘機器、本機到本機(例如 ping 127.0.0.1 也會被跟蹤)。測試

若是鏈接進來比釋放的快,把哈希表塞滿了,新鏈接的數據包會被丟掉,此時netfilter變成了一個黑洞,致使拒絕服務。 這發生在3層(網絡層),應用程序毫無辦法。阿里雲

各發行版區別:

CentOS (7.3) 默認加載該模塊
Ubuntu (16.10+) 和 Kali Linux (2016.1+) 默認不加載,不會有這問題
查看
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,不夠再繼續加

https://wiki.khnet.info/index.php/Conntrack_tuning, 2008-01

縮短超時時間可讓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 秒
調優
A. 調整內核參數
若是不能關掉防火牆,基本思路就是上面說的,調大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

B. 關閉防火牆
對不直接暴露在公網、沒有用到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
【注意】如下是網上有些文章提到的解決方法,其實很差用,只記錄下來做備忘。

C. 設置不跟蹤鏈接的規則(不推薦)
對須要防火牆的機器,能夠在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(針對從本機出去的包) 鏈

缺點:很差維護,服務器對外端口較多或有變化時,容易改出問題

D. 禁用相關模塊(不推薦)
只要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>

缺點:若是環境/配置文件不是徹底由你掌控或沒有很好的管理,容易出問題

相關文章
相關標籤/搜索