ncr_conntrack調優實踐
女主宣言
該文章出自HULK虛擬化團隊(網絡小分隊),主要是基於在奧創版本升級過程中遇到的一個nf_conntrack問題展開的。該問題在日常開啟了iptables的高並發運維場景中也會經常出現。該文章主要是結合實際場景分析了nf_conntrack模塊存儲在hashtable中的時間和空間復雜度,結合線上的使用場景給出了優化建議,希望該文能對大家的日常運維有所啟發。
PS:豐富的一線技術、多元化的表現形式,盡在“HULK一線技術雜談”,點關注哦!
背景描述
最近在日常運維雲平台的過程中,遇到了nf_conntrack table full,然后開始drop packet的問題。當然這個問題屬於老問題了,在網上一搜資料也能搜到一大堆,但是真正深入研究的文章沒多少,淺嘗則止的居多。所以今天針對這個現場,我們深入分析一下這個模塊的一些技術細節,讓大家對這個問題的產生原因和背后細節能有更好的了解。
首先看看我們的問題現場,看看log
Dec 14 15:00:05 w-openstack08 kernel: nf_conntrack: table full, dropping packet
Dec 14 15:00:05 w-openstack08 kernel: nf_conntrack: table full, dropping packet
Dec 14 15:00:05 w-openstack08 kernel: nf_conntrack: table full, dropping packet
Dec 14 15:00:05 w-openstack08 kernel: nf_conntrack: table full, dropping packet
Dec 14 15:00:05 w-openstack08 kernel: nf_conntrack: table full, dropping packet
Dec 14 15:00:05 w-openstack08 kernel: nf_conntrack: table full, dropping packet
Dec 14 15:00:05 w-openstack08 kernel: nf_conntrack: table full, dropping packet
我們的問題是出現在ansible更新M版本openstack的時候觸發的問題,但是該問題的產生從這個問題的日志上看,與ansible中的一些具體步驟沒啥關系,但是有個共同的關系應該是ansible的一些高並發的連接觸發了nf_conntrack的保護性機制,table滿了以后,開始主動drop packet。
所以這里就需要先了解一下nf_conntrack,該內核模塊主要是干嘛的。
實際場景
nf_conntrack從名字上看是connection tracking,是內核模塊中的連接追蹤模塊。與iptables有關。計算節點上的iptables的規則,是因為我們的neutron網絡中使用了安全組,當前我們的計算節點上用了iptables filter表做包的最后一步過濾。
在iptables filter過濾packet的時候用了狀態跟蹤機制,我們使用 -state參數指定了兩種userspace的狀態,ESTABLISHED,RELATED,當使用狀態跟蹤機制的時候,我們就需要nf_conntrack模塊來對每個連接進行tracking。
具體計算節點規則如下,我們的規則中針對ESTABLISHED 和RELATED的連接做了放行,那些不知道屬於哪個連接的和沒有任何狀態的連接,一律drop掉。
[root@w-openstack53 /home/wangbaoping]# iptables -S|grep state
-A neutron-openvswi-i6db946b2-1 -m state --state RELATED,ESTABLISHED -m comment --comment "Direct packets associated with a known session to the RETURN chain." -j RETURN
-A neutron-openvswi-i6db946b2-1 -m state --state INVALID -m comment --comment "Drop packets that appear related to an existing connection (e.g. TCP ACK/FIN) but do not have an entry in conntrack." -j DROP
所以這里我們的nf_conntrack模塊會對宿主機上所有經過該iptables的連接都進行跟蹤。這里就需要知道具體跟蹤是咋跟蹤的,會記錄連接的那些信息,最后會將這些信息怎么存儲,又怎么查詢怎么信息。
這里我們可以先看看conntrack的跟蹤信息記錄,我們可以在/proc/net/nf_conntrack中看到已經被跟蹤的連接,如下:
[root@w-openstack53 /home/wangbaoping]# head /proc/net/nf_conntrack
ipv4 2 tcp 6 115 TIME_WAIT src=xx.xx.xx.xx dst=xx.xx.xx.xx sport=54585 dport=9000 src=xx.xx.xx.xx dst=xx.xx.xx.xx sport=9000 dport=54585 [ASSURED] mark=0 zone=3 use=2
ipv4 2 tcp 6 100 TIME_WAIT src=xx.xx.xx.xx dst=xx.xx.xx.xx sport=40460 dport=9000 src=xx.xx.xx.xx dst=xx.xx.xx.xx sport=9000 dport=40460 [ASSURED] mark=0 zone=3 use=2
ipv4 2 tcp 6 431999 ESTABLISHED src=xx.xx.xx.xx dst=xx.xx.xx.xx sport=42482 dport=80 src=xx.xx.xx.xx dst=xx.xx.xx.xx sport=80 dport=42482 [ASSURED] mark=0 zone=3 use=2
ipv4 2 tcp 6 85 TIME_WAIT
這里我們看看其中的一條ESTABLISHED狀態的trace記錄。
tcp 6 431999 ESTABLISHED src=xx.xx.xx.xx dst=1xx.xx.xx.xx sport=42482 dport=80 src=xx.xx.xx.xx dst=xx.xx.xx.xx sport=80 dport=42482 [ASSURED] mark=0 zone=3 use=2
tcp是跟蹤的協議類型,6很明顯是tcp的協議代碼,這里conntrack可以跟蹤tcp/udp/icmp等各種協議類型。這里431999 就是該連接的生命時間了,默認值是5天。在收到新包之前該值會逐漸變小,如果收到新包,該值會被重置一下,然后又開始重新計時。
接下來就是四元組了,接下來是反向連接的四元組,最后的ASSURED就是該狀態已經確認。
場景分析
其實整體宿主機中conntrack跟蹤的連接基本狀態都已經是time_wait,因為上面的vm提供的都是web短連接服務,server端主動斷開連接成功就會有個2MLS的time_wait,這些連接也都被track,該conntrack也會占用2分鍾的時間。
這都不用並發太高的環境,比如我們的vm web每秒並發個100,2MLS的時間2分鍾得生成260100=12000,基本上這一秒的並發會生成1萬多的time_wait,conntrack記錄。再按每條並發連接持續的時間長短,大概預估一下。
每天產生的time_wait狀態的conntrack數是,就按個比較活躍的業務來說,一天也能產生大概5萬多的conntrack記錄。
我們可以通過查看nf_conntrack_count看看當前,在bjcc的53節點上nf_conntrack跟蹤了多少連接,如下:
[root@w-openstack53 /home/wangbaoping]# sysctl net.netfilter.nf_conntrack_count
net.netfilter.nf_conntrack_count = 215168
差不多21萬的conntrack記錄。基本上97%的都是處於server主動斷開並且處於time_wait狀態的連接,3%是正在建立的establishted狀態的連接。
[root@w-openstack53 /home/wangbaoping]# ss -s
Total: 363 (kernel 1225)
TCP: 34 (estab 24, closed 3, orphaned 0, synrecv 0, timewait 1/0), ports 0
Transport Total IP IPv6
* 1225 - -
RAW 0 0 0
UDP 2 2 0
TCP 31 31 0
INET 33 33 0
FRAG 0 0 0
這里就是個短連接不斷積累的過程,不斷地server主動斷開,不斷地timewait,這些都會被conntrack跟蹤記錄。所以一般默認的nf_conntrack_max=65535,很快就被塞滿了。一旦塞滿了就會隨機的drop包。在外面看來就是丟包情況非常厲害。
原理剖析
前面都是說的nf_conntrack怎么記錄我們的connection。慢慢就輪到了nf_conntrack怎么存儲這些track記錄。
nf_conntrack對connection的track最后都會扔到一個hashtable里面,這里就涉及到一個查詢時間與存儲空間的效率問題了。
我們肯定都希望將這些entry每一條都塞到一個哈希桶里,每個桶都只有一個link node,該node只存儲一個connection entry。這樣每次查詢connection entry就是完美的O(1)的效率,多美好。
但是這樣的存儲空間要爆表了。
好吧,到這里先不說成本的問題,先說說如果真有這么多connection track連接,我們這個hashtable究竟要咋放這么多entry。
這里我們的都知道我們的entry最后會先經過hash,扔到我們的一定數量的hash桶里面。如果桶數量不等於entry數量的話,那一個桶里還得多放幾個entry。
這里官方的hashtable處理沖突用的是鏈地址法,該沖突處理方法也是比較常用的地址沖突處理方法。桶里放得是頭指針,然后相同key的node用單鏈表連起來。
這里hash桶的查找效率肯定是O(1)的,在hash桶里面是常用的link node list單鏈表。單鏈表的查詢效率肯定是o(n)的,所以我們都希望每個entry放在一個桶里,我們希望最快的查詢速度。
但是每個entry都放到桶里,內存消耗是非常大的。
因為看官方對conntrack entry的優化說明,每一條entry會占用大概300字節的空間。我們這里如果上來就分配一個超級大的hashtable,並且nf_conntrack_buckets == nf_conntrack_max 。
比如我們設置nf_conntrack_max = nf_conntrack_max = 12262144,該參數的意思就是我們的hashtable非常大,並且每個桶只放一條entry,可以放12262144條connection entry。
這樣我們會需要12262144308字節= 12262144308/(1024*1024) = 3508.22753906MB ,大約3個G的內存。。當然有余數是因為這個max值沒有設置成2的冪次方。
我們總共就64的內存,光給一個connection track,並且track到的連接都是些沒有實際意義的已經被釋放掉的time_wait的連接。所以這樣配置成本是有點高的。當然我們的服務器上不是這樣的。
所以我們一般都選擇hashtable + linknode list的方案。這樣能在存儲空間和查詢效率之間取個平衡。
因為桶是直接內存占用,這里的內存分配是按nf_conntrack的struck結構體來分配的,可以稍微去看看源碼中這個結構體就能看到有多龐大了。大約300字節。
每個桶里放的是該單鏈表的頭指針,指針要的內存空間要比nf_conntrack結構體要的明顯小太多了,一般這個與cpu架構和編譯環境都有關系,這里一般就按8字節算了。這樣會省很多存儲空間
比如官方一般都推薦一個桶里放4條entry。所以官方的默認參數里面,你一般會看到nf_conntrack_max = nf_conntrack_buckets *4,四倍的關系
如果nf_conntrack_max 遠遠大於nf_conntrack_buckets ,就意味着每個桶里面會放非常長的單鏈表。。這樣查詢速率肯定是會有比較大影響的。
比如我們現在的服務器配置:
[root@w-openstack53 /home/wangbaoping]# sysctl -a|grep nf_conntrack_max
net.netfilter.nf_conntrack_max = 12262144
[root@w-openstack53 /home/wangbaoping]# sysctl -a|grep nf_conntrack_buckets
net.netfilter.nf_conntrack_buckets = 16384
我們創建了一個hashtable,該table設置了16384個桶。那就是每個桶里面可以存放 12262144/16384 = 748.421875,大約可以放750條entry。這樣我們的entry查詢延遲就比官方的大了200多倍。
其實這里我們對線上的修改都只是更改了nf_conntrack_max ,把這個值設置成12262144,但是沒有改過nf_conntrack_buckets ,這個值足夠大,足以滿足我們的目前的connection entry日常產生記錄。
因為我們的線上一般就是30萬左右connection entry數。但是這不是個兼顧空間與時間的最優配置。
在官方建議中一般大於4GB的內存空間,默認nf_conntrack_buckets = 65536,如下。現在centos7.2版本中已經改成這個參數了。
nf_conntrack_max 默認四倍的關系。一個桶里放四個entry。兼顧時間和存儲空間
[root@w-openstack175 /home/wangbaoping]# sysctl -a|grep nf_conntrack_buckets
net.netfilter.nf_conntrack_buckets = 65536
[root@w-openstack175 /home/wangbaoping]# sysctl -a|grep nf_conntrack_max
net.netfilter.nf_conntrack_max = 262144
但是在centos7.1中還是默認的1GB的內存空間配置。默認nf_conntrack_buckets = 16384,默認的nf_conntrack_max 4倍關系 65536,這個max值肯定是滿足不了我們當前的業務記錄的。所以得調大點。
還有一個影響conntrack積累記錄的參數就是nf_conntrack_tcp_timeout_established ,該參數會跟蹤一個記錄5days,這太長了。當然這是官方推薦的時間。
調到一天等,但是我們的connection tracking記錄中只有3%的是established狀態的連接,所以該參數對降低線上table full丟包問題沒啥太大影響。
nf_conntrack_tcp_timeout_established = 432000
總結
所以最后針對該內核模塊的使用,有很多種方法。
1
擁抱高版本ovs
就是不用該模塊了。這樣的場景是在以后,比如說M版本支持ovs2.6,高版本ovs本身就支持在ovs層面做安全限制,不需要在用qbr這種蹩腳的設計了,這樣宿主機就不用啟用iptables,自然就不用connection track了。更不會有現在這個問題了。
2
修改iptables規則
繼續用這個模塊,但是我們直接在neutron 代碼中改一下,針對這些狀態機制的iptables規則做一個動作, -j notrack,這樣的好處是治本,把不需要track的iptables直接notrack,那自然就不會去占hashtable空間了,更不會報錯了。
3
優化核心參數
繼續用這個模塊。但是我們要對參數進行一下調優。參數得設置的更合理一些。
nf_conntrack_buckets 和nf_conntrack_max 的平衡關系就不說了,具體我們的max應該設置成多少呢?
這個與宿主機內存有關系,並且官方有個計算公式,
CONNTRACK_MAX = RAMSIZE (in bytes) / 16384 / (x / 32)
這里我們的宿主機內存一般都是64GB,所以CONNTRACK_MAX = 64 102464 = 4194304,最大支持400多萬,然后桶的數量就是四倍的關系。4194304/4 = 1048576
nf_conntrack_max = 4194304
nf_conntrack_buckets = 1048576
這樣就ok了。已經遠遠大於我們現在業務conn track 記錄數量了。
這樣的配置,用在我們的業務環境中。比如53上
net.netfilter.nf_conntrack_count = 218570
如果換這個參數的話。
我們有100萬個桶,20萬左右的entry放進去足以,每個桶就一個entry,查詢效率o(1),非常高。
同時效率高了,我們繼續算算內存占用。前面已經說了nf_conntrack struck本身初始化就得占用300左右字節,我們的link node指針也就是占用個8個字節而已。
所以我們算算 內存占用 = 4194304 * 300 + 1048576 * 8 = 1266679808字節 = 1208M。一個G而已
對應我們64GB的空間占用這點內存,換個O(1)的iptables查詢效率還是可以的。
看看當前可用mem還有8個G,還是可以接受的
[root@w-openstack53 /home/wangbaoping]# free -m
total used free shared buff/cache available
Mem: 64237 50988 8308 1284 4940 10751
Swap: 32255 803 31452
當前服務器上的配置前面我們已經算過了,內存占用 = 12262144 *300 + 16384 *8 = 3508.35253906M 大約3個G。
至於效率前面更已經算過了。時間等於 = o(n) + o(1) = o(n),這里的n = 12262144/16384 = 748.421875,而我們上面的配置中時間是o(1)的,現在的配置是我們優化后的配置的耗時700倍。
但是cpu真是太快了,700倍在cpu那里都不是個事。但是明顯該優化后的參數再時間和存儲空間兩個維度都是優於線上的。
但是優化后的配置有個不足就是,同時支持track的連接數是400萬。線上是1200萬。意思就是如果我們的宿主機上track的connection到了無可救葯的400萬以上了。那就會觸發丟包。
但是當前觀察所有的業務下的conntrack數目都只是30萬左右。並且track的都是一些time_wait狀態的connection,如下面的shm08,無效的connection track比例達到了97%。。
[root@w-openstack08 /home/wangbaoping]# cat /proc/net/nf_conntrack|grep ESTABLISHED|wc -l
5125
[root@w-openstack08 /home/wangbaoping]# cat /proc/net/nf_conntrack|grep TIME_WAIT|wc -l
136767
所以到這里有兩個選擇,就是在tcp層面去消除time_wait太多的問題。這就選擇范圍太多了。主流的還是tcp_tw_recycle內核參數的使用等等。這里就不展開了。
第二個就是在改一下nf_conntrack_tcp_timeout_time_wait = 120,改的小點。大家都知道time_wait其實就是為了讓包收收尾,以前的網絡太爛,包的路由和傳輸都很慢。預留2分鍾是為了讓這些釋放連接的包能在2分鍾內順利到達。現在網絡環境這么好。我們connection track沒必要跟蹤這么長時間。設置成60s,我們的connection track數量立馬就能降一半。
整體來說我們的max設置成400萬,桶的數量100萬,是在64G內存服務器上最優的解決方案,兼顧內存占用和查詢效率。其他的timeout參數,我們iptables沒必要對time_wait狀態的連接tracking 2分鍾之久。1分鍾就行。establishted狀態我們tracking 5天可以接受。
所以最后推薦配置如下:
nf_conntrack_max = 4194304
nf_conntrack_buckets = 1048576
nf_conntrack_tcp_timeout_time_wait = 60