摘要:kube-proxy採用iptables的方式配置負載均衡,基於iptables的kube-proxy的主要職責包括兩大塊:一塊是偵聽service更新事件,並更新service相關的iptables規則,一塊是偵聽endpoint更新事件,更新endpoint相關的iptables規則(如 KUBE-SVC-鏈中的規則),然後將包請求轉入endpoint對應的Pod。endpoint是k8s集羣中的一個資源對象,存儲在etcd中,用來記錄一個service對應的所有pod的訪問地址。

首先,我們思考這樣一個問題:

訪問k8s集羣中的pod, 客戶端需要知道pod地址,需要感知pod的狀態。那如何獲取各個pod的地址?若某一node上的pod故障,客戶端如何感知?

二、k8s service

什麼是service

  • 是發現後端pod服務;

  • 是爲一組具有相同功能的容器應用提供一個統一的入口地址;

  • 是將請求進行負載分發到後端的各個容器應用上的控制器。

對service的訪問來源

訪問service的請求來源有兩種:k8s集羣內部的程序(Pod)和 k8s集羣外部的程序。

service類型

採用微服務架構時,作爲服務所有者,除了實現業務邏輯以外,還需要考慮如何把服務發佈到k8s集羣或者集羣外部,使這些服務能夠被k8s集羣內的應用、其他k8s集羣的應用以及外部應用使用。因此k8s提供了靈活的服務發佈方式,用戶可以通過ServiceType來指定如何來發布服務,類型有以下幾種:

● ClusterIP:提供一個集羣內部的虛擬IP以供Pod訪問(service默認類型)。

service 結構如下

● NodePort:在每個Node上打開一個端口以供外部訪問

Kubernetes將會在每個Node上打開一個端口並且每個Node的端口都是一樣的,通過:NodePort的方式Kubernetes集羣外部的程序可以訪問Service。

● LoadBalancer:通過外部的負載均衡器來訪問

service selector

service通過selector和pod建立關聯。

k8s會根據service關聯到pod的podIP信息組合成一個endpoint。

若service定義中沒有selector字段,service被創建時,endpoint controller不會自動創建endpoint。

service負載分發策略

service 負載分發策略有兩種:

RoundRobin:輪詢模式,即輪詢將請求轉發到後端的各個pod上(默認模式);

SessionAffinity:基於客戶端IP地址進行會話保持的模式,第一次客戶端訪問後端某個pod,之後的請求都轉發到這個pod上。

三、服務發現

k8s服務發現方式

雖然Service解決了Pod的服務發現問題,但不提前知道Service的IP,怎麼發現service服務呢?

k8s提供了兩種方式進行服務發現:

● 環境變量: 當創建一個Pod的時候,kubelet會在該Pod中注入集羣內所有Service的相關環境變量。需要注意的是,要想一個Pod中注入某個Service的環境變量,則必須Service要先比該Pod創建。這一點,幾乎使得這種方式進行服務發現不可用。

例如:

一個ServiceName爲redis-master的Service,對應的ClusterIP:Port爲10.0.0.11:6379,則其在pod中對應的環境變量爲:

REDIS_MASTER_SERVICE_HOST=10.0.0.11  REDIS_MASTER_SERVICE_PORT=6379  REDIS_MASTER_PORT=tcp://10.0.0.11:6379  REDIS_MASTER_PORT_6379_TCP=tcp://10.0.0.11:6379  REDIS_MASTER_PORT_6379_TCP_PROTO=tcp
REDIS_MASTER_PORT_6379_TCP_PORT=6379  REDIS_MASTER_PORT_6379_TCP_ADDR=10.0.0.11

● DNS:可以通過cluster add-on的方式輕鬆的創建KubeDNS來對集羣內的Service進行服務發現————這也是k8s官方強烈推薦的方式。爲了讓Pod中的容器可以使用kube-dns來解析域名,k8s會修改容器的/etc/resolv.conf配置。

k8s服務發現原理

● endpoint

endpoint是k8s集羣中的一個資源對象,存儲在etcd中,用來記錄一個service對應的所有pod的訪問地址。

service配置selector,endpoint controller纔會自動創建對應的endpoint對象;否則,不會生成endpoint對象.

