深入探索Kubernetes 網絡模型和網絡通信
深入探索Kubernetes 網絡模型和網絡通信
這篇文章主要深入探索Kubernetes網絡模型,並了解容器、pod間如何進行通訊。對於網絡模型的實現將會在後面的文章介紹。Kubernetes 定義了一種簡單、一致的網絡模型,基於扁平網絡結構的設計,無需將主機端口與網絡端口進行映射便可以進行高效地通訊,也無需其他組件進行轉發。該模型也使應用程序很容易從虛擬機或者主機物理機遷移到Kubernetes 管理的pod 中。
這篇文章主要深入探索Kubernetes網絡模型,並了解容器、pod間如何進行通訊。對於網絡模型的實現將會在後面的文章介紹。
Kubernetes 定義了一種簡單、一致的網絡模型,基於扁平網絡結構的設計,無需將主機端口與網絡端口進行映射便可以進行高效地通訊,也無需其他組件進行轉發。該模型也使應用程序很容易從虛擬機或者主機物理機遷移到Kubernetes 管理的pod 中。
這篇文章主要深入探索Kubernetes網絡模型,並了解容器、pod間如何進行通訊。對於網絡模型的實現將會在後面的文章介紹。
Kubernetes 網絡模型
該模型定義了:
- 每個pod 都有自己的IP 地址,這個IP 在集群範圍內可達
- Pod 中的所有容器共享pod IP 地址(包括MAC 地址),並且容器之前可以相互通信(使用localhost)
- Pod 可以使用pod IP 地址與集群中任一節點上的其他pod 通信,無需NAT
- Kubernetes 的組件之間可以相互通信,也可以與pod 通信
- 網絡隔離可以通過網絡策略實現
上面的定義中提到了幾個相關的組件:
- Pod:Kubernetes 中的pod 有點類似虛擬機有唯一的IP 地址,同一個節點上的pod 共享網絡和存儲。
- Container:pod 是一組容器的集合,這些容器共享同一個網絡命名空間。pod 內的容器就像虛擬機上的進程,進程之間可以使用localhost 進行通信;容器有自己獨立的文件系統、CPU、內存和進程空間。需要通過創建Pod 來創建容器。
- Node:pod 運行在節點上,集群中包含一個或多個節點。每個pod 的網絡命名空間都會連接到節點的命名空間上,以打通網絡。
講了這麼多次網絡命名空間,那它到底是如何運作的呢?
該模型定義了:
- 每個pod 都有自己的IP 地址,這個IP 在集群範圍內可達
- Pod 中的所有容器共享pod IP 地址(包括MAC 地址),並且容器之前可以相互通信(使用localhost)
- Pod 可以使用pod IP 地址與集群中任一節點上的其他pod 通信,無需NAT
- Kubernetes 的組件之間可以相互通信,也可以與pod 通信
- 網絡隔離可以通過網絡策略實現
上面的定義中提到了幾個相關的組件:
- Pod:Kubernetes 中的pod 有點類似虛擬機有唯一的IP 地址,同一個節點上的pod 共享網絡和存儲。
- Container:pod 是一組容器的集合,這些容器共享同一個網絡命名空間。pod 內的容器就像虛擬機上的進程,進程之間可以使用localhost 進行通信;容器有自己獨立的文件系統、CPU、內存和進程空間。需要通過創建Pod 來創建容器。
- Node:pod 運行在節點上,集群中包含一個或多個節點。每個pod 的網絡命名空間都會連接到節點的命名空間上,以打通網絡。
講了這麼多次網絡命名空間,那它到底是如何運作的呢?
網絡命名空間如何工作
在Kubernetes 的發行版 k3s 創建一個pod,這個pod 有兩個容器:發送請求的 curl 容器和提供web 服務的 httpbin 容器。
雖然使用發行版,但是其仍然使用Kubernetes 網絡模型,並不妨礙我們了解網絡模型。
apiVersion: v1
kind: Pod
metadata:
name: multi-container-pod
spec:
containers:
- image: curlimages/curl
name: curl
command: ["sleep", "365d"]
- image: kennethreitz/httpbin
name: httpbin
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
登錄到節點上,通過 lsns -t net 當前主機上的網絡命名空間,但是並沒有找到 httpbin 的進程。有個命名空間的命令是 /pause,這個 pause 進程實際上是每個pod 中 不可見 的 sandbox
lsns -t net
NS TYPE NPROCS PID USER NETNSID NSFS COMMAND
4026531992 net 126 1 root unassigned /lib/systemd/systemd --system --deserialize 31
4026532247 net 1 83224 uuidd unassigned /usr/sbin/uuidd --socket-activation
4026532317 net 4 129820 65535 0 /run/netns/cni-607c5530-b6d8-ba57-420e-a467d7b10c56 /pause
- 1.
- 2.
- 3.
- 4.
- 5.
在Kubernetes 的發行版 k3s 創建一個pod,這個pod 有兩個容器:發送請求的 curl 容器和提供web 服務的 httpbin 容器。
雖然使用發行版,但是其仍然使用Kubernetes 網絡模型,並不妨礙我們了解網絡模型。
apiVersion: v1
kind: Pod
metadata:
name: multi-container-pod
spec:
containers:
- image: curlimages/curl
name: curl
command: ["sleep", "365d"]
- image: kennethreitz/httpbin
name: httpbin
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
登錄到節點上,通過 lsns -t net 當前主機上的網絡命名空間,但是並沒有找到 httpbin 的進程。有個命名空間的命令是 /pause,這個 pause 進程實際上是每個pod 中 不可見 的 sandbox
lsns -t net
NS TYPE NPROCS PID USER NETNSID NSFS COMMAND
4026531992 net 126 1 root unassigned /lib/systemd/systemd --system --deserialize 31
4026532247 net 1 83224 uuidd unassigned /usr/sbin/uuidd --socket-activation
4026532317 net 4 129820 65535 0 /run/netns/cni-607c5530-b6d8-ba57-420e-a467d7b10c56 /pause
- 1.
- 2.
- 3.
- 4.
- 5.
既然每個容器都有獨立的進程空間,我們換下命令查看進程類型的空間:
lsns -t pid
NS TYPE NPROCS PID USER COMMAND
4026531836 pid 127 1 root /lib/systemd/systemd --system --deserialize 31
4026532387 pid 1 129820 65535 /pause
4026532389 pid 1 129855 systemd-network sleep 365d
4026532391 pid 2 129889 root /usr/bin/python3 /usr/local/bin/gunicorn -b 0.0.0.0:80 httpbin:app -k gevent
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
通過進程PID 129889 可以找到其所屬的命名空間:
ip netns identify 129889
cni-607c5530-b6d8-ba57-420e-a467d7b10c56
- 1.
- 2.
然後可以在該命名空間下使用 exec 執行命令:
ip netns exec cni-607c5530-b6d8-ba57-420e-a467d7b10c56 ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host
valid_lft forever preferred_lft forever
2: eth0@if17: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue state UP group default
link/ether f2:c8:17:b6:5f:e5 brd ff:ff:ff:ff:ff:ff link-netnsid 0
inet 10.42.1.14/24 brd 10.42.1.255 scope global eth0
valid_lft forever preferred_lft forever
inet6 fe80::f0c8:17ff:feb6:5fe5/64 scope link
valid_lft forever preferred_lft forever
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
從結果來看pod 的IP 地址 10.42.1.14 綁定在接口 eth0 上,而 eth0 被連接到 17 號接口上。
在節點主機上,查看 17 號接口信息。veth7912056b 是主機根命名空間下的虛擬以太接口(vitual ethernet device),是連接pod 網絡和節點網絡的 隧道,對端是pod 命名空間下的接口 eth0。
ip link | grep -A1 ^17
17: veth7912056b@if2: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue master cni0 state UP mode DEFAULT group default
link/ether d6:5e:54:7f:df:af brd ff:ff:ff:ff:ff:ff link-netns cni-607c5530-b6d8-ba57-420e-a467d7b10c56
- 1.
- 2.
- 3.
上面的結果看到,該 veth 連到了個網橋(network bridge)cni0 上。
網橋工作在數據鏈路層(OSI 模型的第2 層),連接多個網絡(可多個網段)。當請求到達網橋,網橋會詢問所有連接的接口(這裡pod 通過veth 以網橋連接)是否擁有原始請求中的IP 地址。如果有接口響應,網橋會將匹配信息(IP -> veth)記錄,並將數據轉發過去。
那如果沒有接口響應怎麼辦?具體流程就要看各個網絡插件的實現了。我準備在後面的文章中介紹常用的網絡插件,比如Calico、Flannel、Cilium 等。
接下來看下Kubernetes 中的網絡通信如何完成,一共有幾種類型:
- 同pod 內容器間通信
- 同節點上的pod 間通信
- 不同節點上的pod 間通信
Kubernetes 網絡如何工作
同pod 內的容器間通信
同pod 內的容器間通信最簡單,這些容器共享網絡命名空間,每個命名空間下都有 lo 回環接口,可以通過 localhost 來完成通信。
同節點上的pod 間通信
當我們將 curl 容器和 httpbin 分別在兩個pod 中運行,這兩個pod 有可能調度到同一個節點上。curl發出的請求根據容器內的路由表到達了pod 內的 eth0 接口。然後通過與 eth0 相連的隧道 veth1 到達節點的根網絡空間。
veth1 通過網橋 cni0 與其他pod 相連虛擬以太接口 vethX 相連,網橋會詢問所有相連的接口是否擁有原始請求中的IP 地址(比如這裡的 10.42.1.9)。收到響應後,網橋會記錄映射信息(10.42.1.9 => veth0),同時將數據轉發過去。最終數據經過 veth0 隧道進入pod httpbin 中。
不同節點的pod 間通信
跨節點的pod 間通信會復雜一些,且 不同網絡插件的處理方式不同,這裡選擇一種容易理解的方式來簡單說明下。
前半部分的流程與同節點pod 間通信類似,當請求到達網橋,網橋詢問哪個pod 擁有該IP 但是沒有得到回應。流程進入主機的路由尋址過程,到更高的集群層面。
在集群層面有一張路由表,裡面存儲著每個節點的Pod IP 網段(節點加入到集群時會分配一個Pod 網段(Pod CIDR),比如在k3s 中默認的Pod CIDR 是 10.42.0.0/16,節點獲取到的網段是 10.42.0.0/24、10.42.1.0/24、10.42.2.0/24,依次類推)。通過節點的Pod IP 網段可以判斷出請求IP 的節點,然後請求被發送到該節點。
總結
現在應該對Kubernetes 的網絡通信有初步的了解了吧。
整個通信的過程需要各種組件的配合,比如Pod 網絡命名空間、pod 以太網接口 eth0、虛擬以太網接口 vethX、網橋(network bridge) cni0 等。其中有些組件與pod 一一對應,與pod 同生命週期。雖然可以通過手動的方式創建、關聯和刪除,但對於pod 這種非永久性的資源會被頻繁地創建和銷毀,太多人工的工作也是不現實的。
實際上這些工作都是由容器委託給網絡插件來完成的,而網絡插件所遵循的規範CNI(Container Network Interface)。
網絡插件都做了什麼?
- 創建pod(容器)的網絡命名空間
- 創建接口
- 創建veth 對
- 設置命名空間網絡
- 設置靜態路由
- 配置以太網橋接器
- 分配IP 地址
- 創建NAT 規則
...
- 參考
https://www.tigera.io/learn/guides/kubernetes-networking/
https://kubernetes.io/docs/concepts/services-networking/
https://matthewpalmer.net/kubernetes-app-developer/articles/kubernetes-networking-guide-beginners.html
https://learnk8s.io/kubernetes-network-packets