在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通訊形式。
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包含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通訊
- 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方案實現。
- 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運算,提高網路效率。
pod與service通訊
我們知道在k8s中容器隨時可能被摧毀,pod的IP顯然不是持久的,會隨著擴充套件或縮小應用規模、或者應用程式崩潰以及節點重啟等而消失和出現。service 設計就是來處理這個問題。service可以管理一組 Pod 的狀態,允許我們跟蹤一組隨時間動態變化的 Pod IP 地址。而客戶端只需要知道service這個不變的虛擬IP就可以了。
我們先來看看典型的service與pod使用,我們建立了一個service,標籤選擇器為app:nginx,將會路由到app=nginx標籤的Pod上。
# 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的發現機制我們會在後面文章中做深入瞭解。
外網與service通訊
其實所謂外網通訊也是service的表現形式。
service幾種型別和不同用途。
- ClusterIP:用於在叢集內部互相訪問的場景,通過ClusterIP訪問Service,即我們上面所說的pod與service。
- NodePort:用於從叢集外部訪問的場景,通過節點上的埠訪問Service。
- LoadBalancer:用於從叢集外部訪問的場景,其實是NodePort的擴充套件,通過一個特定的LoadBalancer訪問Service,這個LoadBalancer將請求轉發到節點的NodePort,而外部只需要訪問LoadBalancer。
- None:用於Pod間的互相發現,這種型別的Service又叫Headless 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本身不是屬於Kubernetes的元件,如果使用雲廠商的容器服務。通常會提供一套他們的負載均衡服務比如阿里雲ACK的SLB、華為雲的ELB等等。Service是基於四層TCP和UDP協議轉發的,而k8s 另外一種資源物件Ingress可以基於七層的HTTP和HTTPS協議轉發,可通過域名和路徑做到更細粒度的劃分,這是後話。
希望小作文對你有些許幫助,如果內容有誤請指正。
您可以隨意轉載、修改、釋出本文,無需經過本人同意。 通過部落格閱讀:iqsing.github.io