1. 程式人生 > 程式設計 >Flannel 在 k8s 中的使用

Flannel 在 k8s 中的使用

在處理過的 k8s 叢集的眾多問題中資料鏈路的問題往往是最複雜,最難排查的。這需要我們對 k8s 叢集的網路通訊的過程有著清晰的認識。Docker 通過 veth 虛擬網路裝置以及 Linux bridge 實現了同一臺主機上的容器網路通訊,再通過 iptables 進行 NAT 實現了容器和外部網路的通訊。而 k8s 作為容器編排平臺,叢集中有眾多節點,並且每個節點上也部署了眾多 pod,要實現 pod 跨節點通訊,就沒有這麼簡單了。

k8s 網路模型

為瞭解決 k8s 當中網路通訊的問題,K8s 作為一個容器編排平臺提出了 k8s 網路模型,但是並沒有自己去實現,具體網路通訊方案通過網路外掛來實現。

其實 K8s 網路模型當中總共只作了三點要求:

  • 執行在一個節點當中的 Pod 能在不經過NAT的情況下跟叢集中所有的 Pod 進行通訊
  • 節點當中的客戶端(system daemon、kubelet)能跟該節點當中的所有 Pod 進行通訊
  • 以 host network 模式執行在一個節點上的Pod能跟叢集中所有的 Pod 進行通訊 從 k8s 的網路模型我們可以看出來,在 k8s 當中希望做到的是每一個 Pod 都有一個在叢集當中獨一無二的IP,並且可以通過這個IP直接跟叢集當中的其他 Pod 以及節點自身的網路進行通訊,一句話概括就是 k8s 當中希望網路是扁平化的。 所以只要再符合 k8s 網路模型的要求,就可以以外掛的方式在 k8s 叢集中作為跨節點網路通訊實現。目前比較流行的實現有 flannel,calico,weave,canal等。 這裡結合我們自己在阿里雲中的 k8s 叢集的 CNI flannel
    來解釋 pod 之間的通訊過程。

Flannel 介紹

Flannel 是由 CoreOS 團隊針對 k8s 設計的一個 Overlay Network(也就是將TCP資料包裝在另一種網路包裡面進行路由轉發和通訊) 實現,目前已經支援 UDP、VxLAN、AWS VPC和GCE路由等資料轉發方式,實現叢集間網路通訊。
在 k8s 各個節點上通過DaemonSet的方式運行了一個 flannel 的Pod
kubectl get ds -n kube-system app=flannel

NAME              DESIRED   CURRENT   READY   UP-TO-DATE   AVAILABLE   NODE SELECTOR                                               AGE
kube-flannel-ds   4         4         4       4            4           beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux   63d
複製程式碼

每一個 flannel 的 Pod 當中都運行了一個 flanneld 程式,且 flanneld 的配置檔案以 ConfigMap 的形式掛載到容器內的 /etc/kube-flannel/目錄供 flanneld 使用。
flannel 通過在每一個節點上啟動一個叫 flanneld 的程式,負責每一個節點上的子網劃分,並將相關的配置資訊如各個節點的子網網段、外部IP等儲存到 etcd 當中,而具體的網路包轉發交給具體的 Backend 來實現。
flanneld 可以在啟動的時候通過配置檔案來指定不同的Backend來進行網路通訊,目前比較成熟的 Backend 有 VXLAN、host-gw以及 UDP 三種方式,也已經有諸如 AWS,GCE and AliVPC 這些還在實驗階段的 Backend。VXLAN 是目前官方最推崇的一種 Backend 實現方式,host-gw 一般用於對網路效能要求比較高的場景,但需要基礎架構本身的支援,UDP 則一般用於 Debug 和一些比較老的不支援 VXLAN 的 Linux 核心。

flannel-arch
flannel 預設使用 UDP 作為叢集間通訊實現,如上圖所示,flannel 通過 etcd 管理整個叢集中所有節點與子網的對映關係,如上圖所示,flannel分別為節點A和B劃分了兩個子網:10.1.15.0/16和10.1.20.0/16。同時通過修改 docker 啟動引數,確保 Docker 啟動的容器能夠特定的網段中如 10.1.15.1/24。

  • 同一 Pod 例項容器間通訊:對於 Pod 而言,其可以包含1~n個容器例項,這些容器例項共享 Pod 的儲存以及網路資源,Pod 直接可以直接通過127.0.0.1進行通訊。其通過 Linux 的 Network Namespace 為這組容器實現了一個隔離網路。
  • 相同主機上 Pod 間通訊:對於 Pod 而言,每一個 Pod 例項都有一個獨立的 Pod IP,該 IP是掛載到虛擬網路卡(VETH)上,並且 bridge 到 docker0 的網路卡上。以節點A為例,其節點上執行的 Pod 均在 10.1.15.1/24 的網段中,其屬於相同網路,因此直接通過 docker0 進行通訊。
  • 對於跨節點間的 Pod 通訊:以節點A和節點B通訊而言,由於不同節點 docker0 網路卡的網段並不相同,因此 flannel 通過主機路由表的方式,將對節點B POD IP網段地址的訪問路由到 flannel0 的網路卡上。 而 flannel0 網路卡的背後執行的則是 flannel 在每個節點上執行的程式 flanneld。由於 flannel 通過 ETCD 維護了節點間所有網路的路由關係,原本容器將的資料報文,被 flanneld 封裝成 UDP 協議,傳送到了目標節點的 flanneld 程式,再對 udp 報文進行解包,後將資料傳送到 docker0,從而實現跨主機的 Pod 通訊。

