雲原生應用負載均衡系列 (2): 入口流量分發、容錯與高可用排程

騰訊雲原生發表於2021-07-02

引言

在雲原生應用負載均衡系列第一篇文章《雲原生應用負載均衡選型指南》介紹了雲原生容器環境的入口流量管理使用場景與解決方案,用 Envoy 作為資料面代理,搭配 Istio 作控制面的 Istio Ingress Gateway,在多叢集流量分發、安全、可觀測性、異構平臺支援等方面的綜合對比中,是雲原生應用流量管理的最佳方案。

在接入層,我們需要配置路由規則、流量行為(超時、重定向、重寫、重試等)、負載均衡演算法、斷路器、跨域等流量管理規則,本文將基於 Istio Ingress Gateway 面向入口流量分發、容錯與高可用排程介紹上述功能的原理與演示。

元件介紹

Istio Ingress Gateway,可作為 Kubernetes 的一種 Ingress Controller 12,由資料面(Envoy 網路代理 1)與控制面 Istiod 13組成,預設以 Deployment 的形式部署 Pod 至 Kubernetes 叢集。

服務發現

Istio Ingress Gateway 控制面 Istiod 可聯通各種服務發現系統(Kubernetes,Consul,DNS)獲取 endpoints 資訊,或者我們可以使用 ServiceEntry 手動新增服務與 endpoints 對應資訊。以常見的 Kubernentes 叢集為例,Istiod 從 API Server 獲取 Kubernetes Services 及其對應的 endpoints,對應關係實時監測與自動更新。獲取服務發現資訊後,Istiod 會將其轉化為資料面標準資料格式,以 Envoy xDS API 的形式 push 到實際執行流量轉發操作的資料面 Envoy 網路代理。

值得注意的是,Istio Ingress Gateway 的資料面 Envoy 是以單獨 Pods 的形式部署於 Kubernetes 叢集,只需使用 Istio 南北向流量管理能力時,無需在業務 Pods 注入 Istio 資料面 sidecar,Ingress Gateway 的 Envoy Pods 即可承載全部入口流量管理的能力,因為 Istio 入口流量管理的功能大部分是在 Envoy 網路代理的 client 端(Istio Ingress Gateway)實現的。

Istio 流量管理模型及 API 介紹

Istio 設計了自己的流量管理 API,主要通過 Gateway,VirtualService,DestinationRule 這幾個 CR(Kubernetes Custom Resource 2)實現。

  • Gateway:配置監聽規則。包括埠、協議、host、SSL 等。
  • VirtualService:配置路由規則。包括匹配條件、流量行為、路由目的服務/版本等。
  • DestinationRule:配置服務版本負載均衡連線池健康檢查策略

我們可以與安裝 Istio 控制面所在叢集的 API Server 互動,提交上述 CR,配置流量管理規則。Istiod 會與該叢集的 API Server 互動,獲取 Istio API,將這些配置轉化為 Envoy 資料面標準資料格式,通過 xDS 介面 push 至資料面(Istio Ingress Gateway),資料面即可根據相應規則轉發流量。

入口流量管理實踐

下面以 Istio Ingress Gateway 為例介紹入口流量分發、容錯與高可用排程的實踐。

環境準備

環境準備可以使用 TCM 控制檯 「一鍵體驗」功能,將自動為您準備 TCM + 2 個跨可用區 Kubernetes 叢集 + TCM demo 的初始環境。

我們準備一個 Istio Ingress Gateway(使用騰訊雲服務網格 TCM 演示) + Kubernetes 叢集(使用騰訊雲容器服務 TKE 演示)環境,首先建立一個服務網格例項,並在網格例項中建立 Istio Ingress Gateway,然後新增一個 TKE 叢集作為網格的服務發現叢集。

在叢集部署 TCM demo 3,無需為業務 Pod 注入 envoy sidecar。該 demo 是一個電商網站,由不同語言開發的 6 個微服務組成,以下是 demo 的完整結構:

本次演示入口流量管理會使用 demo 中的 user、product、cart 三個應用,將其提供的 API 通過 istio- ingressgateway 暴露供客戶端呼叫。

入口流量分發

應用釋出

業務需要將多個後端模組提供的 API 暴露供客戶端呼叫,需要配置閘道器路由規則,將請求路徑 /product 的流量路由至 product 服務,將 /cart 的請求路由至 cart 服務,將 /user 的請求路由至 user 服務。

TCM demo 中,product 服務提供 /product 介面,可獲取在售商品列表;user 服務提供 /user 介面,可獲取使用者資訊;cart 服務提供 /cart 介面,可獲取購物車商品列表。下面我們配置 Istio Ingress Gateway 按照請求的路徑暴露上述介面。

1. 首先我們通過 kubectl 獲取 Ingress Gateway 的 IP,並配置為變數 $INGRESS_HOST,方便後續直接引用。

$ export INGRESS_HOST=$(kubectl -n istio-system get service istio-ingressgateway -o jsonpath='{.status.loadBalancer.ingress[0].ip}')

2. 使用 Gateway 配置 Ingress Gateway 監聽規則,開啟 80 埠,HTTP 協議,暫不配置 SSL。

apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
  name: apis-gw
spec:
  servers:
    - port:
        number: 80
        name: HTTP-80-6wsk
        protocol: HTTP
      hosts:
        - '*'
  selector:
    app: istio-ingressgateway
    istio: ingressgateway

3. 使用 VirtualService 配置路由規則,gateway 引數填寫上一步建立的 default/apis-gw,http 路由匹配規則將 /product 的請求路由至 product 服務,/user 路由至 user 服務,/cart 路由至 cart 服務。

apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: apis-vs
spec:
  hosts:
    - '*'
  gateways:
    - default/apis-gw
  http:
    - match:
        - uri:
            exact: /product
      route:
        - destination:
            host: product.base.svc.cluster.local
    - match:
        - uri:
            exact: /user
      route:
        - destination:
            host: user.base.svc.cluster.local
    - match:
        - uri:
            exact: /cart
      route:
        - destination:
            host: cart.base.svc.cluster.local

4. 使用 curl 驗證上述配置,請求 API 返回的 JSON 字串使用 jq 解析,提取出返回的 service 資訊。請求已按照預設方式按路徑路由至不同服務。

$ curl http://$INGRESS_HOST/product | jq '.info[0].Service'
...
"product-v1"
$ curl http://$INGRESS_HOST/cart | jq '.Info[1].Service'
...
"cart-v1"
$ curl http://$INGRESS_HOST/user | jq '.Info[0].Service'
...
"user-v1"

灰度釋出

業務需要升級 product 服務,已經完成準備新版本映象,可以開始部署新版本的 pods。希望服務端在升級時需考慮版本的平滑、無風險迭代,按百分比調整流量逐步切換至新版本服務,灰度驗證新版本無問題後再下線舊版本。

以下是配置 Ingress Gateway 做 product 服務灰度釋出的流程:

1. 使用 DestinationRule 定義 product 服務的版本(subsets),用標籤鍵值匹配即可定義一個服務內 subsets 與 endpoints 對應關係。

apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
  name: product
  namespace: base
spec:
  host: product
  subsets:
    - name: v1
      labels:
        version: v1
    - name: v2
      labels:
        version: v2
  exportTo:
    - '*'

2. 使用 VirtualService 配置灰度釋出路由策略,釋出最初 v2 版本為 0% 的流量,v1 版本 100%。

apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: apis-vs
spec:
  hosts:
    - '*'
  gateways:
    - default/apis-gw
  http:
    - match:
        - uri:
            exact: /product
      route:
        - destination:
            host: product.base.svc.cluster.local
            subset: v2
          weight: 0
        - destination:
            host: product.base.svc.cluster.local
            subset: v1
          weight: 100
    - match:
        - uri:
            exact: /user
      route:
        - destination:
            host: user.base.svc.cluster.local
    - match:
        - uri:
            exact: /cart
      route:
        - destination:
            host: cart.base.svc.cluster.local

3. 我們把 TCM demo product 服務的 v2 版本(deployment)5 部署到叢集,然後模擬發起 10 次請求呼叫 product 服務,返回結果表明釋出最初所有流量仍然是路由到 v1 版本。

$ for((i=0;i<10;i++)) do curl http://$INGRESS_HOST/product | jq '.info[0].Service'; done
"product-v1"
"product-v1"
"product-v1"
"product-v1"
"product-v1"
"product-v1"
"product-v1"
"product-v1"
"product-v1"
"product-v1"

4. 修改 VirtualService,把 product v1 和 v2 subset 的權重值分別調整為:50,50。模擬發起 10 次請求呼叫 product 服務,結果如預設,v2 和 v1 版本呼叫次數的比例接近 1:1。

$ for((i=0;i<10;i++)) do curl http://$INGRESS_HOST/product | jq '.info[0].Service'; done
"product-v1"
"product-v2"
"product-v1"
"product-v2"
"product-v1"
"product-v2"
"product-v1"
"product-v2"
"product-v1"
"product-v1"

5. 灰度驗證完成後,修改灰度釋出路由規則,修改 VirtualService 調整 v1 v2 subset 權重分別為:0,100,將 100% 請求 /product 的流量路由至 product v2 版本。再次模擬發起 10 次請求驗證,所有請求已被路由至 product v2,灰度釋出完成。

$ for((i=0;i<10;i++)) do curl http://$INGRESS_HOST/product | jq '.info[0].Service'; done
"product-v2"
"product-v2"
"product-v2"
"product-v2"
"product-v2"
"product-v2"
"product-v2"
"product-v2"
"product-v2"
"product-v2"

