深入探索 Kubernetes 網絡模型和網絡通信
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 網絡模型,并不妨礙我們了解網絡模型。
登錄到節點上,通過 lsns -t net? 當前主機上的網絡命名空間,但是并沒有找到 httpbin? 的進程。有個命名空間的命令是 /pause?,這個 pause 進程實際上是每個 pod 中 不可見 的 sandbox
既然每個容器都有獨立的進程空間,我們換下命令查看進程類型的空間:
通過進程 PID 129889 可以找到其所屬的命名空間:
然后可以在該命名空間下使用 exec 執行命令:
從結果來看 pod 的 IP 地址 10.42.1.14? 綁定在接口 eth0? 上,而 eth0? 被連接到 17 號接口上。
在節點主機上,查看 17? 號接口信息。veth7912056b? 是主機根命名空間下的虛擬以太接口(vitual ethernet device),是連接 pod 網絡和節點網絡的 隧道,對端是 pod 命名空間下的接口 eth0。
上面的結果看到,該 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