Kube Proxy 工作模式對比分析

引言

Kube Proxy 是 Kubernetes 生態的核心組件之一,主要負責處理訪問 Service 的流量(包括通過 Cluster IP 以及 Node Port),自動將 Client 對 Kubernetes Service 的請求代理到正確的 Pod 或 Node 後端。截止 1.18 版本,Kube Proxy 能夠支持的運行模式有 4 種,其平臺支持情況如下表:

Mode Linux Windows
userspace Y Y
iptables Y N
ipvs Y N
kernelspace N Y

注意:userspace 模式在實現上其實細分爲 Linux 的 userspace 和 Windows 平臺的 winuserspace,工作原理基本一致。之所以分兩套實現,筆者以爲主要是 Linux 的 userspace 實現仍部分依賴了 Linux 內核的 iptables 功能,而 Windows 平臺由於沒有 iptables 支持,所以使用了另外的機制(將 ClusterIP 綁定到網卡) 單獨實現了一番。

userspace 模式

在 userspace 模式下,kube-proxy 通過監聽 K8s apiserver 獲取關於 Service 和 Endpoint 的變化信息,在內存中維護一份從ClusterIP:Port 到後端 Endpoints 的映射關係,通過反向代理的形式,將收到的數據包轉發給後端,並將後端返回的應答報文轉發給客戶端。該模式下,kube-proxy 會爲每個 Service (每種協議,每個 Service IP,每個 Service Port)在宿主機上創建一個 Socket 套接字(監聽端口隨機)用於接收和轉發 client 的請求。默認條件下,kube-proxy 採用 round-robin 算法從後端 Endpoint 列表中選擇一個響應請求。

[root@machine ~]# kubectl get svc --all-namespaces
NAMESPACE     NAME         TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)                  AGE
default       kubernetes   ClusterIP   10.96.0.1    <none>        443/TCP                  8d
kube-system   kube-dns     ClusterIP   10.96.0.10   <none>        53/UDP,53/TCP,9153/TCP   8d
[root@machine ~]# netstat -apn | grep kube-proxy
tcp6       0      0 :::39748                :::*                    LISTEN      21092/kube-proxy    
tcp6       0      0 :::46122                :::*                    LISTEN      21092/kube-proxy    
tcp6       0      0 :::10256                :::*                    LISTEN      21092/kube-proxy    
tcp6       0      0 :::43892                :::*                    LISTEN      21092/kube-proxy

正如我們看到的,kube-proxy 會根據 Service IP(包括 CLUSTER-IP 和 EXTERNAL-IP)、協議以及端口號確定一個 Socket,在宿主機上打開一個監聽端口。客戶端訪問 Service 的流量會被分別路由到對應的 Socket 監聽端口得到處理。Service 到 Socket 的重定向路由,在 Linux 平臺採用 iptables 實現(在 Windows 平臺通過將 Service ClusterIP 綁定到網卡實現)。

Linux iptables 根據目的 Service 定向到指定的 Socket 監聽端口的規則示意:

[root@machine ~]# iptables -t nat -S
-A KUBE-PORTALS-CONTAINER -d 10.96.0.1/32 -p tcp -m comment --comment "default/kubernetes:https" -m tcp --dport 443 -j REDIRECT --to-ports 46122
-A KUBE-PORTALS-HOST -d 10.96.0.1/32 -p tcp -m comment --comment "default/kubernetes:https" -m tcp --dport 443 -j DNAT --to-destination 192.168.137.139:46122

userspace 模式工作原理示意:

在這裏插入圖片描述

iptables 模式

在 iptables 模式下,kube-proxy 依然需要通過監聽 K8s apiserver 獲取關於 Service 和 Endpoint 的變化信息。不過與 userspace 模式不同的是,kube-proxy 不再爲每個 Service 創建反向代理(也就是無需創建 Socket 監聽),而是通過安裝 iptables 規則,捕獲訪問 Service ClusterIP:Port 的流量,直接重定向到指定的 Endpoints 後端。默認條件下,kube-proxy 會 隨機 從後端 Endpoint 列表中選擇一個響應請求。ipatbles 模式與 userspace 模式的不同之處在於,數據包的轉發不再通過 kube-proxy 在用戶空間通過反向代理來做,而是基於 iptables/netfilter 在內核空間直接轉發,避免了數據的來回拷貝,因此在性能上具有很大優勢,而且也避免了大量宿主機端口被佔用的問題。

