k8s 網絡模型解析之原理


今天研究了一下k8s的網絡模型,該解析基於flannel vxlan+ kubeproxy iptables 模式。

一.Docker

首先分析一下Docker層面的網絡模型,我們知道容器是基於內核的namespace機制去實現資源的隔離的。network是眾多namespace中的一個,那么如何保證一個節點上容器之間的通信呢?Docker的做法是通過虛擬網橋來橋接虛擬網卡。下面具體解釋一下。

首先每一個容器在默認情況下都是在自己的network namespace里面的,也就是說默認情況下它只有一個自己獨立的localhost網絡(或者是什么網絡設備也沒有?TBD),無法與外部進行通信。為了解決這個問題,Docker創建了一對veth pair, 這個veth pair總是承兌出現,可以理解為一對端口,所有從一頭進去的數據都會從另一頭出來。然后docker 會把這對veth pair的一頭加入到容器的namespace中,另一頭橋接到一個虛擬網橋上, 這個虛擬網橋實際上就是宿主機上的docker0網卡,我們可以通過以下指令來觀察:

[wlh@meizu storage]$ brctl show
bridge name    bridge id        STP enabled    interfaces
docker0        8000.02422551422b    no        veth43dc241
                                       veth551eae5
                                       veth844b02c
                                              vethd06364a
                                              vethe95e44c

上圖可以看到docker0上面橋接的各個容器的veth設備,這樣容器內的通信就可以沿着vethA-1 -> vethA-2 -> docker0 -> vethB-2 -> vethB-1流動了

2. Flannel

Docker實現了同一節點上容器之間的通信,那么k8s作為一個容器編排平台,如何實現不同節點上容器的通信呢?這需要第三方插件的支持,目前有多種overlay network解決方案,這里介紹其中比較簡單的一種, flannel。flannel目前支持三種工作模式:vxlan, udp, host-gw,其中udp和vxlan比較像,udp是flannel程序自己在用戶態下將報文封裝,而vxlan是內核對報文進行處理,因此udp會比較慢。所以udp不推薦在生產環境下使用,只是用於debug。而host-gw模式需要所有節點與其他任一節點間都有直接路由(具體可以查閱相關文章), 這里我們使用vxlan作為工作模式進行講解。

在工作的時候,flannel會從k8s的etcd存儲中同步數據,包括使用的工作模式和集群中其它節點的子網。例如,在我的機器上,其etcd中存儲的數據為:

 1 [wlh@xiaomi xuexi]$ etcdctl ls /kube-fujitsu/network
 2 /kube-fujitsu/network/config
 3 /kube-fujitsu/network/subnets
 4 
 5 [wlh@xiaomi xuexi]$ etcdctl get /kube-fujitsu/network/config
 6 {"Network":"172.30.0.0/16","SubnetLen":24,"Backend":{"Type":"vxlan"}}
 7 
 8 [wlh@xiaomi xuexi]$ etcdctl ls /kube-fujitsu/network/subnets
 9 /kube-fujitsu/network/subnets/172.30.20.0-24
10 /kube-fujitsu/network/subnets/172.30.44.0-24
11 /kube-fujitsu/network/subnets/172.30.83.0-24
12 
13 [wlh@xiaomi xuexi]$ etcdctl get /kube-fujitsu/network/subnets/172.30.83.0-24
14 {"PublicIP":"10.167.226.38","BackendType":"vxlan","BackendData":{"VtepMAC":"b6:c7:0f:7f:66:a7"}}

 

這里第6行中的172.30.0.0/16表示的是整個集群的子網段, 而8/9/10三行分別代表了三個節點,每創建一個新的節點,都會從172.30.0.0/16中再分配一個子網給它。各個節點上的flannel進程讀取etcd中的這些配置,然后修改自己節點上的docker進程的啟動參數,在其中添加一個--bip=172.30.20.1/24,這樣該節點上docker啟動的所有容器都會在這個子網段里。通過這些設定,保證了集群中所有的容器之間ip地址是不會重復的。

