【GCP Anthos】 Fail Overの挙動から最適なマルチクラスタ構成を考える
記事の目的
前回の記事でIngress For Anthosを用いてRegionにまたがったKubernetsのマルチクラスタを構築した。 今回は1段深く掘り下げて、クラスタ内に複数のフロントサービスを持つケースを考える。 このケースでAnthosで構築したマルチクラスタのFail Overの挙動を詳しく検証し、 どんなときに、どんな条件でFail Overするのかを整理する。 また、それによって最適なマルチクラスタ構成を考える。 今回はIstioはマルチコントロールプレーン構成にしないので、クラスタ内のサービス間通信のFail Overについてはとりあげない。 【GCP Anthos】 Regionに跨って冗長化したKubernetsのマルチクラスタをロードバランシングする
題材となるサービス
今回は少し実運用を考慮し、クラスタ内に2つのフロントサービス(外部から直接ルーティングされてくるサービス)を作り、それぞれへはIngressGatewayでルーティングすることとする。
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の挙動結果まとめ
ヘルスチェック対象でないサービスがダウン
ヘルスチェック対象のサービスがダウン
Fail Over条件のカスタマイズは不可能
つまり、ヘルスチェックによってのみFail Overするかどうかが決まる。 よって、今の構成だと、ヘルスチェック対象のサービスがUnhealthyの場合、 引きずられてHealthyなサービスまでも、冗長化された別クラスタにFail Overされてしまう。
理想的な挙動はなにか?
フロントサービスに対して個別にヘルスチェックを実施し、 一つのフロントサービスのヘルスチェックステータスが他のサービスのルーティングに影響しないようにしたい。 つまり、今回ならzoneprinterがUnhealthyだからといって、bookinfoまで別クラスタにルーティングしないでほしい。
どういう構成にするべきか
ようは各フロントサービスごとにヘルスチェックしてほしい => 各サービスごとにNEGが作られて、それらがGCLBのバックエンドとして設定されてほしいということになる、 よってフロントサービスの数だけMultiClusterServiceを作成し、単一のIngressGatewayに紐付けることにした。
補足
ちなみに、各フロントサービスに対して直接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 "asia-northeast1-a"</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できる
- 今回のコード