Enjoy Architecting

Twitter: @taisho6339

【GCP Anthos】 Fail Overの挙動から最適なマルチクラスタ構成を考える

記事の目的

前回の記事でIngress For Anthosを用いてRegionにまたがったKubernetsのマルチクラスタを構築した。 今回は1段深く掘り下げて、クラスタ内に複数のフロントサービスを持つケースを考える。 このケースでAnthosで構築したマルチクラスタのFail Overの挙動を詳しく検証し、 どんなときに、どんな条件でFail Overするのかを整理する。 また、それによって最適なマルチクラスタ構成を考える。 今回はIstioはマルチコントロールプレーン構成にしないので、クラスタ内のサービス間通信のFail Overについてはとりあげない。 【GCP Anthos】 Regionに跨って冗長化したKubernetsのマルチクラスタをロードバランシングする

題材となるサービス

今回は少し実運用を考慮し、クラスタ内に2つのフロントサービス(外部から直接ルーティングされてくるサービス)を作り、それぞれへはIngressGatewayでルーティングすることとする。

f:id:taisho6339:20200514120059p:plain

GCLB + NEGsのFailOverの検証

Anthosはクラスタ間のロードバランシングにGCPのCloud Loadbalancer(以下GCLB)とクラスタ内の任意のPodに紐付いたNEGを使う。 よってまずはGCLB + NEGの構成のFailOverの挙動をまずは整理する。

事前準備

  • 東京クラスタ
    • GKE 1.15
    • Anthosへのメンバー登録済み
    • istioインストール済み(v1.5.1)
  • 台湾クラスタ
    • GKE 1.15
    • Anthosへのメンバー登録済み
    • istioインストール済み(v1.5.1)

アプリケーションのデプロイ

ホストヘッダーによってbookinfoとzoneprinterへのルーティングの向き先を変更するようにしている。

apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: zoneprinter
spec:
  hosts:
#    - "*"
    - "www.zoneprinter.com"
  gateways:
    - single-gateway
  http:
    - match:
        - uri:
            exact: /
        - uri:
            exact: /ping
        - uri:
            exact: /location
      route:
        - destination:
            host: zoneprinter
            port:
              number: 80
---
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: bookinfo
spec:
  hosts:
    - "*"
#    - "www.bookinfo.com"
  gateways:
    - single-gateway
  http:
    - match:
        - uri:
            exact: /
        - uri:
            exact: /productpage
        - uri:
            prefix: /static
        - uri:
            exact: /login
        - uri:
            exact: /logout
        - uri:
            prefix: /api/v1/products
      fault:
          abort:
            httpStatus: 503
            percentage:
              value: 100.0
      route:
        - destination:
            host: productpage
            port:
              number: 9080

MCI, MCSリソースのデプロイ

一つのMCSを一つのIngress GatewayのPodに割り当てている。

FailOverの検証方法

MultiClusterServiceが一つなのでIngress GatewayのPodに紐付いた単一のNEGが作られる。 よってこのままだと、ヘルスチェックに使用できるパスが一つのフロントサービスへのルーティングのみになってしまうが、一旦このまま検証する。

  • ヘルスチェック対象のサービス = zoneprinter, ヘルスチェック非対象のサービス = bookinfo
    • ホストヘッダーがwww.bookinfo.comのものはbookinfoへ、それ以外はzoneprinterへルーティング
    • ヘルスチェックは、/ のパスへ
    • ダウンさせるサービスは、IstioのFault Injectionを利用し、エラーが常に返却されるように
    • サンプルコードはこちら

GCLB + NEGのFail Overの挙動結果まとめ

  • ヘルスチェック対象でないサービスがダウン

    • リクエスト時に5xxエラー => Fail Overしない
    • リクエスト時に503(Service Unavailable)エラー => Fail Overしない
    • 一定時間内のほとんどのリクエストが5xxエラー => Fail Overしない
    • 一定時間内のほとんどのリクエストが503エラー => Fail Overしない
  • ヘルスチェック対象のサービスがダウン

    • リクエスト時に5xxエラー => Fail Overしない
    • リクエスト時に503(Service Unavailable)エラー => Fail Overしない
    • 一定時間内のほとんどのリクエストが5xxエラー => Fail Overしない
    • 一定時間内のほとんどのリクエストが503エラー => Fail Overしない
    • ヘルスチェックがエラー => Fail Overする
  • Fail Over条件のカスタマイズは不可能

つまり、ヘルスチェックによってのみFail Overするかどうかが決まる。 よって、今の構成だと、ヘルスチェック対象のサービスがUnhealthyの場合、 引きずられてHealthyなサービスまでも、冗長化された別クラスタにFail Overされてしまう。

理想的な挙動はなにか?

