TKE基於彈性網卡直連Pod的網路負載均衡

前言

Kubernetes在集群接入層設計並提供了兩種原生資源ServiceIngress,分別負責四層和七層的網路接入層配置。

傳統的做法是創建Ingress或LoadBalancer類型的Service來綁定騰訊雲的負載均衡將服務對外暴露。這種做法將用戶流量負載到用戶節點的NodePort上,通過KubeProxy組件轉發到容器網路中,但這種方案在業務的性能和能力支援會有所局限。

為了解決這個問題,TKE容器團隊為在騰訊雲上使用獨立或託管集群的用戶提供了一種新的網路模式,利用彈性網卡直連Pod的方案很大的增強了性能和業務能力的支援

本文將會從傳統的模式的問題入手,比較新舊模式的區別,並在最後提供新直連模式的使用指引。

傳統模式面臨的問題與挑戰

性能與特性

KubeProxy在集群中會將用戶NodePort的流量通過NAT的方式轉發到集群網路中。這個NAT轉髮帶來了以下一些問題。

  1. NAT轉發導致請求在性能上有一定的損失。

    1. 進行NAT操作本身會帶來性能上的損失。
    2. NAT轉發的目的地址可能會使得流量在容器網路內跨節點轉發。
  2. NAT轉發導致請求的來源IP被修改了,客戶端無法獲取來源IP。

  3. 當負載均衡的流量集中到幾個NodePort時。過於集中的流量會導致NodePort的SNAT轉發過多,使得源埠耗盡流量異常。還可能導致 conntrack 插入衝突導致丟包,影響性能。

  4. KubeProxy的轉發具有隨機性,無法支援會話保持。

  5. KubeProxy的每個NodePort其實也起到獨立的負載均衡作用,由於負載均衡無法收斂到一個地方,所以難以達到全局的負載均衡。

為了解決以上問題,我們以前給用戶提供的技術建議主要是通過Local轉發的方式,避免KubeProxyNAT轉髮帶來的問題。但是因為轉發的隨機性,一個節點上部署多個副本時會話保持依舊無法支援。而且Local轉發在滾動更新時,容易出現服務的閃斷,對業務的滾動更新策略以及優雅停機提出了更高的要求。我們有理由去尋找更好的方案解決這個問題。

業務可用性

通過NodePort接入服務時,NodePort的設計存在極大的容錯性。負載均衡會綁定集群所有節點的NodePort作為後端。集群任意一個節點的訪問服務時,流量將隨機分配到集群的工作負載中。這就意味著部分NodePort的不可用,或者是Pod的不可用都不會影響服務的流量接入。

和Local訪問一樣,直接將負載均衡後端連接到用戶Pod的情況下,當業務在滾動更新時,如果負載均衡不能夠及時綁定上新的Pod,業務的快速滾動可能導致業務入口的負載均衡後端數量嚴重不足甚至被清空。因此,業務滾動更新的時候,接入層的負載均衡的狀態良好,方能保證滾動更新的安全平穩。

負載均衡的控制面性能

負載均衡的控制面介面。包括創建刪除修改四層、七層監聽器,創建刪除七層規則,綁定各個監聽器或者規則的後端。這些介面大部分是非同步介面,需要輪詢請求結果,介面的調用時間相對較長。當用戶集群規模較大時,大量的接入層資源同步會導致組件存在很大的時延上的壓力。

新舊模式對比

性能對比

Pod直連模式已經在騰訊TKE上線,是對負載均衡的控制面優化。針對整個同步流程,重點優化了批量調用和後端實例查詢兩個遠程調用比較頻繁的地方。優化完成後,Ingress典型場景下的控制面性能較優化前版本有了95%-97%左右的性能提升。目前同步的耗時主要集中在非同步介面的等待上。

後端節點突增 (應對集群擴容的場景)

七層規則突增(應對業務第一次上線部署到集群的場景)

除去控制面性能優化這樣的硬核優化,負載均衡能夠直接訪問容器網路的Pod就是組件業務能力最重要的組成部分了,其不僅避免了NAT轉發性能上的損失,同時避免了NAT轉髮帶來的各種對集群內業務功能影響。但是在啟動該項目時這一塊還沒有特別好的訪問容器網路的支援。所以一期考慮集群CNI網路模式下Pod有彈性網卡入口,這個入口可以直接接入到負載均衡以達到直接訪問的目的。負載均衡直接後端訪問到容器網路,目前已經有通過雲聯網解決的方案,後續也會繼續跟進這種更貼近集群網路的直連方案。

接下來能夠直接訪問了,如何保證滾動更新時的可用性保證呢?我們找到了官方提供的一個特性ReadinessGate。這個特性在1.12正式提供出來,主要是用來控制Pod的狀態。默認情況下,Pod有以下Condition:PodScheduled、Initialized、ContainersReady,當這幾個狀態都Ready的時候,Pod Ready的Condition就通過了。但是在雲原生的場景下面,Pod的狀態是非常有可能需要參考其他狀態的。ReadinessGate提供了這樣一個機制,允許為Pod的狀態判斷添加一個柵欄,由第三方來進行判斷與控制。這樣Pod的狀態就和第三方關聯起來了。

