本文分享自華爲雲社區《基於istio實現單集羣地域故障轉移》,作者:可以交個朋友。
一 背景
隨着應用程序的增長並變得更加複雜,微服務的數量也會增加,失敗的可能性也會增加。微服務的故障可能多種原因造成,例如硬件問題、網絡延遲、軟件錯誤,甚至人爲錯誤。故障轉移Failover 是系統韌性設計中的一個基礎能力,它們可以確保系統在出現故障時能夠繼續運行,並且能夠在最小化的影響下進行恢復,減少或者消除對使用方或最終用戶的影響,從而提高整個系統對外的可用性。
二 簡介
雲原生K8s、istio默認使用node上特定label作爲地域信息:
- 地區:代表較大的地理區域,例如 us-east。一個地區通常包含許多可用區。 在 Kubernetes 中,標籤topology.kubernetes.io/region 決定了節點所在的地區。
- 區域:區域內的一組計算資源。通過在區域內的多個區域中運行服務,可以在區域內的區域之間進行故障轉移, 同時保持最終用戶的數據地域性。在 Kubernetes 中,標籤topology.kubernetes.io/zone決定了節點所在的區域。
- 分區:允許管理員進一步細分區域,以實現更細粒度的控制,例如“相同機架”。 Kubernetes 中不存在分區的概念。所以 Istio 引入了自定義節點標籤 topology.istio.io/subzone 來定義分區。
kubectl describe node xxx |grep topo
如下圖所示演示環境,helloworld作爲服務端有多個實例分別部署在不同zone中(不同zone節點topology.kubernetes.io/zone的label不同)。通過istio的destinationrule中localityLbSetting.failover(故障轉移策略)和outlierDetection(故障異常點檢測),可以實現客戶端業務訪問helloworld服務時候,優先訪問與客戶端同可用區的服務端,當同可用區的helloworld服務端全部故障後,再訪問指定可用區的服務端,實現故障轉移。
三 實戰演練
事先準備好kubernetes+istio作爲操作環境。可用華爲雲CCE和ASM服務進行操作。
3.1 部署服務端
1.創建sample 命名空間,並設置istio-proxy sidecar自動注入
apiVersion: v1 kind: Namespace metadata: name: sample labels: istio-injection: enabled
2.部署helloworld服務 作爲服務端
將根據以下腳本生成對yaml配置清單
#!/bin/bash set -euo pipefail display_usage() { echo echo "USAGE: ./gen-helloworld.sh [--version] [--includeService value] [--includeDeployment value]" echo " -h|--help: Prints usage information" echo " --version: Specifies the version that will be returned by the helloworld service, default: 'v1'" echo " --includeService: If 'true' the service will be included in the YAML, default: 'true'" echo " --includeDeployment: If 'true' the deployment will be included in the YAML, default: 'true'" } INCLUDE_SERVICE=${INCLUDE_SERVICE:-"true"} INCLUDE_DEPLOYMENT=${INCLUDE_DEPLOYMENT:-"true"} SERVICE_VERSION=${SERVICE_VERSION:-"v1"} while (( "$#" )); do case "$1" in -h|--help) display_usage exit 0 ;; --version) SERVICE_VERSION=$2 shift 2 ;; --includeService) INCLUDE_SERVICE=$2 shift 2 ;; --includeDeployment) INCLUDE_DEPLOYMENT=$2 shift 2 ;; *) echo "Error: Unsupported flag $1" >&2 display_usage exit 1 ;; esac done SERVICE_YAML=$(cat <<EOF apiVersion: v1 kind: Service metadata: name: helloworld labels: app: helloworld service: helloworld spec: ports: - port: 5000 name: http selector: app: helloworld EOF ) DEPLOYMENT_YAML=$(cat <<EOF apiVersion: apps/v1 kind: Deployment metadata: name: helloworld-${SERVICE_VERSION} labels: app: helloworld version: ${SERVICE_VERSION} spec: replicas: 1 selector: matchLabels: app: helloworld version: ${SERVICE_VERSION} template: metadata: labels: app: helloworld version: ${SERVICE_VERSION} spec: affinity: nodeAffinity: requiredDuringSchedulingIgnoredDuringExecution: nodeSelectorTerms: - matchExpressions: - key: topology.istio.io/subzone operator: In values: - ${SERVICE_VERSION} containers: - name: helloworld env: - name: SERVICE_VERSION value: ${SERVICE_VERSION} image: docker.io/istio/examples-helloworld-v1 resources: requests: cpu: "100m" imagePullPolicy: IfNotPresent ports: - containerPort: 5000 EOF ) OUT="" # Add the service to the output. if [[ "$INCLUDE_SERVICE" == "true" ]]; then OUT="${SERVICE_YAML}" fi # Add the deployment to the output. if [[ "$INCLUDE_DEPLOYMENT" == "true" ]]; then # Add a separator if [[ -n "$OUT" ]]; then OUT+=" --- " fi OUT+="${DEPLOYMENT_YAML}" fi echo "$OUT"
執行腳本: for LOC in "beijing" "tianjin" "shenyang"; do ./genHelloWorld.sh --version "$LOC" > "helloworld-${LOC}.yaml"; done 將會生成yaml配置清單,應用到集羣即可。
kubectl apply -f helloworld-xxx.yaml -n sample
3.2 部署客戶端
kubectl apply -f sleep.yaml -n sample
# Sleep service ################################################################################################## apiVersion: v1 kind: ServiceAccount metadata: name: sleep --- apiVersion: v1 kind: Service metadata: name: sleep labels: app: sleep service: sleep spec: ports: - port: 80 name: http selector: app: sleep --- apiVersion: apps/v1 kind: Deployment metadata: name: sleep spec: replicas: 1 selector: matchLabels: app: sleep template: metadata: labels: app: sleep spec: terminationGracePeriodSeconds: 0 serviceAccountName: sleep containers: - name: sleep image: curlimages/curl command: ["/bin/sleep", "infinity"] imagePullPolicy: IfNotPresent volumeMounts: - mountPath: /etc/sleep/tls name: secret-volume volumes: - name: secret-volume secret: secretName: sleep-secret optional: true ---
查看客戶端中的存儲的cluster信息
kubectl exec -it sleep-xxx -c istio-proxy -n sample -- curl localhost:15000/clusters可以看到cluster信息中包含了實例的PodIP和位置信息
3.3 配置服務端地域故障轉移規則
istio的流量治理一般都是通過virtualservice、destinationrule 、envoyfilter等來實現,其中地域故障轉移是通過destinationrule配置實現的。因爲在destinationrule中可以配置outerlineDecetion進行異常點檢測,只有檢測到異常後,纔會進行故障轉移。kubectl apply -f xxx.yaml
apiVersion: networking.istio.io/v1beta1 kind: DestinationRule metadata: name: helloworld namespace: sample spec: host: helloworld.sample.svc.cluster.local trafficPolicy: connectionPool: http: maxRequestsPerConnection: 1 loadBalancer: simple: ROUND_ROBIN localityLbSetting: #開啓地域負載均衡 enabled: true failover: #配置故障轉移策略,failover主要控制Region等上層位置的切換 - from: cn-north-4 to: cn-south-1 outlierDetection: #異常點檢測 consecutive5xxErrors: 1 interval: 1s baseEjectionTime: 1m
以上治理策略表示:
- 異常點檢測:當某個客戶端訪問helloworld服務時,客戶端對應的envoy會根據本次訪問HTTP狀態碼對轉發的服務端進行故障檢測,故障檢測條件爲當發生1次5xx錯誤時實例就會被隔離1m。
- 故障隔離:當指定region的所有後端實例均不正常,觸發故障轉移到下一個地域,確保了超出地區邊界的故障轉移將具有可預測的行爲。如果位於cn-north-4 region的實例異常,流量就會發往cn-south-1 region 的實例。
3.4 驗證地域負載均衡
通過位於cn-north-4/cn-north-4b/tianjin 的Sleep Pod 多次調用 HelloWorld 服務,均訪問成功。
同時可以發現服務端響應的Pod總是同一個
查看sleep實例的proxy日誌,通過日誌中的%UPSTREAM_HOST%字段(紅框標準)172.16.0.136,可以看到5個請求均被髮送到相同的子區域helloworld實例(Pod IP爲172.16.0.136)。
這是因爲istio考慮到網絡開銷,部署在region1/zone1上的sleep實例 大多時候只會訪問部署在同Region同Zone的helloworld實例。
3.5 驗證地域故障轉移
首先模擬故障,通過下述命令 向 enovy 的 admin port 發送請求,關閉envoy的 listener 。enovy 收到請求後,會取消端口監聽,不再接收新的連接和請求。
kubectl exec helloworld-tianjin-xxx -n sample -c istio-proxy -- curl -sSL -X POST 127.0.0.1:15000/drain_listeners
再次通過位於cn-north-4/cn-north-4b/tianjin 的Sleep Pod 多次調用 HelloWorld 服務。
可以發現4個請求被以輪詢的方式發往cn-north-4/cn-north-4b/beijing 和cn-north-4/cn-north-4b/shenyang的 helloworld實例。以上結果說明,在一個區域的服務實例發生故障時,可根據配置,將請求路由到其它地域的服務實例進行處理,增強服務的可靠性。在實踐中可通過From、To 配置region地區信息,控制在不同地區的實例上進行故障轉移。
四 備註
關於地域負載均衡的配置failover主要控制的是跨region的場景,因爲位於region內的zone或者subzone 上的實例默認就可以切換流量。本文檔的實踐主要是在region內進行操作的,所以不能演示完整的跨地域故障轉移。一般也多用在多集羣的治理環境中。