例如,k8s集羣中創建一個名爲k8s-classic-1113-d3的service,就會生成一個同名的endpoint對象,如下圖所示。其中ENDPOINTS就是service關聯的pod的ip地址和端口。

● endpoint controller

endpoint controller是k8s集羣控制器的其中一個組件,其功能如下:

  • 負責生成和維護所有endpoint對象的控制器
  • 負責監聽service和對應pod的變化
  • 監聽到service被刪除,則刪除和該service同名的endpoint對象
  • 監聽到新的service被創建,則根據新建service信息獲取相關pod列表,然後創建對應endpoint對象
  • 監聽到service被更新,則根據更新後的service信息獲取相關pod列表,然後更新對應endpoint對象
  • 監聽到pod事件,則更新對應的service的endpoint對象,將podIp記錄到endpoint中

四、負載均衡

kube-proxy

kube-proxy負責service的實現,即實現了k8s內部從pod到service和外部從node port到service的訪問。

kube-proxy採用iptables的方式配置負載均衡,基於iptables的kube-proxy的主要職責包括兩大塊:一塊是偵聽service更新事件,並更新service相關的iptables規則,一塊是偵聽endpoint更新事件,更新endpoint相關的iptables規則(如 KUBE-SVC-鏈中的規則),然後將包請求轉入endpoint對應的Pod。如果某個service尚沒有Pod創建,那麼針對此service的請求將會被drop掉。

kube-proxy的架構如下:

kube-proxy iptables

kube-proxy監聽service和endpoint的變化,將需要新增的規則添加到iptables中。

kube-proxy只是作爲controller,而不是server,真正服務的是內核的netfilter,體現在用戶態則是iptables。

kube-proxy的iptables方式也支持RoundRobin(默認模式)和SessionAffinity負載分發策略。

kubernetes只操作了filter和nat表。

Filter:在該表中,一個基本原則是隻過濾數據包而不修改他們。filter table的優勢是小而快,可以hook到input,output和forward。這意味着針對任何給定的數據包,只有可能有一個地方可以過濾它。

NAT:此表的主要作用是在PREROUTING和POSTROUNTING的鉤子中,修改目標地址和原地址。與filter表稍有不同的是,該表中只有新連接的第一個包會被修改,修改的結果會自動apply到同一連接的後續包中。

kube-proxy對iptables的鏈進行了擴充,自定義了KUBE-SERVICES,KUBE-NODEPORTS,KUBE-POSTROUTING,KUBE-MARK-MASQ和KUBE-MARK-DROP五個鏈,並主要通過爲KUBE-SERVICES chain增加rule來配製traffic routing 規則。我們可以看下自定義的這幾個鏈的作用:

KUBE-MARK-DROP - [0:0] /*對於未能匹配到跳轉規則的traffic set mark 0x8000,有此標記的數據包會在filter表drop掉*/
KUBE-MARK-MASQ - [0:0] /*對於符合條件的包 set mark 0x4000, 有此標記的數據包會在KUBE-POSTROUTING chain中統一做MASQUERADE*/
KUBE-NODEPORTS - [0:0] /*針對通過nodeport訪問的package做的操作*/
KUBE-POSTROUTING - [0:0]KUBE-SERVICES - [0:0] /*操作跳轉規則的主要chain*/

同時,kube-proxy也爲默認的prerouting、output和postrouting chain增加規則,使得數據包可以跳轉至k8s自定義的chain,規則如下:

-A PREROUTING -m comment --comment "kubernetes service portals" -j KUBE-SERVICES

-A OUTPUT -m comment --comment "kubernetes service portals" -j KUBE-SERVICES

-A POSTROUTING -m comment --comment "kubernetes postrouting rules" -j KUBE-POSTROUTING

如果service類型爲nodePort,(從LB轉發至node的數據包均屬此類)那麼將KUBE-NODEPORTS鏈中每個目的地址是NODE節點端口的數據包導入這個“KUBE-SVC-”鏈:

-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-NODEPORTS -p tcp -m comment --comment "default/es1:http" -m tcp --dport 32135 -j KUBE-MARK-MASQ

-A KUBE-NODEPORTS -p tcp -m comment --comment "default/es1:http" -m tcp --dport 32135 -j KUBE-SVC-LAS23QA33HXV7KBL

Iptables chain支持嵌套並因爲依據不同的匹配條件可支持多種分支,比較難用標準的流程圖來體現調用關係,建單抽象爲下圖:

舉個例子,在k8s集羣中創建了一個名爲my-service的服務,其中:

service vip:10.11.97.177

對應的後端兩副本pod ip:10.244.1.10、10.244.2.10

容器端口爲:80

服務端口爲:80

則kube-proxy爲該service生成的iptables規則主要有以下幾條:

-A KUBE-SERVICES -d 10.11.97.177/32 -p tcp -m comment --comment "default/my-service: cluster IP" -m tcp --dport 80 -j KUBE-SVC-BEPXDJBUHFCSYIC3

-A KUBE-SVC-BEPXDJBUHFCSYIC3 -m comment --comment “default/my-service:” -m statistic --mode random --probability 0.50000000000 -j KUBE-SEP-U4UWLP4OR3LOJBXU  //50%的概率輪詢後端pod
-A KUBE-SVC-BEPXDJBUHFCSYIC3 -m comment --comment "default/my-service:" -j KUBE-SEP-QHRWSLKOO5YUPI7O

-A KUBE-SEP-U4UWLP4OR3LOJBXU -s 10.244.1.10/32 -m comment --comment "default/my-service:" -j KUBE-MARK-MASQ
-A KUBE-SEP-U4UWLP4OR3LOJBXU -p tcp -m comment --comment "default/my-service:" -m tcp -j DNAT --to-destination 10.244.1.10:80
-A KUBE-SEP-QHRWSLKOO5YUPI7O -s 10.244.2.10/32 -m comment --comment "default/my-service:" -j KUBE-MARK-MASQ
-A KUBE-SEP-QHRWSLKOO5YUPI7O -p tcp -m comment --comment "default/my-service:" -m tcp -j DNAT --to-destination 10.244.2.10:80

kube-proxy通過循環的方式創建後端endpoint的轉發,概率是通過probability後的1.0/float64(n-i)計算出來的,譬如有兩個的場景,那麼將會是一個0.5和1也就是第一個是50%概率第二個是100%概率,如果是三個的話類似,33%、50%、100%。

kube-proxy iptables的性能缺陷

k8s集羣創建大規模服務時,會產生很多iptables規則,非增量式更新會引入一定的時延。iptables規則成倍增長,也會導致路由延遲帶來訪問延遲。大規模場景下,k8s 控制器和負載均衡都面臨這挑戰。例如,若集羣中有N個節點,每個節點每秒有M個pod被創建,則控制器每秒需要創建N_M個endpoints,需要增加的iptables則是N_M的數倍。以下是k8s不同規模下訪問service的時延:

從上圖中可以看出,當集羣中服務數量增長時,因爲 IPTables天生不是被設計用來作爲 LB 來使用的,IPTables 規則則會成倍增長,這樣帶來的路由延遲會導致的服務訪問延遲增加,直到無法忍受。

目前有以下幾種解決方案,但各有不足:

● 將endpoint對象拆分成多個對像

優點:減小了單個endpoint大小

缺點:增加了對象的數量和請求量

● 使用集中式負載均衡器

優點:減少了跟apiserver的連接和請求數

缺點:服務路由中又增加了一跳,並且需要集中式LB有很高的性能和高可用性

● 定期任務,批量創建/更新endpoint

優點:減少了每秒的處理數

缺點:在定期任務執行的間隔時間內,端對端延遲明顯增加

五、K8s 1.8 新特性——ipvs

ipvs與iptables的性能差異

隨着服務的數量增長,IPTables 規則則會成倍增長,這樣帶來的問題是路由延遲帶來的服務訪問延遲,同時添加或刪除一條規則也有較大延遲。不同規模下,kube-proxy添加一條規則所需時間如下所示:

可以看出當集羣中服務數量達到5千個時,路由延遲成倍增加。添加 IPTables 規則的延遲,有多種產生的原因,如:

添加規則不是增量的,而是先把當前所有規則都拷貝出來,再做修改然後再把修改後的規則保存回去,這樣一個過程的結果就是 IPTables 在更新一條規則時會把 IPTables 鎖住,這樣的後果在服務數量達到一定量級的時候,性能基本不可接受:在有5千個服務(4萬條規則)時,添加一條規則耗時11分鐘;在右2萬個服務(16萬條規則)時,添加一條規則需要5個小時。