會話保持

同一後端服務一般由多個例項(Pod)承載,通常需要將入口流量在多個後端例項負載均衡(例如 product 服務)負載均衡,此時配置的是 round robin、random 或最小連線數等負載均衡演算法,保持多個後端例項均衡處理流量。
在一些特定情況下,需要將來自同一使用者的請求路由到相同後端例項,保證某些需要會話保持的服務(例如 cart 購物車服務)能夠正常工作。

會話保持有兩種:

  • 基於 IP 的簡單會話保持:來自同一 IP 地址的請求判定為同一使用者,路由至相同後端服務例項。實現簡單、方便,但無法支撐多個客戶端使用代理訪問後端服務的場景,此時同一 IP 地址不代表同一使用者。
  • 基於 cookie(或其他 7 層資訊)的會話保持:使用者第一次請求時,邊緣閘道器為其插入 cookie 並返回,後續客戶端使用此 cookie 訪問,邊緣閘道器根據 cookie 將流量負載至後端服務例項。

Istio Ingress Gateway 可設定基於 IP、cookie、HTTP header 的會話保持,但該策略只對 HTTP 流量生效。下面將配置 cart 服務的 IP、cookie 兩種會話保持負載均衡策略。

如使用開源 Istio Ingress Gateway + CLB,配置邊緣閘道器流量管理規則存在如下問題:

  • 手動修改關聯 Service 規則:使用 Gateway 配置了 Ingress Gateway 的埠後,需要手動配置 Ingress Gateway 關聯的 LoadBalancer Service 的埠規則。
  • 無法獲取真實源 IP:使用預設容器網路模式時,istio-ingressgateway 通過 loadbalancer service 的方式暴露給公網訪問,流量經由 CLB 到節點的 NodePort 後,kube-proxy 會將原始請求做 SNAT 和 DNAT,因此請求到達 istio-ingressgateway 時,源 IP 已不是真實 client IP。

使用 TCM Ingress Gateway + TKE 叢集則可避免上述問題:

  • TCM 已實現 Gateway 埠配置自動化同步至 Ingress Gateway 相關 Kubernetes Service 及關聯 CLB,我們使用 Gateway CR 即可一致化管理 Ingress Gateway 例項的埠。
  • 可以通過指定 istio-ingressgateway service 的 externalTrafficPolicy: Local 來避免流量通過 NAT 在節點之間轉發,保留了真實 client IP。同時我們可以通過新增註解 service.kubernetes.io/local-svc-only-bind-node-with-pod: "true" 來指定 CLB 後端只繫結有 istio-ingressgateway Pod 的節點,避免因後端繫結了不存在 Pod 例項的節點導致健康檢查失敗的問題。也可以增加註解 service.cloud.tencent.com/local-svc-weighted-balance: "true" 讓 CLB 根據後端節點上的 Pod 數量做加權負載均衡,避免因不同節點 Pod 例項數量不一導致的負載不均問題 6

基於 IP 的會話保持

1. Ingress Gateway 可獲取真實 client IP 後,我們通過 DestinationRule 配置 cart 服務的基於 IP 的簡單負載均衡:

apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
  name: cart
  namespace: base
spec:
  host: cart
  trafficPolicy:
    loadBalancer:
      consistentHash:
        useSourceIp: true
  exportTo:
    - '*'

2. 獲取 cart 服務當前的 pods,一共部署了 3 個 pods。

$ kubectl get deployment cart -n base
NAME   READY   UP-TO-DATE   AVAILABLE   AGE
cart   3/3     3            3           4d23h

3. 模擬發起 10 次請求 /cart 驗證,獲取提供購物車服務的 pod 資訊,所有請求均被路由到了同一個 pod,基於 IP 負載均衡配置成功。

$ for((i=0;i<10;i++)) do curl http://$INGRESS_HOST/cart | jq '.Info[1].Pod'; done
"cart-855f9d75ff-x47bq"
"cart-855f9d75ff-x47bq"
"cart-855f9d75ff-x47bq"
"cart-855f9d75ff-x47bq"
"cart-855f9d75ff-x47bq"
"cart-855f9d75ff-x47bq"
"cart-855f9d75ff-x47bq"
"cart-855f9d75ff-x47bq"
"cart-855f9d75ff-x47bq"
"cart-855f9d75ff-x47bq"

基於 cookie 的會話保持

1. 修改 cart 服務的 DestinationRule,配置基於 cookie 的負載均衡。cookie name 為 cookie,cookie 過期時間為 900000 ms(900 s)。

apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
  name: cart
  namespace: base
spec:
  host: cart
  trafficPolicy:
    loadBalancer:
      consistentHash:
        httpCookie:
          name: cookie
          ttl: 900000ms
  exportTo:
    - '*'

