特洛伊木馬-圖解VXLAN容器網(wǎng)絡(luò)通信方案
一篇文章圍繞一張圖,講述一個(gè)主題。不過(guò)這個(gè)主題偏大,我估計(jì)需要好幾篇文章才能說(shuō)得清楚。
云原生的代表技術(shù)包括容器、服務(wù)網(wǎng)格、微服務(wù)、不可變基礎(chǔ)設(shè)施和聲明式API。其中K8s是不可變基礎(chǔ)設(shè)施的壓艙石。典型的K8s集群由數(shù)十個(gè)Node, 成百個(gè)Pod,上千個(gè)Container組成。相互隔離的容器間需要協(xié)作才能完成更大規(guī)模的應(yīng)用。而協(xié)作就需要網(wǎng)絡(luò)通信。
這篇文章我主要通過(guò)下面這張全景圖來(lái)講述K8s是如何利用VXLAN來(lái)實(shí)現(xiàn)K8s的容器通信方案的。網(wǎng)絡(luò)通信不是量子糾纏,網(wǎng)絡(luò)流量是實(shí)打?qū)嵉赝ㄟ^(guò)了各個(gè)虛擬的、實(shí)體的網(wǎng)絡(luò)設(shè)備,途徑每個(gè)設(shè)備節(jié)點(diǎn)時(shí)自然也會(huì)受到設(shè)備上的路由、iptables等策略控制。
圖:VXLAN容器網(wǎng)絡(luò)方案全景圖
K8s的容器通信方案有很多種。譬如flannel實(shí)現(xiàn)的host-gw方案、calico基于三層轉(zhuǎn)發(fā)實(shí)現(xiàn)的方案以及本文著重講述的flannel.1 VXLAN方案。為什么我要挑flannel.1 VXLAN方案來(lái)細(xì)聊呢,因?yàn)樗鼔驈?fù)雜,涉及到了比較多的虛擬網(wǎng)絡(luò)設(shè)備和組網(wǎng)技術(shù)。
這張圖里面涉及到如下幾種網(wǎng)絡(luò)設(shè)備,有機(jī)會(huì)我們單獨(dú)拿一篇出來(lái)過(guò)一下這些設(shè)備。
- eth: 物理網(wǎng)卡在內(nèi)核中的表示。它一端連著網(wǎng)絡(luò)棧,另一端通過(guò)驅(qū)動(dòng)連接著物理網(wǎng)卡。
- veth: virtual eth。它是成對(duì)出現(xiàn)的,類(lèi)似交叉網(wǎng)線連接的一對(duì)物理網(wǎng)卡。從網(wǎng)卡一端流出的數(shù)據(jù)會(huì)原樣流入另外一端。每個(gè)veth都有自己的MAC地址,也可以給它設(shè)置IP地址。
- bridge: bridge的行為類(lèi)似二層交換機(jī),又翻譯成網(wǎng)橋。可以將veth,tap等虛擬網(wǎng)絡(luò)設(shè)備連(插)到它上面。如果數(shù)據(jù)包的目的 MAC 地址為網(wǎng)橋本身,并且網(wǎng)橋設(shè)置了 IP 地址的話(huà),那該數(shù)據(jù)包就會(huì)被認(rèn)為是bridge收到了發(fā)往創(chuàng)建網(wǎng)橋那臺(tái)主機(jī)的數(shù)據(jù)包,這個(gè)數(shù)據(jù)包將不會(huì)轉(zhuǎn)發(fā)到任何設(shè)備,而是直接交給上層(三層)協(xié)議棧去處理。
- VTEP:VXLAN 網(wǎng)絡(luò)的每個(gè)邊緣入口上,布置有一個(gè) VTEP(VXLAN Tunnel Endpoints)設(shè)備,它既可以是物理設(shè)備,也可以是虛擬化設(shè)備,主要負(fù)責(zé) VXLAN 協(xié)議報(bào)文的封包和解包。圖中flannel.1就是一個(gè)VTEP設(shè)備,它既有IP地址,又有MAC地址。
雖然容器間的網(wǎng)絡(luò)方案多種多樣,但所有的容器網(wǎng)絡(luò)通信問(wèn)題,其實(shí)都可以歸結(jié)為以下幾種場(chǎng)景。本篇我們專(zhuān)注容器間通信的場(chǎng)景,故略去了其它通信主體與容器通信的情形,比如本地Node里面的進(jìn)程也會(huì)和容器通信。留個(gè)彩蛋,以后再聊。
- 同一個(gè)Pod內(nèi)的容器間通信。
- 同一個(gè)Node內(nèi)的容器間通信。
- 跨Node的容器間通信。
這里需要強(qiáng)調(diào)的一個(gè)點(diǎn)是,雖然Pod是K8s編排調(diào)度的基本單位,但是通信的需求卻發(fā)端于Pod里面的容器。
環(huán)境說(shuō)明
這張圖里面,Node 1 和Node X位于同一個(gè)局域網(wǎng)17.168.0.0/24。Node 1的IP地址是17.168.0.2,Node X的IP是17.168.0.3。
K8s集群所使用的子網(wǎng)為10.244.0.0/16。對(duì)于網(wǎng)絡(luò)17.168.0.0/24和它里面的交換機(jī)和路由器來(lái)說(shuō),K8s集群所使用的子網(wǎng)是無(wú)效的網(wǎng)絡(luò),交換機(jī)和路由器更是無(wú)從轉(zhuǎn)發(fā)、路由任何源IP或目的IP為K8s子網(wǎng)的數(shù)據(jù)包。
非常明顯的矛盾出現(xiàn)了:K8s集群要通過(guò)子網(wǎng)為10.244.0.0/16通信,而宿主機(jī)環(huán)境卻根本不認(rèn)識(shí)這個(gè)子網(wǎng)。我們接下來(lái)將看到"特洛伊木馬"的故事在這里再次上演。
我們的目標(biāo)是在這種矛盾的網(wǎng)絡(luò)環(huán)境下,解釋清楚pod a里面的container-1訪問(wèn)pod b里面的container-1時(shí)發(fā)生了哪些事情。圖中藍(lán)色的標(biāo)線展示了數(shù)據(jù)流的方向。
圖中的綠色標(biāo)線和綠色的框圖表示了與VXLAN相關(guān)的數(shù)據(jù)流和網(wǎng)絡(luò)封包示意圖。
出于簡(jiǎn)單,Node 1里面只畫(huà)出了一個(gè)Pod, pod a,所有的Pod都連在了bridge cni0上,子網(wǎng)為10.244.0.1/24。Node X里面只畫(huà)了兩個(gè)Pod, pod b和pod c ,所有的Pod也一樣都連在了bridge cni0上,子網(wǎng)為10.244.1.1/24。
每個(gè)Node上面的bridge都分配有IP地址。Pod a的IP地址是10.244.0.2,Pod b的IP地址是10.244.1.3。
同一個(gè)Pod內(nèi)的容器間通信
這是最簡(jiǎn)單的情形,內(nèi)核自帶技能,不需額外的組網(wǎng)技術(shù)加持。
需要強(qiáng)調(diào)的一個(gè)知識(shí)點(diǎn)是Pod內(nèi)部所有的容器是共享同一個(gè)網(wǎng)絡(luò)棧、routes以及iptables的,因?yàn)樗鼈儗儆谕粋€(gè)network namespace。
在一個(gè)k8s cluster內(nèi)部,每個(gè)Pod擁有獨(dú)一無(wú)二的IP地址,Pod內(nèi)部所有的container共享分配Pod的地址。Pod內(nèi)部的容器共享pod的IP地址,但各個(gè)容器的端口不能沖突。
由于Pod調(diào)度的原子性,一個(gè)Pod內(nèi)部的所有container只會(huì)被調(diào)度到一臺(tái)主機(jī)上運(yùn)行。類(lèi)似本地機(jī)器上兩個(gè)應(yīng)用程序通過(guò)localhost進(jìn)行進(jìn)程間通信一樣,同一個(gè)Pod內(nèi)部的容器間可以直接通過(guò)localhost來(lái)通信。此時(shí)的traffic直接通過(guò)loopback 網(wǎng)絡(luò)設(shè)備在兩個(gè)容器間流動(dòng)。圖中的bridge無(wú)法感知這樣的traffic,主機(jī)上的網(wǎng)絡(luò)棧和其它網(wǎng)絡(luò)設(shè)備更不會(huì)感知到。
同一個(gè)Node內(nèi)的容器間通信
圖中Node X上畫(huà)出了多個(gè)Pod。當(dāng)Pod b里面的container-1想要訪問(wèn)Pod c里面的container-1時(shí)屬于這個(gè)場(chǎng)景。
Pod b里面的路由表決定了訪問(wèn)Pod c的traffic需要從自己的interface eth0出去。
- src IP:10.244.1.3,dest IP:10.244.1.8。
- src MAC為Pod b veth MAC,dest MAC為Pod c veth MAC。
從圖中可以看到Pod b和Pod c都是插在了bridge上面。作為一個(gè)虛擬的二層交換機(jī),它按照二層交換機(jī)的行為交換、轉(zhuǎn)發(fā)數(shù)據(jù)包。
在這種場(chǎng)景下,這兩個(gè)container之間的通信行為不會(huì)超出bridge的范圍,包括Pod b的container-1通過(guò)ARP得知目的container的MAC地址也是在bridge內(nèi)處理。也不會(huì)涉及NAT等地址轉(zhuǎn)換操作。
跨Node的容器間通信
這是最常用的通信場(chǎng)景。容器訪問(wèn)api server即是典型的例子。
下面開(kāi)始最復(fù)雜的步驟,這些步驟發(fā)生在Node 1。Node X收到以太幀后的操作是一個(gè)逆過(guò)程,這里不做贅述。
我們按照traffic的流向,以它途徑的各個(gè)網(wǎng)絡(luò)設(shè)備(虛擬的、實(shí)體的)為分割節(jié)點(diǎn),分段講述每段發(fā)生了什么。
從container到cni0
從Pod a的路由表可知,以太幀需要從它的NIC eth0離開(kāi)。因?yàn)閑th0是veth的其中一端,另外一端插在bridge cni0上面,于是以太幀進(jìn)入cni0。此以太幀的目的MAC地址為bridge。
- src IP:10.244.0.2,dest IP:10.244.1.3。
- src MAC為Pod a veth MAC,dest MAC為cni0 MAC。
從cni0到flannel.1
前面提到該網(wǎng)橋配置有IP地址,現(xiàn)在它收到一個(gè)目的MAC地址為自己的數(shù)據(jù)包,于是觸發(fā)了 Linux Bridge 的特殊轉(zhuǎn)發(fā)規(guī)則:網(wǎng)橋不會(huì)將這個(gè)數(shù)據(jù)包轉(zhuǎn)發(fā)給任何設(shè)備,而是直接轉(zhuǎn)交給主機(jī)的三層協(xié)議棧處理。
主機(jī)協(xié)議棧根據(jù)host的路由表,從而得知需要把IP包交給本機(jī)的flannel.1。
從這步以后就是三層路由了,已經(jīng)不在網(wǎng)橋的工作范圍之內(nèi),而是由 Linux 主機(jī)依靠 Netfilter 進(jìn)行 IP 轉(zhuǎn)發(fā)(IP Forward)去實(shí)現(xiàn)的。注意這里是IP包轉(zhuǎn)發(fā),接收者收到的是3層的package,因而它不包含二層的數(shù)據(jù)。
flannel.1組裝內(nèi)部數(shù)據(jù)幀
至此,越過(guò)千山萬(wàn)水,本機(jī)的flannel.1終于收到了IP包。
從這里開(kāi)始,flannel.1需要想辦法營(yíng)造幻象:跨主機(jī)營(yíng)造一個(gè)虛擬的網(wǎng)絡(luò)10.244.0.0/16,好讓Pod a看起來(lái)Pod b和它正處于一個(gè)完全合法的、信息交換自由無(wú)障礙的環(huán)境。天真的Pod們完全不知這個(gè)網(wǎng)絡(luò)是一個(gè)虛擬的、私有的、宿主機(jī)網(wǎng)絡(luò)里面的交換機(jī)和路由器根本不認(rèn)識(shí)它這樣一個(gè)事實(shí)。
前面提到flannel.1收到的是 IP 包,既然是IP包,那它就沒(méi)有MAC地址,但flannel.1同時(shí)又要想辦法把“原始 IP 包”加上一個(gè)目的 MAC 地址(當(dāng)然也需要包含源flannel.1的MAC地址),封裝成一個(gè)完整的二層數(shù)據(jù)幀,然后發(fā)送給位于Node X上的flannel.1。
而大家都知道要組裝一個(gè)完整的二層數(shù)據(jù)幀,首先需要解決的問(wèn)題是目標(biāo) flannel.1的MAC地址是什么呢?下面的提示給出了答案。
Node X上的flannel.1的 MAC 地址是什么?
我們已經(jīng)知道了Node X上的flannel.1的 IP 地址,它是數(shù)據(jù)包的目的地。要根據(jù)三層 IP 地址查詢(xún)對(duì)應(yīng)的二層 MAC 地址,這正是 ARP(Address Resolution Protocol )表的功能。這里要用到的 ARP 記錄,也是 flanneld 進(jìn)程在 Node 1 節(jié)點(diǎn)啟動(dòng)時(shí),自動(dòng)添加在 Node 1 上的。我們可以通過(guò) ip 命令看到它,如下所示:
# 在Node 1上
$ ip neigh show dev flannel.1
10.244.1.0 lladdr 5e:f8:4f:00:e3:37 PERMANENT
通過(guò)ARP,我們知道了目的 flannel.1的MAC是 5e:f8:4f:00:e3:37。到此時(shí),已經(jīng)完整地產(chǎn)生了內(nèi)部數(shù)據(jù)載荷(Inner payload), 內(nèi)部IP頭(Inner IP Header) 10.244.1.3和內(nèi)部Ethernet頭(Inner Ethernet Header)5e:f8:4f:00:e3:37了。
但是,因?yàn)樯厦嫣岬降倪@些 VTEP 設(shè)備的 MAC 地址,對(duì)于宿主機(jī)網(wǎng)絡(luò)來(lái)說(shuō)并沒(méi)有什么實(shí)際意義,所以上面封裝出來(lái)的這個(gè)數(shù)據(jù)幀,并不能在我們的宿主機(jī)二層網(wǎng)絡(luò)里傳輸。為了方便敘述,我們把它稱(chēng)為“內(nèi)部數(shù)據(jù)幀”(Inner Ethernet Frame),或者叫"原始二層數(shù)據(jù)幀"(Original Layer 2 Frame)。
封裝好的內(nèi)部數(shù)據(jù)幀如全景圖中藍(lán)色的方框所示。
接下來(lái),Linux 內(nèi)核還需要再把“原始二層數(shù)據(jù)幀”進(jìn)一步封裝成為宿主機(jī)網(wǎng)絡(luò)里的一個(gè)普通的外部數(shù)據(jù)幀,好讓它載著“原始二層數(shù)據(jù)幀”,通過(guò)宿主機(jī)的 eth0 網(wǎng)卡進(jìn)行傳輸。
flannel.1組裝VXLAN數(shù)據(jù)幀
如下圖所示,原始二層數(shù)據(jù)幀加上VXLAN頭,我們把它叫做“VXLAN數(shù)據(jù)幀”。在全景圖中,我在藍(lán)色的方框上面加了一個(gè)灰色的方框,用來(lái)表示VXLAN頭。需要特別注意下灰色方框中VNI=1這個(gè)部分。VNI(Virtual Network Identifier)長(zhǎng)24-bit,在這里flannel.1默認(rèn)把它設(shè)置為1,這樣Node X上面的flannel.1就知道這個(gè)數(shù)據(jù)幀是需要它處理的。
- Flannel 中,VNI 的默認(rèn)值是 1,這也是宿主機(jī)上的 VTEP 設(shè)備都叫作 flannel.1 的原因。
有了VXLAN數(shù)據(jù)幀,就可以開(kāi)始演繹一個(gè)和“特洛伊木馬”相同的故事。VXLAN數(shù)據(jù)幀如同希臘戰(zhàn)士,但我們的目的不是攻打特洛伊城,而是把這個(gè)VXLAN數(shù)據(jù)幀完整地、神不知鬼不覺(jué)地送到城內(nèi)的flannel.1手里。要達(dá)到這個(gè)目的,我們還需要一個(gè)木馬。
圖:VXLAN數(shù)據(jù)幀
從flannel.1發(fā)起UDP連接
好了,“希臘戰(zhàn)士”有了,我們就差一個(gè)木馬了。接下來(lái)要做的事情是,像把希臘戰(zhàn)士藏到木馬里一樣,Linux 內(nèi)核要把這個(gè)VXLAN數(shù)據(jù)幀塞進(jìn)一個(gè) UDP 包里發(fā)出去。上面的全景圖中,我特意把VXLAN數(shù)據(jù)幀畫(huà)得窄了一些,好讓你感覺(jué)外圍稍胖的UDP包確實(shí)像是個(gè)木馬。
Node 1上的flannel.1 設(shè)備要扮演一個(gè)“網(wǎng)橋”的角色,在二層網(wǎng)絡(luò)進(jìn)行 UDP 包的封包和轉(zhuǎn)發(fā)。在Node 1看來(lái),它會(huì)以為自己的 flannel.1 設(shè)備只是在向另外一臺(tái)宿主機(jī)的 flannel.1 設(shè)備,發(fā)起了一次普通的 UDP 鏈接,卻全然不知它發(fā)送的是一個(gè)木馬(不要緊張,此木馬非木馬病毒)。
但且慢,先回答一個(gè)問(wèn)題:剛才在組裝內(nèi)部數(shù)據(jù)幀的時(shí)候,我們知道 flannel.1 設(shè)備已經(jīng)知道了目的 flannel.1 設(shè)備的 MAC 地址,但這個(gè) UDP 包該發(fā)給哪臺(tái)宿主機(jī)呢?也就是說(shuō),木馬有了,希臘戰(zhàn)士也藏到木馬肚子里了,但特洛伊城在哪里?
是時(shí)候輪到一個(gè)叫作轉(zhuǎn)發(fā)數(shù)據(jù)庫(kù)(FDB, Forwarding Database)上場(chǎng)幫忙了。這個(gè) flannel.1“網(wǎng)橋”對(duì)應(yīng)的 FDB 信息,也是 flanneld 進(jìn)程負(fù)責(zé)維護(hù)的。它的內(nèi)容可以通過(guò) bridge fdb 命令查看到,如下所示:
# 在Node 1上,使用“目的VTEP設(shè)備”的MAC地址進(jìn)行查詢(xún)
$ bridge fdb show flannel.1 | grep 5e:f8:4f:00:e3:37
5e:f8:4f:00:e3:37 dev flannel.1 dst 17.168.0.3 self permanent
在上面這條 FDB 記錄里,指定了這樣一條規(guī)則:發(fā)往我們前面提到的“目的 flannel.1”(MAC 地址是 5e:f8:4f:00:e3:37)的二層數(shù)據(jù)幀,應(yīng)該通過(guò)本機(jī)的flannel.1 設(shè)備,發(fā)往 IP 地址為 17.168.0.3 的主機(jī)。顯然,這臺(tái)主機(jī)正是 Node X,UDP 包要發(fā)往的目的地就找到了。
得到了目的IP地址,自然也會(huì)得知Node X的MAC地址。接下來(lái)的流程,就是一個(gè)正常的,宿主機(jī)網(wǎng)絡(luò)上的封包工作,且最終從 Node 1 的 eth0 網(wǎng)卡發(fā)出去了。只不過(guò)這個(gè)過(guò)程發(fā)生在虛擬設(shè)備flannel.1上面罷了。