圖解 kubernetes 網絡通信原理

來源:https://zhuanlan.zhihu.com/p/81667781

名詞解釋

1、網絡的命名空間:Linux 在網絡棧中引入網絡命名空間,將獨立的網絡協議棧隔離到不同的命令空間中,彼此間無法通信;docker 利用這一特性,實現不容器間的網絡隔離。

2、Veth 設備對:也叫虛擬網絡接口對。Veth 設備對的引入是爲了實現在不同網絡命名空間的通信。

3、Iptables/Netfilter:Netfilter 負責在內核中執行各種掛接的規則 (過濾、修改、丟棄等),運行在內核 模式中;Iptables 模式是在用戶模式下運行的進程,負責協助維護內核中 Netfilter 的各種規則表;通過二者的配合來實現整個 Linux 網絡協議棧中靈活的數據包處理機制。

4、網橋:網橋是一個二層網絡設備, 通過網橋可以將 linux 支持的不同的端口連接起來, 並實現類似交換機那樣的多對多的通信。

5、路由:Linux 系統包含一個完整的路由功能,當 IP 層在處理數據發送或轉發的時候,會使用路由表來決定發往哪裏。

令人頭大的網絡模型

Kubernetes 對集羣內部的網絡進行了重新抽象,以實現整個集羣網絡扁平化。我們可以理解網絡模型時,可以完全抽離物理節點去理解,我們用圖說話,先有基本印象。

其中,重點講解以下幾個關鍵抽象概念。

一個 Service

Service 是 Kubernetes 爲爲屏蔽這些後端實例(Pod)的動態變化和對多實例的負載均衡而引入的資源對象。Service 通常與 deployment 綁定,定義了服務的訪問入口地址,應用 (Pod) 可以通過這個入口地址訪問其背後的一組由 Pod 副本組成的集羣實例。Service 與其後端 Pod 副本集羣之間則是通過 Label Selector 來實現映射。

Service 的類型 (Type) 決定了 Service 如何對外提供服務,根據類型不同,服務可以只在 Kubernetes cluster 中可見,也可以暴露到集羣外部。Service 有三種類型,ClusterIP,NodePort 和 LoadBalancer。具體的使用場景會在下文中進行闡述。

在測試環境查看:

上述信息中該 svc 後端代理了 2 個 Pod 實例: 172.16.2.125:80,172.16.2.229:80

二個 IP

Kubernetes 爲描述其網絡模型的 IP 對象,抽象出 Cluster IP 和 Pod IP 的概念。

PodIP 是 Kubernetes 集羣中每個 Pod 的 IP 地址。它是 Docker Engine 根據 docker0 網橋的 IP 地址段進行分配的,是一個虛擬的二層網絡。Kubernetes 中 Pod 間能夠彼此直接通訊,Pod 裏的容器訪問另外一個 Pod 裏的容器,是通過 Pod IP 所在進行通信。

Cluster IP 僅作用於 Service,其沒有實體對象所對應,因此 Cluster IP 無法被 ping 通。它的作用是爲 Service 後端的實例提供統一的訪問入口。當訪問 ClusterIP 時,請求將被轉發到後端的實例上,默認是輪詢方式。Cluster IP 和 Service 一樣由 kube-proxy 組件維護,其實現方式主要有兩種,iptables 和 IPVS。在 1.8 版本後 kubeproxy 開始支持 IPVS 方式。在上例中,SVC 的信息中包含了 Cluster IP。

這裏未列出 nodeip 概念,由於其本身是物理機的網卡 IP。因此可理解爲 nodeip 就是物理機 IP。

三個 Port

在 Kubernetes 中,涉及容器,Pod,Service,集羣各等多個層級的對象間的通信,爲在網絡模型中區分各層級的通信端口,這裏對 Port 進行了抽象。

Port

該 Port 非一般意義上的 TCP/IP 中的 Port 概念,它是特指 Kubernetes 中 Service 的 port,是 Service 間的訪問端口,例如 Mysql 的 Service 默認 3306 端口。它僅對進羣內容器提供訪問權限,而無法從集羣外部通過該端口訪問服務。