2. 發起第一次 /cart 請求,獲取 Ingress Gateway 返回的 cookie ID 及 pod 資訊,使用該 cookie ID 模擬發起 10 次 /cart 請求,第一次請求和後續 10 次請求,流量均被路由到了同一個 pod(在本例中是 cart-855f9d75ff-dqg6b),基於 cookie 的負載均衡配置生效。

$ curl http://$INGRESS_HOST/cart -i
...
set-cookie: cookie="bc0e96c66ff8994b"; Max-Age=900; HttpOnly
...
Pod":"cart-855f9d75ff-dqg6b"
...
$ for((i=0;i<10;i++)) do curl http://$INGRESS_HOST/cart -b 'cookie=bc0e96c66ff8994b' | jq '.Info[1].Pod'; done
"cart-855f9d75ff-dqg6b"
"cart-855f9d75ff-dqg6b"
"cart-855f9d75ff-dqg6b"
"cart-855f9d75ff-dqg6b"
"cart-855f9d75ff-dqg6b"
"cart-855f9d75ff-dqg6b"
"cart-855f9d75ff-dqg6b"
"cart-855f9d75ff-dqg6b"
"cart-855f9d75ff-dqg6b"
"cart-855f9d75ff-dqg6b"

容錯

連線池管理

連線池是保持分散式系統(服務化應用)穩定的重要配置。分散式系統中其中一個服務因請求數暴增而有故障風險時,快速返回失敗資訊儘快將壓力施加給下游服務能有效避免整個系統發生雪崩。我們可通過連線池為有需要的服務配置 TCP/HTTP 的連線數/請求數閾值,達到閾值後拒絕處理新增流量返回錯誤資訊,能有效保護服務執行的穩定性 7

下面我們配置 user 服務的連線池:

1. 首先我們部署一組 curl pods (30 個)模擬向 user 服務發起併發請求,受各 pod 執行環境影響,實際併發量應該 < 30。

apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    k8s-app: curl
    qcloud-app: curl
  name: curl
  namespace: default
spec:
  replicas: 30
  selector:
    matchLabels:
      k8s-app: curl
      qcloud-app: curl
  template:
    metadata:
      labels:
        k8s-app: curl
        qcloud-app: curl
    spec:
      containers:
      - args:
        - -c
        - while true; do curl http://$INGRESS_HOST/user; done
        command:
        - /bin/sh
        image: docker.io/byrnedo/alpine-curl
        imagePullPolicy: IfNotPresent
        name: curl

2. 使用 DestinationRule 配置 user 服務的連線池,為方便觀察效果,我們配置 http1 的最大請求等待數為 1,http2 的最大請求數為 1。

apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
  name: user
  namespace: base
spec:
  host: user
  trafficPolicy:
    connectionPool:
      http:
        http1MaxPendingRequests: 1
        http2MaxRequests: 1
  exportTo:
    - '*'

3. 在 Grafana Dashboard 檢視 user 服務的監控,可以看到配置了連線池後,因大部分請求被返回 503 Service Unavailable 狀態碼,因此客戶端請求成功率驟降,user 服務超載配置成功,DestinationRule 連線池配置起到了很好的保護服務端穩定性的作用。

健康檢查

當後端服務例項(Pod)在處理流量過程中發生故障時(連續返回錯誤,成功率降低到閾值之下等),Ingress Gateway 需要可以配置將故障的 endpoints 從健康負載均衡池中剔除的策略,保證客戶端呼叫可以由狀態正常的後端服務例項處理。
另外,地域感知負載均衡功能也需要開啟異常檢測,感知各地 endpoint 的健康狀態才能確定流量排程策略。

Ingress Gateway(envoy)的 Outlier Detection 是一種被動健康檢查,當流量出現了類似連續 5xx 錯誤(HTTP)、連線超時/失敗(TCP)等行為時,將其識別為離群值從負載均衡池中剔除一段時間 8。下面我們配置 user 服務的健康檢查策略。

1. 首先我們部署一組會為請求 /user 返回 503 錯誤的 pods 作為 user 服務的不健康 endpoints,部署完成後檢視 user 服務的 endpoint 情況,有 1 個健康 user pod,1 個不健康 user pod。

apiVersion: apps/v1
kind: Deployment
metadata:
  name: user-unhealthy
  namespace: base
spec:
  replicas: 1
  selector:
    matchLabels:
      app: user
  template:
    metadata:
      labels:
        app: user
    spec:
      containers:
      - command:
        - sleep
        - "9000"
        env:
        - name: POD_NAME
          valueFrom:
            fieldRef:
              apiVersion: v1
              fieldPath: metadata.name
        - name: REGION
          value: shanghai-zone1
        image: docker.io/busybox
        imagePullPolicy: IfNotPresent
        name: user
        ports:
        - containerPort: 7000
$ kubectl get deployment -n base user user-unhealthy 
NAME             READY   UP-TO-DATE   AVAILABLE   AGE
user             1/1     1            1           6d19h
user-unhealthy   1/1     1            1           3d20h

2. 當前還未配置 user 服務的 Outlier Detection(被動健康檢查),不健康的 endpoint 不會被從負載均衡池中剔除,因此發起的請求部分成功(200 OK),部分失敗(503 Service Unavailable)。

$ for((i=0;i<10;i++)) do curl -I http://$INGRESS_HOST/user | grep HTTP/1.1; done
HTTP/1.1 200 OK
HTTP/1.1 200 OK
HTTP/1.1 503 Service Unavailable
HTTP/1.1 200 OK
HTTP/1.1 503 Service Unavailable
HTTP/1.1 200 OK
HTTP/1.1 503 Service Unavailable
HTTP/1.1 200 OK
HTTP/1.1 503 Service Unavailable
HTTP/1.1 503 Service Unavailable

3. 我們配置 user 服務的 DestinationRule,設定 Outlier Detection。間隔 10 秒做一次統計,從負載均衡池中剔除連續錯誤數為 3 以上的 endpoint 30 秒,允許最大剔除比例為 100%,最小健康比例為 0%。完成配置後我們模擬請求 user 服務,所有請求均返回 200 OK。(被動健康檢查,需請求返回連續錯誤後才會剔除不健康 endpoint)

apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
  name: user
  namespace: base
spec:
  host: user
  trafficPolicy:
    outlierDetection:
      consecutiveErrors: 3
      interval: 10000ms
      baseEjectionTime: 30000ms
      maxEjectionPercent: 100
      minHealthPercent: 0
  exportTo:
    - '*'
$ for((i=0;i<10;i++)) do curl -I http://$INGRESS_HOST/user | grep HTTP/1.1; doneHTTP/1.1 200 OKHTTP/1.1 200 OKHTTP/1.1 200 OKHTTP/1.1 200 OKHTTP/1.1 200 OKHTTP/1.1 200 OKHTTP/1.1 200 OKHTTP/1.1 200 OKHTTP/1.1 200 OKHTTP/1.1 200 OK

重定向

當應用被遷移到新的 URI,同時又需要保持原有連結可用,此時需要配置 HTTP 重定向 9。重定向可應用於以下場景:

  1. server 端維護/停機期間遷移到新的 URI
  2. 強制使用 HTTPS 協議
  3. 多域名擴大應用覆蓋使用者人群

為確保通過 Ingress Gateway 訪問後端 user 服務的請求強制使用更安全的 HTTPS 協議,需要配置 Ingress Gateway 的 HTTP 重定向。

下面我們配置強制使用 HTTPS 協議的重定向。

1. 用 Gateway 配置 HTTP 重定向,強制使用 HTTPS 協議。

apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
  name: apis-gw
spec:
  servers:
    - port:
        number: 80
        name: HTTP-80-h7pv
        protocol: HTTP
      hosts:
        - '*'
      tls:
        httpsRedirect: true
    - port:
        number: 443
        name: HTTPS-443-p1ph
        protocol: HTTPS
      hosts:
        - '*'
      tls:
        mode: SIMPLE
        credentialName: qcloud-$CERTIFICATE_ID
  selector:
    app: istio-ingressgateway
    istio: ingressgateway

2. 使用 HTTP 請求 /user,返回 301 重定向狀態碼。如果是在瀏覽器訪問,收到重定向返回時,會重新發起新請求到新的 URI。

$ curl http://$INGRESS_HOST/user -I | grep HTTP/1.1
HTTP/1.1 301 Moved Permanently

重寫

使用重定向,客戶端可以感知訪問地址的變化,並且被重定向的流量實際上發起了兩次請求才能正常訪問,有一定效能損耗。而重寫則向客戶端遮蔽了地址的變動,完全由服務端進行重寫操作,使客戶端請求地址與服務端管理解藕。

TCM demo 的 cart 服務提供的 API 資源發生了變化,實現了 /clear 清空購物車的 API,希望在客戶端無感知的情況下,/cart 請求實際呼叫的是 /clear API。

下面我們配置 /cart 的請求重寫為 /clear。

1. 使用 VirtualService 配置 /cart 請求在進行實際呼叫前,將 URI 重寫為 /clear。

apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: apis-vs
  namespace: default
spec:
  hosts:
    - 121.4.8.11
  gateways:
    - default/apis-gw
  http:
    - match:
        - uri:
            exact: /cart
      route:
        - destination:
            host: cart.base.svc.cluster.local
      rewrite:
        uri: /clear

2. 發起 /cart 請求,客戶端不感知重寫操作,由服務端執行,實際呼叫的是 /clear API,cart 服務的 /clear API 成功返回撥用清空購物車成功的資訊。

$ curl http://$INGRESS_HOST/cart -H 'UserID: 1'
{"Success":"true"}

高可用排程

隨著業務規模的增加,或對跨可用區/地域容災、資料合規性、業務之間隔離要求的提升,業務會考慮與實施部署多個 Kubernetes 叢集,把同一個服務部署在跨可用區/地域的多個叢集,做高可用排程。主要有兩種訴求:

  1. 地域&錯誤感知自動 failover:根據服務的地域資訊與 endpoint 健康資訊確定流量的可用區/地域分發策略,當 endpoint 健康度高於閾值時,流量 100% 在本地路由,低於閾值時,視 endpoint 健康度自動 failover 一定比例流量至其他可用區/地域,直至 endpoint 全部不健康時 100% 流量自動 failover 至其他可用區/地域。endpoint 健康資訊感知依賴於健康檢查的能力。
  2. 地域感知流量分發 distribute :不按照地域與錯誤資訊自動 failover 流量,管理員自定義配置跨可用區/地域多叢集流量分發策略,例如配置來自上海一區的流量在上海一區和上海二區按照 80% 和 20% 的比例分發。無須感知 endpoint 健康,不依賴健康檢查的能力。

地域&錯誤感知自動 failover

隨著 TCM demo 網站後臺規模增加,對後臺服務容災的訴求也提上日程,希望實現 user 服務的跨叢集(本次以叢集跨可用區為例)容災,在上海二區新增一個業務叢集部署 user 備份服務,流量仍然是從上海一區的 ingress gateway 訪問 user。希望在上海一區 user 服務 endpoints 都健康時,就近訪問本可用區的 user,當上海一區 user endpoints 健康比例下降到一定程度時(例如 71.4%),開始視健康程度轉移一定比例的流量到上海二區的 user endpoints,直至上海一區 user endpoints 健康程度完全下降為 0% 時將流量完全切到上海二區 user 備份。

環境準備:

1. 新增一個上海二區的 TKE 叢集(如使用 TCM 一鍵體驗功能搭建環境,可跳過,TCM 一鍵體驗已經準備了第二個可用區的服務發現叢集),並在此叢集部署 user 服務(replicas: 14)作為上海一區 user 服務的容災備份服務。

apiVersion: v1
kind: Namespace
metadata:
  name: base
spec:
  finalizers:
    - kubernetes
---
apiVersion: v1
kind: Service
metadata:
  name: user
  namespace: base
  labels:
    app: user
spec:
  ports:
    - port: 7000
      name: http
  selector:
    app: user
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: user
  namespace: base
  labels:
    app: user
spec:
  replicas: 14
  selector:
    matchLabels:
      app: user
  template:
    metadata:
      labels:
        app: user
    spec:
      containers:
        - name: user
          image: ccr.ccs.tencentyun.com/zhulei/testuser:v1
          imagePullPolicy: Always
          env:
            - name: POD_NAME
              valueFrom:
                fieldRef:
                  fieldPath: metadata.name
            - name: REGION
              value: "shanghai-zone2"
          ports:
            - containerPort: 7000

2. 調整原有上海一區叢集 user 服務的 healthy 和 unhealthy pods 數量分別為 10 和 4。調整完成後一區和二區的 user 服務 endpoints 數量情況如下:

$ kubectl get deployment user user-unhealthy -n base
NAME             READY   UP-TO-DATE   AVAILABLE   AGE
user             10/10   10           10          8d
user-unhealthy   4/4     4            4           5d2h
$ kubectl get deployment user -n base
NAME   READY   UP-TO-DATE   AVAILABLE   AGE
user   14/14   14           14          5d2h

下面我們準備配置開啟與測試 Istio Ingress Gateway 的地域感知負載均衡功能。

1. TCM 的地域感知功能預設開啟,我們只需要配置 user 服務的 Outlier Detection,地域感知負載均衡即可生效。且預設當上海一區 user endpoints 健康比例下降至 10/14 (71.4%)後,會開始按比例轉移一區的流量到二區。

apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
  name: user
  namespace: base
spec:
  host: user
  trafficPolicy:
    outlierDetection:
      consecutiveErrors: 3
      interval: 10000ms
      baseEjectionTime: 30000ms
      maxEjectionPercent: 100
      minHealthPercent: 0
  exportTo:
    - '*'

2. 當前從 Ingress Gateway(上海一區)訪問 user 服務的流量,當前上海一區 user 服務健康比例還未小於 10/14 的臨界值,因此訪問 user 服務的流量全部由上海一區的 user endpoints 提供。發起一組請求驗證,所有流量均被路由到了上海一區的 endpoints。

$ for((i=0;i<10;i++)) do curl http://$INGRESS_HOST/user | jq '.Info[0].Region'; done
"shanghai-zone1"
"shanghai-zone1"
"shanghai-zone1"
"shanghai-zone1"
"shanghai-zone1"
"shanghai-zone1"
"shanghai-zone1"
"shanghai-zone1"
"shanghai-zone1"
"shanghai-zone1"

3. 調整上海一區叢集內 user 服務健康與非健康 endpoints 的比例,調整健康 endpoints 為 5,不健康endpoints 為 9。此時健康上海一區 user 服務的健康比例 5/14 已經小於 10/14,應當有部分流量被切至上海二區,發起一組請求驗證,/user 流量部分被路由至上海二區,路由至上海一區/二區的比例大致均衡為 1:1。

$ for((i=0;i<10;i++)) do curl http://$INGRESS_HOST/user | jq '.Info[0].Region'; done
"shanghai-zone2"
"shanghai-zone1"
"shanghai-zone1"
"shanghai-zone1"
"shanghai-zone1"
"shanghai-zone2"
"shanghai-zone2"
"shanghai-zone2"
"shanghai-zone1"
"shanghai-zone2"

4. 繼續調整上海一區叢集內 user 服務健康與非健康 endpoints 的比例,健康 endpoints 為 0,不健康 endpoints 為 14,此時上海一區 user 服務健康比例為 0%,不具備提供後端服務的能力,應當將所有 /user 請求路由到上海二區。發起一組請求驗證,所有流量均被路由至上海二區。

$ for((i=0;i<10;i++)) do curl http://$INGRESS_HOST/user | jq '.Info[0].Region'; done
"shanghai-zone2"
"shanghai-zone2"
"shanghai-zone2"
"shanghai-zone2"
"shanghai-zone2"
"shanghai-zone2"
"shanghai-zone2"
"shanghai-zone2"
"shanghai-zone2"

Istio 預設策略為當流量需要 failover 時,會在下一同等地域優先順序的所有地域內全域性負載均衡,同一個服務若有超過 2 個地域的部署,則需考慮配置 failover 優先順序,例如 user 服務在廣州、上海、北京均有部署時,需要配置當廣州的 endpoints 健康比例下降到閾值之下時,流量 failover 的地域是最近的上海而不是更遠的北京或在上海和北京全域性負載均衡,使用 localityLbSetting 的 failover 引數即可配置。當 user 服務在廣州、上海、北京均有部署時我們可以如下配置地域間 failover 策略,配置方式與 distribute 策略相同。

failover:
   - from: gz
     to: sh
   - from: sh
     to: gz
   - from: bj
     to: sh

TL;DR. 以下內容是地域感知負載均衡獲取 endpoints 健康程度、地域資訊、確定流量轉移比例的背景知識補充。

實現跨可用區/地域多叢集地域感知負載均衡,視服務 endpoints 地域資訊及健康程度按比例 failover 流量,需要具備以下能力:

1. 聯通多叢集網路並發現所有叢集的服務:

跨叢集網路連通後,Istiod 可以從多個叢集的 API Server 中獲取服務與 endpoint 資訊,並推送給資料面代理 Envoy Pod。

2. 獲取服務的地理位置資訊:

實現就近訪問與容災,需要服務的地理位置資訊,在服務網格中,地理位置由有如下三個層級的資訊:region,zone,subzone。其中 region 和 zone 的資訊分別來自叢集節點的 topology.kubernetes.io/region 標籤和 topology.kubernetes.io/region 標籤。這兩個標籤在 TKE 叢集中已經提供 10,例如上海一區節點的標籤為:topology.kubernetes.io/region: shtopology.kubernetes.io/zone: "2000400001";上海二區節點的標籤為:topology.kubernetes.io/region: shtopology.kubernetes.io/zone: "200002"

Kubernetes 不存在 subzone 的概念,Istio 引入了 topology.istio.io/subzone 的標籤定義 subzone,可根據需要配置。

在 Istio 預設使用的地域優先負載均衡策略中,優先順序如下:

  • Priority 0 最高優先順序,同地域同可用區;
  • Priority 1,同地域不同可用區;
  • Priority 2 最低優先順序,不同地域。

3. 獲取服務 endpoints 的健康資訊:

endpoints 健康資訊獲取依賴開啟 Istio 的健康檢查: Outlier Detection。地域&錯誤自動 failover 功能依賴健康檢查,未開啟時,資料面無法得知服務 endpoints 的健康狀況,預設按照全域性的方式進行流量負載均衡。地域感知流量分發 distribute 不依賴開啟健康檢查。

4. 判定服務健康狀態&確定流量轉移比例:

一個服務會部署多個副本,服務的健康狀態不是絕對的 0 和 1 的狀態,流量的轉移是一個逐步轉移的過程,不健康 endpoints 超過一定比例時,再開始按比例逐步進行流量轉移。Istio 的地域負載均衡預設使用地域優先的策略,即控制面告訴資料面在健康狀態下,優先考慮將請求傳送到地理位置最近的例項,地理優先順序最高可用區/地域 endpoints 健康度 100% 時,所有的請求都會路由到這個地域,不會做流量轉移,endpoints 不健康比例超過某閾值,流量將開始按比例逐步轉移。

