譯者注:
這篇文章很全面的羅列出了 Kubernetes 中涉及的網絡知識,從 Linux 內核的網絡內容,到容器、Kubernetes,一一進行了詳細的說明。
文章篇幅有點長,不得不說,網絡是很復雜很麻煩的一層,但恰恰這層多年來變化不大。希望翻譯的內容對大家能有所幫助,有誤的地方,也歡迎大家指正。
本文翻譯獲得 Learnk8s 的授權,原文 Tracing the path of network traffic in Kubernetes 作者 Kristijan Mitevski。
TL;DR: 本文將代理了解 Kubernetes 集群內外的數據流轉。從最初的 Web 請求開始,一直到托管應用程序的容器。
目錄
- 目錄
- Kubernetes 網絡要求
- Linux 網絡命名空間如果在 pod 中工作
- Pause 容器創建 Pod 中的網絡命名空間
- 為 Pod 分配了 IP 地址
- 檢查集群中 pod 到 pod 的流量
- Pod 命名空間連接到以太網橋接器
- 跟蹤同一節點上 pod 間的流量
- 跟蹤不同節點上 pod 間的通信
- 位運算的工作原理
- 容器網絡接口 - CNI
- 檢查 pod 到服務的流量
- 使用 Netfilter 和 Iptables 攔截和重寫流量
- 檢查服務的響應
- 回顧
Kubernetes 網絡要求
在深入了解 Kubernetes 中的數據流轉之前,讓我們先澄清下 Kubernetes 網絡的要求。
Kubernetes 網絡模型定義了一套基本規則:
- 集群中的 pod 應該能夠與任何其他 pod 自由通信,而無需使用網絡地址轉換(NAT)。
- 在不使用 NAT 的情況下,集群節點上運行的任意程序都應該能夠與同一節點上的任意 pod 通信。
- 每個 pod 都有自己的 IP 地址(IP-per Pod),其他 pod 都可以使用同一個地址進行訪問。
這些要求不會將實現限制在單一方案上。
相反,他們概括了集群網絡的特性。
在滿足這些限制時,必須解決如下挑戰:
- 如何保證同一 pod 中的容器間的訪問就像在同一主機上一樣?
- Pod 能否訪問集群中的其他 pod?
- Pod 能否訪問服務(service)?以及服務可以負載均衡請求嗎?
- Pod 可以接收來自集群外的流量嗎?
本文將專注於前三點,從 pod 內部網絡或者容器間的通信說起。
Linux 網絡命名空間如果在 pod 中工作
我們想象下,有一個承載應用程序的主容器和另一個與它一起運行的容器。
在示例 Pod 中有一個 Nginx 容器和 busybox 容器:
apiVersion: v1
kind: Pod
metadata:
name: multi-container-pod
spec:
containers:
- name: container-1
image: busybox
command: ['/bin/sh', '-c', 'sleep 1d']
- name: container-2
image: nginx
在部署時,會出現如下情況:
- Pod 在節點上得到自己的網絡命名空間。
- Pod 分配到一個 IP 地址,兩個容器間共享端口。
- 兩個容器共享同一個網絡命名空間,在本地互相可見。
網絡配置在后台很快完成。
然后,我們退后一步,是這理解為什么上面是容器運行所必須的。
在 Linux 中,網絡命名空間是獨立的、隔離的邏輯空間。
可以將網絡命名空間堪稱將物理網絡接口分割成更小的獨立部分。
每部分都可以單獨配置,並使用自己的網絡規則和資源。
這些可以包括防火牆規則、接口(虛擬或物理)、路由和其他所有與網絡相關的內容。
- 物理接口持有根命名空間。
- 可以使用 Linux 網絡命名空間創建隔離的網絡。每個網絡都是獨立的,除非進行配置否則不會與其他命名空間通信。
物理接口必須處理最后的所有真實數據包,因此所有的虛擬接口都是從中創建的。
網絡命名空間可以通過 ip-netns 管理工具 來管理,可以使用 ip netns list 列出主機上的命名空間。
請注意,創建的網絡命名空間將會出現在 /var/run/netns 目錄下,但 Docker 並沒有遵循這一點。
例如,下面是 Kubernetes 節點的命名空間:
$ 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- 前綴意味着命名空間的創建由 CNI 來完成。
當創建 pod 並分配給節點時,CNI 會:
-
為其創建網絡命名空間。
-
分配 IP 地址。
-
將容器連接到網絡。
如果 pod 像上面的示例一樣包含多個容器,則所有容器都被置於同一個命名空間中。 -
創建 pod 時,CNI 為容器創建網絡命名空間
-
然后分配 IP 地址
-
最后將容器連接到網絡的其余部分
那么當列出節點上的容器時會看到什么?
可以 SSH 到 Kubernetes 節點來查看命名空間:
$ lsns -t net
NS 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 /pause
lsns 命令會列出主機上所有的命名空間。
記住 Linux 中有多種命名空間類型。
Nginx 容器在哪?
那么 pause 容器又是什么?
Pause 容器創建 Pod 中的網絡命名空間
從節點上的所有進程中找出 Nginx 容器:
$ lsns
NS 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;
該容器出現在了掛在(mount mnt)、Unix 分時系統(Unix time-sharing uts)和 PID(pid)命名空間中,但是並不在網絡命名空間(net)中。
不幸的是,lsns 只顯示了每個進程最低的 PID,不過可以根據進程 ID 進一步過濾。
可以通過以下內容檢索Nginx 容器的所有命名空間:
$ sudo lsns -p 5777
NS 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;
ause 進程再次出現,這次它劫持了網絡命名空間。
那是什么?
集群中的每個 pod 都有一個在后台運行的隱藏容器,被稱為 pause。
列出節點上的所有容器並過濾出 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…
將看到對於節點分配到的每個 pod,都有一個匹配的 pause 容器。
該 pause 容器負責創建和維持網絡命名空間。
它包含的代碼極少,部署后立即進入睡眠狀態。
然而,它在 Kubernetes 生態中的首當其沖,發揮着至關重要的作用。。
- 創建 pod 時,CNI 會創建一個帶有睡眠容器的網絡命名空間
- Pod 中的所有容器都會加入到它創建的網絡命名空間中
- 此時 CNI 分配 IP 地址並將容器連接到網絡
進入睡眠狀態的容器有什么用?
要了解它的實用性,我們可以想象下如示例一樣有兩個容器的 pod,但沒有 pause 容器。
容器啟動,CNI:
- 為 Nginx 容器創建一個網絡命名空間。
- 把 busybox 容器加入到前面創建的網絡命名空間中。
- 為 pod 分配 IP 地址。
- 將容器連接到網絡。
假如 Nginx 容器崩潰了會發生什么?
CNI 將不得不再次完成所有流程,兩個容器的網絡都會中斷。
由於 sleep 容器不太可能有任何 bug,因此創建網絡命名空間通常是一個更保險、更健壯的選擇。
如果 pod 中的一個容器崩潰,其余的仍可以處理網絡請求.
為 Pod 分配了 IP 地址
前面提到 pod 和所有容器獲得了同樣的 IP。
這是怎么配置的?
在 pod 網絡命名空間中,創建一個接口並分配 IP 地址。
我們來驗證下。
首先,找到 pod 的 IP 地址:
$ kubectl get pod multi-container-pod -o jsonpath={.status.podIP}
10.244.4.40
接下來,找到相關的網絡命名空間。
由於網絡命名空間是從物理接口創建的,需要訪問集群節點。
如果你運行的是 minikube,可以通過 minikube ssh 訪問節點。如果在雲提供商中運行,應該有某種方法通過 SSH 訪問節點。
進入后,可以找到創建的最新的網絡命名空間:
$ 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。此時,可以在該命名空間總執行 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 default
link/ether 16:a4:f8:4f:56:77 brd ff:ff:ff:ff:ff:ff link-netnsid 0
inet 10.244.4.40/32 brd 10.244.4.40 scope global eth0
valid_lft forever preferred_lft forever
inet6 fe80::14a4:f8ff:fe4f:5677/64 scope link
valid_lft forever preferred_lft forever
10.244.4.40 就是 pod 的 IP 地址。
通過查找 @if12 中的 12 找到網絡接口。
$ ip link | grep -A1 ^12
12: vethweplb3f36a0@if16: mtu 1376 qdisc noqueue master weave state UP mode DEFAULT group default
link/ether 72:1c:73:d9:d9:f6 brd ff:ff:ff:ff:ff:ff link-netnsid 1
還可以驗證 Nginx 容器是否從該命名空間中監聽 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 訪問集群的節點,可以試試 kubectl exec 進入到 busybox 容器,然后使用 ip 和 netstat 命令。
太棒了!
現在我們已經介紹了容器間的通信,接下來看看 Pod 與 Pod 直接如何建立通信。
檢查集群中 pod 到 pod 的流量
當說起 pod 間通信時,會有兩種可能:
Pod 流量流向同一節點上的 pod。
Pod 流量流量另一個節點上的 pod。
為了使整個設置正常工作,我們需要之前討論過的虛擬接口和以太網橋接。
在繼續之前,我們先討論下他們的功能以及為什么他們時必需的。
要完成 pod 與其他 pod 的通信,它必須先訪問節點的根命名空間。
這是使用連接 pod 和根命名空間的虛擬以太網對來實現的。
這些虛擬接口設備(veth 中的 v)連接並充當兩個命名空間間的隧道。
使用此 veth 設備,將一端連接到 pod 的命名空間,另一端連接到根命名空間。
這些 CNI 可以替你做,也可以手動操作:
$ ip link add veth1 netns pod-namespace type veth peer veth2 netns root
現在 pod 的命名空間有了可以訪問根命名空間的隧道。
節點上每個新建的 pod 都會設置如下所示的 veth 對。
創建接口對時其中一部分。
其他的就是為以太網設備分配地址,並創建默認路由。
來看下如何在 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
在節點側,我們創建另一個 veth2 對:
$ ip addr add 169.254.132.141/16 dev veth2
$ ip link set veth2 up
可以像以前一樣檢查現有的 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 default
link/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 default
link/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 命名空間連接到以太網橋接器
橋接器將位於根命名空間中的虛擬接口的每一端“綁定”。
該橋接器將允許流量在虛擬對之間流動,並通過公共根命名空間。
理論時間。
以太網橋接器位於OSI 網絡模型的第二層。
可以將橋接器看作一個虛擬交換機,接受來自不同命名空間和接口的連接。
以太網橋接器允許連接同一個節點上的多個可用網絡。
因此,可以使用該設置連接兩個接口:從 pod 命名空間的 veth 連接到同一節點上另一個 pod 的 veth。
我們繼續看下以太網橋接器和 veth 對的作用。
跟蹤同一節點上 pod 間的流量
假設同一個節點上有兩個 pod,Pod-A 想向 Pod-B 發送消息。
- 由於目標不是同命名空間的容器,Pod-A 向其默認接口 eth0 發送數據包。這個接口與 veth 對的一端綁定,作為隧道。因此數據包將被轉發到節點的根命名空間。
- 以太網橋接器作為虛擬交換機,必須以某種方式將目標 pod IP(Pod-B)解析為其 MAC 地址。
- 輪到ARP 協議上場了。當幀到達橋接器時,會向所有連接的設備發送 ARP 廣播。橋接器喊道誰有 Pod-B 的 IP 地址。
- 收到帶有連接 Pod-B 接口的 MAC 地址的回復,然后此信息存儲在橋接器 ARP 緩存(查找表)中。
- IP 和 MAC 地址的映射存儲完成后,橋接器在表中查找,並將數據包轉發到正確的短點。數據包到達根命名空間中 Pod- B 的 veth,然后從那快速到達 Pod-B 命名空間內的 eth0 接口。
有了這個,Pod-A 和 Pod-B 之間的通信取得了成功。
跟蹤不同節點上 pod 間的通信
對於需要跨不同節點通信的 pod,需要額外的通信跳轉。
- 前幾個步驟保持不變,直到數據包到達根命名空間並需要發送到 Pod- B。
- 當目標地址不在本地網絡中,數據包將被轉發到本節點的默認網關。節點上退出或默認網關通常位於 eth0 接口上 – 將節點連接到網絡的物理接口。
這次並不會發生 ARP 解析,因為源和目標 IP 在不同網絡上。
檢查使用位運算(Bitwise)操作完成。
當目標 IP 不在當前網絡上時,數據包將被轉發到節點的默認網關。
位運算的工作原理
在確定數據包的轉發位置時,源節點必須執行位運算。
這也被稱為與操作。
作為復習,位與操作產生如下結果:
0 AND 0 = 0
0 AND 1 = 0
1 AND 0 = 0
1 AND 1 = 1
除了 1 與 1 以外的都是 false。
如果源節點的 IP 為 192.168.1.1,子網掩碼為 /24,目標 IP 為 172.16.1.1/16,則按位與操作將確認他們不在同一網絡上。
這意味着目標 IP 與數據包的源在不同的網絡上,因此數據包將在默認網關中轉發。
數學時間。
我們必須從二進制文件中的 32 位地址執行與操作開始。
先找出源和目標 IP 的網絡。
| 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 與數據包源節點的子網進行比較。
| 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 會檢查其查詢表來查找默認網關的 MAC 地址。
如果有條目,將立即轉發數據包。
否則,先進行廣播以確定網關的 MAC 地址。
- 數據包現在路由到另一個節點的默認接口,我們叫它 Node-B。
- 以相反的順序。數據包現在位與 Node-B 的根命名空間,並到達橋接器,這里會進行 ARP 解析。
- 收到帶有連接 Pod-B 的接口 MAC地址的回復。
- 這次橋接器通過 Pod-B 的 veth 設備將幀轉發,並到達 Pod-B 自己的命名空間。
此時應該已經熟悉了 pod 之間的流量如何流轉,接下來再探索下 CNI 如何創建上述內容。
容器網絡接口 - CNI
容器網絡接口(CNI)關注當前節點的網絡。
可以將 CNI 看作網絡插件在解決 Kubernetes 某些 需求時要遵循的一套規則。
然而,它不僅僅與 Kubernetes 或者特定網絡插件關聯。
可以使用如下 CNI:
- Calico
- Cilium
- Flannel
- Weave Net
- 其他網絡插件
他們都實現相同的 CNI 標准。
如果沒有 CNI,你需要手動完成如下操作:
- 創建 pod(容器)的網絡命名空間
- 創建接口
- 創建 veth 對
- 設置命名空間網絡
- 設置靜態路由
- 配置以太網橋接器
- 分配 IP 地址
- 創建 NAT 規則
還有太多其他需要手動完成的工作。
更不用說刪除或重新啟動 pod 時刪除或調整上述所有內容了。
CNI 必須支持四個不同的操作:
- ADD - 將容器添加到網絡
- DEL - 從網絡中刪除容器
- CHECK - 如果容器的網絡出現問題,則返回錯誤
- VERSION - 顯示插件的版本
讓我們在實踐中看看它是如何工作的。
當 pod 分配到特定節點時,kubelet 本身不會初始化網絡。
相反,它將任務交給了 CNI。
然后,它指定了配置,並以 JSON 格式將其發送給 CNI 插件。
可以在節點的 /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}}
]
}
每個插件都使用不同類型的配置來設置網絡。
例如,Calico 使用 BGP 路由協議配對的第 3 層網絡來連接 pod。
Cilium 在第 3 到 7 層使用 eBPF 配置覆蓋網絡。
與 Calico 一起,Cilium 支持設置網絡策略來限制流量。
那該如何選擇呢?
這取決於。
CNI 主要有兩組。
第一組中,可以找到使用基本網絡設置(也稱為扁平網絡)的CNI,並將集群 IP 池 中的IP 地址分配給 pod。
這可能會因為快速用盡可用的 IP 地址而成為負擔。
相反,另一種方法是使用覆蓋網絡。
簡而言之,覆蓋網絡是主(底層)網絡之上的輔助網絡。
覆蓋網絡的工作原理是封裝來自底層網絡的所有數據包,這些數據包指向另一個節點上的 pod。
覆蓋網絡的一項流行技術是 VXLAN,它允許在 L3 網絡上隧道傳輸 L2 域。
那么哪種更好?
沒有唯一的答案,通常取決於你的需求。
你是在構建一個擁有數萬個節點的大集群嗎?
可能覆蓋網絡更好。
你是否在意更簡單的設置和在嵌套網絡中不失去檢查網絡流量的能力。
扁平網絡更適合你。
現在已經討論了 CNI,讓我們繼續探索 Pod 到服務(service)的通信是如何完成的。
檢查 pod 到服務的流量
由於 Kubernetes 環境下 pod 的動態特性,分配給 pod 的 IP 地址不是靜態的。
這些 IP 地址是短暫的,每次創建或者刪除 pod 時都會發生變化。
服務解決了這個問題,為連接到一組 pod 提供了穩定的機制。
默認情況下,在 Kubernetes 中創建服務時,會為其預定並分配虛擬 IP 。
使用選擇器將服務於目標 pod 進行管理。
當刪除 pod 並添加新 pod 時會發生什么?
該服務的虛擬 IP 保持不變。
然而,無需敢於,流量將到達新創建的 pod。
換句話說,Kubernetes 中的服務類似於負載均衡器。
但他們時如何工作的?
使用 Netfilter 和 Iptables 攔截和重寫流量
Kubernetes 中的服務基於兩個 Linux 內核組件:
Netfilter
Iptables
Netfilter 是一個框架,允許配置數據包過濾、創建 NAT或端口翻譯規則,並管理網絡中的流量。
此外,它還屏蔽和阻止不請自來的連接訪問服務。
另一方面,Iptables 是一個用戶空間程序,允許你配置 Linux 內核防火牆的 IP 數據包過濾器規則。
iptables 使用不同的 Netfilter 模塊實現。
你可以使用 iptables CLI 實時更改過濾規則,並將其插入 netfilters 的掛點。
過濾器組織在不同的表中,其中包含處理網絡流量數據包的鏈。
每個協議都使用不同的內核模塊和程序。
當提到 iptables 時,通常說的是 IPV4。對於 IPV6 的規則,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
當數據包到達時,根據所處的階段,會“出發” Netfilter 鈎子,該鈎子應用特定的 iptables 過濾。
哎呀,看起來很復雜!
不過不需要擔心。
這就是為什么我們使用 Kubernetes,上面的所有內容都是通過使用服務來抽象的,一個簡單的 YAML 定義就可以自動完成這些規則的設置。
如果對這些 iptables 規則感興趣,可以登陸到節點並運行:
記住,可能會有數百條規則。想象下手動創建的難度。
我們已經解釋了相同和不同節點上的 pod 間如何通信。
在 Pod-to-Service 中,通信的前半部分保持不變。
當 Pod-A 發出請求時,希望到達 Pod-B(這種情況下,Pod-B 位與服務之后),轉移的過程中會發生其他變化。
原始請求從 Pod-A 命名空間中的 eth0 接口出來。
從那里穿過 veth 對,到達根命名空間的以太網橋。
一旦到達橋接器,數據包立即通過默認網關轉發。
與 Pod-to-Pod 部分一樣,主機進行位比較,由於服務的 vIP 不是節點 CIDR 的一部分,數據包將立即通過默認網關轉發出去。
如果查找表中尚沒有默認網關的 MAC 地址,則會進行相同的 ARP 解析。
現在魔法發生了。
在數據包經過節點的路由處理之前,Netfilter 鈎子 NF_IP_PRE_ROUTING 被觸發並應用一條 iptables 規則。規則進行了 DNAT 轉換,重寫了 POD-A 數據包的目標 IP 地址。
原來服務 vIP 地址被重寫稱 POD-B 的IP 地址。
從那里,路由就像 Pod-to-Pod 直接通信一樣。
然而,在所有這些通信之間,使用了第三個功能。
這個功能被稱為 conntrack,或連接跟蹤。
Conntrack 將數據包與連接關聯起來,並在 Pod-B 發送回響應時跟蹤其來源。
NAT 嚴重依賴 contrack 工作。
如果沒有連接跟蹤,它將不知道將包含響應的數據包發送回哪里。
使用 conntrack 時,數據包的返回路徑可以輕松設置相同的源或目標 NAT 更改。
另一半使用相反的順序執行。
Pod-B 接收並處理了請求,現在將數據發送回 Pod-A。
此時會發生什么?
檢查服務的響應
現在 Pod-B 發送響應,將其 IP 地址設置為源地址,Pod-A IP 地址設置為目標地址。
- 當數據包到達 Pod-A 所在節點的接口時,就會發生另一個 NAT
- 這次,使用 conntrack 更改源 IP 地址,iptables 規則執行 SNAT 將 Pod-B IP 地址替換為原始服務的 VIP 地址。
- 從 Pod-A 來看像是服務發回的響應,而不是 Pod-B。
其他部分都一樣;一旦 SNAT 完成,數據包到達根命名空間中的以太網橋接器,並通過 veth 對轉發到 Pod-A。
回顧
讓我們來總結下你在本文中學到的東西:
- 容器如何在本地或 pod 內通信。
- 當 pod 位於相同和不同的節點上時,Pod-to- Pod 如何通信。
- Pod-to-Service - 當 pod 向 Kubernetes 服務背后的 pod 發送流量時。
- Kubernetes 網絡工具箱中有效通信所需的命名空間、veth、iptables、鏈、Netfilter、CNI、覆蓋網絡以及所有其他內容。