當(dāng)從Pod訪問百度時(shí)會用到VTEP嗎
大家好,我是二哥。
一個(gè)公眾號關(guān)注者私信問我一個(gè)問題:從 Pod 內(nèi)發(fā)起的,向外網(wǎng)的訪問過程會涉及到 VTEP 嗎?涉及到的 NAT 細(xì)節(jié)是什么?
我大概整理了一下,寫成此文。
寫公眾號大概有半年左右的時(shí)間了,寫一段小感想:于我,每篇文章的選題和文章所涉及知識點(diǎn)的匯編、學(xué)習(xí)、整理、成文都是一個(gè)歷練過程;于你,是一個(gè)恰好數(shù)分鐘的的短暫停留;于我們,是一個(gè)交流的紐帶。利是斷然沒有的,虛名會有一丁點(diǎn),但那絕非我初衷。
相關(guān)話題
在文章《??tun設(shè)備的妙用-Flannel UDP模式篇??》中,二哥借助 tun 設(shè)備,畫了幾張高清大圖和老鐵們詳細(xì)聊了下 Pod 間通信時(shí),數(shù)據(jù)流向是什么樣子的,以及在這個(gè)數(shù)據(jù)流淌過程中涉及到了哪些網(wǎng)絡(luò)設(shè)備,這些設(shè)備又是如何在各自的位置上盡心盡責(zé)地處理、搬移每一份網(wǎng)絡(luò)包的。
那篇文章涉及到的是 Pod 間網(wǎng)絡(luò)通信的場景。在 K8s Overlay 網(wǎng)絡(luò)模型下,這個(gè)過程不需要對進(jìn)、出 Pod 的 traffic 進(jìn)行任何的 NAT。在 VXLAN 這個(gè)神器加持下,宿主機(jī)網(wǎng)絡(luò)根本不關(guān)心、也看不到 Pod traffic 的細(xì)節(jié),因?yàn)?traffic 都被封裝起來了。
但當(dāng) Pod 想訪問外網(wǎng),比如 www.baidu.com 的時(shí)候,就是另外一個(gè)場景了。一是 VXLAN 在這里幫不上忙,VXLAN 是在通信雙方所涉及到的 work node 上利用各自的 VTEP 分別進(jìn)行封包和解包,很顯然外網(wǎng) remote service 不可能有這個(gè)對等的 VTEP。二是也沒有必要使用 VXLAN 。
其實(shí)大家多多少少能大概猜得出來或隱約覺得這里面涉及到了 NAT ??杉懿蛔〖?xì)問,比如:
- 一定需要NAT嗎?
- 假如要NAT的話,從 Pod a 看來,既然 NAT 涉及到對去程的網(wǎng)絡(luò)包進(jìn)行 IP 或 Port 的修改,那它勢必需要記得這些連接在被修改過之前的信息,這樣才能在網(wǎng)絡(luò)包回來的時(shí)候再修改一番。那這些連接信息記錄在那里呢?
- NAT 這個(gè)過程到底發(fā)生在哪里呢?Pod 里還是宿主機(jī)上?具體是網(wǎng)絡(luò)包流經(jīng)到哪個(gè)位置發(fā)生了 NAT 呢?
先回答第一個(gè)問題:當(dāng) traffic 離開宿主機(jī)的時(shí)候不一定需要NAT。這取決于 K8s 所使用的網(wǎng)絡(luò)模型。比如在 Underlay 模型下,就完全沒有必要用到NAT, 具體內(nèi)容詳見二哥在《多圖匯總梳理VPC與三種K8s網(wǎng)絡(luò)模型》中的總結(jié)。但在 Overlay 模型下,NAT就很有必要了,所以本文的場景限定于 Overlay 模型。
另外,本文所討論的 NAT 只會到 work node 這一級。我們知道從網(wǎng)絡(luò)包離開 work node 到最終達(dá)到百度服務(wù)器,中間可能會經(jīng)過若干次 NAT ,那些我們就管不著了。不過二哥在《廣角-聊聊Underlay》這篇文章里,畫出了數(shù)據(jù)中心網(wǎng)絡(luò)拓?fù)涓咔宕髨D,如果你感興趣的話,可以打開看看。
來吧,進(jìn)入正題。
Netfilter conntrack
照例,我們先從 NAT 所涉及到的基礎(chǔ)知識開始。
Netfilter conntrack 又名 CT ,譯作連接跟蹤。它是一個(gè)內(nèi)核模塊(nf_conntrack),用于維護(hù)可跟蹤協(xié)議(trackable protocols)的連接狀態(tài)。目前只支持以下六種協(xié)議:TCP、UDP、ICMP、DCCP、SCTP、GRE。
既然說跟蹤,得有一個(gè)或一組標(biāo)記可供記錄、查找、回溯。在這里 CT 利用到了每個(gè)網(wǎng)絡(luò)包里面的源 IP ,目的 IP ,源 Port ,目的 Port 和 Protocol 這5個(gè)參數(shù)所組成的一組標(biāo)記。更簡單地說,CT 認(rèn)為這5個(gè)參數(shù)可用來唯一標(biāo)識一個(gè)單向連接。注意,這里說的是單向連接。網(wǎng)絡(luò)通信涉及到去和回兩個(gè)單向連接。
你可以用命令 conntrack -L 查看本機(jī)的 CT 記錄。如下表所示,第一條記錄了去、回兩個(gè)方向的記錄。每一個(gè)這樣的記錄叫做:connection tracking entry (conntrack entry)。
# conntrack -L
udp 17 172 src=127.0.0.1 dst=127.0.0.53 sport=59837 dport=53 src=127.0.0.53 dst=127.0.0.1 sport=53 dport=59837 [ASSURED] mark=0 use=1
tcp 6 5 TIME_WAIT src=127.0.0.1 dst=127.0.0.1 sport=35074 dport=2379 src=127.0.0.1 dst=127.0.0.1 sport=2379 dport=35074 [ASSURED] mark=0 use=1
看完了一條 conntrack entry 長什么樣子,我們再來看下記錄是在哪里發(fā)生的。下圖中 routing + iptables of root namespace 里面,你會看到一共兩處有橢圓形所標(biāo)記的 conntrack 。一處是在 PREROUTING 鏈附近,一處是在 OUTPUT 鏈附近。
為什么是這兩個(gè) hook 點(diǎn)創(chuàng)建連接跟蹤記錄呢?因?yàn)樗鼈兌际切逻B接的第一個(gè)包最先達(dá)到的地方:
- PRE_ROUTING 是外部包或者源自本機(jī)其它 network namepsace 的包最先到達(dá)的地方。從 eth0 進(jìn)來的包為外部包,而從 1.5 處 cni0 bridge 進(jìn)來的則為本機(jī)上其它 network namepsace 的包。
- LOCAL_OUT 是本機(jī)通過 root network namepsace 主動(dòng)與對方通信時(shí),網(wǎng)絡(luò)包最先到達(dá)的地方。這里的“對方”既可以是外部服務(wù),也可以是位于本機(jī)上的,但使用的是其它 network namepsace 的進(jìn)程。
當(dāng)然,在LOCAL_IN和POSTROUTING處還會執(zhí)行一次 confirm 操作,以確保這樣一件事情:剛才所說的這個(gè)新創(chuàng)建的 conntrack entry ,它對應(yīng)的是一條新的單向連接,這個(gè)連接的第一個(gè)包經(jīng)過各種處理,一直到這里仍然健在、沒有被丟棄。這并非本文重點(diǎn),略過不聊。
沒有特殊情況,我們可以粗略地認(rèn)為前文所提 conntrack entry 在 PREROUTING 和 OUTPUT 鏈被成功地記錄下來了,以備后用。
圖 1:從 Pod a 訪問外網(wǎng)時(shí)的數(shù)據(jù)流
NAT
連接跟蹤是許多網(wǎng)絡(luò)應(yīng)用的基礎(chǔ),例如 Kubernetes Service、ServiceMesh sidecar、 軟件四層負(fù)載均衡器 LVS/IPVS、Docker network、OVS、iptables 主機(jī)防火墻等等都依賴連接跟蹤功能。NAT更是離不開 CT 。
我們結(jié)合上圖來看看當(dāng) Pod a 訪問百度的時(shí)候,網(wǎng)絡(luò)包流經(jīng)協(xié)議棧以及在 routing table 和 iptables 共同作用下,具體發(fā)生了什么。
www.baidu.com IP 地址是 180.101.49.11 , 我們的 Pod a IP 地址為 10.244.0.2 。
1.1:從Pod a發(fā)起的請求會從 1.1 一路來到 1.5 的位置。二哥在《??tun設(shè)備的妙用-Flannel UDP模式篇??》里面已細(xì)聊過細(xì)節(jié),這里就跳過去了。
1.6:當(dāng)bridge將網(wǎng)絡(luò)包遞交到網(wǎng)絡(luò)層后,網(wǎng)絡(luò)包在 1.6 處便開始了它在 root network namespace 的新旅程。與之對應(yīng)的是從 1.1 ~ 1.4 網(wǎng)絡(luò)包還依舊只是在 Pod a 自己的 network namespace 里面轉(zhuǎn)悠。在 1.5 處,網(wǎng)絡(luò)包從一個(gè) network namespace 跳轉(zhuǎn)到了另外一個(gè)。
1.7:如前一節(jié)所述,這里的 conntrack 會記錄新的連接。
1.8:經(jīng)過路由選擇,網(wǎng)絡(luò)包需要從宿主機(jī)的 eth0 離開。于是走 FORWARD 鏈,來到 POSTROUTING 鏈。
1.9:現(xiàn)在來到了 NAT 現(xiàn)場。結(jié)合下面的 iptables dump,我們可以清楚地看到當(dāng)源IP位于 CIDR 10.244.0.0/16 ,且不是去 docker0 這個(gè)interface ,則會進(jìn)行 NAT 。將Pod a的源 IP 地址修改成了 Node 1 的 IP 地址。
1.10:開始將網(wǎng)絡(luò)包送到鏈路層。
1.11:網(wǎng)絡(luò)包從 eth0 離開宿主機(jī)。
-A POSTROUTING -s 10.244.0.0/16 ! -o docker0 -j MASQUERADE
再看問題
看完 Pod 訪問百度時(shí),CT 和 NAT 的介入細(xì)節(jié),我們再來梳理一下這位同學(xué)的問題:為什么 Pod a 訪問百度時(shí) VTEP 不會介入?
圖2是文章《tun設(shè)備的妙用-Flannel UDP模式篇》的配圖。它詳細(xì)地標(biāo)出了從 Node 1 上的 Pod a 訪問 Node X 的 Pod b 時(shí)的數(shù)據(jù)流。為了方便理解,我們將 Flannel VXLAN 模式所用的 VTEP 換成了 UDP 模式所用的 tun 設(shè)備 + flannel daemon 組合。這樣的更換只是把原本位于內(nèi)核態(tài)的數(shù)據(jù)解、封裝動(dòng)作挪動(dòng)到用戶態(tài)的 daemon 里面來完成了,換湯不換藥。
當(dāng)請求從 Pod a 發(fā)起,無論是訪問外網(wǎng)還是訪問另一個(gè) Pod,圖1和圖2中 1.1 ~ 1.5 這個(gè)流程是不變的。
當(dāng)網(wǎng)絡(luò)包從 1.5 處進(jìn)入網(wǎng)絡(luò)層后,網(wǎng)絡(luò)包的去向發(fā)生了變化:
- 如果是 Pod 間通信,網(wǎng)絡(luò)包會經(jīng)由圖2中的 1.7 流動(dòng)到 1.9,再完成后續(xù)的數(shù)據(jù)封包過程。
- 與之相比,當(dāng) Pod 訪問百度時(shí),網(wǎng)絡(luò)包在圖1中 1.9 處經(jīng)過 NAT 處理后,直接從 1.11 處離開了宿主機(jī),不需要任何的封包過程,也就不需要VTEP的介入。
圖 2:Pod間通信時(shí)的數(shù)據(jù)流
以上就是本文的全部內(nèi)容。