但是將數據轉發完全交給 iptables 來做也有個缺點,就是一旦選擇的後端沒有響應,連接就會直接失敗了,而不會像 userspace 模式那樣,反向代理可以支持自動重新選擇後端重試,算是失去了一定的重試靈活性。不過,官方建議使用 Readiness 探針來解決這個問題,一旦檢測到後端故障,就自動將其移出 Endpoint 列表,避免請求被代理到存在問題的後端。

[root@machine ~]# iptables -t nat -S
-A KUBE-SEP-XVJ25MO2MX75T34R -s 192.168.137.139/32 -j KUBE-MARK-MASQ
-A KUBE-SEP-XVJ25MO2MX75T34R -p tcp -m tcp -j DNAT --to-destination 192.168.137.139:6443
-A KUBE-SERVICES -d 10.96.0.1/32 -p tcp -m comment --comment "default/kubernetes:https cluster IP" -m tcp --dport 443 -j KUBE-SVC-NPX46M4PTMTKRN6Y
-A KUBE-SERVICES -m comment --comment "kubernetes service nodeports; NOTE: this must be the last rule in this chain" -m addrtype --dst-type LOCAL -j KUBE-NODEPORTS
-A KUBE-SVC-NPX46M4PTMTKRN6Y -j KUBE-SEP-XVJ25MO2MX75T34R

正如上面 iptables 規則列出的,訪問 10.96.0.1 443 端口的流量會被交由 KUBE-SVC-NPX46M4PTMTKRN6Y 處理,KUBE-SVC-NPX46M4PTMTKRN6Y 繼而跳轉到 KUBE-SEP-XVJ25MO2MX75T34R,最終 DNAT 到目的 Node 的 6443 端口,這正是 kubernetes apiserver 的監聽地址。因此,iptables 模式下,完全通過 iptables 自身就實現了流量轉發,不需要爲每個 Service 打開 Socket 監聽端口了。注意,這裏最上邊那條是 DNAT 的配對策略,DNAT 的回包需要做 UN-DNAT(即 SNAT/MASQ)。

iptables 模式工作原理示意:

在這裏插入圖片描述

ipvs 模式

在 ipvs 模式下,kube-proxy 通過監聽 K8s apiserver 獲取關於 Service 和 Endpoint 的變化信息,然後調用 netlink 接口創建 ipvs 規則,並週期性地同步 Kubernetes Service/Endpoint 與 ipvs 規則,保證狀態匹配。當客戶端請求 Service 時,ipvs 會將流量重定向到指定的後端。

ipvs 相比 iptables 的優勢在於通過 hash table 實現了 O(1) 時間複雜度的規則匹配。當 Service 和 Endpoint 的數量急劇增加時,iptables 的匹配表項會變得十分龐大,而其採用的順序匹配模式會嚴重影響其性能。相反,ipvs 無論在小規模集羣還是大規模集羣,都擁有幾乎相同的性能表現,因此相比其他代理模式,能提供更高的網絡吞吐量,更能滿足大規模集羣擴展性和高性能的需要。同時,ipvs 支持對後端的健康檢查和連接重試,可靠性相比 iptables 更佳。此外,ipvs 模式還爲用戶提供了多種負載均衡策略以供選擇,例如:

  • rr: round-robin (輪詢,默認採用)
  • lc: least connection (最少連接數)
  • dh: destination hashing (根據目的哈希)
  • sh: source hashing (根據源哈希)
  • sed: shortest expected delay (最小延遲)
  • nq: never queue (不排隊等待,有空閒 Server 直接分配給空閒 Server 處理,否則通過 sed 策略處理)

ipvs 仍然使用 iptables 實現對數據包的過濾、SNAT 或 MASQ,但使用 ipset 來存儲需要執行 MASQ 或 DROP 的源地址和目的地址,從而保證在大量 Service 存在的情況下,iptables 表項數量仍能維持在常數級。

通過 ipvsadm、ipset 工具可以查看 Service ClisterIP:Port 到後端 Pod/Node 映射關係。