フロントサービスに対して個別にヘルスチェックを実施し、 一つのフロントサービスのヘルスチェックステータスが他のサービスのルーティングに影響しないようにしたい。 つまり、今回ならzoneprinterがUnhealthyだからといって、bookinfoまで別クラスタにルーティングしないでほしい。

どういう構成にするべきか

ようは各フロントサービスごとにヘルスチェックしてほしい => 各サービスごとにNEGが作られて、それらがGCLBのバックエンドとして設定されてほしいということになる、 よってフロントサービスの数だけMultiClusterServiceを作成し、単一のIngressGatewayに紐付けることにした。

f:id:taisho6339:20200514115840p:plain

補足

ちなみに、各フロントサービスに対して直接NEGを紐付ければいいのでは?と思うかもしれないが、 そうなるとIngressGatewayのEnvoyを通過せずに直接リクエスト先サービスのPodにルーティングされてしまうので、 VirtualServiceやDestinationRuleの設定が無視されてしまう。 この辺の挙動のイメージは、こちらにまとめた。 また、IngressGatewayのPodへのNEGを作るので、IngressGatewayにアタッチされているL4のGCLBは不要になる。 よってIstio Operatorなどを使ってNodePortに変更する。

実際のコード

apiVersion: cloud.google.com/v1beta1
kind: BackendConfig
metadata:
  name: multi-gateway-bookinfo-cfg
  namespace: istio-system
spec:
  healthCheck:
    checkIntervalSec: 15
    timeoutSec: 10
    healthyThreshold: 1
    unhealthyThreshold: 2
    type: HTTP
    port: 80
    requestPath: /
    hostHeader: "www.bookinfo.com"
---
apiVersion: networking.gke.io/v1
kind: MultiClusterService
metadata:
  name: multi-gateway-bookinfo
  namespace: istio-system
  annotations:
    beta.cloud.google.com/backend-config:
      '{"ports": {"80":"multi-gateway-bookinfo-cfg"}}'
spec:
  template:
    spec:
      selector:
        app: istio-ingressgateway
      ports:
        - name: multi-gateway-bookinfo
          protocol: TCP
          port: 80
          targetPort: 80
---
apiVersion: cloud.google.com/v1beta1
kind: BackendConfig
metadata:
  name: multi-gateway-zoneprinter-cfg
  namespace: istio-system
spec:
  healthCheck:
    checkIntervalSec: 15
    timeoutSec: 10
    healthyThreshold: 1
    unhealthyThreshold: 2
    type: HTTP
    port: 80
    requestPath: /
    hostHeader: "www.zoneprinter.com"
---
apiVersion: networking.gke.io/v1
kind: MultiClusterService
metadata:
  name: multi-gateway-zoneprinter
  namespace: istio-system
  annotations:
    beta.cloud.google.com/backend-config:
      '{"ports": {"80":"multi-gateway-zoneprinter-cfg"}}'
spec:
  template:
    spec:
      selector:
        app: istio-ingressgateway
      ports:
        - name: multi-gateway-zoneprinter
          protocol: TCP
          port: 80
          targetPort: 80
apiVersion: networking.gke.io/v1
kind: MultiClusterIngress
metadata:
  name: multi-gateway
  namespace: istio-system
spec:
  template:
    spec:
      rules:
        - host: "www.bookinfo.com"
          http:
            paths:
              - path: /
                backend:
                  serviceName: multi-gateway-bookinfo
                  servicePort: 80
        - host: "www.zoneprinter.com"
          http:
            paths:
              - path: /
                backend:
                  serviceName: multi-gateway-zoneprinter
                  servicePort: 80
      backend:
        serviceName: multi-gateway-zoneprinter
        servicePort: 80

確認

この状態でbookinfoのヘルスチェックエンドポイントに対してFaultInjectionし、 zoneprinterにcurlすると、

❯❯❯ curl -H "Host:www.zoneprinter.com" http://35.241.18.9/location
<!DOCTYPE html>
<h4>Welcome from Google Cloud datacenters at:</h4>
<h1>Tokyo, Japan</h1>
<h3>You are now connected to &quot;asia-northeast1-a&quot;</h3>
<img src="https://upload.wikimedia.org/wikipedia/en/9/9e/Flag_of_Japan.svg" style="width: 640px; height: auto; border: 1px solid black"/>

となり、引きずられてFailOverしていないことが分かる。

また、逆にzoneprinterにFaultInjectionし、 bookinfoにアクセスすると、 台湾クラスタの方のログにアクセスが来たことが分かる。

まとめ

  • GCLBはヘルスチェックによってのみFailOverする
  • MCSに対して一つずつNEGが作られるので、複数のNEGを作ることで複数のフロントサービスに対してヘルスチェックを行うことができ、サービスごとにFailOverできる
  • 今回のコード