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 -l 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 默認使用 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,BROADCAST,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,BROADCAST,RUNNING,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,BROADCAST,RUNNING,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,BROADCAST,RUNNING,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 中建立的路由規則。

    例如數據包的目標 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 。上述過程我們用下面這張示意圖來總結:

 

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

--------------------------------------------------------
作者:lxkaka
鏈接:https://juejin.im/post/5d63ce96f265da03ea5a8a98
來源:掘金


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM