1. 程式人生 > >淺談 kubernetes service 那些事(上篇)

淺談 kubernetes service 那些事(上篇)

缺陷 resolv 拆分 load 性能 mar 基本原則 -i let

歡迎訪問網易雲社區,了解更多網易技術產品運營經驗。

一、問題

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

訪問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 定義如下:


     service 結構如下:


技術分享圖片

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

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

    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被創建,則控制器每秒需要創建NM個endpoints,需要增加的iptables則是NM的數倍。以下是k8s不同規模下訪問service的時延:

技術分享圖片

技術分享圖片


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

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

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

優點:減小了單個endpoint大小

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

● 使用集中式負載均衡器

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

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

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

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

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


相關閱讀:淺談 kubernetes service 那些事 (下篇)

本文來自網易實踐者社區,經作者張文娟授權發布。


相關文章:
【推薦】 Python包管理工具小結
【推薦】 移動端爬蟲工具與方法介紹

淺談 kubernetes service 那些事(上篇)