這個閾值由 envoy 的 overprovisioning factor 控制,預設為 1.4,根據該 factor 及服務 endpints 健康比例可確定不同地理 Priority Level 的流量比例。例如,假設目前某服務在廣州和上海兩個地域均有 endpoints,流量入口 Ingress Gateway 部署在廣州。通過廣州 Ingress Gateway 訪問該服務的流量,按照優先順序廣州的服務為 P0 優先順序,上海的服務為 P1 優先順序。假設上海作為容災的地域,,endpoints 健康比例一直為 100%,假設廣州上海兩個地域的權重相等,overprovisioning factor 為預設值 1.4。兩個地域流量負載比例計算過程如下:

  • 廣州服務 endpoints 健康比例:P0_health = 廣州服務健康 endpoints 數量 / 廣州服務 endpoints 總數;
  • 廣州服務流量負載比例:P0_traffic = min(1, P0_health * 1.4);
  • 上海服務流量負載比例:P1_traffic = 1 - P0_traffic = max(0, 1 - P0_health * 1.4)。

按照該計算規則,overprovisioning factor 為 1.4 時:

  • 廣州服務 endpoints 健康比例 P0_health 低於 71.4% 時,該地域訪問該服務的流量才會開始切換至上海地域;
  • 當廣州地域的 endpoints 健康比例為 50% 時,會有 1 - 50% * 1.4 = 30% 的流量轉移到上海地域的服務;
  • 當廣州地域 endpoints 完全不可用 P0_health = 0% 時,流量才會被完全切換到上海地域。

PX_traffic = min(100, PX_health * 1.4 * 100) 反映某地域某服務當前的流量承載能力,Envoy 社群稱為健康評分。當所有地域的健康評分總和低於 100 時,Envoy 則認為當前健康狀態沒有完全處理請求的的能力,此時 Envoy 會根據健康評分的比例分配請求。例如廣州和上海的健康評分分別為 20 和 30 時,分別會承擔 40% 和 60% 的負載 11

地域感知流量分發 distribute

業務不希望流量根據地域和健康資訊自動 failover,而是自定義流量分發策略,來自 Istio Ingress Gateway(上海一區)的 /user 請求在一區和二區按照 1:1 的比例均衡分發,而不是應用 Istio 預設的自動 failover 策略:100% 健康時,來自上海一區的請求 100% 負載均衡至上海一區 user endpoints。

可通過 meshconfig(配置網格全域性)或 DestinationRule(配置單個服務)的 distribute 引數來配置自定義地域感知流量分發策略。

1. 恢復上海一區和二區 user 服務 健康/不健康 endpoints 數量至最初狀態,一區一共 14 個endpoints 全部健康,二區一共 14 個endpoints 全部健康。按照 Istio Ingress Gateway 預設的地域感知策略,從 Ingress Gateway(上海一區)訪問 /user 的流量會全部路由至上海一區的 endpoints。

2. 配置 user 服務的 DestinationRule,自定義流量排程規則,來自上海一區的流量,均勻路由至上海一區和二區的 endpoints。

apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
  name: user
  namespace: base
spec:
  host: user
  trafficPolicy:
    loadBalancer:
      localityLbSetting:
        distribute:
          - from: sh/2000400001/*
            to:
              sh/2000400001/*: 50
              sh/200002/*: 50
        enabled: true
    outlierDetection:
      consecutiveErrors: 3
      interval: 10000ms
      baseEjectionTime: 30000ms
      maxEjectionPercent: 100
      minHealthPercent: 0
  exportTo:
    - '*'

3. 發起一組 /user 請求驗證,流量被比較均衡的路由到了一區和二區的 endpoints,而不是 Istio Ingress Gateway 預設地域/錯誤感知自動 failover(100% 流量路由到上海一區)。

$ for((i=0;i<10;i++)) do curl http://$INGRESS_HOST/user | jq '.Info[0].Region'; done
"shanghai-zone2"
"shanghai-zone2"
"shanghai-zone2"
"shanghai-zone2"
"shanghai-zone1"
"shanghai-zone2"
"shanghai-zone1"
"shanghai-zone1"
"shanghai-zone2"
"shanghai-zone2"

結語

本文介紹了 Istio Ingress Gateway 流量管理的技術原理及流量管理模型。並從入口流量分發、容錯與高可用排程三個方面實操演示了內容路由、權重路由、負載均衡、斷路器、地域&錯誤感知自動 failover、地域感知流量分發等功能。

除基本的入口流量管理外,南北流量管理還有安全、可觀測性、異構服務支援等場景,將在後續系列文章中繼續討論。

相關文章