解決了容器ip地址重復的問題后,下面就是實現容器跨節點通信了。在vxlan模式下,flannel會在節點上創建一個虛擬網卡叫flannel.1,它的MAC地址就是上面輸出中的VtepMAC。同樣的節點的路由表也會被修改,如下圖所示:

1 [wlh@meizu storage]$ route
2 Kernel IP routing table
3 Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
4 //....more 
5 172.30.20.0     0.0.0.0         255.255.255.0   U     0      0        0 docker0
6 172.30.44.0     172.30.44.0     255.255.255.0   UG    0      0        0 flannel.1
7 172.30.83.0     172.30.83.0     255.255.255.0   UG    0      0        0 flannel.1

這里可以看到,目的地址為172.30.20.0/24的都會被發到docker0,這其實就是本主機上的容器。而其他節點上的容器則會被路由到flannel.1網卡上進行處理。flannel將flannel.1網卡上收到的數據進行處理,加上flannel規定好的報文頭,然后從綁定的網卡中發出去。這個封裝好的報文是udp協議,目標地址是容器所在的節點的物理地址,並且其默認端口是8472(udp模式的默認端口是8285)。也就是說vxlan模式的底層實現也是用udp報文發送的,只是vxlan模式中報文封裝是在內核態中完成,而udp模式中報文封裝是在用戶態完成。目標容器所在的主機上,flannel會監聽8472端口,去掉報文的flannel頭,然后傳送給docker0網卡,docker0網卡收到的就是普通的容器通信的報文,不會感知到底層的這些處理。

3. kube-proxy

我們知道,k8s中有service的概念,它擁有自己的ip地址。那么對service的訪問是如何分發給后端的pod呢。這些工作是由kube-proxy完成的,它有三種工作模式,userspace(older), iptables(faster),ipvs(experimental)。其中userspace是早期的模式,它本質上是利用kube-proxy做一個代理,所有對service的訪問都會轉發給kube-proxy組件,然后由它再分發請求到pod。顯然這種模式對於一個大規模集群來說是一個速度瓶頸。iptables模式是通修改iptable來實現請求分發的。ipvs模式不太了解。

下面以一個例子來具體說明iptables模式。首先創建下面列出的deployment和service:

 1 apiVersion: apps/v1
 2 kind: Deployment
 3 metadata:
 4   name: nginx
 5   labels:
 6     name: nginx
 7 spec:
 8   selector:
 9     matchLabels:
10       name: nginx1
11   replicas: 3
12   template:
13     metadata:
14       labels:
15         name: nginx1
16     spec:
17       nodeName: meizu
18       containers:
19       - name: nginx
20         image: nginx
21         ports:
22         - containerPort: 80
23 ---
24 apiVersion: v1
25 kind: Service
26 metadata:
27   name: nginx
28   labels:
29     name: nginx1
30 spec:
31   ports:
32   - port: 4432
33     targetPort: 80
34   selector:
35     name: nginx1
[wlh@xiaomi xuexi]$ kubectl get pod -o wide|grep nginx
nginx-cb648c7f5-c8h26       1/1     Running   0          24m    172.30.20.7   meizu    <none>           <none>
nginx-cb648c7f5-pptl9       1/1     Running   0          40m    172.30.20.6   meizu    <none>           <none>
nginx-cb648c7f5-zbsvz       1/1     Running   0          24m    172.30.20.8   meizu    <none>           <none>

[wlh@xiaomi xuexi]$ kubectl get svc -o wide
nginx        ClusterIP   10.254.40.119   <none>        4432/TCP   38m    name=nginx1

這里創建一個service,在4432端口向外提供簡單的nginx service。觀察到這些資源被創建以后,kube-proxy會在節點上的iptables的NAT表中添加以下規則:

