本文詳解之flannel-udp模式

本文詳解之flannel-udp模式

flanneld可以在啟動的時候通過配置文件來指定不同的Backend來進行網絡通信,目前比較成熟的Backend有VXLAN、host-gw以及UDP三種方式,

UDP- 工作在內核態和用戶態之間

UDP模式相對容易理解

採用UDP模式時需要在flanneld的配置文件當中指定Backend type為UDP,可以通過直接修改flanneld的ConfigMap的方式實現,配置修改完成之後如下:

[root@10-10-88-192 ~]# kubectl get cm -n kube-system -o yaml kube-flannel-cfg
apiVersion: v1
data:
 cni-conf.json: |
 {
 "name": "cbr0",
 "type": "flannel",
 "delegate": {
 "isDefaultGateway": true
 }
 }
 net-conf.json: |
 {
 "Network": "10.244.0.0/16",
 "Backend": {
 "Type": "udp" #修改為udp模式
 }
 }
kind: ConfigMap
metadata:
 creationTimestamp: 2018-10-30T08:34:01Z
 labels:
 app: flannel
 tier: node
 name: kube-flannel-cfg
 namespace: kube-system
 resourceVersion: "33718154"
 selfLink: /api/v1/namespaces/kube-system/configmaps/kube-flannel-cfg
 uid: 8d981eff-dc1e-11e8-8103-fa900126bc00
[root@10-10-88-192 ~]#

關鍵字段為Backend當中的Type字段,採用UDP模式時Backend Port默認為8285,即flanneld的監聽端口。

當採用UDP模式時,flanneld進程在啟動時會通過打開/dev/net/tun的方式生成一個TUN設備,TUN設備可以簡單理解為Linux當中提供的一種內核網絡與用戶空間(應用程序)通信的一種機制,即應用可以通過直接讀寫tun設備的方式收發RAW IP包。

通過可以ip -d link show flannel0可以看到這是一個tun設備:

[root@10-10-88-192 ~]# ip -d link show flannel0
5: flannel0:  mtu 1472 qdisc pfifo_fast state UNKNOWN mode DEFAULT qlen 500
 link/none promiscuity 0
 tun
[root@10-10-88-192 ~]#
[root@10-10-88-192 ~]# netstat -ulnp | grep flanneld
udp 0 0 172.16.130.140:8285 0.0.0.0:* 2373/flanneld
[root@10-10-88-192 ~]#

我們發現這裡的mtu是1472,相比Kubernetes集群網絡接口eth1小了28個字節,為什麼呢?

容器跨節點通信實現流程:

假設在節點A上有容器A(10.244.1.96),在節點B上有容器B(10.244.2.194),此時容器A向容器發送一個ICMP請求報文(ping),我們來逐步分析一下ICMP報文是如何從容器A到達容器B的。

本文詳解之flannel-udp模式

步驟詳解:

1、容器A當中發出ICMP請求報文,通過IP封裝後形式為:10.244.1.96 -> 10.244.2.194,此時通過容器A內的路由表匹配到應該將IP包發送到網關10.244.1.1(cni0網橋)。下圖為完整的幀格式:

本文詳解之flannel-udp模式

2、此時到達cni0的IP包目的地IP 10.244.2.194匹配到節點A上第一條路由規則(10.244.0.0),內核將RAW IP包發送給flannel0接口。

3、flannel0為tun設備,發送給flannel0接口的RAW IP包(無MAC信息)將被flanneld進程接收到,flanneld進程接收到RAW IP包後在原有的基礎上進行UDP封包,UDP封包的形式為:172.16.130.140:src port -> 172.16.130.164:8285。

這裡有一個問題就是flanneld怎麼知道10.244.2.194這個容器到底是在哪個節點上呢?

flanneld在啟動時會將該節點的網絡信息通過api-server保存到etcd當中,故在發送報文時可以通過查詢etcd得到10.244.2.194這個容器的IP屬於host B,且host B的IP為172.16.130.164。

我們發現這裡的mtu是1472,相比Kubernetes集群網絡接口eth1小了28個字節,為什麼呢?

4、flanneld將封裝好的UDP報文經eth1發出,從這裡可以看出網絡包在通過eth1發出前先是加上了UDP頭(8個字節),再然後加上了IP頭(20個字節)進行封裝,這也是為什麼flannel0的MTU要比eth1的MTU小28個字節的原因(防止封裝後的以太網幀超過eth1的MTU而在經過eth1時被丟棄)。 下面是完整的以太網幀格式圖:


本文詳解之flannel-udp模式


5、網絡包經節點A和節點B之間的網絡連接到達host B。

6、host B收到UDP報文後經Linux內核通過UDP端口號8285將包交給正在監聽的應用flanneld。

7、運行在host B當中的flanneld將UDP包解包後得到RAW IP包:10.244.1.96 -> 10.244.2.194。

8、解封后的RAW IP包匹配到host B上的路由規則(10.244.2.0),內核將RAW IP包發送到cni0。下面是完整的以太網幀格式圖:


本文詳解之flannel-udp模式


9、cni0將IP包轉發給連接在cni0網橋上的container B,而flanneld在整個過程中主要主要負責兩個工作:

  • UDP封包解包
  • 節點上的路由表的動態更新

總結:

從上面虛線部分就可以看到container A和container B雖然在物理網絡上並沒有直接相連,但在邏輯上就好像是處於同一個三層網絡當中,這種基於底下的物理網絡設備通過Flannel等軟件定義網絡技術實現的網絡我們稱之為Overlay網絡。

那麼上面通過UDP這種Backend實現的網絡傳輸過程有沒有問題呢?

最明顯的問題就是,網絡數據包先是通過tun設備從內核當中複製到用戶態的應用,然後再由用戶態的應用複製到內核,僅一次網絡傳輸就進行了兩次用戶態和內核態的切換,顯然這種效率是不會很高的。那麼有沒有高效一點的辦法呢?當然,最簡單的方式就是把封包解包這些事情都交給內核去幹好了,事實上Linux內核本身也提供了比較成熟的網絡封包解包(隧道傳輸)實現方案VXLAN,下面我們就來看看通過內核的VXLAN跟flanneld自己通過UDP封裝網絡包在實現上有什麼差別


分享到:


相關文章: