?Flannel 介紹

Flannel 是一個非常簡單的 overlay 網(wǎng)絡(luò)(VXLAN),是 Kubernetes 網(wǎng)絡(luò) CNI 的解決方案之一。Flannel 在每臺主機上運行一個簡單的輕量級 agentflanneld?來監(jiān)聽集群中節(jié)點的變更,并對地址空間進行預(yù)配置。Flannel 還會在每臺主機上安裝 vtepflannel.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。

sudo mkdir -p /opt/cni/bincurl -sSL https://github.com/containernetworking/plugins/releases/download/v1.1.1/cni-plugins-linux-amd64-v1.1.1.tgz | sudo tar -zxf - -C /opt/cni/bin

安裝 k3s 的控制平面。

export INSTALL_K3S_VERSION=v1.23.8+k3s2curl -sfL https://get.k3s.io | sh -s - --disable traefik --flannel-backend=none --write-kubeconfig-mode 644 --write-kubeconfig ~/.kube/config

安裝 Flannel。這里注意,F(xiàn)lannel 默認的 Pod CIRD 是10.244.0.0/16?,我們將其修改為 k3s 默認的10.42.0.0/16。

curl -s https://raw.githubusercontent.com/flannel-io/flannel/master/Documentation/kube-flannel.yml | sed "s|10.244.0.0/16|10.42.0.0/16|g" | kubectl apply -f -

添加另一個節(jié)點到集群。

export INSTALL_K3S_VERSION=v1.23.8+k3s2export MASTER_IP=export NODE_TOKEN=curl -sfL https://get.k3s.io | K3S_URL=https://${MASTER_IP}:6443 K3S_TOKEN=${NODE_TOKEN} sh -

查看節(jié)點狀態(tài)。

kubectl get nodeNAME          STATUS   ROLES                  AGE   VERSIONubuntu-dev3   Ready                     13m   v1.23.8+k3s2ubuntu-dev2   Ready    control-plane,master   17m   v1.23.8+k3s2

運行兩個 pod:curl?和httpbin,為了探尋

NODE1=ubuntu-dev2NODE2=ubuntu-dev3kubectl apply -n default -f - <網(wǎng)絡(luò)配置

接下來,一起看下 CNI 插件如何配置 pod 網(wǎng)絡(luò)。

初始化

Flannel 是通過Daemonset?的方式部署的,每臺節(jié)點上都會運行一個 flannel 的 pod。通過掛載本地磁盤的方式,在 Pod 啟動時會通過初始化容器將二進制文件和 CNI 的配置復(fù)制到本地磁盤中,分別位于/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)絡(luò)配置,網(wǎng)絡(luò)名稱為cbr0;IP 地址的管理,默認委托(見flannel-cni 源碼 `flannel_linux.go#L40`[5])host-local 插件[6]完成。

#cni-conf.json 復(fù)制到 /etc/cni/net.d/10-flannel.conflist{  "name": "cbr0",  "cniVersion": "0.3.1",  "plugins": [    {      "type": "flannel",      "delegate": {        "hairpinMode": true,        "isDefaultGateway": true      }    },    {      "type": "portmap",      "capabilities": {        "portMappings": true      }    }  ]}

還有 Flannel 的網(wǎng)絡(luò)配置,配置中有我們設(shè)置的 Pod CIDR10.42.0.0/16?以及后端(backend)的類型vxlan?。這也是 flannel 默認的類型,此外還有多種后端類型[7]可選,如host-gw、wireguard、udp、Alloc、IPIP、IPSec。

#net-conf.json 掛載到 pod 的 /etc/kube-flannel/net-conf.json{  "Network": "10.42.0.0/16",  "Backend": {    "Type": "vxlan"  }}

Flannel Pod 運行啟動flanneld?進程,指定了參數(shù)--ip-masq?和--kube-subnet-mgr?,后者開啟了kube subnet manager模式。

運行

