在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