TKE 容器網路中的 ARP Overflow 問題探究及其解決之道
阿新 • • 發佈:2021-03-04
作者朱瑜堅,騰訊雲後臺開發工程師,熟悉 CNI 容器網路相關技術,負責騰訊雲 TKE 的容器網路的構建和相關網路元件的開發維護工作,作為主力開發實現了 TKE 下一代容器網路方案。
# 1. 問題背景
## 1.1 問題描述
最近,某內部客戶的 TKE VPC-CNI 模式的獨立網絡卡叢集上出現了 pod 間訪問不通的情況,問題 pod ping 不通任何其他 pod 和節點。
檢視 dmesg 核心日誌,有如下報錯資訊:neighbour: arp_cache: neighbor table overflow!(下圖為後續復現的日誌截圖)
![](https://img2020.cnblogs.com/other/2041406/202103/2041406-20210304171202251-1908800982.png)
並且,這個叢集規模較大,約有 1000 個節點,30000 個 pod,基本可以懷疑是由於叢集規模較大,導致 ARP 表項過多,從而引起 ARP Overflow 的問題。
## 1.2 名詞解釋
| 名詞 | 說明 |
| :----------: | :----------------------------------------------------------: |
| TKE | 全稱 Tencent Kubernetes Engine, 騰訊雲容器服務,是基於原生 kubernetes 提供以容器為核心的、高度可擴充套件的高效能容器管理服務 |
| VPC-CNI 模式 | 是容器服務 TKE 基於 CNI 和 VPC 彈性網絡卡實現的容器網路能力 |
| Pod | Pod 是 kubernetes 的基本資源管理單位,擁有獨立的網路名稱空間,1個 Pod 可包含多個容器 |
# 2. 問題初步分析
從如上報錯資訊可知,這個問題的基本原因在於 ARP 快取表打滿了。這裡涉及到核心的 ARP 快取垃圾回收機制。當 ARP 表項太多且又沒有可回收的表項的時候,新表項就會無法插入。
這就導致網路包傳送時無法找到對應的硬體地址(MAC)。使得網路包不能傳送。
那麼具體什麼情況會導致新表項無法插入呢?回答這個問題,我們需要先深入瞭解一下 ARP 快取老化及垃圾回收機制。
# 3. ARP 快取老化回收機制
## 3.1 ARP 快取表項狀態機
![](https://img2020.cnblogs.com/other/2041406/202103/2041406-20210304171202634-1665772470.png)
如上圖,是整個 ARP 表項的生命週期及其狀態機。
我們知道,對於 TCP/IP 網路包傳送時,網路棧需要對端的 MAC 地址才能讓網路包轉換成二層的資料結構——幀,從而在網路中傳輸。而對於不同廣播域的 IP 地址,其對端 MAC 地址為閘道器,傳送端會將網路包發給閘道器讓其轉發,而對於同廣播域中的 IP 地址,其對端 MAC 地址即與 IP 地址對應。
而通過 IP 地址找到 MAC 地址就是 ARP 協議的主要工作內容。ARP 協議的工作過程此處不再贅述,而通過 ARP 協議找到 IP 地址對應的 MAC 地址後,會將該對應關係儲存在本機上一段時間,以減少 ARP 協議的通訊頻率,加快網路包的傳送。該對應關係,即 ARP 快取表項,其狀態機或整個生命週期可描述如下:
1. 初始時,對於任何網路包傳送時,核心協議棧需要找到目的 IP 地址對應的對端 MAC 地址,如果這時 ARP 快取中沒有命中,則會新插入一條狀態為 Incomplete 的表項。Incomplete 狀態會嘗試傳送 ARP 包,請求某 IP 地址對應的 MAC 地址。
2. 若收到 ARP 迴應的,表項狀態就變為 Reachable。
3. 若嘗試了一定次數後沒收到響應,表項即變為 Failed。
4. Reachable 表項在到達超時時間後,會變成 Stale 狀態,Stale 狀態的表項已不可再使用。
5. Stale 的表項若有被引用來發包,則表項會變為 Delay 狀態。
6. Delay 狀態的表項也不可使用來發包,但在 Delay 狀態到期前,收到 ARP 的**本機確認**,則重新轉為 Reachable 狀態。
7. Delay 狀態到期,表項變為 Probe 狀態,該狀態與 Incomplete 狀態類似。
8. Stale 狀態到期後,會被啟動的垃圾回收起回收刪除。
通過以下命令可檢視當前網路名稱空間(network namespace) 中 arp 表項及其狀態:
```
ip neigh
```
如:
![](https://img2020.cnblogs.com/other/2041406/202103/2041406-20210304171203069-1771104899.png)
**本機確認**:這是代指本機收到了一個源 mac 地址匹配的網路包,這個網路包表示此次網路通訊的“上一跳”即是該 mac 地址的機器,能收到這個網路包即說明該 mac 地址可達。因此即可把該表項轉為 Reachable 狀態。通過這一機制,核心可減少 ARP 的通訊需求。
## 3.2 涉及到的核心引數
以下列出了該機制中主要涉及的核心引數:
| 引數 | 含義 | 預設值 |
| :-----------------------------------------------------: | :----------------------------------------------------------: | :----: |
| /proc/sys/net/ipv4/neigh/default/base_reachable_time | Reachable 狀態基礎過期時間,每個表項過期時間是在[1/2*base_reachable_time,3/2*base_reachable_time]之間 | 30秒 |
| /proc/sys/net/ipv4/neigh/default/base_reachable_time_ms | Reachable 狀態基礎過期時間,毫秒錶示 | 30秒 |
| /proc/sys/net/ipv4/neigh/default/gc_stale_time | Stale 狀態過期時間 | 60秒 |
| /proc/sys/net/ipv4/neigh/default/delay_first_probe_time | delay 狀態過期到 Probe 的時間 | 5秒 |
| /proc/sys/net/ipv4/neigh/default/gc_interval | gc 啟動的週期時間 | 30秒 |
| /proc/sys/net/ipv4/neigh/default/gc_thresh1 | 少於這個值,gc 不會啟動 | 2048 |
| /proc/sys/net/ipv4/neigh/default/gc_thresh2 | ARP表的最多紀錄的軟限制,允許超過該數字5秒 | 4096 |
| /proc/sys/net/ipv4/neigh/default/gc_thresh3 | ARP表的最多紀錄的硬限制,大於該數目,gc立即啟動,並強制回收 | 8192 |
其中,gc 相關的核心引數是對**所有網絡卡(interface)**生效的。但是各種到期時間的設定是僅對單獨網絡卡(interface)生效的,default 值僅對新增介面裝置生效。
## 3.3 ARP 快取垃圾回收機制
由其快取表項的狀態機我們知道,不是所有的表項都會被回收,只有 Stale 狀態過期後,Failed 的表項可能會被回收。另外,ARP 快取表項的垃圾回收是觸發式的,需要回收的表項不一定立刻會被回收,ARP 快取表項的垃圾回收有四種啟動邏輯:
1. arp 表項數量 < gc_thresh1,不啟動。
2. gc_thresh1 =< arp 表項數量 <= gc_thresh2,按照 gc_interval 定期啟動
3. gc_thresh2 < arp 表項數量 <= gc_thresh3,5秒後啟動
4. arp 表項數量 > gc_thresh3,立即啟動
對於不可回收的表項,垃圾回收即便啟動了也不會對其進行回收。因此當不可回收的表項數量大於 gc_thresh3 的時候,垃圾回收也無能為力了。
# 4. 進一步探究
## 4.1 垃圾回收閾值是按名稱空間級別生效還是子機級別生效
我們知道,每個獨立的網路名稱空間是有完整的網路協議棧的。那麼,ARP 快取的垃圾回收也是每個名稱空間單獨處理的嗎?
從涉及的核心引數可以看出,gc 相關的核心引數是對所有介面裝置生效的,因此,這裡可以推測垃圾回收的閾值也是子機級別生效的,而不是按網路名稱空間。
這裡做了一個簡單的實驗來驗證:
1. 在節點 default ns 上的 gc_thresh1, gc_thresh2 和 gc_thresh3 設定成60 。
2. 在節點上建立了 19 個獨立網絡卡模式的 Pod
3. 任意選擇一個 pod ping 其他的 pod,以此產生 arp 快取
4. 用 shell 指令碼掃描節點上的所有 pod,計算 arp 表項的和,可以得到:
![](https://img2020.cnblogs.com/other/2041406/202103/2041406-20210304171203510-1795235657.png)
![](https://img2020.cnblogs.com/other/2041406/202103/2041406-20210304171204001-721835452.png)
可以發現, 各名稱空間的累計 arp 表項的數目在每次達到 60 之後就會快速下降,也就是達到 60 之後就產生了垃圾回收。重複幾次都是類似的結果,因此,這說明了垃圾回收在計算 ARP 表項是否觸發閾值時,是計算各名稱空間的累計值,也就是按子機級別生效,而非名稱空間級別。
## 4.2 不可回收的 ARP 表項達到 gc_thresh3 時,會發生什麼
由前面的介紹我們知道,垃圾回收機制並非回收任意 ARP 快取,因此,當所有可達狀態的 ARP 表項打滿 ARP 快取表時,也即達到 gc_thresh3 時,會發生什麼行為?可以推測,此時舊的無法回收,新的 ARP 表項也無法插入,新的網路包會無法傳送,也即發生了本次文章所描述的問題。
為了驗證這一點,繼續在以上環境中實驗:
1. 將任意兩個 Pod 的基礎老化時間 base_reachable_time 調長到 1800秒,以此產生不可回收的 ARP 表項。
2. 設定 gc_thresh3 為 40,以此更容易觸發問題
3. 選擇調整了老化時間的 pod ping 其他的 pod,以此產生 arp 快取。
4. 可以發現,當到達閾值的時候,ping 會產生丟包或不通:
![](https://img2020.cnblogs.com/other/2041406/202103/2041406-20210304171204743-775528417.png)
![](https://img2020.cnblogs.com/other/2041406/202103/2041406-20210304171205222-1461708804.png)
檢視核心日誌 dmesg -T,可以看到文章開頭描述的資訊:neighbour: arp_cache: neighbor table overflow!
以上實驗說明了,不可回收的 ARP 表項打滿 ARP 表會讓新的表項無法插入,從而網路不通。
## 4.3 為什麼相比 TKE 的全域性路由模式和單網絡卡多 IP 模式,獨立網絡卡模式更容易產生這個問題
要回答這個問題,我們先簡單看一下 TKE 各網路模式的原理介紹
### 全域性路由模式
![](https://img2020.cnblogs.com/other/2041406/202103/2041406-20210304171205702-1257418519.png)
該網路模式下,每個節點上的容器 IP 是預先分配到節點上的,這些 IP 同屬一個子網,且每個節點就是一個小子網。我們知道,ARP 協議是為二層通訊服務的,因此,該網路方案中,每個 Pod 的網路名稱空間內的 ARP 表最大可能儲存了節點上所有其他 Pod 的 ARP 表項,最後節點的 ARP 表項的數量最大即為 **節點子網 IP 數的平方**,如節點的子網大小是128,則其 ARP 表項最大可能為 127 的平方,約 16000。
### 共享網絡卡模式
![](https://img2020.cnblogs.com/other/2041406/202103/2041406-20210304171206168-911142146.png)
該網路模式下,每個節點會繫結輔助彈性網絡卡,節點上的 Pod 共享使用該輔助網絡卡,每個 Pod 內不會做網路包的路由,只會有一條 ARP 表項,實際的路由控制在節點的 default 名稱空間內完成。因此,該網路模式下,ARP 快取表幾乎是共享的,又因為網絡卡只能屬於 1 個子網,因此每個節點的 Pod ARP 快取表只能儲存一個子網的 IP-MAC 對映關係,至多數量為**各網絡卡所在子網內 IP 的數量和**,如子網是 /22,即含有約 1000 個 ip, 那麼 arp 表項也大概有 1000,由於節點網絡卡配額一般不超過 10,因此該節點的最大 ARP 表項一般不超過 10000。
### 下一代網路方案——獨立網絡卡模式
![](https://img2020.cnblogs.com/other/2041406/202103/2041406-20210304171206814-269910151.png)
獨立網絡卡模式是 TKE 團隊推出的下一代“零損耗”容器網路方案,其基本原理如下圖所示:
即母機虛擬出的彈性網絡卡,直接置於容器中,使容器獲得與 CVM 子機一樣的網路通訊能力和網路管理能力,大大提升了容器網路的資料面能力,真正做到“零損耗”。
目前,**獨立網絡卡網路方案已在 TKE 產品中開放白名單測試,歡迎內外部客戶體驗試用。**
以上網路方案中,每個 Pod 都會獨佔一個網絡卡,也會擁有獨立的名稱空間和獨立的 ARP 快取表。而每個網絡卡都可以屬於不同的子網。因此,在獨立網絡卡模式裡,ARP 快取表項數量至多為**同可用區的子網 IP 數量之和**。這一數量量級是可以很輕易上萬的,很容易就突破了預設的 ARP 快取設定。也就觸發了這個問題。
# 5. 解決方案
從以上的分析可以看出,這個問題,調大垃圾回收的閾值,可以比較好的解決問題。因此,臨時的解決方案,就是調大 ARP 快取表的垃圾回收閾值:
```
echo 8192 > /proc/sys/net/ipv4/neigh/default/gc_thresh1echo 16384 > /proc/sys/net/ipv4/neigh/default/gc_thresh2echo 32768 > /proc/sys/net/ipv4/neigh/default/gc_thresh3
```
# 6. 總結
ARP 快取打滿之後,Pod 就會網路不通。初看起來很簡單,但是其背後的 ARP 快取老化和垃圾回收機制也是比較複雜的。查詢了很多資料,但是都對“垃圾回收閾值是對各名稱空間的 ARP 表項累積值生效還是單獨生效”,“垃圾回收會回收哪些表項”,“表項打滿後的具體行為如何”等問題說不清、道不明。因此,筆者嘗試通過幾個小實驗驗證了具體的行為模式。相比直接閱讀晦澀的核心原始碼,實驗法也許也是一個研究問題和理解機制的捷徑了。希望能夠幫助到各位讀者。
>【騰訊雲原生】雲說新品、雲研新術、雲遊新活、雲賞資訊,掃碼關注同名公眾號,及時獲取更多幹貨!!
![](https://img2020.cnblogs.com/other/2041406/202103/2041406-20210304171207350-1807336