整合 CNI 的常見問題 — 坑就是挖給人踩的!

smalltown
Starbugs Weekly 星巴哥技術專欄
10 min readMay 10, 2022

Background

一直以來自己所負責維運的 K8s 都是由 SRE 所架設而來,以 OS 和 CNI 的組合來說,一開始使用著 CoreOS + Flannel,因為在好幾年前也沒有太多的替代方案可供選擇,所以一用也用了好幾年,後來 CoreOS 被 RedHat 收購,分裂成 FlatCar CoreOS 和 Fedora CoreOS;而 CNI 隨著市場需求跟 Cloud Provider 大力支援 K8s 之下,有了越來越多種的選擇,目前自己所架設的 K8s 組合變成 FlatCar CoreOS + Amazon VPC CNI,自己在此組合下討生活這一兩年來也算是三折肱成良醫,所以想要將一些常遇到的問題透過這篇文章記錄下來

What is Container OS?

https://www.netapp.com/blog/containers-vs-vms/

自己一開始並沒有選擇常見的 Linux Distribution,例如 Ubuntu,而是選了 Container OS,這個詞從我接觸 CoreOS 才學習到,他是一種專門為了運行 Container 而為此進行最佳化的的作業系統,或許稱為 Container Optimized OS 會更好理解,後來群雄割據有越來越多的 Container OS 誕生在市場上,例如 Fedora CoreOS (RedHat), FlatCar CoreOS (MicroSoft), Bottlerocket (AWS), RancherOS (Rancher)…等,或許大家可以發現每一種 Container OS 背後都有富爸爸在撐腰,換句話說,當一間公司要提供 K8s 託管服務時,現在好像也都會去準備好符合自己形狀的 Container OS,根據我自己使用過 CoreOS, FlatCar CoreOS, Fedora CoreOS 的經驗,來分析一下 Container OS 的優缺點

😃 Pros:

  • OS 變成像一般軟體的發布流程:根據功能或是安全需求,一個月就有好幾次的更新版本推出,不像傳統 Linux Distribution,幾年出個大版號更新,有問題時就進去修修補補 (Reference)
  • 讓 Container 運行的最小需求環境:移除掉不必要的套件,而且使用者也不能夠在後續任意安裝東西,必須要盡量透過 Container 的方式去滿足需求,很多檔案都是唯讀模式無法修改,在安全性上提升很多
  • 從一開始就考慮好組態管理:預設就有內建 Provision 模組以及與各種平台整合的方式,讓使用者可以輕鬆完成作業系統內的各種設定,例如 CoreOS 體系所使用的 Ignition

🥲 Cons:

  • 不彈性的更新方式:以往遇到高風險 CVE 時可以選擇只修補有問題的地方,但 Container OS 每次更新都是一大包,有時候會變相強迫使用者安裝其他與安全修補無關的更新,導致遇到不預期的錯誤,例如在以前要是 Container Runtime 需要安全修補的話,直接更新與測試他就搞定了,但 Container OS 每次發佈的版本都含有不少東西,除了目標安全更新之外可能還含有 iptables 跟 systemd-networkd 的相關修改,導致使用者需要測試的範圍不小心就變大了
  • 無法任意客製化:因為不能自己安裝東西,假如遇到無法透過 Container 達成的事情,就只能等官方支援,有時候一等就要好久,例如我希望官方可以支援新版的 Ignition,結果花了一年才實作完成 (Reference)

當然這些缺點可以透過自己去 Build Container OS 來避掉,但這樣要維護的東西會變得更多,時間應該要投資在更有價值的事情;自己覺得這個領域在這兩年來相對低調不少,不過還是有持續在發展,或許是因為大部分的人都使用託管服務,所以就不用去在意運行 K8s 底層的作業系統是哪一個

What is CNI?

