追踪 Kubernetes 中的网络流量
作者 | Addo Zhang
來源 |?云原生指北
譯者注:
這篇文章很全面的羅列出了 Kubernetes 中涉及的網(wǎng)絡知識,從 Linux 內(nèi)核的網(wǎng)絡內(nèi)容,到容器、Kubernetes,一一進行了詳細的說明。
文章篇幅有點長,不得不說,網(wǎng)絡是很復雜很麻煩的一層,但恰恰這層多年來變化不大。希望翻譯的內(nèi)容對大家能有所幫助,有誤的地方,也歡迎大家指正。
本文翻譯獲得?Learnk8s 的授權,原文?Tracing the path of network traffic in Kubernetes[1]?作者?Kristijan Mitevski。
TL;DR:?本文將代理了解 Kubernetes 集群內(nèi)外的數(shù)據(jù)流轉。從最初的 Web 請求開始,一直到托管應用程序的容器。
目錄
Kubernetes 網(wǎng)絡要求
Linux 網(wǎng)絡命名空間如果在 pod 中工作
Pause 容器創(chuàng)建 Pod 中的網(wǎng)絡命名空間
為 Pod 分配了 IP 地址
檢查集群中 pod 到 pod 的流量
Pod 命名空間連接到以太網(wǎng)橋接器
跟蹤同一節(jié)點上 pod 間的流量
跟蹤不同節(jié)點上 pod 間的通信
位運算的工作原理
容器網(wǎng)絡接口 - CNI
檢查 pod 到服務的流量
使用 Netfilter 和 Iptables 攔截和重寫流量
檢查服務的響應
回顧
Kubernetes 網(wǎng)絡要求
在深入了解 Kubernetes 中的數(shù)據(jù)流轉之前,讓我們先澄清下 Kubernetes 網(wǎng)絡的要求。
Kubernetes 網(wǎng)絡模型定義了一套基本規(guī)則:
集群中的 pod 應該能夠與任何其他 pod 自由通信,而無需使用網(wǎng)絡地址轉換(NAT)。
在不使用 NAT 的情況下,集群節(jié)點上運行的任意程序都應該能夠與同一節(jié)點上的任意 pod 通信。
每個 pod 都有自己的 IP 地址(IP-per Pod),其他 pod 都可以使用同一個地址進行訪問。
這些要求不會將實現(xiàn)限制在單一方案上。
相反,他們概括了集群網(wǎng)絡的特性。
在滿足這些限制時,必須解決如下挑戰(zhàn)[2]:
如何保證同一 pod 中的容器間的訪問就像在同一主機上一樣?
Pod 能否訪問集群中的其他 pod?
Pod 能否訪問服務(service)?以及服務可以負載均衡請求嗎?
Pod 可以接收來自集群外的流量嗎?
本文將專注于前三點,從 pod 內(nèi)部網(wǎng)絡或者容器間的通信說起。
Linux 網(wǎng)絡命名空間如果在 pod 中工作
我們想象下,有一個承載應用程序的主容器和另一個與它一起運行的容器。
在示例 Pod 中有一個 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在部署時,會出現(xiàn)如下情況:
Pod 在節(jié)點上得到自己的網(wǎng)絡命名空間。
Pod?分配到一個 IP 地址,兩個容器間共享端口。
兩個容器共享同一個網(wǎng)絡命名空間,在本地互相可見。
網(wǎng)絡配置在后臺很快完成。
然后,我們退后一步,是這理解為什么上面是容器運行所必須的。
在 Linux 中,網(wǎng)絡命名空間是獨立的、隔離的邏輯空間。[3]
可以將網(wǎng)絡命名空間堪稱將物理網(wǎng)絡接口分割成更小的獨立部分。
每部分都可以單獨配置,并使用自己的網(wǎng)絡規(guī)則和資源。
這些可以包括防火墻規(guī)則、接口(虛擬或物理)、路由和其他所有與網(wǎng)絡相關的內(nèi)容。
物理接口持有根命名空間。
可以使用 Linux 網(wǎng)絡命名空間創(chuàng)建隔離的網(wǎng)絡。每個網(wǎng)絡都是獨立的,除非進行配置否則不會與其他命名空間通信。
物理接口必須處理最后的所有真實數(shù)據(jù)包,因此所有的虛擬接口都是從中創(chuàng)建的。
網(wǎng)絡命名空間可以通過?`ip-netns` 管理工具[4]?來管理,可以使用?ip netns list?列出主機上的命名空間。
請注意,創(chuàng)建的網(wǎng)絡命名空間將會出現(xiàn)在?/var/run/netns?目錄下,但?Docker 并沒有遵循這一點[5]。
例如,下面是 Kubernetes 節(jié)點的命名空間:
$?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 來完成。
當創(chuàng)建 pod 并分配給節(jié)點時,CNI[6]?會:
為其創(chuàng)建網(wǎng)絡命名空間。
分配 IP 地址。
將容器連接到網(wǎng)絡。
如果 pod 像上面的示例一樣包含多個容器,則所有容器都被置于同一個命名空間中。
創(chuàng)建 pod 時,CNI 為容器創(chuàng)建網(wǎng)絡命名空間
然后分配 IP 地址
最后將容器連接到網(wǎng)絡的其余部分
那么當列出節(jié)點上的容器時會看到什么?
可以 SSH 到 Kubernetes 節(jié)點來查看命名空間:
$?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?命令會列出主機上所有的命名空間。
記住 Linux 中有多種命名空間類型[7]。
Nginx 容器在哪?
那么?pause?容器又是什么?
Pause 容器創(chuàng)建 Pod 中的網(wǎng)絡命名空間
從節(jié)點上的所有進程中找出 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 分時系統(tǒng)(Unix time-sharing?uts)和 PID(pid)命名空間中,但是并不在網(wǎng)絡命名空間(net)中。
不幸的是,lsns?只顯示了每個進程最低的 PID,不過可以根據(jù)進程 ID 進一步過濾。
可以通過以下內(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?進程再次出現(xiàn),這次它劫持了網(wǎng)絡命名空間。
那是什么?
集群中的每個 pod 都有一個在后臺運行的隱藏容器,被稱為?pause。
列出節(jié)點上的所有容器并過濾出 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…將看到對于節(jié)點分配到的每個 pod,都有一個匹配的?pause?容器。
該?pause?容器負責創(chuàng)建和維持網(wǎng)絡命名空間。
它包含的代碼極少,部署后立即進入睡眠狀態(tài)。
然而,它在 Kubernetes 生態(tài)中的首當其沖,發(fā)揮著至關重要的作用。[8]。
創(chuàng)建 pod 時,CNI 會創(chuàng)建一個帶有睡眠容器的網(wǎng)絡命名空間
Pod 中的所有容器都會加入到它創(chuàng)建的網(wǎng)絡命名空間中
此時 CNI 分配 IP 地址并將容器連接到網(wǎng)絡
進入睡眠狀態(tài)的容器有什么用?
要了解它的實用性,我們可以想象下如示例一樣有兩個容器的 pod,但沒有?pause?容器。
容器啟動,CNI:
為 Nginx 容器創(chuàng)建一個網(wǎng)絡命名空間。
把 busybox 容器加入到前面創(chuàng)建的網(wǎng)絡命名空間中。
為 pod 分配 IP 地址。
將容器連接到網(wǎng)絡。
假如 Nginx 容器崩潰了會發(fā)生什么?
CNI 將不得不再次完成所有流程,兩個容器的網(wǎng)絡都會中斷。
由于?sleep?容器不太可能有任何 bug,因此創(chuàng)建網(wǎng)絡命名空間通常是一個更保險、更健壯的選擇。
如果 pod 中的一個容器崩潰,其余的仍可以處理網(wǎng)絡請求。
為 Pod 分配了 IP 地址
前面提到 pod 和所有容器獲得了同樣的 IP。
這是怎么配置的?
在 pod 網(wǎng)絡命名空間中,創(chuàng)建一個接口并分配 IP 地址。
我們來驗證下。
首先,找到 pod 的 IP 地址:
$?kubectl?get?pod?multi-container-pod?-o?jsonpath={.status.podIP} 10.244.4.40接下來,找到相關的網(wǎng)絡命名空間。
由于網(wǎng)絡命名空間是從物理接口創(chuàng)建的,需要訪問集群節(jié)點。
如果你運行的是 minikube,可以通過?minikube ssh?訪問節(jié)點。如果在云提供商中運行,應該有某種方法通過 SSH 訪問節(jié)點。
進入后,可以找到創(chuàng)建的最新的網(wǎng)絡命名空間:
$?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。此時,可以在該命名空間總執(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 地址。
通過查找?@if12?中的?12?找到網(wǎng)絡接口。
$?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還可以驗證 Nginx 容器是否從該命名空間中監(jiān)聽 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如果無法通過 SSH 訪問集群的節(jié)點,可以試試?kubectl exec?進入到 busybox 容器,然后使用?ip?和?netstat?命令。
太棒了!
現(xiàn)在我們已經(jīng)介紹了容器間的通信,接下來看看 Pod 與 Pod 直接如何建立通信。
檢查集群中 pod 到 pod 的流量
當說起 pod 間通信時,會有兩種可能:
Pod 流量流向同一節(jié)點上的 pod。
Pod 流量流量另一個節(jié)點上的 pod。
為了使整個設置正常工作,我們需要之前討論過的虛擬接口和以太網(wǎng)橋接。
在繼續(xù)之前,我們先討論下他們的功能以及為什么他們時必需的。
要完成 pod 與其他 pod 的通信,它必須先訪問節(jié)點的根命名空間。
這是使用連接 pod 和根命名空間的虛擬以太網(wǎng)對來實現(xiàn)的。
這些虛擬接口設備[9](veth?中的?v)連接并充當兩個命名空間間的隧道。
使用此?veth?設備,將一端連接到 pod 的命名空間,另一端連接到根命名空間。
這些 CNI 可以替你做,也可以手動操作:
$?ip?link?add?veth1?netns?pod-namespace?type?veth?peer?veth2?netns?root現(xiàn)在 pod 的命名空間有了可以訪問根命名空間的隧道。
節(jié)點上每個新建的 pod 都會設置如下所示的 veth 對。
創(chuàng)建接口對時其中一部分。
其他的就是為以太網(wǎng)設備分配地址,并創(chuàng)建默認路由。
來看下如何在 pod 的命名空間中設置?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é)點側,我們創(chuàng)建另一個?veth2?對:
$?ip?addr?add?169.254.132.141/16?dev?veth2 $?ip?link?set?veth2?up可以像以前一樣檢查現(xiàn)有的?veth?對。
在 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?進行查找(或者滾動到目標所在):
$?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?接口上的符號。
在 pod 命名空間中,eth0?接口連接到根命名空間中編號為?12?的接口。因此是?@if12。
在?veth?對的另一端,根命名空間連接到 pod 命名空間的?3?號接口。
接下來是連接?veth?對兩端的橋接器(bridge)。
Pod 命名空間連接到以太網(wǎng)橋接器
橋接器將位于根命名空間中的虛擬接口的每一端“綁定”。
該橋接器將允許流量在虛擬對之間流動,并通過公共根命名空間。
理論時間。
以太網(wǎng)橋接器位于OSI 網(wǎng)絡模型[10]的第二層。
可以將橋接器看作一個虛擬交換機,接受來自不同命名空間和接口的連接。[11]
以太網(wǎng)橋接器允許連接同一個節(jié)點上的多個可用網(wǎng)絡。
因此,可以使用該設置連接兩個接口:從 pod 命名空間的?veth?連接到同一節(jié)點上另一個 pod 的?veth。
我們繼續(xù)看下以太網(wǎng)橋接器和 veth 對的作用。
跟蹤同一節(jié)點上 pod 間的流量
假設同一個節(jié)點上有兩個 pod,Pod-A 想向 Pod-B 發(fā)送消息。
由于目標不是同命名空間的容器,Pod-A 向其默認接口?eth0?發(fā)送數(shù)據(jù)包。這個接口與?veth?對的一端綁定,作為隧道。因此數(shù)據(jù)包將被轉發(fā)到節(jié)點的根命名空間。
以太網(wǎng)橋接器作為虛擬交換機,必須以某種方式將目標 pod IP(Pod-B)解析為其 MAC 地址。
輪到ARP 協(xié)議上場了。當幀到達橋接器時,會向所有連接的設備發(fā)送 ARP 廣播。橋接器喊道誰有 Pod-B 的 IP 地址。
收到帶有連接 Pod-B 接口的 MAC 地址的回復,然后此信息存儲在橋接器 ARP 緩存(查找表)中。
IP 和 MAC 地址的映射存儲完成后,橋接器在表中查找,并將數(shù)據(jù)包轉發(fā)到正確的短點。數(shù)據(jù)包到達根命名空間中 Pod- B 的?veth,然后從那快速到達 Pod-B 命名空間內(nèi)的?eth0?接口。
有了這個,Pod-A 和 Pod-B 之間的通信取得了成功。
跟蹤不同節(jié)點上 pod 間的通信
對于需要跨不同節(jié)點通信的 pod,需要額外的通信跳轉。
前幾個步驟保持不變,直到數(shù)據(jù)包到達根命名空間并需要發(fā)送到 Pod- B。
當目標地址不在本地網(wǎng)絡中,數(shù)據(jù)包將被轉發(fā)到本節(jié)點的默認網(wǎng)關。節(jié)點上退出或默認網(wǎng)關通常位于?eth0?接口上 -- 將節(jié)點連接到網(wǎng)絡的物理接口。
這次并不會發(fā)生 ARP 解析,因為源和目標 IP 在不同網(wǎng)絡上。
檢查使用位運算(Bitwise)操作完成。
當目標 IP 不在當前網(wǎng)絡上時,數(shù)據(jù)包將被轉發(fā)到節(jié)點的默認網(wǎng)關。
位運算的工作原理
在確定數(shù)據(jù)包的轉發(fā)位置時,源節(jié)點必須執(zhí)行位運算。
這也被稱為與操作。[12]
作為復習,位與操作產(chǎn)生如下結果:
0?AND?0?=?0 0?AND?1?=?0 1?AND?0?=?0 1?AND?1?=?1除了 1 與 1 以外的都是 false。
如果源節(jié)點的 IP 為 192.168.1.1,子網(wǎng)掩碼為 /24,目標 IP 為 172.16.1.1/16,則按位與操作將確認他們不在同一網(wǎng)絡上。
這意味著目標 IP 與數(shù)據(jù)包的源在不同的網(wǎng)絡上,因此數(shù)據(jù)包將在默認網(wǎng)關中轉發(fā)。
數(shù)學時間。
我們必須從二進制文件中的 32 位地址執(zhí)行與操作開始。
先找出源和目標 IP 的網(wǎng)絡。
|?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?????????|位運算操作后,需要將目標 IP 與數(shù)據(jù)包源節(jié)點的子網(wǎng)進行比較。
|?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進行位比較后,ARP 會檢查其查詢表來查找默認網(wǎng)關的 MAC 地址。
如果有條目,將立即轉發(fā)數(shù)據(jù)包。
否則,先進行廣播以確定網(wǎng)關的 MAC 地址。
數(shù)據(jù)包現(xiàn)在路由到另一個節(jié)點的默認接口,我們叫它 Node-B。
以相反的順序。數(shù)據(jù)包現(xiàn)在位與 Node-B 的根命名空間,并到達橋接器,這里會進行 ARP 解析。
收到帶有連接 Pod-B 的接口 MAC地址的回復。
這次橋接器通過 Pod-B 的?veth?設備將幀轉發(fā),并到達 Pod-B 自己的命名空間。
此時應該已經(jīng)熟悉了 pod 之間的流量如何流轉,接下來再探索下 CNI 如何創(chuàng)建上述內(nèi)容。
容器網(wǎng)絡接口 - CNI
容器網(wǎng)絡接口(CNI)關注當前節(jié)點的網(wǎng)絡。[13]
可以將 CNI 看作網(wǎng)絡插件在解決 Kubernetes?某些?需求時要遵循的一套規(guī)則。
然而,它不僅僅與 Kubernetes 或者特定網(wǎng)絡插件關聯(lián)。
可以使用如下 CNI:
Calico[14]
Cilium[15]
Flannel[16]
Weave Net[17]
其他網(wǎng)絡插件[18]
他們都實現(xiàn)相同的 CNI 標準。
如果沒有 CNI,你需要手動完成如下操作:
創(chuàng)建 pod(容器)的網(wǎng)絡命名空間
創(chuàng)建接口
創(chuàng)建 veth 對
設置命名空間網(wǎng)絡
設置靜態(tài)路由
配置以太網(wǎng)橋接器
分配 IP 地址
創(chuàng)建 NAT 規(guī)則
還有太多其他需要手動完成的工作。
更不用說刪除或重新啟動 pod 時刪除或調(diào)整上述所有內(nèi)容了。
CNI 必須支持四個不同的操作[19]:
ADD?- 將容器添加到網(wǎng)絡
DEL?- 從網(wǎng)絡中刪除容器
CHECK?- 如果容器的網(wǎng)絡出現(xiàn)問題,則返回錯誤
VERSION?- 顯示插件的版本
讓我們在實踐中看看它是如何工作的。
當 pod 分配到特定節(jié)點時,kubelet 本身不會初始化網(wǎng)絡。
相反,它將任務交給了 CNI。
然后,它指定了配置,并以 JSON 格式將其發(fā)送給 CNI 插件。
可以在節(jié)點的?/etc/cni/net.d?目錄中,找到當前 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}}] }每個插件都使用不同類型的配置來設置網(wǎng)絡。
例如,Calico 使用 BGP 路由協(xié)議配對的第 3 層網(wǎng)絡來連接 pod。
Cilium 在第 3 到 7 層使用 eBPF 配置覆蓋網(wǎng)絡。
與 Calico 一起,Cilium 支持設置網(wǎng)絡策略來限制流量。
那該如何選擇呢?
這取決于。
CNI 主要有兩組。
第一組中,可以找到使用基本網(wǎng)絡設置(也稱為扁平網(wǎng)絡)的CNI,并將集群 IP 池 中的IP 地址分配給 pod。
這可能會因為快速用盡可用的 IP 地址而成為負擔。
相反,另一種方法是使用覆蓋網(wǎng)絡。
簡而言之,覆蓋網(wǎng)絡是主(底層)網(wǎng)絡之上的輔助網(wǎng)絡。
覆蓋網(wǎng)絡的工作原理是封裝來自底層網(wǎng)絡的所有數(shù)據(jù)包,這些數(shù)據(jù)包指向另一個節(jié)點上的 pod。
覆蓋網(wǎng)絡的一項流行技術是?VXLAN[20],它允許在 L3 網(wǎng)絡上隧道傳輸 L2 域。
那么哪種更好?
沒有唯一的答案,通常取決于你的需求。
你是在構建一個擁有數(shù)萬個節(jié)點的大集群嗎?
可能覆蓋網(wǎng)絡更好。
你是否在意更簡單的設置和在嵌套網(wǎng)絡中不失去檢查網(wǎng)絡流量的能力。
扁平網(wǎng)絡更適合你。
現(xiàn)在已經(jīng)討論了 CNI,讓我們繼續(xù)探索 Pod 到服務(service)的通信是如何完成的。
檢查 pod 到服務的流量
由于 Kubernetes 環(huán)境下 pod 的動態(tài)特性,分配給 pod 的 IP 地址不是靜態(tài)的。
這些 IP 地址是短暫的,每次創(chuàng)建或者刪除 pod 時都會發(fā)生變化。
服務解決了這個問題,為連接到一組 pod 提供了穩(wěn)定的機制。
默認情況下,在 Kubernetes 中創(chuàng)建服務時,會為其預定并分配虛擬 IP?[21]。
使用選擇器將服務于目標 pod 進行管理。
當刪除 pod 并添加新 pod 時會發(fā)生什么?
該服務的虛擬 IP 保持不變。
然而,無需敢于,流量將到達新創(chuàng)建的 pod。
換句話說,Kubernetes 中的服務類似于負載均衡器。
但他們時如何工作的?
使用 Netfilter 和 Iptables 攔截和重寫流量
Kubernetes 中的服務基于兩個 Linux 內(nèi)核組件:
Netfilter
Iptables
Netfilter 是一個框架,允許配置數(shù)據(jù)包過濾、創(chuàng)建 NAT或端口翻譯規(guī)則,并管理網(wǎng)絡中的流量。
此外,它還屏蔽和阻止不請自來的連接訪問服務。
另一方面,Iptables 是一個用戶空間程序,允許你配置 Linux 內(nèi)核防火墻的 IP 數(shù)據(jù)包過濾器規(guī)則。
iptables 使用不同的 Netfilter 模塊實現(xiàn)。
你可以使用 iptables CLI 實時更改過濾規(guī)則,并將其插入 netfilters 的掛點。
過濾器組織在不同的表中,其中包含處理網(wǎng)絡流量數(shù)據(jù)包的鏈。
每個協(xié)議都使用不同的內(nèi)核模塊和程序。
當提到 iptables 時,通常說的是 IPV4。對于 IPV6 的規(guī)則,CLI 是 ip6tables。
Iptables 有五種類型的鏈,每種鏈都直接映射到 Netfilter 鉤子。
從 iptables 角度看是:
PRE_ROUTING
INPUT
FORWARD
OUTPUT
POST_ROUTING
對應映射到 Netfilter 鉤子:
NF_IP_PRE_ROUTING
NF_IP_LOCAL_IN
NF_IP_FORWARD
NF_IP_LOCAL_OUT
NF_IP_POST_ROUTING
當數(shù)據(jù)包到達時,根據(jù)所處的階段,會“出發(fā)” Netfilter 鉤子,該鉤子應用特定的 iptables 過濾。
哎呀,看起來很復雜!
不過不需要擔心。
這就是為什么我們使用 Kubernetes,上面的所有內(nèi)容都是通過使用服務來抽象的,一個簡單的 YAML 定義就可以自動完成這些規(guī)則的設置。
如果對這些 iptables 規(guī)則感興趣,可以登陸到節(jié)點并運行:
iptables-save也可以使用可視化工具[22]瀏覽節(jié)點上的 iptables 鏈。
記住,可能會有數(shù)百條規(guī)則。想象下手動創(chuàng)建的難度。
我們已經(jīng)解釋了相同和不同節(jié)點上的 pod 間如何通信。
在 Pod-to-Service 中,通信的前半部分保持不變。
當 Pod-A 發(fā)出請求時,希望到達 Pod-B(這種情況下,Pod-B 位與服務之后),轉移的過程中會發(fā)生其他變化。
原始請求從 Pod-A 命名空間中的?eth0?接口出來。
從那里穿過?veth?對,到達根命名空間的以太網(wǎng)橋。
一旦到達橋接器,數(shù)據(jù)包立即通過默認網(wǎng)關轉發(fā)。
與 Pod-to-Pod 部分一樣,主機進行位比較,由于服務的 vIP 不是節(jié)點 CIDR 的一部分,數(shù)據(jù)包將立即通過默認網(wǎng)關轉發(fā)出去。
如果查找表中尚沒有默認網(wǎng)關的 MAC 地址,則會進行相同的 ARP 解析。
現(xiàn)在魔法發(fā)生了。
在數(shù)據(jù)包經(jīng)過節(jié)點的路由處理之前,Netfilter 鉤子?NF_IP_PRE_ROUTING?被觸發(fā)并應用一條 iptables 規(guī)則。規(guī)則進行了 DNAT 轉換,重寫了 POD-A 數(shù)據(jù)包的目標 IP 地址。
原來服務 vIP 地址被重寫稱 POD-B 的IP 地址。
從那里,路由就像 Pod-to-Pod 直接通信一樣。
然而,在所有這些通信之間,使用了第三個功能。
這個功能被稱為 conntrack[23],或連接跟蹤。
Conntrack 將數(shù)據(jù)包與連接關聯(lián)起來,并在 Pod-B 發(fā)送回響應時跟蹤其來源。
NAT 嚴重依賴 contrack 工作。
如果沒有連接跟蹤,它將不知道將包含響應的數(shù)據(jù)包發(fā)送回哪里。
使用 conntrack 時,數(shù)據(jù)包的返回路徑可以輕松設置相同的源或目標 NAT 更改。
另一半使用相反的順序執(zhí)行。
Pod-B 接收并處理了請求,現(xiàn)在將數(shù)據(jù)發(fā)送回 Pod-A。
此時會發(fā)生什么?
檢查服務的響應
現(xiàn)在 Pod-B 發(fā)送響應,將其 IP 地址設置為源地址,Pod-A IP 地址設置為目標地址。
當數(shù)據(jù)包到達 Pod-A 所在節(jié)點的接口時,就會發(fā)生另一個 NAT
這次,使用 conntrack 更改源 IP 地址,iptables 規(guī)則執(zhí)行 SNAT 將 Pod-B IP 地址替換為原始服務的 VIP 地址。
從 Pod-A 來看像是服務發(fā)回的響應,而不是 Pod-B。
其他部分都一樣;一旦 SNAT 完成,數(shù)據(jù)包到達根命名空間中的以太網(wǎng)橋接器,并通過 veth 對轉發(fā)到 Pod-A。
回顧
讓我們來總結下你在本文中學到的東西:
容器如何在本地或 pod 內(nèi)通信。
當 pod 位于相同和不同的節(jié)點上時,Pod-to- Pod 如何通信。
Pod-to-Service - 當 pod 向 Kubernetes 服務背后的 pod 發(fā)送流量時。
Kubernetes 網(wǎng)絡工具箱中有效通信所需的命名空間、veth、iptables、鏈、Netfilter、CNI、覆蓋網(wǎng)絡以及所有其他內(nèi)容。
往期推薦
好難啊……一個 try-catch 問出這么多花樣
k8s集群居然可以圖形化安裝了?
惡意流量威脅新趨勢,揭秘網(wǎng)絡黑產(chǎn)3大核心本質
將 k8s 制作成 3D 射擊游戲,好玩到停不下來
點分享
點收藏
點點贊
點在看
總結
以上是生活随笔為你收集整理的追踪 Kubernetes 中的网络流量的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 产品经理教你玩转阿里云负载均衡SLB系列
- 下一篇: IoT日志利器:嵌入式日志客户端(C P