負載均衡流量對比

傳統NodePort模式

傳統NodePort接入

請求細節過程

  1. 請求流量進入負載均衡
  2. 請求被負載均衡轉發到某一個節點的NodePort
  3. KubeProxy將來自NodePort的流量進行NAT轉發,目的地址是隨機的一個Pod。
  4. 請求進入容器網路,並根據Pod地址轉發到對應節點。
  5. 請求來到Pod所屬節點,轉發到Pod。

新的Pod直連模式

ENI彈性網卡直連

請求細節過程

  1. 請求流量進入負載均衡
  2. 請求被負載均衡轉發到某一個Pod的ENI彈性網卡

直連與Local訪問的區別

看起來這兩種訪問方式的效果是一樣的,但是在細節上還是存在一些差別。

  1. 從性能上區別不大,開啟Local訪問時,流量不會進行NAT操作也不會進行跨節點轉發,所以僅僅多了一個到容器網路的路由。
  2. 沒有進行NAT操作,來源IP就能夠正確獲取了。會話保持功能可能會有以下問題,當一個節點上存在多個Pod時,流量到哪一個Pod是隨機的,這個機制可能會使話保持出現問題。

ReadinessGate的引入

前面有兩個細節,可以在這裡得到解答。

  1. 為什麼要求集群版本高於 1.12
  2. 為什麼kubectl get pod -o wide的結果中READINESS GATES列有內容。

這裡涉及到一個滾動更新相關的問題
當用戶開始為應用做滾動更新的時候,Kubernetes會根據更新策略進行滾動更新。但是其判斷一批Pod啟動的標識僅包括Pod自身的狀態,並不會考慮這個Pod在負載均衡上是否已經進行配置健康檢查是否通過。有的時候當接入層組件高負載,不能及時對這些Pod進行及時調度的話,這些滾動更新成功的Pod可能並沒有正在對外提供服務,從而導致服務的中斷。為了將滾動更新和負載均衡的後端狀態關聯起來,TKE接入層組件引入了Kubernetes 1.12中引入的新特性ReadinessGate。TKE接入層組件只有在確認後端綁定成功並且健康檢查通過時,通過配置ReadinessGate的狀態來使Pod達到Ready的狀態,從而推動整個工作負載的滾動更新。

在集群中使用ReadinessGate的細節
Kubernetes集群提供的是一個服務註冊的機制,你只需要將你的服務以MutatingWebhookConfigurations資源的形式註冊到集群中就可以了。集群會在Pod創建的時候按照你的配置的回調路徑通知你,這個時候就可以對Pod做一些創建前的操作,在這個Case裡面就是給Pod加上ReadinessGate。唯一需要注意的就是這個回調過程必須是Https的,所以標配需要在MutatingWebhookConfigurations中配置簽發請求的CA,並在服務端配置該CA簽發的證書。

ReadinessGate機制的災難恢復
用戶集群中的服務註冊或是證書有可能被用戶刪除,雖然這些系統組件資源不應該被用戶修改或破壞。但在用戶對集群的探索或是誤操作下,這類問題會不可避免的出現。所以接入層組件在啟動時會檢查以上資源的完整性,在完整性受到破壞時會重建以上資源,加強系統的魯棒性。

QPS和網路時延對比

直連與NodePort是服務應用的接入層方案,其實最終參與工作的還是用戶部署的工作負載,用戶工作負載的能力直接決定了業務的QPS等指標。所以我們針對這兩種接入層方案,在工作負載壓力較低的情況下,重點針對網路鏈路的時延進行了一些對比測試。直連在接入層的網路鏈路上能夠優化10%左右的時間。同時測試中的監控也發現,直連模式減少了大量VPC網路內的流量。測試場景,從20節點到80節點,逐步增大集群規模,通過wrk工具對集群進行網路延時的測試。針對QPS和網路時延,下圖給出了直連場景與NodePort的對比測試。

KubeProxy的一些設計思考

KubeProxy的缺點也在前文中提到的一樣明顯。但是基於雲上負載均衡、VPC網路的各種特性,我們能給出各種其他更加本地化的接入層方案。但這並不意味著KubeProxy的設計不好或是作用不大。其對集群接入層的設計極具普適性、容錯性,基本適用於所有業務場景下的集群,作為一個官方提供的組件這個設計是非常合適的。

新模式使用指引

前置要求

  1. Kubernetes集群版本需要高於 1.12。
  2. 集群網路模式必須開啟VPC-CNI彈性網卡模式。
  3. 直連模式Service使用的工作負載需使用VPC-CNI彈性網卡模式。

控制台操作指引

  1. 登錄 容器服務控制台

  2. 參考控制台 創建 Service 步驟,進入 「新建Service」 頁面,根據實際需求設置 Service 參數。

    其中,部分關鍵參數資訊需進行如下設置,如下圖所示:

    • 服務訪問方式:選擇為【提供公網訪問】或【VPC內網訪問】。
    • 網路模式:勾選【採用負載均衡直連Pod模式】。
    • Workload綁定:選擇【引用Worklocad】,並在彈出窗口中選擇 VPC-CNI 模式的後端工作負載。
  3. 單擊【創建服務】,完成創建。