[wlh@meizu storage]$ sudo iptables-save|grep nginx
-A KUBE-SERVICES ! -s 10.254.0.0/16 -d 10.254.40.119/32 -p tcp -m comment --comment "default/nginx: cluster IP" -m tcp --dport 4432 -j KUBE-MARK-MASQ
-A KUBE-SERVICES -d 10.254.40.119/32 -p tcp -m comment --comment "default/nginx: cluster IP" -m tcp --dport 4432 -j KUBE-SVC-4N57TFCL4MD7ZTDA
[wlh@meizu storage]$
sudo iptables-save|grep 0x4000/0x4000 -A KUBE-MARK-MASQ -j MARK --set-xmark 0x4000/0x4000 -A KUBE-POSTROUTING -m comment --comment "kubernetes service traffic requiring SNAT" -m mark --mark 0x4000/0x4000 -j MASQUERADE -A KUBE-FORWARD -m comment --comment "kubernetes forwarding rules" -m mark --mark 0x4000/0x4000 -j ACCEPT

 

輸出的第一行是做一個標記,意思是所有發往10.254.40.118:4432(nginx服務)的請求(除了source ip 為10.254.0.0/16的報文)都會被打上一個標記,這個報文被打上這個標記后會在filter表中進行后續處理。在filter表中會對打上標記的報文進行MASQUERADE處理,實際上就是SNAT,將報文的source ip地址轉化為本地主機物理網卡的地址,然后再發出去,否則如果直接用容器的ip地址的話,物理網絡很顯然是不會認識這個地址的。

輸出的第二行的作用是所有發往10.254.40.119:4432(也就是service) 的地址,全部跳到KUBE-SVC-4N57TFCL4MD7ZTDA進行處理,那么這個KUBE-SVC-4N57TFCL4MD7ZTDA 是啥呢?

1 [wlh@xiaomi prensentation]$ sudo iptables-save|grep KUBE-SVC-4N57TFCL4MD7ZTDA
2 :KUBE-SVC-4N57TFCL4MD7ZTDA - [0:0]
3 -A KUBE-SERVICES -d 10.254.40.119/32 -p tcp -m comment --comment "default/nginx: cluster IP" -m tcp --dport 4432 -j KUBE-SVC-4N57TFCL4MD7ZTDA
4 -A KUBE-SVC-4N57TFCL4MD7ZTDA -m statistic --mode random --probability 0.33332999982 -j KUBE-SEP-TMIUL2YW4YRKUWF7
5 -A KUBE-SVC-4N57TFCL4MD7ZTDA -m statistic --mode random --probability 0.50000000000 -j KUBE-SEP-H2SQHV5FZD5TQOIZ
6 -A KUBE-SVC-4N57TFCL4MD7ZTDA -j KUBE-SEP-GK74E3IZTU4ZAMUJ

這里的輸出中,第二行是統計流量的,和我們本篇文章關系不大。第三行之前分析過了, 第四五六行實際上就是load balance, 這里集群啟動了三個pod作為這個service的后端,對應的有三個服務節點,分別被賦予了訪問概率。四五六行中最后的KUBE-SEP自然就是對應了各自的pod了。以第四行為例:

1 [wlh@xiaomi prensentation]$ sudo iptables-save|grep KUBE-SEP-TMIUL2YW4YRKUWF7
2 :KUBE-SEP-TMIUL2YW4YRKUWF7 - [0:0]
3 -A KUBE-SEP-TMIUL2YW4YRKUWF7 -s 172.30.20.6/32 -j KUBE-MARK-MASQ
4 -A KUBE-SEP-TMIUL2YW4YRKUWF7 -p tcp -m tcp -j DNAT --to-destination 172.30.20.6:80
5 -A KUBE-SVC-4N57TFCL4MD7ZTDA -m statistic --mode random --probability 0.33332999982 -j KUBE-SEP-TMIUL2YW4YRKUWF7

第四行定義了具體的下一跳,這邊的意思是所有跳到這里的請求全部進行DNAT操作,將目標地址改為172.30.20.6:80。這樣結合前面的利用KUBE-MARK-MASQ打標記然后轉換源地址的操作,整個訪問就變成:

source ip            des ip

pod ip          ->      service

host ip         ->      backend pod ip

 


免責聲明!

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



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