集群初始化時使用了默認的 Pod CIDR10.42.0.0/16?,當(dāng)有節(jié)點加入集群,集群會從該網(wǎng)段上為節(jié)點分配屬于節(jié)點的 Pod CIDR10.42.X.1/24。

flannel 在kube subnet manager模式下,連接到 apiserver 監(jiān)聽節(jié)點更新的事件,從節(jié)點信息中獲取節(jié)點的 Pod CIDR。

kubectl get no ubuntu-dev2 -o jsnotallow={.spec} | jq{  "podCIDR": "10.42.0.0/24",  "podCIDRs": [    "10.42.0.0/24"  ],  "providerID": "k3s://ubuntu-dev2"}

然后在主機上寫子網(wǎng)配置文件,下面展示的是其中一個節(jié)點的子網(wǎng)配置文件的內(nèi)容。另一個節(jié)點的內(nèi)容差異在FLANNEL_SUBNET=10.42.1.1/24,使用的是對應(yīng)節(jié)點的 Pod CIDR。

#node 192.168.1.12cat /run/flannel/subnet.envFLANNEL_NETWORK=10.42.0.0/16FLANNEL_SUBNET=10.42.0.1/24FLANNEL_MTU=1450FLANNEL_IPMASQ=true
CNI 插件執(zhí)行

CNI 插件的執(zhí)行是由容器運行時觸發(fā)的,具體細節(jié)可以看上一篇《源碼解析:從 kubelet、容器運行時看 CNI 的使用》。

Flannel Plugin Flow

flannel 插件

flannel?CNI 插件(/opt/cni/bin/flannel?)執(zhí)行的時候,接收傳入的cni-conf.json?,讀取上面初始化好的subnet.env?的配置,輸出結(jié)果,委托給bridge進行下一步。

cat /var/lib/cni/flannel/e4239ab2706ed9191543a5c7f1ef06fc1f0a56346b0c3f2c742d52607ea271f0 | jq{  "cniVersion": "0.3.1",  "hairpinMode": true,  "ipMasq": false,  "ipam": {    "ranges": [      [        {          "subnet": "10.42.0.0/24"        }      ]    ],    "routes": [      {        "dst": "10.42.0.0/16"      }    ],    "type": "host-local"  },  "isDefaultGateway": true,  "isGateway": true,  "mtu": 1450,  "name": "cbr0",  "type": "bridge"}
bridge 插件

bridge使用上面的輸出連同參數(shù)一起作為輸入,根據(jù)配置完成如下操作:

創(chuàng)建網(wǎng)橋cni0(節(jié)點的根網(wǎng)絡(luò)命名空間)創(chuàng)建容器網(wǎng)絡(luò)接口eth0( pod 網(wǎng)絡(luò)命名空間)創(chuàng)建主機上的虛擬網(wǎng)絡(luò)接口vethX(節(jié)點的根網(wǎng)絡(luò)命名空間)將vethX?連接到網(wǎng)橋cni0委托 ipam 插件分配 IP 地址、DNS、路由將 IP 地址綁定到 pod 網(wǎng)絡(luò)命名空間的接口eth0上檢查網(wǎng)橋狀態(tài)設(shè)置路由設(shè)置 DNS

最后輸出如下的結(jié)果:

cat /var/li/cni/results/cbr0-a34bb3dc268e99e6e1ef83c732f5619ca89924b646766d1ef352de90dbd1c750-eth0 | jq .result{  "cniVersion": "0.3.1",  "dns": {},  "interfaces": [    {      "mac": "6a:0f:94:28:9b:e7",      "name": "cni0"    },    {      "mac": "ca:b4:a9:83:0f:d4",      "name": "veth38b50fb4"    },    {      "mac": "0a:01:c5:6f:57:67",      "name": "eth0",      "sandbox": "/var/run/netns/cni-44bb41bd-7c41-4860-3c55-4323bc279628"    }  ],  "ips": [    {      "address": "10.42.0.5/24",      "gateway": "10.42.0.1",      "interface": 2,      "version": "4"    }  ],  "routes": [    {      "dst": "10.42.0.0/16"    },    {      "dst": "0.0.0.0/0",      "gw": "10.42.0.1"    }  ]}

