深入探索Kubernetes 網絡模型和網絡通信

2022.12.05

深入探索Kubernetes 網絡模型和網絡通信


這篇文章主要深入探索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 的網絡命名空間都會連接到節點的命名空間上,以打通網絡。

講了這麼多次網絡命名空間,那它到底是如何運作的呢?

網絡命名空間如何工作

在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