您好,登錄后才能下訂單哦!
這篇文章將為大家詳細講解有關如何分析Kubernetes中的容器網絡,文章內容質量較高,因此小編分享給大家做個參考,希望大家閱讀完這篇文章后對相關知識有一定的了解。
隨著云計算的興起,各大平臺之爭也落下了帷幕,Kubernetes作為后起之秀已經成為了事實上的PaaS平臺標準,而網絡又是云計算環境當中最復雜的部分,總是讓人琢磨不透。圍繞在Kubernetes環境當中同一個節點(work node)上的Pod之間是如何進行網絡通信的這個問題進行展開,暫且不考慮跨節點網絡通信的情況。
Network Namespace
Network Namespace是Linux 2.6.24才開始引入的,直到Linux 2.6.29才完成的特性。Network Namespace實際上實現的是對網絡棧的虛擬化,且在創建出來時默認只有一個回環網絡接口lo,每一個網絡接口(不管是物理接口還是虛擬化接口)都只能存在于一個Network Namespace當中,但可以在不同的Network Namespace之間切換,每一個Network Namespace都有自己獨立的IP地址、路由表、防火墻以及套接字列表等網絡相關資源。當刪除一個Network Namespace時,其內部的網絡資源(網路接口等)也會同時被刪掉,而物理接口則會被切換回之前的Network Namespace當中。
容器與Pod
在Kubernetes的定義當中,Pod為一組不可分離的容器,且共享同一個Network Namespace,故不存在同一個Pod當中容器間網絡通信的問題,對于同一個Pod當中的容器來講,通過Localhost即可與其他的容器進行網絡通信。
所以同一個節點上的兩個Pod如何進行網絡通信的問題可以轉變為,同一個節點上的兩個容器如何進行網絡通信。
Namespace實操
在回答上面提出的Network Namespace網絡通信的問題前,我們先來做一些簡單的命令行操作,先對Namespace有一個感性地認識,實驗環境如下:
通過命令lsns可以查看到宿主機上所有的Namespace(注意需要使用root用戶執行,否則可能會出現有些Namespace看不到的情況):
lsns默認會輸出所有可以看到的Namespace,簡單解釋一下lsns命令各個輸出列的含義:
與Network Namespace相關性較強的還有另外一個命令 ip netns,主要用于持久化命名空間的管理,包括Network Namespace的創建、刪除以和配置等。 ip netns命令在創建Network Namespace時默認會在/var/run/netns目錄下創建一個bind mount的掛載點,從而達到持久化Network Namespace的目的,即允許在該命名空間當中沒有進程的情況下依然保留該命名空間。Docker當中由于缺少了這一步,玩過Docker的同學就會發現通過Docker創建容器后并不能在宿主機上通過 ip netns查看到相關的Network Namespace(這個后面會講怎么才能夠看到,稍微小操作一下就行)。
與Network Namespace相關操作命令:
ip netns add < namespace name > # 添加network namespace ip netns list # 查看Network Namespace ip netns delete < namespace name > # 刪除Network Namespace ip netns exec < namespace name > <command> # 進入到Network Namespace當中執行命令
創建名為netA的Network Namespace:
查看創建的Network Namespace:
可以看到Network Namespace netA當中僅有一個環回網絡接口lo,且有獨立的路由表(為空)。
宿主機(root network namespace)上有網絡接口eth0(10.10.88.170)和eth2(172.16.130.164),此時可以直接ping通IP 172.16.130.164。
嘗試將root network namespace當中的eth0接口添加到network namespce netA當中:
ip link set dev eth0 netns netA
將宿主機上的網絡接口eth0(10.10.88.170)加入到網絡命名空間netA后:
1. 宿主機上看不到eth0網絡接口了(同一時刻網絡接口只能在一個Network Namespace)
2. netA network namespace里面無法ping通root namespace當中的eth2(網絡隔離)
從上面的這些操作我們只是知道了Network Namespace的隔離性,但仍然無法達到我們想要的結果,即讓兩個容器或者說兩個不同的Network Namespace進行網絡通信。在真實的生活場景中,當我們要連接同一個集團兩個相距千里的分公司的局域網時,我們有3種解決方案:第一種是對數據比較隨意的,直接走公網連接,但存在網絡安全的問題。第二種是不差錢的,直接拉一根專線將兩個分公司的網絡連接起來,這樣雖然遠隔千里,但仍然可以處于一個網絡當中。另外一種是兼顧網絡安全集和性價比的VPN連接,但存在性能問題。很顯然,不管是哪一種方案都需要有一根“線”將兩端連接起來,不管是虛擬的VPN還是物理的專線。
前面提到了容器通過Network Namespace進行網絡隔離,但是又由于Network Namespace的隔離導致兩個不同的Network Namespace無法進行通信,這個時候我們聯想到了實際生活場景中連接一個集團的兩個分公司局域網的處理方式。實際上Linux當中也存在類似像網線一樣的虛擬設備vEth(此時是不是覺得Linux簡直無所不能?),全稱為Virtual Ethernet Device,是一種虛擬的類似于以太網絡的設備。
vEth有以下幾個特點:
vEth作為一種虛擬以太網絡設備,可以連接兩個不同的Network Namespace。
vEth總是成對創建,所以一般叫veth pair。(因為沒有只有一頭的網線)。
vEth當中一端收到數據包后另一端也會立馬收到。
可以通過ethtool找到vEth的對端接口。(注意后面會用到)
理解了以上幾點對于我們后面理解容器間的網絡通信就容易多了。
vEth實操
創建vEth:
ip link add < veth name > type veth peer name < veth peer name >
創建名為veth0A,且對端為veth0B的vEth設備。
可以看到root network namespace當中多出來了兩個網絡接口veth0A和veth0B,網絡接口名稱@后面的接的正是對端的接口名稱。
創建Network Namespace netA和netB:
分別將接口veth0A加入到netA,將接口veth0B加入到netB:
ip link set veth0A netns netA ip link set veth0B netns netB
這個時候通過IP a查看宿主機(root network namespace)網絡接口時可以發現,已經看不到接口veth0A和veth0B了(同一時刻一個接口只能處于一個Network Namespace下面)。
再分別到netA和netB兩個Network Namespace當中去查看,可以看到兩個Network Namespace當中都多了一個網絡接口。
分別拉起兩個網絡接口并配上IP,這里將為veth0A配置IP 192.168.100.1,veth0B配置IP 192.168.100.2:
ip netns exec netA ip link set veth0A up ip netns exec netA ip addr add 192.168.100.1/24 dev veth0A
ip netns exec netB ip addr add 192.168.100.2/24 dev veth0B
測試通過veth pair連接的兩個Network Namespace netA和netB之間的網絡連接。
在netA(192.168.100.1)當中ping netB(192.168.100.2):
在netB(192.168.100.2)當中ping netA(192.168.100.1):
可以發現netA跟netB這兩個Network Namespace在通過veth pair連接后已經可以進行正常的網絡通信了。
解決了容器Network Namespace隔離的問題,這個時候有云計算經驗或者熟悉OpenStack和ZStack的同學就會發現,現在的場景跟虛擬機之間的網絡互聯是不是簡直一模一樣了?
vEth作為一個二層網絡設備,當需要跟別的網絡設備相連時該怎么處理呢?在現實生活場景當中我們會拿一個交換機將不同的網線連接起來。實際上在虛擬化場景下也是如此,Linux Bridge和Open vSwith(OVS)是當下比較常用的兩種連接二層網絡的解決方案,而Docker當中采用的是Linux Bridge。
Kubernetes作為一個容器編排平臺,在容器引擎方面既可以選擇Docker也可以選擇rkt,這里直接分別通過Docker和Kubernetes創建容器來進行簡單比對。 Kubernetes在創建Pod時首先會創建一個pause容器,它的唯一作用就是保留和占據一個被Pod當中所有容器所共享的網絡命名空間(Network Namespace),就這樣,一個Pod IP并不會隨著Pod當中的一個容器的起停而改變。
Docker下的容器網絡
我們先來看一下在沒有Kubernetes的情況下是什么樣子的。在Docker啟動的時候默認會創建一個名為docker0的網橋,且默認配置下采用的網絡段為172.17.0.0/16,每一個在該節點上創建的容器都會被分配一個在該網段下的IP。容器通過連接到docker0上進行相互通信。
手動創建兩個容器:
docker run -it --name testA busybox sh docker run -it --name testB busybox sh
查看網絡接口狀況。
容器testA:
容器testB:
查看網橋狀態:
可以發現docker0上面已經連接了兩個虛擬網絡接口(vEth)。
在docker0上通過tcpdump抓包:
tcpdump -n -i docker0
可以發現容器testA和容器testB正是通過docker0網橋進行網絡包轉發的。
加入Kubernetes后的容器網絡
其實加入Kubernetes后本質上容器網絡通信模式并沒有發生變更,但Kubernetes出于網絡地址規劃的考慮,重新創建了一個網橋cni0用于取代docker0,來負責本節點上網絡地址的分配,而實際的網絡段管理由Flannel處理。
下面還是以創建2個運行BusyBox鏡像的Pod作為例子進行說明。
先給Kubernetes集群當中的兩個work node打上label以方便將Pod調度到相同的節點上面進行測試:
[root@10-10-88-192 network]# kubectl get node --show-labels NAME STATUS ROLES AGE VERSION LABELS 10-10-88-170 Ready <none> 47d v1.10.5-28+187e1312d40a02 beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io/hostname=10-10-88-170 10-10-88-192 Ready master 47d v1.10.5-28+187e1312d40a02 beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io/hostname=10-10-88-192,node-role.kubernetes.io/master= 10-10-88-195 Ready <none> 47d v1.10.5-28+187e1312d40a02 beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io/hostname=10-10-88-195 [root@10-10-88-192 network]# [root@10-10-88-192 network]# kubectl label --overwrite node 10-10-88-170 host=node1node "10-10-88-170" labeled [root@10-10-88-192 network]# [root@10-10-88-192 network]# kubectl label --overwrite node 10-10-88-195 host=node2node "10-10-88-195" labeled [root@10-10-88-192 network]# [root@10-10-88-192 network]# kubectl get node --show-labels NAME STATUS ROLES AGE VERSION LABELS 10-10-88-170 Ready <none> 47d v1.10.5-28+187e1312d40a02 beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,host=node1,kubernetes.io/hostname=10-10-88-170 10-10-88-192 Ready master 47d v1.10.5-28+187e1312d40a02 beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io/hostname=10-10-88-192,node-role.kubernetes.io/master= 10-10-88-195 Ready <none> 47d v1.10.5-28+187e1312d40a02 beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,host=node2,kubernetes.io/hostname=10-10-88-195 [root@10-10-88-192 network]#
創建兩個Pod并通過添加nodeSelector使其調度到同一個節點(host1)。
編輯Pod的yaml配置文件:
基于yaml文件創建Pod:
可以看到兩個Pod都按照預期調度到了10-10-88-170這個節點上面。
通過IP a命令可以看到在Pod的宿主機上多出來了2個vethXXX樣式的網絡接口:
[root@10-10-88-170 ~]# ip a 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN qlen 1 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 inet 127.0.0.1/8 scope host lo valid_lft forever preferred_lft forever inet6 ::1/128 scope host valid_lft forever preferred_lft forever 2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP qlen 1000 link/ether fa:35:b6:5e:ac:00 brd ff:ff:ff:ff:ff:ff inet 10.10.88.170/24 brd 10.10.88.255 scope global eth0 valid_lft forever preferred_lft forever inet6 fe80::f835:b6ff:fe5e:ac00/64 scope link valid_lft forever preferred_lft forever 3: eth2: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP qlen 1000 link/ether fa:88:2a:44:2b:01 brd ff:ff:ff:ff:ff:ff inet 172.16.130.164/24 brd 172.16.130.255 scope global eth2 valid_lft forever preferred_lft forever inet6 fe80::f888:2aff:fe44:2b01/64 scope link valid_lft forever preferred_lft forever 4: docker0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN link/ether 02:42:43:a1:fc:ad brd ff:ff:ff:ff:ff:ff inet 172.17.0.1/16 scope global docker0 valid_lft forever preferred_lft forever 5: flannel.1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue state UNKNOWN link/ether 0e:c4:2c:84:a5:ea brd ff:ff:ff:ff:ff:ff inet 10.244.2.0/32 scope global flannel.1 valid_lft forever preferred_lft forever inet6 fe80::cc4:2cff:fe84:a5ea/64 scope link valid_lft forever preferred_lft forever 6: cni0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue state UP qlen 1000 link/ether 0a:58:0a:f4:02:01 brd ff:ff:ff:ff:ff:ff inet 10.244.2.1/24 scope global cni0 valid_lft forever preferred_lft forever inet6 fe80::f0a0:7dff:feec:3ffd/64 scope link valid_lft forever preferred_lft forever 9: veth3a69de99@if3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue master cni0 state UP link/ether 86:70:76:4f:de:2b brd ff:ff:ff:ff:ff:ff link-netnsid 2 inet6 fe80::8470:76ff:fe4f:de2b/64 scope link valid_lft forever preferred_lft forever 10: vethc8ca82e9@if3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue master cni0 state UP link/ether 76:ad:89:ae:21:68 brd ff:ff:ff:ff:ff:ff link-netnsid 3 inet6 fe80::74ad:89ff:feae:2168/64 scope link valid_lft forever preferred_lft forever 39: veth786e1634@if3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue master cni0 state UP link/ether 66:99:fe:30:d2:e1 brd ff:ff:ff:ff:ff:ff link-netnsid 4 inet6 fe80::6499:feff:fe30:d2e1/64 scope link valid_lft forever preferred_lft forever 40: vethef16d6b0@if3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue master cni0 state UP link/ether c2:7f:73:93:85:fc brd ff:ff:ff:ff:ff:ff link-netnsid 5 inet6 fe80::c07f:73ff:fe93:85fc/64 scope link valid_lft forever preferred_lft forever [root@10-10-88-170 ~]# [root@10-10-88-170 ~]# brctl show bridge name bridge id STP enabled interfaces cni0 8000.0a580af40201 no veth3a69de99 veth786e1634 vethc8ca82e9 vethef16d6b0 docker0 8000.024243a1fcad no [root@10-10-88-170 ~]#
此時兩個Pod的網絡連接如圖所示:
網絡包從Container A發送到Container B的過程如下:
1. 網絡包從busybox1的eth0發出,并通過vethef16d6b0進入到root netns(網絡包從vEth的一端發送后另一端會立馬收到)。
2. 網絡包被傳到網橋cni0,網橋通過發送“who has this IP?”的ARP請求來發現網絡包需要轉發到的目的地(10.244.2.208)。
3. busybox2回答到它有這個IP,所以網橋知道應該把網絡包轉發到veth786e1634(busybox2)。
4. 網絡包到達veth786e1634接口,并通過vEth進入到busybox2的netns,從而完成網絡包從一個容器busybox1到另一個容器busybox2的過程。
對于以上流程有疑問的同學也可以自己動手驗證一下結論,最好的方式就是通過tcpdump命令在各個網絡接口上進行抓包驗證,看網絡包是如何經過網橋再由veth pair流轉到另一個容器網絡當中的。
容器網絡在很大程度上依托于虛擬網絡的發展,這也正是技術發展的趨勢所在,正所謂站在巨人的肩膀上。
關于如何分析Kubernetes中的容器網絡就分享到這里了,希望以上內容可以對大家有一定的幫助,可以學到更多知識。如果覺得文章不錯,可以把它分享出去讓更多的人看到。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。