從 Flannel 學習 Kubernetes overlay 網(wǎng)絡
?Flannel 介紹
Flannel 是一個非常簡單的 overlay 網(wǎng)絡(VXLAN),是 Kubernetes 網(wǎng)絡 CNI 的解決方案之一。Flannel 在每臺主機上運行一個簡單的輕量級 agent flanneld? 來監(jiān)聽集群中節(jié)點的變更,并對地址空間進行預配置。Flannel 還會在每臺主機上安裝 vtep flannel.1(VXLAN tunnel endpoints),與其他主機通過 VXLAN 隧道相連。
flanneld 監(jiān)聽在 8472 端口,通過 UDP 與其他節(jié)點的 vtep 進行數(shù)據(jù)傳輸。到達 vtep 的二層包會被原封不動地通過 UDP 的方式發(fā)送到對端的 vtep,然后拆出二層包進行處理。簡單說就是用四層的 UDP 傳輸二層的數(shù)據(jù)幀。
vxlan-tunnel
在 Kubernetes 發(fā)行版 K3S[1] 中將 Flannel 作為默認的 CNI 實現(xiàn)。K3S 集成了 flannel,在啟動后 flannel 以 go routine 的方式運行。
環(huán)境搭建
Kubernetes 集群使用 k3s 發(fā)行版,但在安裝集群的時候,禁用 k3s 集成的 flannel,使用獨立安裝的 flannel 進行驗證。
安裝 CNI 的 plugin,需要在所有的 node 節(jié)點上執(zhí)行下面的命令,下載 CNI 的官方 bin。
安裝 k3s 的控制平面。
安裝 Flannel。這里注意,F(xiàn)lannel 默認的 Pod CIRD 是 10.244.0.0/16?,我們將其修改為 k3s 默認的 10.42.0.0/16。
添加另一個節(jié)點到集群。
查看節(jié)點狀態(tài)。
運行兩個 pod:curl? 和 httpbin,為了探尋
網(wǎng)絡配置
接下來,一起看下 CNI 插件如何配置 pod 網(wǎng)絡。
初始化
Flannel 是通過 Daemonset? 的方式部署的,每臺節(jié)點上都會運行一個 flannel 的 pod。通過掛載本地磁盤的方式,在 Pod 啟動時會通過初始化容器將二進制文件和 CNI 的配置復制到本地磁盤中,分別位于 /opt/cni/bin/flannel? 和 /etc/cni/net.d/10-flannel.conflist。
通過查看 kube-flannel.yml[2] 中的 ConfigMap?,可以找到 CNI 配置,flannel 默認委托(見 flannel-cni 源碼 `flannel_linux.go#L78`[3])給 bridge 插件[4] 進行網(wǎng)絡配置,網(wǎng)絡名稱為 cbr0;IP 地址的管理,默認委托(見 flannel-cni 源碼 `flannel_linux.go#L40`[5]) host-local 插件[6] 完成。
還有 Flannel 的網(wǎng)絡配置,配置中有我們設置的 Pod CIDR 10.42.0.0/16? 以及后端(backend)的類型 vxlan?。這也是 flannel 默認的類型,此外還有 多種后端類型[7] 可選,如 host-gw、wireguard、udp、Alloc、IPIP、IPSec。
Flannel Pod 運行啟動 flanneld? 進程,指定了參數(shù) --ip-masq? 和 --kube-subnet-mgr?,后者開啟了 kube subnet manager 模式。
運行
集群初始化時使用了默認的 Pod CIDR 10.42.0.0/16?,當有節(jié)點加入集群,集群會從該網(wǎng)段上為節(jié)點分配 屬于節(jié)點的 Pod CIDR 10.42.X.1/24。
flannel 在 kube subnet manager 模式下,連接到 apiserver 監(jiān)聽節(jié)點更新的事件,從節(jié)點信息中獲取節(jié)點的 Pod CIDR。
然后在主機上寫子網(wǎng)配置文件,下面展示的是其中一個節(jié)點的子網(wǎng)配置文件的內容。另一個節(jié)點的內容差異在 FLANNEL_SUBNET=10.42.1.1/24,使用的是對應節(jié)點的 Pod CIDR。
CNI 插件執(zhí)行
CNI 插件的執(zhí)行是由容器運行時觸發(fā)的,具體細節(jié)可以看上一篇 《源碼解析:從 kubelet、容器運行時看 CNI 的使用》。
Flannel Plugin Flow
flannel 插件
flannel? CNI 插件(/opt/cni/bin/flannel?)執(zhí)行的時候,接收傳入的 cni-conf.json?,讀取上面初始化好的 subnet.env? 的配置,輸出結果,委托給 bridge 進行下一步。
bridge 插件
bridge 使用上面的輸出連同參數(shù)一起作為輸入,根據(jù)配置完成如下操作:
- 創(chuàng)建網(wǎng)橋cni0(節(jié)點的根網(wǎng)絡命名空間)
- 創(chuàng)建容器網(wǎng)絡接口eth0( pod 網(wǎng)絡命名空間)
- 創(chuàng)建主機上的虛擬網(wǎng)絡接口vethX(節(jié)點的根網(wǎng)絡命名空間)
- 將vethX? 連接到網(wǎng)橋 cni0
- 委托 ipam 插件分配 IP 地址、DNS、路由
- 將 IP 地址綁定到 pod 網(wǎng)絡命名空間的接口eth0 上
- 檢查網(wǎng)橋狀態(tài)
- 設置路由
- 設置 DNS
最后輸出如下的結果:
port-mapping 插件
該插件會將來自主機上一個或多個端口的流量轉發(fā)到容器。
Debug
讓我們在第一個節(jié)點上,使用 tcpdump? 對接口 cni0 進行抓包。
從 pod curl? 中使用 pod httpbin? 的 IP 地址 10.42.1.2 發(fā)送請求:
cni0
從在 cni0 上的抓包結果來看,第三層的 IP 地址均為 Pod 的 IP 地址,看起來就像是兩個 pod 都在同一個網(wǎng)段。
tcpdump-on-cni0
host eth0
文章開頭提到 flanneld 監(jiān)聽 udp 8472 端口。
我們直接在以太網(wǎng)接口上抓取 UDP 的包:
再次發(fā)送請求,可以看到抓取到 UDP 數(shù)據(jù)包,傳輸?shù)呢撦d是二層的封包。
tcpdump-on-host-eth0
Overlay 網(wǎng)絡下的跨節(jié)點通信
在系列的第一篇中,我們研究 pod 間的通信時提到不同 CNI 插件的處理方式不同,這次我們探索了 flannel 插件的工作原理。希望通過下面的圖可以對 overlay 網(wǎng)絡處理跨節(jié)點的網(wǎng)絡通信有個比較直觀的認識。
當發(fā)送到 10.42.1.2? 流量到達節(jié)點 A 的網(wǎng)橋 cni0?,由于目標 IP 并不屬于當前階段的網(wǎng)段。根據(jù)系統(tǒng)的路由規(guī)則,進入到接口 flannel.1?,也就是 VXLAN 的 vtep。這里的路由規(guī)則也由 flanneld 來維護,當節(jié)點上線或者下線時,都會更新路由規(guī)則。
flannel.1? 將原始的以太封包使用 UDP 協(xié)議重新封裝,將其發(fā)送到目標地址 10.42.1.0? (目標的 MAC 地址通過 ARP 獲?。?。對端的 vtep 也就是 flannel.1? 的 UDP 端口 8472 收到消息,解幀出以太封包,然后對以太封包進行路由處理,發(fā)送到接口 cni0,最終到達目標 pod 中。
響應的數(shù)據(jù)傳輸與請求的處理也是類似,只是源地址和目的地址調換。
參考資料
[1] K3S: https://k3s.io/
[2] kube-flannel.yml: https://raw.githubusercontent.com/flannel-io/flannel/master/Documentation/kube-flannel.yml
[3] flannel-cni 源碼 flannel_linux.go#L78?: https://github.com/flannel-io/cni-plugin/blob/v1.1.0/flannel_linux.go#L78
[4] bridge 插件: https://www.cni.dev/plugins/current/main/bridge/
[5] flannel-cni 源碼 flannel_linux.go#L40?: https://github.com/flannel-io/cni-plugin/blob/v1.1.0/flannel_linux.go#L40
[6] host-local 插件: https://www.cni.dev/plugins/current/ipam/host-local/
[7] 多種后端類型: https://github.com/flannel-io/flannel/blob/master/Documentation/backends.md