nodePort

nodePort 爲外部機器提供了訪問集羣內服務的方式。比如一個 Web 應用需要被其他用戶訪問,那麼需要配置 type=NodePort,而且配置 nodePort=30001,那麼其他機器就可以通過瀏覽器訪問 scheme://node:30001 訪問到該服務,例如 node:30001

targetPort

targetPort 是容器的端口(最根本的端口入口),與製作容器時暴露的端口一致(DockerFile 中 EXPOSE),例如 docker.io 官方的 nginx 暴露的是 80 端口。

舉一個例子來看如何配置 Service 的 port:

這裏舉出了一個 service 的 yaml,其部署在 abcdocker 的 namespace 中。這裏配置了 nodePort,因此其類型 Type 就是 NodePort,注意大小寫。若沒有配置 nodePort,那這裏需要填寫 ClusterIP,即表示只支持集羣內部服務訪問。

集羣內部通信

單節點通信

集羣單節點內的通信,主要包括兩種情況,同一個 pod 內的多容器間通信以及同一節點不同 pod 間的通信。由於不涉及跨節點訪問,因此流量不會經過物理網卡進行轉發。

通過查看路由表,也能窺見一二:

1 Pod 內通信

如下圖所示:

這種情況下,同一個 pod 內共享網絡命名空間,容器之間通過訪問 127.0.0.1:(端口)即可。圖中的 veth * 即指 veth 對的一端(另一端未標註,但實際上是成對出現),該 veth 對是由 Docker Daemon 掛載在 docker0 網橋上,另一端添加到容器所屬的網絡命名空間,圖上顯示是容器中的 eth0。

圖中演示了 bridge 模式下的容器間通信。docker1 向 docker2 發送請求,docker1,docker2 均與 docker0 建立了 veth 對進行通訊。

當請求經過 docker0 時,由於容器和 docker0 同屬於一個子網,因此請求經過 docker2 與 docker0 的 veth * 對,轉發到 docker2,該過程並未跨節點,因此不經過 eth0。

2 Pod 間通信

同節點 pod 間通信

由於 Pod 內共享網絡命名空間(由 pause 容器創建),所以本質上也是同節點容器間的通信。同時,同一 Node 中 Pod 的默認路由都是 docker0 的地址,由於它們關聯在同一個 docker0 網橋上,地址網段相同,所有它們之間應當是能直接通信的。來看看實際上這一過程如何實現。如上圖,Pod1 中容器 1 和容器 2 共享網絡命名空間,因此對 pod 外的請求通過 pod1 和 Docker0 網橋的 veth 對(圖中掛在 eth0 和 ethx 上)實現。

訪問另一個 pod 內的容器,其請求的地址是 PodIP 而非容器的 ip,實際上也是同一個子網間通信,直接經過 veth 對轉發即可。

跨節點通信

CNI:容器網絡接口

CNI 是一種標準,它旨在爲容器平臺提供網絡的標準化。不同的容器平臺(比如目前的 kubernetes、mesos 和 rkt)能夠通過相同的接口調用不同的網絡組件。

目前 kubernetes 支持的 CNI 組件種類很多,例如:bridge calico calico-ipam dhcp flannel host-local ipvlan loopback macvlan portmap ptp sample tuning vlan。在 docker 中,主流的跨主機通信方案主要有一下幾種:

1)基於隧道的 overlay 網絡:按隧道類型來說,不同的公司或者組織有不同的實現方案。docker 原生的 overlay 網絡就是基於 vxlan 隧道實現的。ovn 則需要通過 geneve 或者 stt 隧道來實現的。flannel 最新版本也開始默認基於 vxlan 實現 overlay 網絡。

2)基於包封裝的 overlay 網絡:基於 UDP 封裝等數據包包裝方式,在 docker 集羣上實現跨主機網絡。典型實現方案有 weave、flannel 的早期版本。