公有云中的 flannel

上面我們解釋了 flannel backend 為 UDP的通訊過程。由於 UDP 作為backend存在明顯的效能問題,一般生產環境不會使用這種方式,而在像 AWS 阿里雲這樣的公有云廠商提供的 k8s 叢集服務中的 flannel 優先選擇 vpc 作為backend,這種方式其實類似 host-gw(Host Gateway)。 就像上面我們所說,flannel 通過 etcd 會統一為每個節點分配相應的網段, 下面為一個特定節點的網段

$ cat /run/flannel/subnet.env
FLANNEL_NETWORK=172.20.0.0/16
FLANNEL_SUBNET=172.20.1.1/25
FLANNEL_MTU=1500
FLANNEL_IPMASQ=true
複製程式碼

如上所示,flannel 建立了一個 172.20.0.0/16 的大網段,而這個節點則分配了 一個 172.20.1.1/25 的小網段。所以該節點上所有Pod的IP地址一定是在該網段中(172.20.1.1 ~ 172.20.1.126). 節點的網路卡資訊如下

cni0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 172.20.1.1  netmask 255.255.255.128  broadcast 0.0.0.0
        ether 0a:58:ac:14:01:01  txqueuelen 1000  (Ethernet)
        RX packets 78766736  bytes 6621717501 (6.1 GiB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 106139605  bytes 12076081112 (11.2 GiB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

docker0: flags=4099<UP,MULTICAST>  mtu 1500
        inet 169.254.123.1  netmask 255.255.255.0  broadcast 169.254.123.255
        ether 02:42:ed:90:79:ea  txqueuelen 0  (Ethernet)
        RX packets 0  bytes 0 (0.0 B)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 0  bytes 0 (0.0 B)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

eth0: flags=4163<UP,MULTICAST>  mtu 1500
        inet 172.19.220.255  netmask 255.255.240.0  broadcast 172.19.223.255
        ether 00:16:3e:1a:6b:dc  txqueuelen 1000  (Ethernet)
        RX packets 119587253  bytes 30187869460 (28.1 GiB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 84789814  bytes 20787336532 (19.3 GiB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

lo: flags=73<UP,LOOPBACK,RUNNING>  mtu 65536
        inet 127.0.0.1  netmask 255.0.0.0
        loop  txqueuelen 1000  (Local Loopback)
        RX packets 1191  bytes 172882 (168.8 KiB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 1191  bytes 172882 (168.8 KiB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

veth005c911b: flags=4163<UP,MULTICAST>  mtu 1500
        ether d2:de:24:f6:e2:13  txqueuelen 0  (Ethernet)
        RX packets 66417441  bytes 6329850773 (5.8 GiB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 95129695  bytes 8452269811 (7.8 GiB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

veth027b91b9: flags=4163<UP,MULTICAST>  mtu 1500
        ether b6:e3:ee:c6:86:2f  txqueuelen 0  (Ethernet)
        RX packets 1746691  bytes 150672105 (143.6 MiB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 1790627  bytes 164054145 (156.4 MiB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0
# 其他veth省略
複製程式碼

其中 veth177b8616 是每個 Pod 的虛擬網路卡,並且連線到網橋 cni0:

$ brctl show cni0
bridge name	bridge id		      STP enabled	interfaces
cni0        8000.0a58ac140101	  no	        veth005c911b
                                                veth027b91b9
                                                veth4963357e
                                                veth6b1d3200
                                                veth6c793e18
                                                veth8e551bfa
複製程式碼
  • 資料包出
    該點上的 pod 內的資料包通過網橋方式傳送到 cni0.
    cni0怎麼轉發資料包呢?這裡是使用的就是在 vpc 中建立的路由規則

    vpc-route
    例如資料包的目標 pod ip 是 172.20.1.43 則會匹配到圖中的路由規則,然後轉發到 pod 所在的節點。

  • 資料包入
    此時,資料包已經到達節點,那麼怎麼傳送到目標 pod 呢? 檢視接收流量主機的路由規則:

$ route -n
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
0.0.0.0         172.19.223.253  0.0.0.0         UG    0      0        0 eth0
169.254.0.0     0.0.0.0         255.255.0.0     U     1002   0        0 eth0
169.254.123.0   0.0.0.0         255.255.255.0   U     0      0        0 docker0
172.19.208.0    0.0.0.0         255.255.240.0   U     0      0        0 eth0
172.20.1.0      0.0.0.0         255.255.255.128 U     0      0        0 cni0
複製程式碼

根據主機路由表規則,傳送到 pod 172.16.1.43 的請求會落到路由表 172.20.1.0 0.0.0.0 255.255.255.128 U 0 0 0 cni0 然後資料包被髮給網橋 cni0 然後經 veth 虛擬網路卡 傳送到 pod. 上述過程我們用下面這張示意圖來總結

flannel-vpc

像開始我們說的那樣一個主機中的容器通過 bridge 模式就能實現容器與容器,容器與主機的通訊。flannel 更像是這種模式的擴充套件,叢集的主機如果都在一個子網內,就配置路由轉發過去;若是不在一個子網內,就通過隧道轉發過去。