4.3 Kubernetes網絡組件之 FlannelFlannel是CoreOS維護的一個網絡組件,Flannel爲每一個Pod提供全局惟一的IP,Flannel使用ETCD來存儲Pod子網與Node IP之間的關係。flanneld守護進程在每臺主機上運行,並負責維護ETCD信息和路由數據包。
其實k8s網絡組件flannel和calico主要解決的問題是k8s節點之間容器網絡的通訊,flannel要保證每一個pod的IP是惟一的,怎麼保證是惟一的,大部分組件的作法是在每一個Node上分配一個惟一的子網,node1是一個單獨的子網,node2是一個單獨的子網,能夠理解是不一樣網段,不一樣vlan,因此每一個節點都是一個子網,因此flannel會預先設置一個大的子網,而後在這個每一個node上分配子網,這些信息都會由flannel存儲到etcd中,而且每一個子網綁定到node上都有關係記錄的,而後方便下次進行二次的數據包傳輸,而且flannel在node上會啓動一個守護進程並運行,守護進程主要維護的是本地的路由規則,和維護etcd中的信息。node
一、Flannel 部署linux
https://github.com/coreos/flannel kubectl apply -f https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml
部署好以後會以daemonset的形式在每一個node上啓動一個pod,來啓動一個flannel的守護進程,主要負責本機路由表的設定和etcd中的數據,本地的子網上報到etcd中,因此守護進程是很是重要的
能夠在flannel的配置文件去設定大的子網,還有屬性模式git
net-conf.json: | { "Network": "10.244.0.0/16", "Backend": { "Type": "vxlan" } } ---
這個配置完以後會放到cni這個目錄下,因爲flannel是使用網橋的模式,實現的同節點數據包到達宿主機這個的通訊,因此子網信息並沒寫到這個配置文件裏,而是放到了這個 cat /var/run/flannel/subnet.env 下,這個經過ip a也能看到設備分配的ip,每一個節點都會分配一個子網,網絡接口設備爲cni0,也就是一個node上能夠分配255個小的子網github
[root@k8s-node2 ~]# cat /var/run/flannel/subnet.env FLANNEL_NETWORK=10.244.0.0/16 FLANNEL_SUBNET=10.244.0.1/24 FLANNEL_MTU=1450 FLANNEL_IPMASQ=true
還有一個cni的二進制文件, /opt/cni/bin,這個就是kubelet調用這個二進制接口爲建立的每一個pod建立網絡信息,而且是從咱們的配置的子網中去拿IPdocker
配置的話修改的是就是預先設定它的子網,以及工做模式,另外就是這個網絡不能與k8s自己的內網衝突,不然致使網絡不通的情況數據庫
二、 Flannel工做模式及原理
Flannel支持多種數據轉發方式:
UDP:最先支持的一種方式,因爲性能最差,目前已經棄用。
VXLAN:Overlay Network方案,源數據包封裝在另外一種網絡包裏面進行路由轉發和通訊
這也是網絡的虛擬化技術,也就是原來是有一個包數據包,有源IP和目的IP,但因爲某些狀況這個數據包到達不了目的地址上,這可能就會藉助物理上的以太網網絡進行封裝一個數據包帶上,而後經過這種物理網絡傳輸到目的地址上,這是一種疊加式的網絡,裏面是有兩種數據包的,這種也叫作隧道方案
Host-GW:Flannel經過在各個節點上的Agent進程,將容器網絡的路由信息刷到主機的路由表上,這樣一來全部的主機都有整個容器網絡的路由數據了,這樣它就知道這個數據包到達這個節點轉發到這個機器上,也就是路由表之間轉發的,這種也叫路由方案
VXLANjson
使用kubeadm部署的話默認是支持的網絡
kubeadm部署指定Pod網段 kubeadm init --pod-network-cidr=10.244.0.0/16
可是使用二進制部署就得去啓動cni的支持,默認我ansible部署的k8s集羣都是啓動的
二進制部署指定app
cat /opt/kubernetes/cfg/kube-controller-manager.conf --allocate-node-cidrs=true \ 容許node自動分配cidr這個網絡 --cluster-cidr=10.244.0.0/16 \ 指定pod網絡的網段,這個網段要和flannel的網段對應上
另外也都要在每一個node節點的kubelet的配置文件上進行對cni的支持ide
[root@k8s-node1 ~]# cat /opt/kubernetes/cfg/kubelet.conf --network-plugin=cni \
這樣的話就能以cni的標準來爲k8s配置網絡
kube-flannel.yml net-conf.json: | { "Network": "10.244.0.0/16", "Backend": { "Type": "vxlan" } }
在節點1上有個容器,與節點2上的容器進行通訊,這兩個是進行跨主機進行的通訊,若是本機通訊之間使用網橋使用二層的傳輸了,像原生的docker網就能解決了,最重要的是這兩個節點的數據包傳輸
flannel保證每一個node都是惟一的ip,它是在每一個node上都分配一個子網
能夠看到flannel是基於宿主機建立的,它會爲每一個node建立獨立的子網,併爲當前pod分配ip
[root@k8s-master1 ~]# kubectl get pod -n kube-system -o wide kube-flannel-ds-amd64-4jjmm 1/1 Running 0 14d 10.4.7.11 k8s-master1 <none> <none> kube-flannel-ds-amd64-9f9vq 1/1 Running 0 14d 10.4.7.21 k8s-node2 <none> <none> kube-flannel-ds-amd64-gcf9s 1/1 Running 0 14d 10.4.7.12 k8s-node1 <none> <none>
爲了可以在二層網絡上打通「隧道」,VXLAN 會在宿主機上設置一個特殊的網絡設備做爲「隧道」的兩端。這個設備就叫做 VTEP,即:VXLAN Tunnel End Point(虛擬隧道端點)。下圖flannel.1的設備就是VXLAN所需的VTEP設備。示意圖以下:
vxlan是怎麼工做的?
vlan是Linux上支持的一個隧道的技術,隧道也就是點到點,端到端的兩個設備的通訊,其實vxlan實現是有一個vtep的設備作數據包的封裝與解封裝,而如今已經封裝到flannel.1這個進程裏面了,也就是這個虛擬網卡包含了veth來去使用對這個vxlan進行封裝和解封裝。
如今是Node1節點上的pod 1是1.10,如今要與Node2節點上的pod 2的2.10進行通訊,他們是不在一個網絡的,當這個數據包發出去的時候,pod1 的容器的網卡eth0,先出這個網卡,而後會鏈接這個veth這個比如就是一個網線,etch0是一頭,veth是一頭,也就是veth是這個設備的另外一頭,
這個veth是在宿主機上,那麼這個宿主機就拿到了這個容器的數據包,而後這個數據包到達這個網橋上面,這個網橋也比如一個二層的交換機,全部的容器都會加入到這個網橋裏面,能夠經過yum -y install bridge-utils看到veth的另外一端是否是加入到cni的網橋中,這個網橋就是flannel建立的,而且這個網橋有獨立的mac地址和IP均可以看到
[root@k8s-node2 ~]# brctl show cni0 bridge name bridge id STP enabled interfaces cni0 8000.4a025e87aa87 no veth08925d5a veth2591a36f veth676a1e86 veth718beeac veth81dadcbd veth8a96f11c veth8c90fdb6 veth8f350182 veth90818f0b vetha471152b
這個就是當咱們建立好pod的時候由flannel去分配並加入這個網橋中的,這個後面有個interfaces有這個接口,這個至關於交換機的接口,這正是宿主機上的虛擬網卡,若是本地的話,直接走這個網橋就能直接找到了,而後就能夠發送一個ARP廣播包進行封包傳輸了,cni0就至關於一個二層交換機,幫你擴散,找目的的mac進行響應,因此說同節點就能夠直接走網橋這個,那麼這個目的地址不在這個網橋裏面,就像2.10,當前的node是不知道2.10上的pod在哪,那麼它只能走路由表了,也就是它它不必定目的地址的時候就會走默認網關,因此flannel會在宿主機上生成不少路由表經過ip router能夠看到
[root@k8s-node2 ~]# ip route default via 10.4.7.1 dev eth0 proto static metric 100 10.4.7.0/24 dev eth0 proto kernel scope link src 10.4.7.21 metric / 10.244.0.0/24 dev cni0 proto kernel scope link src 10.244.0.1 / 10.244.1.0/24 via 10.244.1.0 dev flannel.1 onlink / 10.244.2.0/24 via 10.244.2.0 dev flannel.1 onlink / 172.17.0.0/16 dev docker0 proto kernel scope link src 172.17.0.1
部署docker生成的路由表,這裏的docker0網橋是沒有用到,當部署flannel的時候是默認使用的是本身的網橋,這個的原理和flannel的是同樣的,只不過flannel用的是本身作的,也是爲了方便本身處理數據包
這裏的路由表都記錄下來了,它會找哪一個是目的地址2.10pod2的IP地址,因此它會根據這個路由表,而後發送到flannel的這個設備上,這個flannel是採用vxlan的模式,vxlan須要veth的數據封裝與解封裝,因此flannel就把這個數據包交給vxlan,而vxlan是一個內核級的驅動程序,有它去封裝這個包,由於vxlan自己是工做在二層的,它還須要目的的mac地址
那麼就能夠經過ip neigh show dev flannel.1去查看mac地址
[root@k8s-node2 ~]# ip neigh show dev flannel.1 10.244.2.0 lladdr ea:ca:d6:62:be:21 PERMANENT 10.244.1.0 lladdr 4e:e3:fa:5f:d2:34 PERMANENT
而flannel.1的vxlan實現是有一個vtep的設備作數據包的封裝與解封裝,由於它在2層進行封包,就要知道目的的mac地址,那麼這個目的mac就由flannel去提供給vetp,flannel去存儲對應下一跳的網關,那麼這個網關確定不是在本地,當咱們拿到目的的mac地址以後大家就封裝成一個完整的幀,那麼封裝好以後,對於宿主機沒有太多的實際意義,由於這個數據包幀發不出去,要是按二層的走確定到不了另一個節點,由於在不一樣的子網裏面,若是沒有路由的介入確定是通訊不了的,接下來就須要linux內核的數據幀封裝一個宿主機普通的數據幀,也就是udp封裝一個普通的數據幀,也就是在這之上再加一層udp的包,這樣作的目的能讓數據包直接傳輸到目的容器的主機上。
vxlan是使用的udp協議,它會將原始的報文放在內部,而外部由udp封裝的源IP與目的地址
[root@k8s-master1 ~]# bridge fdb show dev flannel.1 a6:a4:e5:5d:19:9b dst 10.4.7.21 self permanent ea:ca:d6:62:be:21 dst 10.4.7.12 self permanent
能夠看到,上面用的對方flannel.1的MAC地址對應宿主機IP,也就是UDP要發往的目的地。使用這個目的IP進行封裝。
也就是這些flannel都是知道的,爲何說flannel維護這etcd的數據,守護本地的路由規則,其實etcd的數據要和flannel,把它當前的數據寫到etcd中,由各個節點都存儲一份,因此根據這個地址拿到了mac地址,而後這又是一個完整的包,由vxlan封裝的udp的包,這個udp包裏面就有兩個IP包的存在,udp就直接能發送到node2的節點上,數據包已經傳輸過去了,由於宿主機之間是同網段的,到達31.63上以後,接收到udp的包以後,會進行拆分,解包會將原始的包拿出來,因此這裏就有一個vxlan的標記,自己flannel是由vtep處理的,因此在封包的時候對着幹包打了個標記,也就是vxlan header的標記,首先打上vxlan的頭部,那麼這就意味着這就是一個vxlan的數據包,而且加了一個VNI的編號,VNI是爲了區分vxlan的點對點隧道,多個數據包也是分外多個編號,也是爲了區分,而這個編號被flannel引用到了,因此這是內部的一個編號,確認這個數據包無誤,而後交給flannel.1這個設備,它處理這個數據包,拿到了源IP和目的IP,而去判斷,會發現這個是cni網橋的,因此它根據路由表放到了cni網橋,根據這個路由表拆分這個目的地址,正好這個目的地址匹配到了,因此它會將這個轉發到cni網橋裏,到cni就跟以前同樣了,就至關於一個二層交換機,拿到這個數據包,它會進行一個ARP的廣播,發現正在這個網橋裏面,而後就進行數據包的轉發了。
今後看來;vxlan使用重疊網絡,進行封包解封包,性能就降低了不少
小結:
/ # ip route default via 10.244.0.1 dev eth0 10.244.0.0/24 dev eth0 scope link src 10.244.0.45 10.244.0.0/16 via 10.244.0.1 dev eth0
ip route default via 192.168.31.1 dev ens33 proto static metric 100 10.244.0.0/24 dev cni0 proto kernel scope link src 10.244.0.1 10.244.1.0/24 via 10.244.1.0 dev flannel.1 onlink 10.244.2.0/24 via 10.244.2.0 dev flannel.1 onlink
ip neigh show dev flannel.1 10.244.1.0 lladdr ca:2a:a4:59:b6:55 PERMANENT 10.244.2.0 lladdr d2:d0:1b:a7:a9:cd PERMANENT
封裝到UDP包發出去:如今能直接發UDP包嘛?到目前爲止,咱們只知道另外一端的flannel.1設備的MAC地址,殊不知道對應的宿主機地址是什麼。
flanneld進程也維護着一個叫作FDB的轉發數據庫,能夠經過bridge fdb命令查看:
bridge fdb show dev flannel.1 d2:d0:1b:a7:a9:cd dst 192.168.31.61 self permanent ca:2a:a4:59:b6:55 dst 192.168.31.63 self permanent
能夠看到,上面用的對方flannel.1的MAC地址對應宿主機IP,也就是UDP要發往的目的地。使用這個目的IP進行封裝。
Host-GW
host-gw模式相比vxlan簡單了許多, 直接添加路由,將目的主機當作網關,直接路由原始封包。
切換成host-gw的模式,上面的轉發仍是同樣,pod1容器的網卡先鏈接veth到宿主機上,而後到達cni0的網橋上,這個網橋就至關於一個二層的交換機,這個數據包到cni的網橋以後,也就是到達宿主機上,那麼宿主機的網絡協議棧會根據路由表決定轉發到哪一個網關上,由於它的目的IP地址不是同網段的,確定走路由表,它會根據路由表判斷目的地址是2.10,也就是來自這個數據的數據包轉發到了它的下一跳,也就是網關,經過接口之間轉發到31.63上了,也就是直接安照宿主機的網絡,由於這個數據包是宿主機處理的,因此宿主機要想訪問31.63,會進行從新封包,目的地址就是31.63,它判斷了31.63下一跳的網關是同一子網,並且是二層的傳輸,二層的傳輸又須要獲取到目的的mac地址,若是它本地不知道31.63的mac地址的話,它會發送一個ARP廣播包,知道了對方的mac就進行封包,因此通過二層的傳輸到達31.63,31.63收到以後數據包以後,它又會去判斷路由表了,而後進入cni的網橋,二層又轉發到了容器裏面。
最重要兩條,host-gw是把每一個節點都當成一個網關,它會加入其餘節點並設成網關,當數據包到達這個節點的時候,就根據路由表之間發送到下一跳了,也就是節點IP,這個都是同網段的IP,直接經過2層之間把這個數據,轉發到另外一個節點上,另外一個節點再根據另外一條規則,根據目的的地址轉發到cni網橋,cni網橋根據2層又轉發到容器裏面,一個是數據的流入,就是當數據包到達這個節點以後,而後發給誰,這是流入數據包,一個是數據包的流出,當從節點出來的數據包,應該轉發到哪一個node上,這些都是由flannel去維護的
這個的侷限是每一個node在2層都能通,不然下一跳轉發不過去,可是它的性能要比vxlan的性能高不少,不須要封包解封包,這種接近原生,性能也是最好的
下面是示意圖:
kube-flannel.yml net-conf.json: | { "Network": "10.244.0.0/16", "Backend": { "Type": "host-gw" } }
看名字就能看出hots-gw它把目的的主機看成網關,直接路由原始的封包
將vxlan切換成host-gw的模式,重建以後能夠看到路由表發生變化,切換的時候也會對網絡進行影響,通常是在夜深人靜的時候去作
以前的路由表都是經過flannel.1去轉發到設備上,也就是使用host-gw,flannel.1這個設備就不用了,因此就不會用vxlan進行去封包了
當你設置flannel使用host-gw模式,flanneld會在宿主機上建立節點的路由表:
ip route default via 192.168.31.1 dev ens33 proto static metric 100 10.244.0.0/24 dev cni0 proto kernel scope link src 10.244.0.1 10.244.1.0/24 via 192.168.31.63 dev ens33 10.244.2.0/24 via 192.168.31.61 dev ens33 192.168.31.0/24 dev ens33 proto kernel scope link src 192.168.31.62 metric 100
目的 IP 地址屬於 10.244.1.0/24 網段的 IP 包,應該通過本機的 eth0 設備發出去(即:dev eth0);而且,它下一跳地址是 192.168.31.63(即:via 192.168.31.63)。
一旦配置了下一跳地址,那麼接下來,當 IP 包從網絡層進入鏈路層封裝成幀的時候,eth0 設備就會使用下一跳地址對應的 MAC 地址,做爲該數據幀的目的 MAC 地址。
而 Node 2 的內核網絡棧從二層數據幀裏拿到 IP 包後,會「看到」這個 IP 包的目的 IP 地址是 10.244.1.20,即 container-2 的 IP 地址。這時候,根據 Node 2 上的路由表,該目的地址會匹配到第二條路由規則(也就是 10.244.1.0 對應的路由規則),從而進入 cni0 網橋,進而進入到 container-2 當中。
小結:若是想追求性能的話,二層能夠通訊,那麼就能夠選擇host-gw,那麼若是兩個節點之間是不能經過二層通訊,那麼可能須要路由的轉發,那麼可能在不一樣的vlan中,那麼使用vxlan是最好的,由於能夠知足這樣的一個需求。