CNI 全名為 Container Network Interface,其中包含著配置 Linux Container 網路的規範與函示庫 (Reference),主要功能就是在 Container Runtime 建立 Container 時,先建立好 Network Namespace,並且調用 CNI Plugin 為其配置相關網路設定,在 CNI 內多個 Plugin 中,自己覺得比較重要的功能有以下兩個:

  • 負責給 Container 配置網路:透過實作 AddNetwork Interface 來配置網路給 Container,然後透過實作 DelNetwork Interface 來刪除 Container 網路
  • 負責給 Container 分配 IP 位址:就如同字面上的意思,只是不同的 CNI 有不同的 IP 位址配置方式

當 kubelet 在建立 Pod 的時候,他要怎麼透過 CNI 來讓 Pod 可以成功設定好網路呢?其實可以看到在 kubelet 的參數中有兩個跟 cni 相關的參數,分別是…

  • cni-bin-dir (預設 /opt/cni/bin):存放各種 CNI Plugin 執行檔案的位置,例如上面提到的配置網路,刪除網路,分配 IP 位址的功能
  • cni-conf-dir (預設 /etc/cni/net.d):存放 CNI 的組態檔案,讓 kubelet 知道 Pod 在設定網路時,需要透過哪一些 Plugin 來完成

這兩個資料夾裡面的東西有準備好的話,應該就算成功一半了XD

Amazon VPC CNI

https://github.com/aws/amazon-vpc-cni-k8s/blob/master/docs/cni-proposal.md

Kubernetes 本身對於 Pod 的網路配置有一些規定:

  • 所有的 Container 必須在沒有 NAT 的幫助之下跟其他 Container 進行連線溝通
  • 所有的 Node 必須在沒有 NAT 的幫助之下跟其他 Container 進行連線溝通,反之亦然
  • Container 本身使用的 IP 位址跟別人與他進行連線溝通時看到的 IP 位址必須為同一個

因此底下以 Amazon VPC CNI 為例,簡單說一下當一個 Pod 產生時,需要完成哪一些步驟,他才能夠達到上面 K8s 所要求的目標

  • 配置 IP 位址:首先 Pod 所在的 Node (EC2) 透過 ipamd 分配到一個 VPC 中的閒置 IP 位址
  • 設定虛擬網路卡介面:建立一組 veth,一個 veth 在 Host Namespace,另外一個 veth 在 Pod Namespace
  • 設定 Pod 內網路:在 Pod 內將 IP 位址配置給 Pod 的 eth0,新增預設 Gateway 跟預設 Route 給 Pod 的 Route Table,最後在預設 Gateway 新增靜態 ARP
  • 設定 Host 網路:在 Host 這端則要新增 Host Route 跟 Rule,這樣子從外面來的連線才能後成功連到 Pod
  • 設定對外連線:而 Pod 到 VPC 以外網路的溝通連線方式則是透過 iptables SNAT 利用在主要 ENI 的主要 IP 來達成

雖然此處是以 AWS VPC CNI 為例,但是其他種 CNI 或多或少也有會類似的步驟,所以深入了解一種 CNI 之後,要去學習其他種時也會相對快上手

How to Troubleshooting CNI?

CNI 的開發其實都通過了完整的測試,但怎麼我自己使用起來還是遇到不少問題呢?

  • 因為凡是軟體就一定會有 Bug,測試也不可能把所有的 Case 都測試出來
  • CNI 必須要跟作業系統完美合作才能發揮功能,但不同的作業系統使用不同的網路管理套件與組態配置,這些是在開發測試時不容易涵蓋到的

但目前整個 K8s 的升級頻率相當的高,為了保持系統安全並享受到最新功能,所以自己在升級作業系統或是 CNI 時就遇到不少問題過,根據上面的知識補充,獲知 Pod 的建立時要怎麼知道如何使用 CNI 來配置網路,以及 CNI 如何去幫 Pod 去配置網路,所以當 Pod 網路連線不符合預期時,對於應該要朝什麼方向去查問題就比較有一點方向了

