基於istio實現單叢集地域故障轉移

华为云开发者联盟發表於2024-04-10

本文分享自華為雲社群《基於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

cke_114.png

如下圖所示演示環境,helloworld作為服務端有多個例項分別部署在不同zone中(不同zone節點topology.kubernetes.io/zone的label不同)。透過istio的destinationrule中localityLbSetting.failover(故障轉移策略)和outlierDetection(故障異常點檢測),可以實現客戶端業務訪問helloworld服務時候,優先訪問與客戶端同可用區的服務端,當同可用區的helloworld服務端全部故障後,再訪問指定可用區的服務端,實現故障轉移。

cke_115.png

三 實戰演練

事先準備好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

cke_116.png

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
---

cke_117.png

檢視客戶端中的儲存的cluster資訊

kubectl exec -it sleep-xxx -c istio-proxy -n sample -- curl localhost:15000/clusters可以看到cluster資訊中包含了例項的PodIP和位置資訊

cke_118.png

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 服務,均訪問成功。

cke_119.png

同時可以發現服務端響應的Pod總是同一個

cke_120.png

檢視sleep例項的proxy日誌,透過日誌中的%UPSTREAM_HOST%欄位(紅框標準)172.16.0.136,可以看到5個請求均被髮送到相同的子區域helloworld例項(Pod IP為172.16.0.136)。

cke_121.png

這是因為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

cke_122.png

再次透過位於cn-north-4/cn-north-4b/tianjin 的Sleep Pod 多次呼叫 HelloWorld 服務。

cke_123.png

可以發現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內進行操作的,所以不能演示完整的跨地域故障轉移。一般也多用在多叢集的治理環境中。

cke_124.png

點選關注,第一時間瞭解華為雲新鮮技術~

相關文章