您好,登錄后才能下訂單哦!
這篇文章將為大家詳細講解有關(guān)如何分析Kubernetes中的容器網(wǎng)絡(luò),文章內(nèi)容質(zhì)量較高,因此小編分享給大家做個參考,希望大家閱讀完這篇文章后對相關(guān)知識有一定的了解。
隨著云計算的興起,各大平臺之爭也落下了帷幕,Kubernetes作為后起之秀已經(jīng)成為了事實上的PaaS平臺標(biāo)準(zhǔn),而網(wǎng)絡(luò)又是云計算環(huán)境當(dāng)中最復(fù)雜的部分,總是讓人琢磨不透。圍繞在Kubernetes環(huán)境當(dāng)中同一個節(jié)點(work node)上的Pod之間是如何進行網(wǎng)絡(luò)通信的這個問題進行展開,暫且不考慮跨節(jié)點網(wǎng)絡(luò)通信的情況。
Network Namespace
Network Namespace是Linux 2.6.24才開始引入的,直到Linux 2.6.29才完成的特性。Network Namespace實際上實現(xiàn)的是對網(wǎng)絡(luò)棧的虛擬化,且在創(chuàng)建出來時默認(rèn)只有一個回環(huán)網(wǎng)絡(luò)接口lo,每一個網(wǎng)絡(luò)接口(不管是物理接口還是虛擬化接口)都只能存在于一個Network Namespace當(dāng)中,但可以在不同的Network Namespace之間切換,每一個Network Namespace都有自己獨立的IP地址、路由表、防火墻以及套接字列表等網(wǎng)絡(luò)相關(guān)資源。當(dāng)刪除一個Network Namespace時,其內(nèi)部的網(wǎng)絡(luò)資源(網(wǎng)路接口等)也會同時被刪掉,而物理接口則會被切換回之前的Network Namespace當(dāng)中。
容器與Pod
在Kubernetes的定義當(dāng)中,Pod為一組不可分離的容器,且共享同一個Network Namespace,故不存在同一個Pod當(dāng)中容器間網(wǎng)絡(luò)通信的問題,對于同一個Pod當(dāng)中的容器來講,通過Localhost即可與其他的容器進行網(wǎng)絡(luò)通信。
所以同一個節(jié)點上的兩個Pod如何進行網(wǎng)絡(luò)通信的問題可以轉(zhuǎn)變?yōu)?,同一個節(jié)點上的兩個容器如何進行網(wǎng)絡(luò)通信。
Namespace實操
在回答上面提出的Network Namespace網(wǎng)絡(luò)通信的問題前,我們先來做一些簡單的命令行操作,先對Namespace有一個感性地認(rèn)識,實驗環(huán)境如下:
通過命令lsns可以查看到宿主機上所有的Namespace(注意需要使用root用戶執(zhí)行,否則可能會出現(xiàn)有些Namespace看不到的情況):
lsns默認(rèn)會輸出所有可以看到的Namespace,簡單解釋一下lsns命令各個輸出列的含義:
與Network Namespace相關(guān)性較強的還有另外一個命令 ip netns,主要用于持久化命名空間的管理,包括Network Namespace的創(chuàng)建、刪除以和配置等。 ip netns命令在創(chuàng)建Network Namespace時默認(rèn)會在/var/run/netns目錄下創(chuàng)建一個bind mount的掛載點,從而達到持久化Network Namespace的目的,即允許在該命名空間當(dāng)中沒有進程的情況下依然保留該命名空間。Docker當(dāng)中由于缺少了這一步,玩過Docker的同學(xué)就會發(fā)現(xiàn)通過Docker創(chuàng)建容器后并不能在宿主機上通過 ip netns查看到相關(guān)的Network Namespace(這個后面會講怎么才能夠看到,稍微小操作一下就行)。
與Network Namespace相關(guān)操作命令:
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當(dāng)中執(zhí)行命令
創(chuàng)建名為netA的Network Namespace:
查看創(chuàng)建的Network Namespace:
可以看到Network Namespace netA當(dāng)中僅有一個環(huán)回網(wǎng)絡(luò)接口lo,且有獨立的路由表(為空)。
宿主機(root network namespace)上有網(wǎng)絡(luò)接口eth0(10.10.88.170)和eth2(172.16.130.164),此時可以直接ping通IP 172.16.130.164。
嘗試將root network namespace當(dāng)中的eth0接口添加到network namespce netA當(dāng)中:
ip link set dev eth0 netns netA
將宿主機上的網(wǎng)絡(luò)接口eth0(10.10.88.170)加入到網(wǎng)絡(luò)命名空間netA后:
1. 宿主機上看不到eth0網(wǎng)絡(luò)接口了(同一時刻網(wǎng)絡(luò)接口只能在一個Network Namespace)
2. netA network namespace里面無法ping通root namespace當(dāng)中的eth2(網(wǎng)絡(luò)隔離)
從上面的這些操作我們只是知道了Network Namespace的隔離性,但仍然無法達到我們想要的結(jié)果,即讓兩個容器或者說兩個不同的Network Namespace進行網(wǎng)絡(luò)通信。在真實的生活場景中,當(dāng)我們要連接同一個集團兩個相距千里的分公司的局域網(wǎng)時,我們有3種解決方案:第一種是對數(shù)據(jù)比較隨意的,直接走公網(wǎng)連接,但存在網(wǎng)絡(luò)安全的問題。第二種是不差錢的,直接拉一根專線將兩個分公司的網(wǎng)絡(luò)連接起來,這樣雖然遠隔千里,但仍然可以處于一個網(wǎng)絡(luò)當(dāng)中。另外一種是兼顧網(wǎng)絡(luò)安全集和性價比的VPN連接,但存在性能問題。很顯然,不管是哪一種方案都需要有一根“線”將兩端連接起來,不管是虛擬的VPN還是物理的專線。
前面提到了容器通過Network Namespace進行網(wǎng)絡(luò)隔離,但是又由于Network Namespace的隔離導(dǎo)致兩個不同的Network Namespace無法進行通信,這個時候我們聯(lián)想到了實際生活場景中連接一個集團的兩個分公司局域網(wǎng)的處理方式。實際上Linux當(dāng)中也存在類似像網(wǎng)線一樣的虛擬設(shè)備vEth(此時是不是覺得Linux簡直無所不能?),全稱為Virtual Ethernet Device,是一種虛擬的類似于以太網(wǎng)絡(luò)的設(shè)備。
vEth有以下幾個特點:
vEth作為一種虛擬以太網(wǎng)絡(luò)設(shè)備,可以連接兩個不同的Network Namespace。
vEth總是成對創(chuàng)建,所以一般叫veth pair。(因為沒有只有一頭的網(wǎng)線)。
vEth當(dāng)中一端收到數(shù)據(jù)包后另一端也會立馬收到。
可以通過ethtool找到vEth的對端接口。(注意后面會用到)
理解了以上幾點對于我們后面理解容器間的網(wǎng)絡(luò)通信就容易多了。
vEth實操
創(chuàng)建vEth:
ip link add < veth name > type veth peer name < veth peer name >
創(chuàng)建名為veth0A,且對端為veth0B的vEth設(shè)備。
可以看到root network namespace當(dāng)中多出來了兩個網(wǎng)絡(luò)接口veth0A和veth0B,網(wǎng)絡(luò)接口名稱@后面的接的正是對端的接口名稱。
創(chuàng)建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)網(wǎng)絡(luò)接口時可以發(fā)現(xiàn),已經(jīng)看不到接口veth0A和veth0B了(同一時刻一個接口只能處于一個Network Namespace下面)。
再分別到netA和netB兩個Network Namespace當(dāng)中去查看,可以看到兩個Network Namespace當(dāng)中都多了一個網(wǎng)絡(luò)接口。
分別拉起兩個網(wǎng)絡(luò)接口并配上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之間的網(wǎng)絡(luò)連接。
在netA(192.168.100.1)當(dāng)中ping netB(192.168.100.2):
在netB(192.168.100.2)當(dāng)中ping netA(192.168.100.1):
可以發(fā)現(xiàn)netA跟netB這兩個Network Namespace在通過veth pair連接后已經(jīng)可以進行正常的網(wǎng)絡(luò)通信了。
解決了容器Network Namespace隔離的問題,這個時候有云計算經(jīng)驗或者熟悉OpenStack和ZStack的同學(xué)就會發(fā)現(xiàn),現(xiàn)在的場景跟虛擬機之間的網(wǎng)絡(luò)互聯(lián)是不是簡直一模一樣了?
vEth作為一個二層網(wǎng)絡(luò)設(shè)備,當(dāng)需要跟別的網(wǎng)絡(luò)設(shè)備相連時該怎么處理呢?在現(xiàn)實生活場景當(dāng)中我們會拿一個交換機將不同的網(wǎng)線連接起來。實際上在虛擬化場景下也是如此,Linux Bridge和Open vSwith(OVS)是當(dāng)下比較常用的兩種連接二層網(wǎng)絡(luò)的解決方案,而Docker當(dāng)中采用的是Linux Bridge。
Kubernetes作為一個容器編排平臺,在容器引擎方面既可以選擇Docker也可以選擇rkt,這里直接分別通過Docker和Kubernetes創(chuàng)建容器來進行簡單比對。 Kubernetes在創(chuàng)建Pod時首先會創(chuàng)建一個pause容器,它的唯一作用就是保留和占據(jù)一個被Pod當(dāng)中所有容器所共享的網(wǎng)絡(luò)命名空間(Network Namespace),就這樣,一個Pod IP并不會隨著Pod當(dāng)中的一個容器的起停而改變。
Docker下的容器網(wǎng)絡(luò)
我們先來看一下在沒有Kubernetes的情況下是什么樣子的。在Docker啟動的時候默認(rèn)會創(chuàng)建一個名為docker0的網(wǎng)橋,且默認(rèn)配置下采用的網(wǎng)絡(luò)段為172.17.0.0/16,每一個在該節(jié)點上創(chuàng)建的容器都會被分配一個在該網(wǎng)段下的IP。容器通過連接到docker0上進行相互通信。
手動創(chuàng)建兩個容器:
docker run -it --name testA busybox sh docker run -it --name testB busybox sh
查看網(wǎng)絡(luò)接口狀況。
容器testA:
容器testB:
查看網(wǎng)橋狀態(tài):
可以發(fā)現(xiàn)docker0上面已經(jīng)連接了兩個虛擬網(wǎng)絡(luò)接口(vEth)。
在docker0上通過tcpdump抓包:
tcpdump -n -i docker0
可以發(fā)現(xiàn)容器testA和容器testB正是通過docker0網(wǎng)橋進行網(wǎng)絡(luò)包轉(zhuǎn)發(fā)的。
加入Kubernetes后的容器網(wǎng)絡(luò)
其實加入Kubernetes后本質(zhì)上容器網(wǎng)絡(luò)通信模式并沒有發(fā)生變更,但Kubernetes出于網(wǎng)絡(luò)地址規(guī)劃的考慮,重新創(chuàng)建了一個網(wǎng)橋cni0用于取代docker0,來負責(zé)本節(jié)點上網(wǎng)絡(luò)地址的分配,而實際的網(wǎng)絡(luò)段管理由Flannel處理。
下面還是以創(chuàng)建2個運行BusyBox鏡像的Pod作為例子進行說明。
先給Kubernetes集群當(dāng)中的兩個work node打上label以方便將Pod調(diào)度到相同的節(jié)點上面進行測試:
[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]#
創(chuàng)建兩個Pod并通過添加nodeSelector使其調(diào)度到同一個節(jié)點(host1)。
編輯Pod的yaml配置文件:
基于yaml文件創(chuàng)建Pod:
可以看到兩個Pod都按照預(yù)期調(diào)度到了10-10-88-170這個節(jié)點上面。
通過IP a命令可以看到在Pod的宿主機上多出來了2個vethXXX樣式的網(wǎng)絡(luò)接口:
[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的網(wǎng)絡(luò)連接如圖所示:
網(wǎng)絡(luò)包從Container A發(fā)送到Container B的過程如下:
1. 網(wǎng)絡(luò)包從busybox1的eth0發(fā)出,并通過vethef16d6b0進入到root netns(網(wǎng)絡(luò)包從vEth的一端發(fā)送后另一端會立馬收到)。
2. 網(wǎng)絡(luò)包被傳到網(wǎng)橋cni0,網(wǎng)橋通過發(fā)送“who has this IP?”的ARP請求來發(fā)現(xiàn)網(wǎng)絡(luò)包需要轉(zhuǎn)發(fā)到的目的地(10.244.2.208)。
3. busybox2回答到它有這個IP,所以網(wǎng)橋知道應(yīng)該把網(wǎng)絡(luò)包轉(zhuǎn)發(fā)到veth786e1634(busybox2)。
4. 網(wǎng)絡(luò)包到達veth786e1634接口,并通過vEth進入到busybox2的netns,從而完成網(wǎng)絡(luò)包從一個容器busybox1到另一個容器busybox2的過程。
對于以上流程有疑問的同學(xué)也可以自己動手驗證一下結(jié)論,最好的方式就是通過tcpdump命令在各個網(wǎng)絡(luò)接口上進行抓包驗證,看網(wǎng)絡(luò)包是如何經(jīng)過網(wǎng)橋再由veth pair流轉(zhuǎn)到另一個容器網(wǎng)絡(luò)當(dāng)中的。
容器網(wǎng)絡(luò)在很大程度上依托于虛擬網(wǎng)絡(luò)的發(fā)展,這也正是技術(shù)發(fā)展的趨勢所在,正所謂站在巨人的肩膀上。
關(guān)于如何分析Kubernetes中的容器網(wǎng)絡(luò)就分享到這里了,希望以上內(nèi)容可以對大家有一定的幫助,可以學(xué)到更多知識。如果覺得文章不錯,可以把它分享出去讓更多的人看到。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。