引言
在雲原生應用負載均衡系列第一篇文章《雲原生應用負載均衡選型指南》介紹了雲原生容器環境的入口流量管理使用場景與解決方案,用 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。重定向可應用於以下場景:
- server 端維護/停機期間遷移到新的 URI
- 強制使用 HTTPS 協議
- 多域名擴大應用覆蓋用戶人群
為確保通過 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 集群,把同一個服務部署在跨可用區/地域的多個集群,做高可用調度。主要有兩種訴求:
- 地域&錯誤感知自動 failover:根據服務的地域信息與 endpoint 健康信息確定流量的可用區/地域分發策略,當 endpoint 健康度高於閾值時,流量 100% 在本地路由,低於閾值時,視 endpoint 健康度自動 failover 一定比例流量至其他可用區/地域,直至 endpoint 全部不健康時 100% 流量自動 failover 至其他可用區/地域。endpoint 健康信息感知依賴於健康檢查的能力。
- 地域感知流量分發 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: sh
,topology.kubernetes.io/zone: "2000400001"
;上海二區節點的標簽為:topology.kubernetes.io/region: sh
,topology.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、地域感知流量分發等功能。
除基本的入口流量管理外,南北流量管理還有安全、可觀測性、異構服務支持等場景,將在后續系列文章中繼續討論。