1. 概述
接上一篇《深入 K8s 網路原理(一)- Flannel VXLAN 模式分析》,今天我們繼續來分析 Kubernetes Service 的實現原理。
2. 準備 Service 和 Pods 資源
映象和上一篇一樣;
Deployment 的 YAML 如下:
nginx-deploy.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
spec:
replicas: 2
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:test1
ports:
- containerPort: 80
對應的 Service YAML 如下:
nginx-service.yaml
apiVersion: v1
kind: Service
metadata:
name: nginx-service
spec:
type: NodePort
selector:
app: nginx
ports:
- protocol: TCP
port: 80
nodePort: 30007
3. K8s 裡 Service 的實現原理
Kubernetes 提供了幾種不同型別的 Service,包括:
ClusterIP
:這是最常見的 Service 型別,為 Service 提供一個叢集內部的 IP 地址,使得 Service 只能在叢集內部訪問。NodePort
:這種型別的 Service 在每個節點上開放一個埠(NodePort),從而允許從叢集外部透過 <NodeIP>:<NodePort> 訪問 Service。LoadBalancer
:這種型別的 Service 通常由雲提供商支援,它會在叢集外部建立一個負載均衡器,將外部流量分發到叢集內的 Pods。ExternalName
:透過返回一個名字(而非 IP 地址)來指向外部服務。
接著具體來看 Service 的實現。
3.1 kube-proxy 元件
Kubernetes 叢集中每個節點上會執行一個關鍵元件 kube-proxy,它負責為 Service 物件實現網路代理,使得網路流量可以透明地定向到後端 Pods。kube-proxy 支援幾種不同的代理模式,最常見的是 iptables
模式和 IPVS
模式。
- iptables 模式
- 在這種模式下,kube-proxy 使用 iptables 規則來捕獲到達 Service 的流量,並將其重定向到後端 Pods。每當 Service 或 Pod 發生變化時,kube-proxy 都會更新 iptables 規則。
- iptables 模式是最簡單且廣泛使用的,但在大規模叢集中可能會面臨效能問題,因為每個網路包都需要透過不短的規則鏈進行處理。
- IPVS (IP Virtual Server) 模式
- IPVS 模式使用核心的 IPVS 功能,該功能提供了內建的負載均衡功能。與 iptables 相比,IPVS 可以處理更大規模的流量,擁有更好的效能和更復雜的負載均衡演算法(最少連線等)。
- 在這種模式下,kube-proxy 會建立一個虛擬伺服器,為每個 Service 分配一個虛擬 IP(VIP),並將流量負載均衡到後端 Pods。
以 kube-proxy 的 iptables 模式為例,我們具體來看下 Service 建立後,iptables 是如何將 Service 流量轉到 pods 上的。
3.2 iptables 簡介
簡單介紹下 iptables:
iptables
是一種在 Linux 系統中廣泛使用的工具,它允許管理員配置核心的 netfilter 模組,以控制網路資料包的流入流出。這個工具提供了一個框架,用於定義規則,這些規則決定了如何處理經過網路介面的資料包。iptables 的核心功能主要包括:
- 資料包過濾:iptables 最常用於過濾資料包,即決定哪些資料包可以透過網路介面,哪些應該被阻止。
- 網路地址轉換 (NAT):它可以用於修改資料包的源或目的地址,常用於路由和隱藏內部網路結構。
- 埠轉發:iptables 可以重定向到特定埠的資料流,用於設定埠轉發。
剛從鋪灰的硬碟裡發現一張三年前畫的 iptables 相關的圖(我都忘記這個圖應該叫啥名字了):
3.3 iptables 規則
下面具體看下 Service 對應的 iptables 規則。
3.3.1 Service,Pod 和 Host 資訊
前面建立了 Service 和 Deployment,對應的 Service 和 Pod 資源如下:
kgsvc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
nginx-service NodePort 10.107.33.105 <none> 80:30007/TCP 152m
kgpoowide
NAME READY STATUS RESTARTS AGE IP NODE
nginx-deployment-7fbb8f4b4c-89bds 1/1 Running 0 155m 10.244.2.4 minikube-m03
nginx-deployment-7fbb8f4b4c-d29zm 1/1 Running 0 155m 10.244.1.5 minikube-m02
此外,我的 K8s 叢集包括3個節點,nodes 的 ip/hostname 資訊如下:
192.168.49.2 minikube
192.168.49.3 minikube-m02
192.168.49.4 minikube-m03
3.3.2 從 NodePort 入手尋找 iptables 規則
現在進到 minikube 節點,也就是 K8s 主節點,檢視下 nat 表的鏈資訊,過濾下 NodePort 埠 30007:
sudo iptables -t nat -L | grep 30007
KUBE-EXT-V2OKYYMBY3REGZOG tcp -- anywhere anywhere /* default/nginx-service */ tcp dpt:30007
可以看到一條鏈:KUBE-EXT-V2OKYYMBY3REGZOG,具體看下這條鏈的資訊:
sudo iptables -t nat -L KUBE-EXT-V2OKYYMBY3REGZOG -v
Chain KUBE-EXT-V2OKYYMBY3REGZOG (1 references)
pkts bytes target prot opt in out source destination
0 0 KUBE-SVC-V2OKYYMBY3REGZOG all -- any any anywhere anywhere
從這裡可以看到 KUBE-EXT-V2OKYYMBY3REGZOG 有一條子鏈 KUBE-SVC-V2OKYYMBY3REGZOG。繼續看 KUBE-SVC-V2OKYYMBY3REGZOG 鏈:
sudo iptables -t nat -L KUBE-SVC-V2OKYYMBY3REGZOG -v
Chain KUBE-SVC-V2OKYYMBY3REGZOG (2 references)
pkts bytes target prot opt in out source destination
0 0 KUBE-SEP-J2DHXTF62PN2AN4F all -- any any anywhere anywhere /* default/nginx-service -> 10.244.1.5:80 */ statistic mode random probability 0.50000000000
0 0 KUBE-SEP-A4R5AW5RLMEQF7RP all -- any any anywhere anywhere /* default/nginx-service -> 10.244.2.4:80 */
進一步可以找到兩條 KUBE-SVC-V2OKYYMBY3REGZOG 的子鏈 KUBE-SEP-J2DHXTF62PN2AN4F 和 KUBE-SEP-A4R5AW5RLMEQF7RP,這裡對應2個 pods。以 KUBE-SEP-J2DHXTF62PN2AN4F 為例繼續跟:
sudo iptables -t nat -L KUBE-SEP-J2DHXTF62PN2AN4F -v
Chain KUBE-SEP-J2DHXTF62PN2AN4F (1 references)
pkts bytes target prot opt in out source destination
0 0 DNAT tcp -- any any anywhere anywhere /* default/nginx-service */ tcp to:10.244.1.5:80
到這裡 target 不再是其他鏈,而是 DNAT,也就是請求 Service 的 NodePort 最終流量被 DNAT 到了 10.244.1.5:80
和 10.244.2.4:80
這兩個 Endpoints,它們分別對應2個 pods。
3.3.3 從 PREROUTING 和 OUTPUT 鏈尋找 K8s 相關子鏈
先看 PREROUTING:
sudo iptables -t nat -L PREROUTING -v
Chain PREROUTING (policy ACCEPT 5 packets, 300 bytes)
pkts bytes target prot opt in out source destination
99 5965 KUBE-SERVICES all -- any any anywhere anywhere /* kubernetes service portals */
可以看到入向流量全部都會經過 KUBE-SERVICES 鏈處理。繼續看 OUTPUT:
sudo iptables -t nat -L OUTPUT -v
Chain OUTPUT (policy ACCEPT 3355 packets, 202K bytes)
pkts bytes target prot opt in out source destination
29961 1801K KUBE-SERVICES all -- any any anywhere anywhere /* kubernetes service portals */
同樣全部出向流量也被 KUBE-SERVICES 鏈處理。繼續看下 KUBE-SERVICES 鏈:
sudo iptables -t nat -L KUBE-SERVICES -v
Chain KUBE-SERVICES (2 references)
pkts bytes target prot opt in out source destination
0 0 KUBE-SVC-V2OKYYMBY3REGZOG tcp -- any any anywhere 10.107.33.105 /* default/nginx-service cluster IP */ tcp dpt:http
3391 203K KUBE-NODEPORTS all -- any any anywhere anywhere /* kubernetes service nodeports; NOTE: this must be the last rule in this chain */ ADDRTYPE match dst-type LOCAL
可以看到2條子鏈,一個是表示 Nginx Service 的 Cluster IP 的子鏈 KUBE-SVC-V2OKYYMBY3REGZOG,另外一個是表示叢集 NodePort 的 KUBE-NODEPORTS 子鏈。KUBE-SVC-V2OKYYMBY3REGZOG 在前面已經具體看過了,那麼 KUBE-NODEPORTS 子鏈具體又包含啥資訊呢:
sudo iptables -t nat -L KUBE-NODEPORTS -v
Chain KUBE-NODEPORTS (1 references)
pkts bytes target prot opt in out source destination
0 0 KUBE-EXT-V2OKYYMBY3REGZOG tcp -- any any anywhere anywhere /* default/nginx-service */ tcp dpt:30007
可以看到當 TCP 流量的目的埠是 30007 的時候,就會匹配到 KUBE-EXT-V2OKYYMBY3REGZOG 子鏈,KUBE-EXT-V2OKYYMBY3REGZOG 子鏈的內容前面已經具體看過了。換言之,每多建立一個 NodePort 型別的 Service,kube-proxy 就會在 KUBE-NODEPORTS 子鏈下新掛一條 KUBE-EXT-XXX 子鏈。
3.3.4 總結下
IP 包進出主機都會經過 KUBE-SERVICES 鏈,進而根據 destination 地址匹配到不同的子鏈:
- 如果目的地址是某個 Service 的 Cluster IP,那麼就匹配到具體的 KUBE-SVC-XXX 處理;
- 否則,就匹配到 KUBE-NODEPORTS 處理;流量匹配到 KUBE-NODEPORTS 後,會進一步根據 tcp 目的埠 來匹配具體的子鏈 KUBE-EXT-XXX;
如果流量匹配到 KUBE-EXT-XXX 子鏈,埠命中,那麼下一條依舊是表示 Cluster IP 的 KUBE-SVC-XXX,所以兩條子鏈在這裡匯合。而 KUBE-SVC-XXX 鏈上的規則會進一步將 IP 包匹配到 KUBE-SEP-XXX 子鏈上,這些子連結串列達的是“Service Endpoints”。預設情況下 KUBE-SVC-XXX 會根據 Pod 數量按照相等的機率將流量分流到多個 KUBE-SEP-XXX 上進一步匹配。而 KUBE-SEP-XXX 鏈會執行類似“DNAT to:10.244.1.5:80”過程,最終將一開始請求 Service 的流量就丟給了 Pod。
所以,當你透過 NodePort/ClusterIP 方式訪問 pods 的時候,以兩副本為例,整體流量匹配過程大致如下圖:
4. 總結
不總結。明天見。