k8s網路模型與叢集通訊

iqsing發表於2021-11-16

在k8s中,我們的應用會以pod的形式被排程到各個node節點上,在設計叢集如何處理容器之間的網路時是一個不小的挑戰,今天我們會從pod(應用)通訊來展開關於k8s網路的討論。

小作文包含如下內容:

  • k8s網路模型與實現方案
  • pod內容器通訊
  • pod與pod通訊
  • pod與service通訊
  • 外網與service通訊

k8s網路模型與實現方案

k8s叢集中的每一個Pod(最小排程單位)都有自己的IP地址,即ip-per-pod模型

ip-per-pod模型中每一個pod在叢集中保持唯一性,我們不需要顯式地在每個 Pod 之間建立連結, 不需要處理容器埠到主機埠之間的對映。從埠分配、命名、服務發現、 負載均衡、應用配置和遷移的角度來看,Pod 可以被視作獨立虛擬機器或者物理主機。

如下圖,從表面上來看兩個容器在docker網路與k8s網路中與client通訊形式。

ip-per-pod

k8s是一套龐大的分散式系統,為了保持核心功能的精簡(模組化)以及適應不同業務使用者的網路環境,k8s通過CNI(Container Network Interface)即容器網路介面整合各種網路方案。這些網路方案必須符合k8s網路模型要求:

  • 節點上的 Pod 可以不通過 NAT 和其他任何節點上的 Pod 通訊
  • 節點上的代理(比如:系統守護程式、kubelet)可以和節點上的所有Pod通訊

備註:僅針對那些支援 Pods 在主機網路中執行的平臺(比如:Linux):

  • 那些執行在節點的主機網路裡的 Pod 可以不通過 NAT 和所有節點上的 Pod 通訊

如此操作,是不是有點像美團?將配送業務外包(CNI)給三方公司(實現方案),騎手是通過哪種飛機大炮(網路)送餐的我不管,只要符合準時、不撒漏(模型要求)等相關規矩這就是一次合格的配送。

CNI 做兩件事,容器建立時的網路分配,和當容器被刪除時釋放網路資源。 常用的 CNI 實現方案有 Flannel、Calico、Weave以及各種雲廠商根據自身網路推出的CNI外掛如華為的 CNI-Genie、阿里雲Terway。關於各實現方案的原理不是本次討論重點,有機會單獨寫一篇。


pod內容器通訊

Pod內容器非常簡單,在同一個 Pod 內,所有容器共享儲存、網路即使用同一個 IP 地址和埠空間,並且可以通過 localhost 發現對方。Pod 使用了一箇中間容器 Infra,Infra 在 Pod 中首先被建立,而其他容器則通過 Join Network Namespace 的方式與 Infra 容器關聯在一起。

pod-network

我們有一個pod包含busybox、nginx這兩個容器

kubectl get pod -n training
NAME                             READY   STATUS    RESTARTS   AGE
pod-localhost-765b965cfc-8sh76   2/2     Running   0          2m56s

在busybox中使用telnet連線nginx容器的 80埠看看。

kubectl exec -it  pod-localhost-765b965cfc-8sh76 -c container-si1nrb -n training -- /bin/sh

# telnet localhost 80
Connected to localhost

一個pod有多個容器時可以通過-c指定進入的容器名(通過describe檢視容器名稱),顯然通過localhost就可以輕鬆訪問到同一個pod中的nginx容器80埠。這也是在許多關係密切的應用中通常會部署在同一個pod中。


pod與pod通訊

  1. pod在同一主機

我們通過node選擇器將兩個pod排程到同一個node中

 ...
 nodeSelector:
        kubernetes.io/hostname: node2
 ...

兩個容器分別獲得一個IP地址,同樣通過IP地址雙方網路正常互通。

# kubectl get pod -o wide -n training 
NAME                                  READY   STATUS    RESTARTS   AGE     IP              NODE                    NOMINATED NODE   READINESS GATES

pod-to-pod-64444686ff-w7c4g           1/1     Running   0          6m53s   100.82.98.206   node2        <none>           <none>
pod-to-pod-busybox-7b9db67bc6-tl27c   1/1     Running   0          5m3s    100.82.98.250   node2        <none>           <none>
# kubectl exec -it  pod-to-pod-busybox-7b9db67bc6-tl27c  -n training -- /bin/sh
/# telnet 100.82.98.206 80
Connected to 100.82.98.206

同一主機網路的pod互通和我們之前學習的docker bridge相似,通過linux網橋新增虛擬裝置對veth pair連線容器和主機主機名稱空間。具體可檢視文章《docker容器網路bridge》。

我們把之前的圖拿過來,在k8s中只不過把灰色部分替換成CNI方案實現。

CNI-B

  1. pod在不同主機

此時我們的pod分佈如下:

kubectl get pod -o wide -n training 
NAME                                        READY   STATUS    RESTARTS   AGE    IP              NODE                    NOMINATED NODE   READINESS GATES

pod-to-pod-64444686ff-w7c4g                 1/1     Running   0          104m   100.82.98.206   node2        <none>           

pod-to-pod-busybox-node2-6476f7b7f9-mqcw9   1/1     Running   0          42s    100.91.48.208   node3        <none>    

# kubectl exec -it  pod-to-pod-busybox-node2-6476f7b7f9-mqcw9  -n training -- /bin/sh
/ # telnet 100.82.98.206 80
Connected to 100.82.98.206

pod在不同主機的通訊依賴於CNI外掛,這裡我們以Calico為例的做簡單瞭解,從Calico架構圖中可以看到每個node節點的自身依然採用容器網路模式,Calico在每個節點都利用Linux 核心實現了一個高效的虛擬路由器vRouter來負責資料轉發。每個虛擬路由器將路由資訊廣播到網路中,並新增路由轉發規則。同時基於iptables還提供了豐富的網路策略,實現k8s的Network Policy策略,提供容器間網路可達性限制的功能。

簡單理解就是通過在主機上啟動虛擬路由器(calico node),將每個主機作為路由器使用實現互聯互通的網路拓撲。

Calico節點組網時可以直接利用資料中心的網路結構(L2或者L3),不需要額外的NAT、隧道或者Overlay Network,沒有額外的封包解包,能夠節約CPU運算,提高網路效率。

calico


pod與service通訊

我們知道在k8s中容器隨時可能被摧毀,pod的IP顯然不是持久的,會隨著擴充套件或縮小應用規模、或者應用程式崩潰以及節點重啟等而消失和出現。service 設計就是來處理這個問題。service可以管理一組 Pod 的狀態,允許我們跟蹤一組隨時間動態變化的 Pod IP 地址。而客戶端只需要知道service這個不變的虛擬IP就可以了。

我們先來看看典型的service與pod使用,我們建立了一個service,標籤選擇器為app:nginx,將會路由到app=nginx標籤的Pod上。

service

# kubectl get service -n training
NAME               TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)    AGE
training-service   ClusterIP   10.96.229.238   <none>        8881/TCP   10m

Service對外暴露的埠8881,這樣在叢集的中的pod即可通過8881訪問到與service 繫結的label為app=nginx的pod

kubectl run -it --image nginx:alpine curl --rm /bin/sh
/ # curl 10.96.229.238:8881
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
...

其實大多數時候在自動化部署服務時並不知道service ip,所以另一種常見方式通過DNS進行域名解析後,可以使用“ServiceName:Port”訪問Service,可以自己嘗試一下。

service 是如何做到服務發現的?

Endpoints是k8s中的一種資源物件,k8s通過Endpoints監控到Pod的IP,service又關聯Endpoints從而實現Pod的發現。大致如下圖所示,service的發現機制我們會在後面文章中做深入瞭解。

endpoints


外網與service通訊

其實所謂外網通訊也是service的表現形式。

service幾種型別和不同用途。

  • ClusterIP:用於在叢集內部互相訪問的場景,通過ClusterIP訪問Service,即我們上面所說的pod與service。
  • NodePort:用於從叢集外部訪問的場景,通過節點上的埠訪問Service。
  • LoadBalancer:用於從叢集外部訪問的場景,其實是NodePort的擴充套件,通過一個特定的LoadBalancer訪問Service,這個LoadBalancer將請求轉發到節點的NodePort,而外部只需要訪問LoadBalancer。
  • None:用於Pod間的互相發現,這種型別的Service又叫Headless Service。

我們先來看NodePort:

service-nodeport

我們在service中指定type: NodePort建立出的service將會包含一個在所有node 開放的埠30678,這樣我們訪問任意節點IP:30678即可訪問到我們的pod

# kubectl get service -n training
NAME               TYPE       CLUSTER-IP      EXTERNAL-IP   PORT(S)          AGE
training-service   NodePort   10.96.229.238   <none>        8881:30678/TCP   55m

# curl 192.168.1.86:30678
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
....

LoadBalancer型別和它名字一樣,為負載均衡而生。它的結構如下圖所示,

loadbalancer

LoadBalancer本身不是屬於Kubernetes的元件,如果使用雲廠商的容器服務。通常會提供一套他們的負載均衡服務比如阿里雲ACK的SLB、華為雲的ELB等等。Service是基於四層TCP和UDP協議轉發的,而k8s 另外一種資源物件Ingress可以基於七層的HTTP和HTTPS協議轉發,可通過域名和路徑做到更細粒度的劃分,這是後話。


希望小作文對你有些許幫助,如果內容有誤請指正。

您可以隨意轉載、修改、釋出本文,無需經過本人同意。 通過部落格閱讀:iqsing.github.io

相關文章