這樣的延遲時間,對生產環境是不可以的,那該性能問題有哪些解決方案呢?從根本上解決的話,可以使用 “IP Virtual Server”(IPVS )來替換當前 kube-proxy 中的 IPTables 實現,這樣能帶來顯著的性能提升以及更智能的負載均衡功能如支持權重、支持重試等等。

那什麼是 “IP Virtual Server”(IPVS ) 呢?

ipvs 簡介

k8s 1.8 版本中,社區 SIG Network 增強了 NetworkPolicy API,以支持 Pod 出口流量策略,以及允許策略規則匹配源或目標 CIDR 的匹配條件。這兩個增強特性都被設計爲 beta 版本。 SIG Network 還專注於改進 kube-proxy,除了當前的 iptables 和 userspace 模式,kube-proxy 還引入了一個 alpha 版本的 IPVS 模式。

作爲 Linux Virtual Server(LVS) 項目的一部分,IPVS 是建立於 Netfilter之上的高效四層負載均衡器,支持 TCP 和 UDP 協議,支持3種負載均衡模式:NAT、直接路由(通過 MAC 重寫實現二層路由)和IP 隧道。ipvs(IP Virtual Server)安裝在LVS(Linux Virtual Server)集羣作爲負載均衡主節點上,通過虛擬出一個IP地址和端口對外提供服務。客戶端通過訪問虛擬IP+端口訪問該虛擬服務,之後訪問請求由負載均衡器調度到後端真實服務器上。

ipvs相當於工作在netfilter中的input鏈。

配置方法:IPVS 負載均衡模式在 kube-proxy 處於測試階段還未正式發佈,完全兼容當前 Kubernetes 的行爲,通過修改 kube-proxy 啓動參數,在 mode=userspace 和 mode=iptables 的基礎上,增加 mode=IPVS 即可啓用該功能。

ipvs轉發模式

● DR模式(Direct Routing)

特點:

<1> 數據包在LB轉發過程中,源/目的IP和端口都不會變化。LB只修改數據包的MAC地址爲RS的MAC地址

<2> RS須在環回網卡上綁定LB的虛擬機服務IP

<3> RS處理完請求後,響應包直接回給客戶端,不再經過LB

缺點:

<1> LB和RS必須位於同一子網

● NAT模式(Network Address Translation)

特點:

<1> LB會修改數據包地址:對於請求包,進行DNAT;對於響應包,進行SNAT

<2> 需要將RS的默認網關地址配置爲LB的虛擬IP地址

缺點:

<1> LB和RS必須位於同一子網,且客戶端和LB不能位於同一子網

● FULLNAT模式

特點:

<1> LB會對請求包和響應包都做SNAT+DNAT

<2> LB和RS對於組網結構沒有要求

<3> LB和RS必須位於同一子網,且客戶端和LB不能位於同一子網

● 三種轉發模式性能從高到低:DR > NAT >FULLNAT

ipvs 負載均衡器常用調度算法

● 輪詢(Round Robin)

LB認爲集羣內每臺RS都是相同的,會輪流進行調度分發。從數據統計上看,RR模式是調度最均衡的。

● 加權輪詢(Weighted Round Robin)

LB會根據RS上配置的權重,將消息按權重比分發到不同的RS上。可以給性能更好的RS節點配置更高的權重,提升集羣整體的性能。

● 最少連接調度

LB會根據和集羣內每臺RS的連接數統計情況,將消息調度到連接數最少的RS節點上。在長連接業務場景下,LC算法對於系統整體負載均衡的情況較好;但是在短連接業務場景下,由於連接會迅速釋放,可能會導致消息每次都調度到同一個RS節點,造成嚴重的負載不均衡。

● 加權最少連接調度

最小連接數算法的加權版。

● 原地址哈希,鎖定請求的用戶

根據請求的源IP,作爲散列鍵(Hash Key)從靜態分配的散列表中找出對應的服務器。若該服務器是可用的且未超載,將請求發送到該服務器。

原文出處

作者:陳Sir的知識庫

原文鏈接: https://www.jianshu.com/p/535e15109477

---本文結束感謝您的閱讀。 微信掃描二維碼,關注我的公衆號---

相關文章