追踪 Kubernetes 中的网络流量
作者 | Addo Zhang
來(lái)源 |?云原生指北
譯者注:
這篇文章很全面的羅列出了 Kubernetes 中涉及的網(wǎng)絡(luò)知識(shí),從 Linux 內(nèi)核的網(wǎng)絡(luò)內(nèi)容,到容器、Kubernetes,一一進(jìn)行了詳細(xì)的說(shuō)明。
文章篇幅有點(diǎn)長(zhǎng),不得不說(shuō),網(wǎng)絡(luò)是很復(fù)雜很麻煩的一層,但恰恰這層多年來(lái)變化不大。希望翻譯的內(nèi)容對(duì)大家能有所幫助,有誤的地方,也歡迎大家指正。
本文翻譯獲得?Learnk8s 的授權(quán),原文?Tracing the path of network traffic in Kubernetes[1]?作者?Kristijan Mitevski。
TL;DR:?本文將代理了解 Kubernetes 集群內(nèi)外的數(shù)據(jù)流轉(zhuǎn)。從最初的 Web 請(qǐng)求開(kāi)始,一直到托管應(yīng)用程序的容器。
目錄
Kubernetes 網(wǎng)絡(luò)要求
Linux 網(wǎng)絡(luò)命名空間如果在 pod 中工作
Pause 容器創(chuàng)建 Pod 中的網(wǎng)絡(luò)命名空間
為 Pod 分配了 IP 地址
檢查集群中 pod 到 pod 的流量
Pod 命名空間連接到以太網(wǎng)橋接器
跟蹤同一節(jié)點(diǎn)上 pod 間的流量
跟蹤不同節(jié)點(diǎn)上 pod 間的通信
位運(yùn)算的工作原理
容器網(wǎng)絡(luò)接口 - CNI
檢查 pod 到服務(wù)的流量
使用 Netfilter 和 Iptables 攔截和重寫(xiě)流量
檢查服務(wù)的響應(yīng)
回顧
Kubernetes 網(wǎng)絡(luò)要求
在深入了解 Kubernetes 中的數(shù)據(jù)流轉(zhuǎn)之前,讓我們先澄清下 Kubernetes 網(wǎng)絡(luò)的要求。
Kubernetes 網(wǎng)絡(luò)模型定義了一套基本規(guī)則:
集群中的 pod 應(yīng)該能夠與任何其他 pod 自由通信,而無(wú)需使用網(wǎng)絡(luò)地址轉(zhuǎn)換(NAT)。
在不使用 NAT 的情況下,集群節(jié)點(diǎn)上運(yùn)行的任意程序都應(yīng)該能夠與同一節(jié)點(diǎn)上的任意 pod 通信。
每個(gè) pod 都有自己的 IP 地址(IP-per Pod),其他 pod 都可以使用同一個(gè)地址進(jìn)行訪問(wèn)。
這些要求不會(huì)將實(shí)現(xiàn)限制在單一方案上。
相反,他們概括了集群網(wǎng)絡(luò)的特性。
在滿足這些限制時(shí),必須解決如下挑戰(zhàn)[2]:
如何保證同一 pod 中的容器間的訪問(wèn)就像在同一主機(jī)上一樣?
Pod 能否訪問(wèn)集群中的其他 pod?
Pod 能否訪問(wèn)服務(wù)(service)?以及服務(wù)可以負(fù)載均衡請(qǐng)求嗎?
Pod 可以接收來(lái)自集群外的流量嗎?
本文將專注于前三點(diǎn),從 pod 內(nèi)部網(wǎng)絡(luò)或者容器間的通信說(shuō)起。
Linux 網(wǎng)絡(luò)命名空間如果在 pod 中工作
我們想象下,有一個(gè)承載應(yīng)用程序的主容器和另一個(gè)與它一起運(yùn)行的容器。
在示例 Pod 中有一個(gè) Nginx 容器和 busybox 容器:
apiVersion:?v1 kind:?Pod metadata:name:?multi-container-pod spec:containers:-?name:?container-1image:?busyboxcommand:?['/bin/sh',?'-c',?'sleep?1d']-?name:?container-2image:?nginx在部署時(shí),會(huì)出現(xiàn)如下情況:
Pod 在節(jié)點(diǎn)上得到自己的網(wǎng)絡(luò)命名空間。
Pod?分配到一個(gè) IP 地址,兩個(gè)容器間共享端口。
兩個(gè)容器共享同一個(gè)網(wǎng)絡(luò)命名空間,在本地互相可見(jiàn)。
網(wǎng)絡(luò)配置在后臺(tái)很快完成。
然后,我們退后一步,是這理解為什么上面是容器運(yùn)行所必須的。
在 Linux 中,網(wǎng)絡(luò)命名空間是獨(dú)立的、隔離的邏輯空間。[3]
可以將網(wǎng)絡(luò)命名空間堪稱將物理網(wǎng)絡(luò)接口分割成更小的獨(dú)立部分。
每部分都可以單獨(dú)配置,并使用自己的網(wǎng)絡(luò)規(guī)則和資源。
這些可以包括防火墻規(guī)則、接口(虛擬或物理)、路由和其他所有與網(wǎng)絡(luò)相關(guān)的內(nèi)容。
物理接口持有根命名空間。
可以使用 Linux 網(wǎng)絡(luò)命名空間創(chuàng)建隔離的網(wǎng)絡(luò)。每個(gè)網(wǎng)絡(luò)都是獨(dú)立的,除非進(jìn)行配置否則不會(huì)與其他命名空間通信。
物理接口必須處理最后的所有真實(shí)數(shù)據(jù)包,因此所有的虛擬接口都是從中創(chuàng)建的。
網(wǎng)絡(luò)命名空間可以通過(guò)?`ip-netns` 管理工具[4]?來(lái)管理,可以使用?ip netns list?列出主機(jī)上的命名空間。
請(qǐng)注意,創(chuàng)建的網(wǎng)絡(luò)命名空間將會(huì)出現(xiàn)在?/var/run/netns?目錄下,但?Docker 并沒(méi)有遵循這一點(diǎn)[5]。
例如,下面是 Kubernetes 節(jié)點(diǎn)的命名空間:
$?ip?netns?list cni-0f226515-e28b-df13-9f16-dd79456825ac?(id:?3) cni-4e4dfaac-89a6-2034-6098-dd8b2ee51dcd?(id:?4) cni-7e94f0cc-9ee8-6a46-178a-55c73ce58f2e?(id:?2) cni-7619c818-5b66-5d45-91c1-1c516f559291?(id:?1) cni-3004ec2c-9ac2-2928-b556-82c7fb37a4d8?(id:?0)注意?cni-?前綴意味著命名空間的創(chuàng)建由 CNI 來(lái)完成。
當(dāng)創(chuàng)建 pod 并分配給節(jié)點(diǎn)時(shí),CNI[6]?會(huì):
為其創(chuàng)建網(wǎng)絡(luò)命名空間。
分配 IP 地址。
將容器連接到網(wǎng)絡(luò)。
如果 pod 像上面的示例一樣包含多個(gè)容器,則所有容器都被置于同一個(gè)命名空間中。
創(chuàng)建 pod 時(shí),CNI 為容器創(chuàng)建網(wǎng)絡(luò)命名空間
然后分配 IP 地址
最后將容器連接到網(wǎng)絡(luò)的其余部分
那么當(dāng)列出節(jié)點(diǎn)上的容器時(shí)會(huì)看到什么?
可以 SSH 到 Kubernetes 節(jié)點(diǎn)來(lái)查看命名空間:
$?lsns?-t?netNS?TYPE?NPROCS???PID?USER?????NETNSID?NSFS???????????????????????????COMMAND 4026531992?net?????171?????1?root??unassigned?/run/docker/netns/default??????/sbin/init?noembed?norestore 4026532286?net???????2??4808?65535??????????0?/run/docker/netns/56c020051c3b?/pause 4026532414?net???????5??5489?65535??????????1?/run/docker/netns/7db647b9b187?/pauselsns?命令會(huì)列出主機(jī)上所有的命名空間。
記住 Linux 中有多種命名空間類型[7]。
Nginx 容器在哪?
那么?pause?容器又是什么?
Pause 容器創(chuàng)建 Pod 中的網(wǎng)絡(luò)命名空間
從節(jié)點(diǎn)上的所有進(jìn)程中找出 Nginx 容器:
$?lsnsNS?TYPE???NPROCS???PID?USER????????????COMMAND #?truncated?output 4026532414?net?????????5??5489?65535???????????/pause 4026532513?mnt?????????1??5599?root????????????sleep?1d 4026532514?uts?????????1??5599?root????????????sleep?1d 4026532515?pid?????????1??5599?root????????????sleep?1d 4026532516?mnt?????????3??5777?root????????????nginx:?master?process?nginx?-g?daemon?off; 4026532517?uts?????????3??5777?root????????????nginx:?master?process?nginx?-g?daemon?off; 4026532518?pid?????????3??5777?root????????????nginx:?master?process?nginx?-g?daemon?off;該容器出現(xiàn)在了掛在(mount?mnt)、Unix 分時(shí)系統(tǒng)(Unix time-sharing?uts)和 PID(pid)命名空間中,但是并不在網(wǎng)絡(luò)命名空間(net)中。
不幸的是,lsns?只顯示了每個(gè)進(jìn)程最低的 PID,不過(guò)可以根據(jù)進(jìn)程 ID 進(jìn)一步過(guò)濾。
可以通過(guò)以下內(nèi)容檢索Nginx 容器的所有命名空間:
$?sudo?lsns?-p?5777NS?TYPE???NPROCS???PID?USER??COMMAND 4026531835?cgroup????178?????1?root??/sbin/init?noembed?norestore 4026531837?user??????178?????1?root??/sbin/init?noembed?norestore 4026532411?ipc?????????5??5489?65535?/pause 4026532414?net?????????5??5489?65535?/pause 4026532516?mnt?????????3??5777?root??nginx:?master?process?nginx?-g?daemon?off; 4026532517?uts?????????3??5777?root??nginx:?master?process?nginx?-g?daemon?off; 4026532518?pid?????????3??5777?root??nginx:?master?process?nginx?-g?daemon?off;pause?進(jìn)程再次出現(xiàn),這次它劫持了網(wǎng)絡(luò)命名空間。
那是什么?
集群中的每個(gè) pod 都有一個(gè)在后臺(tái)運(yùn)行的隱藏容器,被稱為?pause。
列出節(jié)點(diǎn)上的所有容器并過(guò)濾出 pause 容器:
$?docker?ps?|?grep?pause fa9666c1d9c6???k8s.gcr.io/pause:3.4.1??"/pause"??k8s_POD_kube-dns-599484b884-sv2js… 44218e010aeb???k8s.gcr.io/pause:3.4.1??"/pause"??k8s_POD_blackbox-exporter-55c457d… 5fb4b5942c66???k8s.gcr.io/pause:3.4.1??"/pause"??k8s_POD_kube-dns-599484b884-cq99x… 8007db79dcf2???k8s.gcr.io/pause:3.4.1??"/pause"??k8s_POD_konnectivity-agent-84f87c…將看到對(duì)于節(jié)點(diǎn)分配到的每個(gè) pod,都有一個(gè)匹配的?pause?容器。
該?pause?容器負(fù)責(zé)創(chuàng)建和維持網(wǎng)絡(luò)命名空間。
它包含的代碼極少,部署后立即進(jìn)入睡眠狀態(tài)。
然而,它在 Kubernetes 生態(tài)中的首當(dāng)其沖,發(fā)揮著至關(guān)重要的作用。[8]。
創(chuàng)建 pod 時(shí),CNI 會(huì)創(chuàng)建一個(gè)帶有睡眠容器的網(wǎng)絡(luò)命名空間
Pod 中的所有容器都會(huì)加入到它創(chuàng)建的網(wǎng)絡(luò)命名空間中
此時(shí) CNI 分配 IP 地址并將容器連接到網(wǎng)絡(luò)
進(jìn)入睡眠狀態(tài)的容器有什么用?
要了解它的實(shí)用性,我們可以想象下如示例一樣有兩個(gè)容器的 pod,但沒(méi)有?pause?容器。
容器啟動(dòng),CNI:
為 Nginx 容器創(chuàng)建一個(gè)網(wǎng)絡(luò)命名空間。
把 busybox 容器加入到前面創(chuàng)建的網(wǎng)絡(luò)命名空間中。
為 pod 分配 IP 地址。
將容器連接到網(wǎng)絡(luò)。
假如 Nginx 容器崩潰了會(huì)發(fā)生什么?
CNI 將不得不再次完成所有流程,兩個(gè)容器的網(wǎng)絡(luò)都會(huì)中斷。
由于?sleep?容器不太可能有任何 bug,因此創(chuàng)建網(wǎng)絡(luò)命名空間通常是一個(gè)更保險(xiǎn)、更健壯的選擇。
如果 pod 中的一個(gè)容器崩潰,其余的仍可以處理網(wǎng)絡(luò)請(qǐng)求。
為 Pod 分配了 IP 地址
前面提到 pod 和所有容器獲得了同樣的 IP。
這是怎么配置的?
在 pod 網(wǎng)絡(luò)命名空間中,創(chuàng)建一個(gè)接口并分配 IP 地址。
我們來(lái)驗(yàn)證下。
首先,找到 pod 的 IP 地址:
$?kubectl?get?pod?multi-container-pod?-o?jsonpath={.status.podIP} 10.244.4.40接下來(lái),找到相關(guān)的網(wǎng)絡(luò)命名空間。
由于網(wǎng)絡(luò)命名空間是從物理接口創(chuàng)建的,需要訪問(wèn)集群節(jié)點(diǎn)。
如果你運(yùn)行的是 minikube,可以通過(guò)?minikube ssh?訪問(wèn)節(jié)點(diǎn)。如果在云提供商中運(yùn)行,應(yīng)該有某種方法通過(guò) SSH 訪問(wèn)節(jié)點(diǎn)。
進(jìn)入后,可以找到創(chuàng)建的最新的網(wǎng)絡(luò)命名空間:
$?ls?-lt?/var/run/netns total?0 -r--r--r--?1?root?root?0?Sep?25?13:34?cni-0f226515-e28b-df13-9f16-dd79456825ac -r--r--r--?1?root?root?0?Sep?24?09:39?cni-4e4dfaac-89a6-2034-6098-dd8b2ee51dcd -r--r--r--?1?root?root?0?Sep?24?09:39?cni-7e94f0cc-9ee8-6a46-178a-55c73ce58f2e -r--r--r--?1?root?root?0?Sep?24?09:39?cni-7619c818-5b66-5d45-91c1-1c516f559291 -r--r--r--?1?root?root?0?Sep?24?09:39?cni-3004ec2c-9ac2-2928-b556-82c7fb37a4d8本示例中,它是?cni-0f226515-e28b-df13-9f16-dd79456825ac。此時(shí),可以在該命名空間總執(zhí)行?exec命令:
$?ip?netns?exec?cni-0f226515-e28b-df13-9f16-dd79456825ac?ip?a #?output?truncated 3:?eth0@if12:?<BROADCAST,MULTICAST,UP,LOWER_UP>?mtu?1450?qdisc?noqueue?state?UP?group?defaultlink/ether?16:a4:f8:4f:56:77?brd?ff:ff:ff:ff:ff:ff?link-netnsid?0inet?10.244.4.40/32?brd?10.244.4.40?scope?global?eth0valid_lft?forever?preferred_lft?foreverinet6?fe80::14a4:f8ff:fe4f:5677/64?scope?linkvalid_lft?forever?preferred_lft?forever10.244.4.40?就是 pod 的 IP 地址。
通過(guò)查找?@if12?中的?12?找到網(wǎng)絡(luò)接口。
$?ip?link?|?grep?-A1?^12 12:?vethweplb3f36a0@if16:?mtu?1376?qdisc?noqueue?master?weave?state?UP?mode?DEFAULT?group?defaultlink/ether?72:1c:73:d9:d9:f6?brd?ff:ff:ff:ff:ff:ff?link-netnsid?1還可以驗(yàn)證 Nginx 容器是否從該命名空間中監(jiān)聽(tīng) HTTP 流量:
$?ip?netns?exec?cni-0f226515-e28b-df13-9f16-dd79456825ac?netstat?-lnp Active?Internet?connections?(only?servers) Proto?Recv-Q?Send-Q?Local?Address???????????Foreign?Address?????????State???????PID/Program?name tcp????????0??????0?0.0.0.0:80??????????????0.0.0.0:*???????????????LISTEN??????692698/nginx:?master tcp6???????0??????0?:::80???????????????????:::*????????????????????LISTEN??????692698/nginx:?master如果無(wú)法通過(guò) SSH 訪問(wèn)集群的節(jié)點(diǎn),可以試試?kubectl exec?進(jìn)入到 busybox 容器,然后使用?ip?和?netstat?命令。
太棒了!
現(xiàn)在我們已經(jīng)介紹了容器間的通信,接下來(lái)看看 Pod 與 Pod 直接如何建立通信。
檢查集群中 pod 到 pod 的流量
當(dāng)說(shuō)起 pod 間通信時(shí),會(huì)有兩種可能:
Pod 流量流向同一節(jié)點(diǎn)上的 pod。
Pod 流量流量另一個(gè)節(jié)點(diǎn)上的 pod。
為了使整個(gè)設(shè)置正常工作,我們需要之前討論過(guò)的虛擬接口和以太網(wǎng)橋接。
在繼續(xù)之前,我們先討論下他們的功能以及為什么他們時(shí)必需的。
要完成 pod 與其他 pod 的通信,它必須先訪問(wèn)節(jié)點(diǎn)的根命名空間。
這是使用連接 pod 和根命名空間的虛擬以太網(wǎng)對(duì)來(lái)實(shí)現(xiàn)的。
這些虛擬接口設(shè)備[9](veth?中的?v)連接并充當(dāng)兩個(gè)命名空間間的隧道。
使用此?veth?設(shè)備,將一端連接到 pod 的命名空間,另一端連接到根命名空間。
這些 CNI 可以替你做,也可以手動(dòng)操作:
$?ip?link?add?veth1?netns?pod-namespace?type?veth?peer?veth2?netns?root現(xiàn)在 pod 的命名空間有了可以訪問(wèn)根命名空間的隧道。
節(jié)點(diǎn)上每個(gè)新建的 pod 都會(huì)設(shè)置如下所示的 veth 對(duì)。
創(chuàng)建接口對(duì)時(shí)其中一部分。
其他的就是為以太網(wǎng)設(shè)備分配地址,并創(chuàng)建默認(rèn)路由。
來(lái)看下如何在 pod 的命名空間中設(shè)置?veth1?接口:
$?ip?netns?exec?cni-0f226515-e28b-df13-9f16-dd79456825ac?ip?addr?add?10.244.4.40/24?dev?veth1 $?ip?netns?exec?cni-0f226515-e28b-df13-9f16-dd79456825ac?ip?link?set?veth1?up $?ip?netns?exec?cni-0f226515-e28b-df13-9f16-dd79456825ac?ip?route?add?default?via?10.244.4.40在節(jié)點(diǎn)側(cè),我們創(chuàng)建另一個(gè)?veth2?對(duì):
$?ip?addr?add?169.254.132.141/16?dev?veth2 $?ip?link?set?veth2?up可以像以前一樣檢查現(xiàn)有的?veth?對(duì)。
在 pod 的命名空間中,檢查?eth0?接口的后綴。
$?ip?netns?exec?cni-0f226515-e28b-df13-9f16-dd79456825ac?ip?link?show?type?veth 3:?eth0@if12:?<BROADCAST,MULTICAST,UP,LOWER_UP>?mtu?1450?qdisc?noqueue?state?UP?mode?DEFAULT?group?defaultlink/ether?16:a4:f8:4f:56:77?brd?ff:ff:ff:ff:ff:ff?link-netnsid?0這種情況下可以使用?grep -A1 ^12?進(jìn)行查找(或者滾動(dòng)到目標(biāo)所在):
$?ip?link?show?type?veth #?output?truncated 12:?cali97e50e215bd@if3:?<BROADCAST,MULTICAST,UP,LOWER_UP>?mtu?1450?qdisc?noqueue?state?UP?mode?DEFAULT?group?defaultlink/ether?ee:ee:ee:ee:ee:ee?brd?ff:ff:ff:ff:ff:ff?link-netns?cni-0f226515-e28b-df13-9f16-dd79456825ac也可以使用?ip -n cni-0f226515-e28b-df13-9f16-dd79456825ac link show type veth命令。
注意?3: eth0@if12?和?12: cali97e50e215bd@if3?接口上的符號(hào)。
在 pod 命名空間中,eth0?接口連接到根命名空間中編號(hào)為?12?的接口。因此是?@if12。
在?veth?對(duì)的另一端,根命名空間連接到 pod 命名空間的?3?號(hào)接口。
接下來(lái)是連接?veth?對(duì)兩端的橋接器(bridge)。
Pod 命名空間連接到以太網(wǎng)橋接器
橋接器將位于根命名空間中的虛擬接口的每一端“綁定”。
該橋接器將允許流量在虛擬對(duì)之間流動(dòng),并通過(guò)公共根命名空間。
理論時(shí)間。
以太網(wǎng)橋接器位于OSI 網(wǎng)絡(luò)模型[10]的第二層。
可以將橋接器看作一個(gè)虛擬交換機(jī),接受來(lái)自不同命名空間和接口的連接。[11]
以太網(wǎng)橋接器允許連接同一個(gè)節(jié)點(diǎn)上的多個(gè)可用網(wǎng)絡(luò)。
因此,可以使用該設(shè)置連接兩個(gè)接口:從 pod 命名空間的?veth?連接到同一節(jié)點(diǎn)上另一個(gè) pod 的?veth。
我們繼續(xù)看下以太網(wǎng)橋接器和 veth 對(duì)的作用。
跟蹤同一節(jié)點(diǎn)上 pod 間的流量
假設(shè)同一個(gè)節(jié)點(diǎn)上有兩個(gè) pod,Pod-A 想向 Pod-B 發(fā)送消息。
由于目標(biāo)不是同命名空間的容器,Pod-A 向其默認(rèn)接口?eth0?發(fā)送數(shù)據(jù)包。這個(gè)接口與?veth?對(duì)的一端綁定,作為隧道。因此數(shù)據(jù)包將被轉(zhuǎn)發(fā)到節(jié)點(diǎn)的根命名空間。
以太網(wǎng)橋接器作為虛擬交換機(jī),必須以某種方式將目標(biāo) pod IP(Pod-B)解析為其 MAC 地址。
輪到ARP 協(xié)議上場(chǎng)了。當(dāng)幀到達(dá)橋接器時(shí),會(huì)向所有連接的設(shè)備發(fā)送 ARP 廣播。橋接器喊道誰(shuí)有 Pod-B 的 IP 地址。
收到帶有連接 Pod-B 接口的 MAC 地址的回復(fù),然后此信息存儲(chǔ)在橋接器 ARP 緩存(查找表)中。
IP 和 MAC 地址的映射存儲(chǔ)完成后,橋接器在表中查找,并將數(shù)據(jù)包轉(zhuǎn)發(fā)到正確的短點(diǎn)。數(shù)據(jù)包到達(dá)根命名空間中 Pod- B 的?veth,然后從那快速到達(dá) Pod-B 命名空間內(nèi)的?eth0?接口。
有了這個(gè),Pod-A 和 Pod-B 之間的通信取得了成功。
跟蹤不同節(jié)點(diǎn)上 pod 間的通信
對(duì)于需要跨不同節(jié)點(diǎn)通信的 pod,需要額外的通信跳轉(zhuǎn)。
前幾個(gè)步驟保持不變,直到數(shù)據(jù)包到達(dá)根命名空間并需要發(fā)送到 Pod- B。
當(dāng)目標(biāo)地址不在本地網(wǎng)絡(luò)中,數(shù)據(jù)包將被轉(zhuǎn)發(fā)到本節(jié)點(diǎn)的默認(rèn)網(wǎng)關(guān)。節(jié)點(diǎn)上退出或默認(rèn)網(wǎng)關(guān)通常位于?eth0?接口上 -- 將節(jié)點(diǎn)連接到網(wǎng)絡(luò)的物理接口。
這次并不會(huì)發(fā)生 ARP 解析,因?yàn)樵春湍繕?biāo) IP 在不同網(wǎng)絡(luò)上。
檢查使用位運(yùn)算(Bitwise)操作完成。
當(dāng)目標(biāo) IP 不在當(dāng)前網(wǎng)絡(luò)上時(shí),數(shù)據(jù)包將被轉(zhuǎn)發(fā)到節(jié)點(diǎn)的默認(rèn)網(wǎng)關(guān)。
位運(yùn)算的工作原理
在確定數(shù)據(jù)包的轉(zhuǎn)發(fā)位置時(shí),源節(jié)點(diǎn)必須執(zhí)行位運(yùn)算。
這也被稱為與操作。[12]
作為復(fù)習(xí),位與操作產(chǎn)生如下結(jié)果:
0?AND?0?=?0 0?AND?1?=?0 1?AND?0?=?0 1?AND?1?=?1除了 1 與 1 以外的都是 false。
如果源節(jié)點(diǎn)的 IP 為 192.168.1.1,子網(wǎng)掩碼為 /24,目標(biāo) IP 為 172.16.1.1/16,則按位與操作將確認(rèn)他們不在同一網(wǎng)絡(luò)上。
這意味著目標(biāo) IP 與數(shù)據(jù)包的源在不同的網(wǎng)絡(luò)上,因此數(shù)據(jù)包將在默認(rèn)網(wǎng)關(guān)中轉(zhuǎn)發(fā)。
數(shù)學(xué)時(shí)間。
我們必須從二進(jìn)制文件中的 32 位地址執(zhí)行與操作開(kāi)始。
先找出源和目標(biāo) IP 的網(wǎng)絡(luò)。
|?Type?????????????|?Binary??????????????????????????????|?Converted??????????| |?----------------?|?-----------------------------------?|?------------------?| |?Src.?IP?Address??|?11000000.10101000.00000001.00000001?|?192.168.1.1????????| |?Src.?Subnet?Mask?|?11111111.11111111.11111111.00000000?|?255.255.255.0(/24)?| |?Src.?Network?????|?11000000.10101000.00000001.00000000?|?192.168.1.0????????| |??????????????????|?????????????????????????????????????|????????????????????| |?Dst.?IP?Address??|?10101100.00010000.00000001.00000001?|?172.16.1.1?????????| |?Dst.?Subnet?Mask?|?11111111.11111111.00000000.00000000?|?255.255.0.0(/16)???| |?Dst.?Network?????|?10101100.00010000.00000000.00000000?|?172.16.0.0?????????|位運(yùn)算操作后,需要將目標(biāo) IP 與數(shù)據(jù)包源節(jié)點(diǎn)的子網(wǎng)進(jìn)行比較。
|?Type?????????????|?Binary??????????????????????????????|?Converted??????????| |?----------------?|?-----------------------------------?|?------------------?| |?Dst.?IP?Address??|?10101100.00010000.00000001.00000001?|?172.16.1.1?????????| |?Src.?Subnet?Mask?|?11111111.11111111.11111111.00000000?|?255.255.255.0(/24)?| |?Network??Result??|?10101100.00010000.00000001.00000000?|?172.16.1.0進(jìn)行位比較后,ARP 會(huì)檢查其查詢表來(lái)查找默認(rèn)網(wǎng)關(guān)的 MAC 地址。
如果有條目,將立即轉(zhuǎn)發(fā)數(shù)據(jù)包。
否則,先進(jìn)行廣播以確定網(wǎng)關(guān)的 MAC 地址。
數(shù)據(jù)包現(xiàn)在路由到另一個(gè)節(jié)點(diǎn)的默認(rèn)接口,我們叫它 Node-B。
以相反的順序。數(shù)據(jù)包現(xiàn)在位與 Node-B 的根命名空間,并到達(dá)橋接器,這里會(huì)進(jìn)行 ARP 解析。
收到帶有連接 Pod-B 的接口 MAC地址的回復(fù)。
這次橋接器通過(guò) Pod-B 的?veth?設(shè)備將幀轉(zhuǎn)發(fā),并到達(dá) Pod-B 自己的命名空間。
此時(shí)應(yīng)該已經(jīng)熟悉了 pod 之間的流量如何流轉(zhuǎn),接下來(lái)再探索下 CNI 如何創(chuàng)建上述內(nèi)容。
容器網(wǎng)絡(luò)接口 - CNI
容器網(wǎng)絡(luò)接口(CNI)關(guān)注當(dāng)前節(jié)點(diǎn)的網(wǎng)絡(luò)。[13]
可以將 CNI 看作網(wǎng)絡(luò)插件在解決 Kubernetes?某些?需求時(shí)要遵循的一套規(guī)則。
然而,它不僅僅與 Kubernetes 或者特定網(wǎng)絡(luò)插件關(guān)聯(lián)。
可以使用如下 CNI:
Calico[14]
Cilium[15]
Flannel[16]
Weave Net[17]
其他網(wǎng)絡(luò)插件[18]
他們都實(shí)現(xiàn)相同的 CNI 標(biāo)準(zhǔn)。
如果沒(méi)有 CNI,你需要手動(dòng)完成如下操作:
創(chuàng)建 pod(容器)的網(wǎng)絡(luò)命名空間
創(chuàng)建接口
創(chuàng)建 veth 對(duì)
設(shè)置命名空間網(wǎng)絡(luò)
設(shè)置靜態(tài)路由
配置以太網(wǎng)橋接器
分配 IP 地址
創(chuàng)建 NAT 規(guī)則
還有太多其他需要手動(dòng)完成的工作。
更不用說(shuō)刪除或重新啟動(dòng) pod 時(shí)刪除或調(diào)整上述所有內(nèi)容了。
CNI 必須支持四個(gè)不同的操作[19]:
ADD?- 將容器添加到網(wǎng)絡(luò)
DEL?- 從網(wǎng)絡(luò)中刪除容器
CHECK?- 如果容器的網(wǎng)絡(luò)出現(xiàn)問(wèn)題,則返回錯(cuò)誤
VERSION?- 顯示插件的版本
讓我們?cè)趯?shí)踐中看看它是如何工作的。
當(dāng) pod 分配到特定節(jié)點(diǎn)時(shí),kubelet 本身不會(huì)初始化網(wǎng)絡(luò)。
相反,它將任務(wù)交給了 CNI。
然后,它指定了配置,并以 JSON 格式將其發(fā)送給 CNI 插件。
可以在節(jié)點(diǎn)的?/etc/cni/net.d?目錄中,找到當(dāng)前 CNI 的配置文件:
$?cat?10-calico.conflist {"name":?"k8s-pod-network","cniVersion":?"0.3.1","plugins":?[{"type":?"calico","datastore_type":?"kubernetes","mtu":?0,"nodename_file_optional":?false,"log_level":?"Info","log_file_path":?"/var/log/calico/cni/cni.log","ipam":?{?"type":?"calico-ipam",?"assign_ipv4"?:?"true",?"assign_ipv6"?:?"false"},"container_settings":?{"allow_ip_forwarding":?false},"policy":?{"type":?"k8s"},"kubernetes":?{"k8s_api_root":"https://10.96.0.1:443","kubeconfig":?"/etc/cni/net.d/calico-kubeconfig"}},{"type":?"bandwidth","capabilities":?{"bandwidth":?true}},{"type":?"portmap",?"snat":?true,?"capabilities":?{"portMappings":?true}}] }每個(gè)插件都使用不同類型的配置來(lái)設(shè)置網(wǎng)絡(luò)。
例如,Calico 使用 BGP 路由協(xié)議配對(duì)的第 3 層網(wǎng)絡(luò)來(lái)連接 pod。
Cilium 在第 3 到 7 層使用 eBPF 配置覆蓋網(wǎng)絡(luò)。
與 Calico 一起,Cilium 支持設(shè)置網(wǎng)絡(luò)策略來(lái)限制流量。
那該如何選擇呢?
這取決于。
CNI 主要有兩組。
第一組中,可以找到使用基本網(wǎng)絡(luò)設(shè)置(也稱為扁平網(wǎng)絡(luò))的CNI,并將集群 IP 池 中的IP 地址分配給 pod。
這可能會(huì)因?yàn)榭焖儆帽M可用的 IP 地址而成為負(fù)擔(dān)。
相反,另一種方法是使用覆蓋網(wǎng)絡(luò)。
簡(jiǎn)而言之,覆蓋網(wǎng)絡(luò)是主(底層)網(wǎng)絡(luò)之上的輔助網(wǎng)絡(luò)。
覆蓋網(wǎng)絡(luò)的工作原理是封裝來(lái)自底層網(wǎng)絡(luò)的所有數(shù)據(jù)包,這些數(shù)據(jù)包指向另一個(gè)節(jié)點(diǎn)上的 pod。
覆蓋網(wǎng)絡(luò)的一項(xiàng)流行技術(shù)是?VXLAN[20],它允許在 L3 網(wǎng)絡(luò)上隧道傳輸 L2 域。
那么哪種更好?
沒(méi)有唯一的答案,通常取決于你的需求。
你是在構(gòu)建一個(gè)擁有數(shù)萬(wàn)個(gè)節(jié)點(diǎn)的大集群?jiǎn)?#xff1f;
可能覆蓋網(wǎng)絡(luò)更好。
你是否在意更簡(jiǎn)單的設(shè)置和在嵌套網(wǎng)絡(luò)中不失去檢查網(wǎng)絡(luò)流量的能力。
扁平網(wǎng)絡(luò)更適合你。
現(xiàn)在已經(jīng)討論了 CNI,讓我們繼續(xù)探索 Pod 到服務(wù)(service)的通信是如何完成的。
檢查 pod 到服務(wù)的流量
由于 Kubernetes 環(huán)境下 pod 的動(dòng)態(tài)特性,分配給 pod 的 IP 地址不是靜態(tài)的。
這些 IP 地址是短暫的,每次創(chuàng)建或者刪除 pod 時(shí)都會(huì)發(fā)生變化。
服務(wù)解決了這個(gè)問(wèn)題,為連接到一組 pod 提供了穩(wěn)定的機(jī)制。
默認(rèn)情況下,在 Kubernetes 中創(chuàng)建服務(wù)時(shí),會(huì)為其預(yù)定并分配虛擬 IP?[21]。
使用選擇器將服務(wù)于目標(biāo) pod 進(jìn)行管理。
當(dāng)刪除 pod 并添加新 pod 時(shí)會(huì)發(fā)生什么?
該服務(wù)的虛擬 IP 保持不變。
然而,無(wú)需敢于,流量將到達(dá)新創(chuàng)建的 pod。
換句話說(shuō),Kubernetes 中的服務(wù)類似于負(fù)載均衡器。
但他們時(shí)如何工作的?
使用 Netfilter 和 Iptables 攔截和重寫(xiě)流量
Kubernetes 中的服務(wù)基于兩個(gè) Linux 內(nèi)核組件:
Netfilter
Iptables
Netfilter 是一個(gè)框架,允許配置數(shù)據(jù)包過(guò)濾、創(chuàng)建 NAT或端口翻譯規(guī)則,并管理網(wǎng)絡(luò)中的流量。
此外,它還屏蔽和阻止不請(qǐng)自來(lái)的連接訪問(wèn)服務(wù)。
另一方面,Iptables 是一個(gè)用戶空間程序,允許你配置 Linux 內(nèi)核防火墻的 IP 數(shù)據(jù)包過(guò)濾器規(guī)則。
iptables 使用不同的 Netfilter 模塊實(shí)現(xiàn)。
你可以使用 iptables CLI 實(shí)時(shí)更改過(guò)濾規(guī)則,并將其插入 netfilters 的掛點(diǎn)。
過(guò)濾器組織在不同的表中,其中包含處理網(wǎng)絡(luò)流量數(shù)據(jù)包的鏈。
每個(gè)協(xié)議都使用不同的內(nèi)核模塊和程序。
當(dāng)提到 iptables 時(shí),通常說(shuō)的是 IPV4。對(duì)于 IPV6 的規(guī)則,CLI 是 ip6tables。
Iptables 有五種類型的鏈,每種鏈都直接映射到 Netfilter 鉤子。
從 iptables 角度看是:
PRE_ROUTING
INPUT
FORWARD
OUTPUT
POST_ROUTING
對(duì)應(yīng)映射到 Netfilter 鉤子:
NF_IP_PRE_ROUTING
NF_IP_LOCAL_IN
NF_IP_FORWARD
NF_IP_LOCAL_OUT
NF_IP_POST_ROUTING
當(dāng)數(shù)據(jù)包到達(dá)時(shí),根據(jù)所處的階段,會(huì)“出發(fā)” Netfilter 鉤子,該鉤子應(yīng)用特定的 iptables 過(guò)濾。
哎呀,看起來(lái)很復(fù)雜!
不過(guò)不需要擔(dān)心。
這就是為什么我們使用 Kubernetes,上面的所有內(nèi)容都是通過(guò)使用服務(wù)來(lái)抽象的,一個(gè)簡(jiǎn)單的 YAML 定義就可以自動(dòng)完成這些規(guī)則的設(shè)置。
如果對(duì)這些 iptables 規(guī)則感興趣,可以登陸到節(jié)點(diǎn)并運(yùn)行:
iptables-save也可以使用可視化工具[22]瀏覽節(jié)點(diǎn)上的 iptables 鏈。
記住,可能會(huì)有數(shù)百條規(guī)則。想象下手動(dòng)創(chuàng)建的難度。
我們已經(jīng)解釋了相同和不同節(jié)點(diǎn)上的 pod 間如何通信。
在 Pod-to-Service 中,通信的前半部分保持不變。
當(dāng) Pod-A 發(fā)出請(qǐng)求時(shí),希望到達(dá) Pod-B(這種情況下,Pod-B 位與服務(wù)之后),轉(zhuǎn)移的過(guò)程中會(huì)發(fā)生其他變化。
原始請(qǐng)求從 Pod-A 命名空間中的?eth0?接口出來(lái)。
從那里穿過(guò)?veth?對(duì),到達(dá)根命名空間的以太網(wǎng)橋。
一旦到達(dá)橋接器,數(shù)據(jù)包立即通過(guò)默認(rèn)網(wǎng)關(guān)轉(zhuǎn)發(fā)。
與 Pod-to-Pod 部分一樣,主機(jī)進(jìn)行位比較,由于服務(wù)的 vIP 不是節(jié)點(diǎn) CIDR 的一部分,數(shù)據(jù)包將立即通過(guò)默認(rèn)網(wǎng)關(guān)轉(zhuǎn)發(fā)出去。
如果查找表中尚沒(méi)有默認(rèn)網(wǎng)關(guān)的 MAC 地址,則會(huì)進(jìn)行相同的 ARP 解析。
現(xiàn)在魔法發(fā)生了。
在數(shù)據(jù)包經(jīng)過(guò)節(jié)點(diǎn)的路由處理之前,Netfilter 鉤子?NF_IP_PRE_ROUTING?被觸發(fā)并應(yīng)用一條 iptables 規(guī)則。規(guī)則進(jìn)行了 DNAT 轉(zhuǎn)換,重寫(xiě)了 POD-A 數(shù)據(jù)包的目標(biāo) IP 地址。
原來(lái)服務(wù) vIP 地址被重寫(xiě)稱 POD-B 的IP 地址。
從那里,路由就像 Pod-to-Pod 直接通信一樣。
然而,在所有這些通信之間,使用了第三個(gè)功能。
這個(gè)功能被稱為 conntrack[23],或連接跟蹤。
Conntrack 將數(shù)據(jù)包與連接關(guān)聯(lián)起來(lái),并在 Pod-B 發(fā)送回響應(yīng)時(shí)跟蹤其來(lái)源。
NAT 嚴(yán)重依賴 contrack 工作。
如果沒(méi)有連接跟蹤,它將不知道將包含響應(yīng)的數(shù)據(jù)包發(fā)送回哪里。
使用 conntrack 時(shí),數(shù)據(jù)包的返回路徑可以輕松設(shè)置相同的源或目標(biāo) NAT 更改。
另一半使用相反的順序執(zhí)行。
Pod-B 接收并處理了請(qǐng)求,現(xiàn)在將數(shù)據(jù)發(fā)送回 Pod-A。
此時(shí)會(huì)發(fā)生什么?
檢查服務(wù)的響應(yīng)
現(xiàn)在 Pod-B 發(fā)送響應(yīng),將其 IP 地址設(shè)置為源地址,Pod-A IP 地址設(shè)置為目標(biāo)地址。
當(dāng)數(shù)據(jù)包到達(dá) Pod-A 所在節(jié)點(diǎn)的接口時(shí),就會(huì)發(fā)生另一個(gè) NAT
這次,使用 conntrack 更改源 IP 地址,iptables 規(guī)則執(zhí)行 SNAT 將 Pod-B IP 地址替換為原始服務(wù)的 VIP 地址。
從 Pod-A 來(lái)看像是服務(wù)發(fā)回的響應(yīng),而不是 Pod-B。
其他部分都一樣;一旦 SNAT 完成,數(shù)據(jù)包到達(dá)根命名空間中的以太網(wǎng)橋接器,并通過(guò) veth 對(duì)轉(zhuǎn)發(fā)到 Pod-A。
回顧
讓我們來(lái)總結(jié)下你在本文中學(xué)到的東西:
容器如何在本地或 pod 內(nèi)通信。
當(dāng) pod 位于相同和不同的節(jié)點(diǎn)上時(shí),Pod-to- Pod 如何通信。
Pod-to-Service - 當(dāng) pod 向 Kubernetes 服務(wù)背后的 pod 發(fā)送流量時(shí)。
Kubernetes 網(wǎng)絡(luò)工具箱中有效通信所需的命名空間、veth、iptables、鏈、Netfilter、CNI、覆蓋網(wǎng)絡(luò)以及所有其他內(nèi)容。
往期推薦
好難啊……一個(gè) try-catch 問(wèn)出這么多花樣
k8s集群居然可以圖形化安裝了?
惡意流量威脅新趨勢(shì),揭秘網(wǎng)絡(luò)黑產(chǎn)3大核心本質(zhì)
將 k8s 制作成 3D 射擊游戲,好玩到停不下來(lái)
點(diǎn)分享
點(diǎn)收藏
點(diǎn)點(diǎn)贊
點(diǎn)在看
總結(jié)
以上是生活随笔為你收集整理的追踪 Kubernetes 中的网络流量的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 产品经理教你玩转阿里云负载均衡SLB系列
- 下一篇: IoT日志利器:嵌入式日志客户端(C P