[root@machine ~]# iptables -t nat -S
-A PREROUTING -m comment --comment "kubernetes service portals" -j KUBE-SERVICES
-A KUBE-SERVICES -m set --match-set KUBE-CLUSTER-IP dst,dst -j ACCEPT
[root@machine ~]# ip addr show dev kube-ipvs0
68: kube-ipvs0: <BROADCAST,NOARP> mtu 1500 qdisc noop state DOWN group default 
    link/ether fe:04:6f:5a:01:3e brd ff:ff:ff:ff:ff:ff
    inet 10.96.0.10/32 brd 10.96.0.10 scope global kube-ipvs0
       valid_lft forever preferred_lft forever
    inet 10.96.0.1/32 brd 10.96.0.1 scope global kube-ipvs0
       valid_lft forever preferred_lft forever
[root@machine ~]# ipset list
Name: KUBE-CLUSTER-IP
Type: hash:ip,port
Revision: 2
Header: family inet hashsize 1024 maxelem 65536
Size in memory: 16656
References: 2
Members:
10.96.0.10,tcp:53
10.96.0.10,tcp:9153
10.96.0.10,udp:53
10.96.0.1,tcp:443
[root@machine ~]# ipvsadm -ln
IP Virtual Server version 1.2.1 (size=4096)
Prot LocalAddress:Port Scheduler Flags
  -> RemoteAddress:Port           Forward Weight ActiveConn InActConn
TCP  10.96.0.1:443 rr
  -> 192.168.137.139:6443         Masq    1      1          0         
TCP  10.96.0.10:53 rr
  -> 10.80.1.233:53               Masq    1      0          0         
TCP  10.96.0.10:9153 rr
  -> 10.80.1.233:9153             Masq    1      0          0         
UDP  10.96.0.10:53 rr
  -> 10.80.1.233:53               Masq    1      0          0   

如上所示的,首先系統通過 iptables 允許訪問 KUBE-CLUSTER-IP ipset 的流量進入,並通過在虛擬網卡 kube-ipvs0 上配置 VIP 的方式,告訴內核識別 VIP 爲本機 IP,從而走 iptables 的 INPUT 鏈,繼而通過 IP Virtual Server 找到 Real Server 提供服務。

注意:由於 KUBE-CLUSTER-IP 的類型爲 hash:ip,port,在 --match-set KUBE-CLUSTER-IP dst,dst 匹配規則中,第一個 dst 表示將 set 中的 IP 作爲目的地址,第二個 dst 表示將 set 中的 Port 作爲目的端口。

ipvs 模式工作原理示意:

在這裏插入圖片描述

kernelspace 模式

kernelspace 也可以說是 winkernel 模式,因爲它只應用於 Windows 平臺。在 winkernel 模式下,kube-proxy 通過監聽 K8s apiserver 獲取關於 Service 和 Endpoint 的變化信息,然後通過 HNS (Host Network Service) 接口直接配置 Windows 內核的 LoadBalancer 以及 Endpoint ,實現基於 winkernel 的 Kubernetes Service 代理服務。

winkernel 模式相對 winuserspace 模式的改進與 iptables 模式相對 userspace 模式的改進類似,即避免了主動創建大量的 Socket 監聽,也免去了頻繁且低效的用戶態-內核態數據拷貝,直接通過配置系統的 LoadBalancer 和 Endpoint 實現代理,性能上具有明顯優勢。

winkernel 模式工作原理示意:

在這裏插入圖片描述

附加:設置會話保持

在以上幾種模式中,客戶端對 Service ClusterIP:Port 的訪問都被透明代理到合適的後端,客戶端不會感知到 Kubernetes、Service 或是 Pod。默認條件下,代理後端認爲每次請求都是獨立的,會分別按照策略選擇後端進行響應。如果你希望來自同一個客戶端的請求每次都被同一個後端響應(即會話保持),可以通過設置 Kubernetes Service 的 service.spec.sessionAffinity 字段爲 “ClientIP”,保證同一 Client 對 Service 的請求被定向轉發到同一 Endpoint(其內部其實是通過一個 Map 結構記錄了 Client 與 Endpoint 的對應關係)。同時,你還可以通過設置 service.spec.sessionAffinityConfig.clientIP.timeoutSeconds 字段定義會話保持的最長時間,默認是 3 小時。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章