Kubectl操作指引

  • Workload示例:nginx-deployment-eni.yaml

    注意spec.template.metadata.annotations中聲明了tke.cloud.tencent.com/networks: tke-route-eni,在工作負載使用VPC-CNI彈性網卡模式。

apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: nginx
name: nginx-deployment-eni
spec:
replicas: 3
selector:
matchLabels:
app: nginx
template:
metadata:
annotations:
tke.cloud.tencent.com/networks: tke-route-eni
labels:
app: nginx
spec:
containers:
– image: nginx:1.7.9
name: nginx
ports:
– containerPort: 80
protocol: TCP


- Service示例:nginx-service-eni.yaml

> 注意:`metadata.annotations`中聲明了`service.cloud.tencent.com/direct-access: "true"`,Service在同步負載均衡時將採用直連的方式配置訪問後端。

 ```yaml
apiVersion: v1
kind: Service
metadata:
annotations:
 service.cloud.tencent.com/direct-access: "true"
labels:
 app: nginx
name: nginx-service-eni
spec:
externalTrafficPolicy: Cluster
ports:
 - name: 80-80-no
   port: 80
   protocol: TCP
   targetPort: 80
selector:
 app: nginx
sessionAffinity: None
type: LoadBalancer
 ```

- 部署以上內容到集群

> 注意:在你的環境你首先需要連接到集群(沒有集群的需要先創建集群),可以參考文章尾部的幫助文檔配置kubectl連接集群。

 ```shell
➜  ~ kubectl apply -f nginx-deployment-eni.yaml
deployment.apps/nginx-deployment-eni created

➜  ~ kubectl apply -f nginx-service-eni.yaml
service/nginx-service-eni configured

➜  ~ kubectl get pod -o wide
NAME                                   READY   STATUS    RESTARTS   AGE   IP               NODE          NOMINATED NODE   READINESS GATES
nginx-deployment-eni-bb7544db8-6ljkm   1/1     Running   0          24s   172.17.160.191   172.17.0.3    <none>           1/1
nginx-deployment-eni-bb7544db8-xqqtv   1/1     Running   0          24s   172.17.160.190   172.17.0.46   <none>           1/1
nginx-deployment-eni-bb7544db8-zk2cx   1/1     Running   0          24s   172.17.160.189   172.17.0.9    <none>           1/1
➜  ~ kubectl get service -o wide
NAME                TYPE           CLUSTER-IP      EXTERNAL-IP      PORT(S)        AGE    SELECTOR
kubernetes          ClusterIP      10.187.252.1    <none>           443/TCP        6d4h   <none>
nginx-service-eni   LoadBalancer   10.187.254.62   150.158.221.31   80:32693/TCP   6d1h   app=nginx
 ```

## 總結

與業界對比,

- AWS有類似方案,通過彈性網卡的方式實現了Pod直連。
- GKE(Google Kubernetes Engine)也有類似方案。結合CLB(Google Cloud Load Balancing)的NEG(Network Endpoint Groups)特性實現接入層直連Pod。

現在騰訊雲TKE也利用彈性網卡實現了Pod直連的網路模式,現已在騰訊雲TKE上線。接下來,我們還計劃對這個特性進行更多優化,包括

1. 不依賴VPC-ENI的網路模式,實現普通容器網路下的Pod直連。
2. 支援在Pod刪除之前,摘除負載均衡後端。

歡迎大家一起來使用!

## 相關參考

1. [Kubernetes Service介紹](//kubernetes.io/docs/concepts/services-networking/service)
2. [Kubernetes Ingress介紹](//kubernetes.io/docs/concepts/services-networking/ingress)
3. [Kubernetes Deployments 滾動更新策略](//kubernetes.io/docs/concepts/workloads/controllers/deployment/#strategy)
4. [Kubernetes Pods ReadinessGate特性](//kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle/#pod-readiness-gate)
5. [Kubernetes 通過Local轉發獲取來源IP](//kubernetes.io/docs/tasks/access-application-cluster/create-external-load-balancer/#preserving-the-client-source-ip)
6. [TKE容器服務 網路模式選型](//cloud.tencent.com/document/product/457/41636)
7. [TKE容器服務 VPC-CNI網路模式](//cloud.tencent.com/document/product/457/34993)
8. [TKE容器服務 配置kubectl並連接集群](//cloud.tencent.com/document/product/457/32191)
9. [AWS ALB Ingress Controller](//aws.amazon.com/cn/blogs/opensource/kubernetes-ingress-aws-alb-ingress-controller/)
10. [GKE 通過獨立 NEG 配置容器原生負載平衡](//cloud.google.com/kubernetes-engine/docs/how-to/standalone-neg)
>【騰訊雲原生】雲說新品、雲研新術、雲遊新活、雲賞資訊,掃碼關注同名公眾號,及時獲取更多乾貨!!
![](//img2020.cnblogs.com/other/2041406/202009/2041406-20200914091252172-200457670.png)