3)基於三層實現 SDN 網絡:基於三層協議和路由,直接在三層上實現跨主機網絡,並且通過 iptables 實現網絡的安全隔離。典型的方案爲 Project Calico。同時對不支持三層路由的環境,Project Calico 還提供了基於 IPIP 封裝的跨主機網絡實現

通信方式

集羣內跨節點通信涉及到不同的子網間通信,僅靠 docker0 無法實現,這裏需要藉助 CNI 網絡插件來實現。圖中展示了使用 flannel 實現跨節點通信的方式。

簡單說來,flannel 的用戶態進程 flanneld 會爲每個 node 節點創建一個 flannel.1 的網橋,根據 etcd 或 apiserver 的全局統一的集羣信息爲每個 node 分配全局唯一的網段,避免地址衝突。同時會爲 docker0 和 flannel.1 創建 veth 對,docker0 將報文丟給 flannel.1,。

Flanneld 維護了一份全局 node 的網絡表,通過 flannel.1 接收到請求後,根據 node 表,將請求二次封裝爲 UDP 包,扔給 eth0,由 eth0 出口進入物理網路發送給目的 node。

在另一端以相反的流程。Flanneld 解包併發往 docker0,進而發往目的 Pod 中的容器。

外部訪問集羣

從集羣外訪問集羣有多種方式,比如 loadbalancer,Ingress,nodeport,nodeport 和 loadbalancer 是 service 的兩個基本類型,是將 service 直接對外暴露的方式,ingress 則是提供了七層負載均衡,其基本原理將外部流量轉發到內部的 service,再轉發到後端 endpoints,在平時的使用中,我們可以依據具體的業務需求選用不同的方式。這裏主要介紹 nodeport 和 ingress 方式。

Nodeport

通過將 Service 的類型設置爲 NodePort,就可以在 Cluster 中的主機上通過一個指定端口暴露服務。注意通過 Cluster 中每臺主機上的該指定端口都可以訪問到該服務,發送到該主機端口的請求會被 kubernetes 路由到提供服務的 Pod 上。採用這種服務類型,可以在 kubernetes cluster 網絡外通過主機 IP:端口的方式訪問到服務。

這裏給出一個 influxdb 的例子,我們也可以針對這個模板去修改成其他的類型:

Ingress

Ingress 是推薦在生產環境使用的方式,它起到了七層負載均衡器和 Http 方向代理的作用,可以根據不同的 url 把入口流量分發到不同的後端 Service。外部客戶端只看到 foo.bar.com 這個服務器,屏蔽了內部多個 Service 的實現方式。採用這種方式,簡化了客戶端的訪問,並增加了後端實現和部署的靈活性,可以在不影響客戶端的情況下對後端的服務部署進行調整。

其部署的 yaml 可以參考如下模板:

這裏我們定義了一個 ingress 模板,定義通過 test.name.com 來訪問服務,在虛擬主機 test.name.com 下面定義了兩個 Path,其中 / test 被分發到後端服務 s1,/name 被分發到後端服務 s2。

集羣中可以定義多個 ingress,來完成不同服務的轉發,這裏需要一個 ingress controller 來管理集羣中的 Ingress 規則。Ingress Contronler 通過與 Kubernetes API 交互,動態的去感知集羣中 Ingress 規則變化,然後讀取它,按照自定義的規則,規則就是寫明瞭哪個域名對應哪個 service,生成一段 Nginx 配置,再寫到 Nginx-ingress-control 的 Pod 裏,這個 Ingress Contronler 的 pod 裏面運行着一個 nginx 服務,控制器會把生成的 nginx 配置寫入 / etc/nginx.conf 文件中,然後 reload 使用配置生效。

Kubernetes 提供的 Ingress Controller 模板如下:

總結及展望

本文針對 kubernetes 的網絡模型,從一個 service,二個 IP,三個 port 出發進行圖解。詳解 kubernetes 集羣內及集羣外部訪問方式。後續還將針對各網絡細節進行深入分析,敬請關注。

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/lZ9b5dPJqJWVtYgzbjAWyg