port-mapping 插件

該插件會將來自主機上一個或多個端口的流量轉(zhuǎn)發(fā)到容器。

Debug

讓我們在第一個節(jié)點上,使用tcpdump?對接口cni0進行抓包。

tcpdump -i cni0 port 80 -vvv

從 podcurl?中使用 podhttpbin?的 IP 地址10.42.1.2發(fā)送請求:

kubectl exec curl -n default -- curl -s 10.42.1.2/get

cni0

從在cni0上的抓包結(jié)果來看,第三層的 IP 地址均為 Pod 的 IP 地址,看起來就像是兩個 pod 都在同一個網(wǎng)段。

tcpdump-on-cni0

host eth0

文章開頭提到 flanneld 監(jiān)聽 udp 8472 端口。

netstat -tupln | grep 8472udp        0      0 0.0.0.0:8472            0.0.0.0:*                           -

我們直接在以太網(wǎng)接口上抓取 UDP 的包:

tcpdump -i eth0 port 8472 -vvv

再次發(fā)送請求,可以看到抓取到 UDP 數(shù)據(jù)包,傳輸?shù)呢撦d是二層的封包。

tcpdump-on-host-eth0

Overlay 網(wǎng)絡(luò)下的跨節(jié)點通信

在系列的第一篇中,我們研究 pod 間的通信時提到不同 CNI 插件的處理方式不同,這次我們探索了 flannel 插件的工作原理。希望通過下面的圖可以對 overlay 網(wǎng)絡(luò)處理跨節(jié)點的網(wǎng)絡(luò)通信有個比較直觀的認識。

當(dāng)發(fā)送到10.42.1.2?流量到達節(jié)點 A 的網(wǎng)橋cni0?,由于目標(biāo) IP 并不屬于當(dāng)前階段的網(wǎng)段。根據(jù)系統(tǒng)的路由規(guī)則,進入到接口flannel.1?,也就是 VXLAN 的 vtep。這里的路由規(guī)則也由flanneld來維護,當(dāng)節(jié)點上線或者下線時,都會更新路由規(guī)則。

#192.168.1.12Destination     Gateway         Genmask         Flags Metric Ref    Use Ifacedefault         _gateway        0.0.0.0         UG    0      0        0 eth010.42.0.0       0.0.0.0         255.255.255.0   U     0      0        0 cni010.42.1.0       10.42.1.0       255.255.255.0   UG    0      0        0 flannel.1192.168.1.0     0.0.0.0         255.255.255.0   U     0      0        0 eth0#192.168.1.13Destination     Gateway         Genmask         Flags Metric Ref    Use Ifacedefault         _gateway        0.0.0.0         UG    0      0        0 eth010.42.0.0       10.42.0.0       255.255.255.0   UG    0      0        0 flannel.110.42.1.0       0.0.0.0         255.255.255.0   U     0      0        0 cni0192.168.1.0     0.0.0.0         255.255.255.0   U     0      0        0 eth0

flannel.1?將原始的以太封包使用 UDP 協(xié)議重新封裝,將其發(fā)送到目標(biāo)地址10.42.1.0?(目標(biāo)的 MAC 地址通過 ARP 獲取)。對端的 vtep 也就是flannel.1?的 UDP 端口 8472 收到消息,解幀出以太封包,然后對以太封包進行路由處理,發(fā)送到接口cni0,最終到達目標(biāo) pod 中。

響應(yīng)的數(shù)據(jù)傳輸與請求的處理也是類似,只是源地址和目的地址調(diào)換。

參考資料

[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

標(biāo)簽: Flannel