nf_conntrack詳解 轉

(服務器用的阿里雲主機,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)來匹配防火牆過濾規則。
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>

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章