最近發現自己似乎從來沒學明白過Kubernetes網絡通信方案,特開一貼復習總結一下。
在k8s中,每個 Pod 都擁有一個獨立的 IP 地址,而且假定所有 Pod 都在一個可以直接連通的、扁平的網絡空間中。所以不管它們是否允許在同一個 Node(宿主機)中,都要求它可以直接通過對方的 IP 進行訪問。用戶不需要額外考慮如何建立 Pod 之間的連接,也不需要考慮將容器端口映射到主機端口等問題。同時,外界的
省流,K8s網絡的目標是為了實現:
- pod之間的互聯互通
- 對外提供服務
為了實現上述功能,需要為每一個pod都維護完整的網絡協議棧,同時,維護pod之間的網絡路由。
CNI容器網絡接口
首先,當你起了一個裸的、沒有網絡的pod之后,這個pod是沒有任何基于TCP/IP手段和外界進行通信的。你可能只能通過crictl的客戶端看到自己起了這么一個pod。
為了建立pod和外界的(基于TCP/IP的)通信,并且將不同pod的網絡隔離開,需要為每一個pod構建一個網絡命名空間。網絡命名空間(Network Namespace)是一種內核級別的隔離機制,用于將不同進程組(比如一個pod就是一個被隔離的進程組)的網絡環境隔離開來。每個網絡命名空間擁有獨立的網絡棧,實現網絡資源的完全隔離。
具體一點,網絡命名空間的核心功能及特性是:
- 網絡隔離
不同網絡命名空間的進程無法直接通信,需通過跨命名空間的網絡設備(如 veth 對、網橋)或物理設備連接。例如,pod A 在命名空間ns1,pod B 在命名空間ns2,它們的(虛擬)物理層、數據鏈路層、網絡層、傳輸層、應用層都實現了相互獨立。 - 獨立網絡棧
每個命名空間可獨立配置 IP 地址、子網、網關、DNS 服務器等。 - 輕量級隔離
相比虛擬機,網絡命名空間的資源開銷極小,適合容器場景。
我們首先實現同一臺物理機內pod之間的相互通信。
為了實現不同網絡命名空間之間的通信,需要把若干的pod從物理層到網絡層連起來。
我們首先實現(虛擬)物理層和數據鏈路層的相互連接,這基于veth設備對。
你可以把它想象為一根帶有兩個水晶頭的網線,兩端(veth1和veth2)分別插在兩個pod上。如下圖所示:

在有多個pod的情況下,一般會新建一個公用的虛擬交換機(如圖所示的bridge),然后所有的pod都接到這個交換機上,也能通過這個交換機實現pod之間的通信。如下圖所示:

圖里面這個bridge是一個虛擬網絡設備,所以具有網絡設備的特征,可以配置IP、MAC地址等;其次,bridge是一個虛擬交換機,和物理交換機有類似的功能。
對于普通的網絡設備來說,只有兩端,從一端進來的數據會從另一端出去,如物理網卡從外面網絡中收到的數據會轉發給內核協議棧,而從協議棧過來的數據會轉發到外面的物理網絡中。
而bridge不同,bridge有多個端口,數據可以從任何端口進來,進來之后從哪個口出去和物理交換機的原理差不多,要看數據包指向的終端MAC地址。
進一步地,我們給pod、網橋都配上IP地址,于是我們實現了同一臺物理機內pod之間的相互通信。

進一步地,我們需要實現跨不同主機pod之間的通信。
一種簡單的想法是,建一個跨多個主機的bridge不就得了?

這種在已有的網絡上通過軟件構建一個擴展版虛擬網絡的方法,被稱為:Overlay Network(覆蓋網絡)。從實現效果來看,就是把若干個節點上的小bridge整合成了一個大bridge。
Calico和Flannel是overlay network的兩種實現方案。
Calico和Flannel
為了實現跨節點之間的數據通信,這就需要calico和flannel出場了。
跨節點數據包路由,主要就是兩種情形:
- 節點和節點之間能夠通過IP直連(三層可達)
- 節點和節點之間連接復雜(三層不可達)
解決方案也很簡單,對于三層可達的網絡,可以直接配置節點的路由表進行轉發。對于三層不可達的網絡,搭一個隧道實現網絡穿透就可以了。
從這個視角看,Calico和Flannel兩個插件雖然名字不太一樣,但是實現的功能大同小異。
calico中,主要提供兩種跨node包路由的模式:
- BGP 模式:在三層可達的時候,Calico 在每個節點上運行 BGP 客戶端,將 Pod 的 IP 路由信息通過 BGP 協議通告給其他節點。pod間流量直接通過路由規則進行轉發。
- IPIP 模式:當兩個節點不在同一子網(三層不可達)時,Calico 將Pod間數據包的外邊再包一層IP 頭,通過隧道實現傳輸。

flannel中,也提供兩種跨節點包路由的方式:
- VXLAN模式:VXLAN 是 Linux 內核原生支持的網絡虛擬化技術,通過在 IP 包內封裝二層以太網幀,實現跨節點的虛擬二層網絡。原理和Calico的IPIP模式類似。
- Host-gateway模式:直接利用節點的物理網絡進行路由,無需隧道封裝,通過將 Pod 子網的路由直接指向目標節點的物理 IP,實現跨節點通信。

Kube-proxy
在前文中,我們已經通過calico / flannel等技術實現了同一個集群內pod的互聯互通。但是仍然有一些比較關鍵的需求需要解決。
在實際使用集群的過程中,pod啟動時的IP是隨機分配的,這意味著訪問集群內部pod必不能用硬編碼方式實現。此外, Pod 實例會動態變化,需要設計動態服務發現和動態負載均衡方案。要在pod不斷的變化的IP中追求不變,并且抓住這個不變實現集群內部pod的訪問。
K8S的解決方案是service+DNS。Service里記錄了pod的標簽和端口映射規則(只是一個配置文件)。在用戶向集群提交service的配置后,集群會創建和service同名的endpoint對象,用于根據service記錄的標簽去匹配對應的某些pod。用戶在訪問某些特定pod的時候,只需要訪問service/endpoint的IP即可,由endpoint實現流量的路由和負載均衡。
由于endpoint的IP也在變,所以集群內引入了DNS機制,為每一個service都賦予了一個域名,通過service域名訪問對應的pod。
為了實現上述功能,需要不斷地監控pod的狀態,進而實現路由轉發。Kube-proxy提供了這樣的解決方案。
kube - proxy 會持續監控 API Server,當 Service 或 Endpoint 資源發生變更時,kube - proxy 會通過list-watch手段獲取這些信息。
kube - proxy 根據這些信息更新本地的iptables/ipvs規則,這些規則決定了如何將流量從 Service 的 IP 和端口轉發到后端的 Pod。
references
https://segmentfault.com/a/1190000009491002
https://learn.lianglianglee.com
https://zhuanlan.zhihu.com/p/439920165
https://blog.csdn.net/weixin_42587823/article/details/144938902