🛠️ kubelet

  • 查詢 kubelet log:kubelet 是負責建立 K8s Pod 的角色,所以 K8s Pod 網路要是一開始就完全設定不起來的話,可以去看看 kubelet 的 Log 有沒有提到什麼重要資訊
  • 檢查 CNI 相關檔案:檢查看看 cni-bin-dir 和 cni-conf-dir 這兩個參數所設定的資料夾裡面有沒有缺東西

🛠️ CNI

  • 查詢 CNI Plugin Log:CNI 主要的功能為配置網路,清理網路,分配 IP…等,假如是這幾個重要步驟執行時遇到問題,應該可以在 Log 找到一些線索,然後就可以去該 CNI 的 GitHub Repository 找找看 Issue 或是檢視一下程式碼,看看要怎麼解掉遇到的問題

🛠️ 作業系統網路管理套件

  • 確定使用何種網路管理套件:作業系統內都會有一個管理網路的套件,例如 FlatCar CoreOS 使用的為 systemd-networkd,當 CNI 在為 Pod 配置跟刪除網路時,會去新增虛擬網路卡介面, IP Route/Rule
  • 查詢網路管理套件 Debug Log:對應的動作都可以在網路管理套件的 Debug Log 裡面查到才對,例如自己就踩過一個坑,有些網路套件會把沒有定義在組態內的 IP Route/Rule 給刪除掉,所以就會發生 CNI 將 IP Route/Rule 新增完成後,網路管理套件將其都刪除掉,這種情況可以從 Debug 裡面就抓到兇手

💡 解決方式有兩種 (細節可以參考這個我剛好有一起參與的 GitHub Issue)

  • 使用全域參數:以 systemd-networkd 來說,有兩個參數叫做 ManageForeignRoutesManageForeignRoutingPolicyRules ,當設定他們為 Flase 時,systemd-networkd 就不會去刪除由別人 (CNI) 所添加的 IP Route/Rule
  • 特定網路介面:假如網路管理套件沒有類似上面的參數可以設定的話,那就是參考上面 GitHub Issue 中提到的方法,針對特定的網路介面去做設定,讓 CNI 所添加的 IP Route/Rule 不會被刪除掉

🛠️ iptables/nftables

  • iptables rule 有問題:不同的 CNI 會透過 iptables 去做不同的設定,可以研究看看是不是有規則漏加或是錯誤,假如有的話,可以在 kubelet 啟動之前先加上正確的 Rule 去當作 Workaround,但自己覺得在 CNI 發個 Issue 讓他主動去添加比較正規
  • iptables 被淘汰掉:不知道大家有沒有聽過 nftables?他是要用來取代 iptables 的封包過濾工具,有一些作業系統已經開始將 iptables 換成 nftables,假如 CNI 裡面還是使用 iptables 去添加規則,但是 Host 已經置換成 nftables,在這種情況之下就會遭遇問題,自己最近剛好遇到 (再貼一個 GitHub Issue)

🛠️ sysctl

  • 查詢 sysctl 的 net 相關參數:最後就是查詢作業系統看看有沒有將 sysctl 中跟網路相關的設定給打開來,例如:net.bridge.bridge-nf-call-iptables, net.ipv4.ip_forward…等 (Reference)

Conclusion

網路世界博大精深,每次遇到問題解掉之後就覺得自己懂了,但下次遇到不同問題又覺得好像什麼都不懂XD 要是之後自己又遇到有關於 CNI 的問題就會再補近來這篇文章中;目前各家 Cloud Provider 都有開發自己的 CNI,讓 K8s Cluster 可以跟既有的網路環境完美的整合再一起,而在地端自架的話,之前自己使用過 Calico,最近則是常常聽到 Cilium,等到哪天要落地的話再來研究看看 ==+

--

--

smalltown
Starbugs Weekly 星巴哥技術專欄

原來只是一介草 QA,但開始研究自動化維運雲端服務後,便一頭栽進 DevOps 的世界裏,熱愛鑽研各種可以提升雲端服務品質及增進團隊開發效率的開源技術