重み付けトラフィック分割用のマルチクラスタ Gateway をデプロイする

このドキュメントでは、2 つの GKE クラスタ間でサンプル store アプリケーションの Blue/Green デプロイを行う方法について説明します。Blue/Green デプロイは、リスクを最小限に抑えながらアプリケーションを新しい GKE クラスタに移行するための効果的な戦略です。トラフィックを現在のクラスタ(Blue)から新しいクラスタ(Green)に段階的に移行することで、完全な切り替えを行う前に、本番環境で新しい環境を検証できます。

マルチクラスタ Gateway は、複数の GKE クラスタにデプロイされたサービスのトラフィックを管理する強力な方法を提供します。Google のグローバル ロード バランシング インフラストラクチャを使用すると、アプリケーションの単一のエントリ ポイントを作成できるため、管理が簡素化され、信頼性が向上します。

このチュートリアルでは、サンプル store アプリケーションを使用して、オンライン ショッピング サービスを複数のチームが所有して管理し、共有 GKE クラスタのフリート全体にデプロイしている実際のシナリオをシミュレートします。

始める前に

マルチクラスタ Gateway をデプロイする前に環境を用意する必要があります。続行する前に、マルチクラスタ Gateway 用に環境を準備するの手順を行ってください。

  1. GKE クラスタをデプロイします。

  2. クラスタをフリートに登録します(まだ登録されていない場合)。

  3. マルチクラスタ Service コントローラとマルチクラスタ Gateway コントローラを有効にします。

最後に、お使いの環境でコントローラを使用する前に、GKE Gateway コントローラの制限事項と既知の問題をご確認ください。

Gateway による Blue/Green マルチクラスタ ルーティング

gke-l7-global-external-managed-*gke-l7-regional-external-managed-*gke-l7-rilb-* の GatewayClass では、トラフィック分割、ヘッダー マッチング、ヘッダー操作、トラフィック ミラーリングなど、多くの高度なトラフィック ルーティング機能を使用できます。この例では、重み付けに基づいてトラフィック分割を行い、2 つの GKE クラスタ間のトラフィックの比率を明示的に制御しています。

この例では、サービス オーナーがアプリケーションを新しい GKE クラスタに移動または拡張する際に行う具体的な手順を説明します。Blue/Green デプロイの目標は、新しいクラスタが正しく動作していることを確認する複数の検証手順を行い、リスクを減らすことです。この例では、デプロイの 4 つのステージについて説明します。

  1. 100% - ヘッダーに基づくカナリア: HTTP ヘッダーのルーティングを使用して、テストまたは合成トラフィックのみを新しいクラスタに送信します。
  2. 100% - トラフィックのミラーリング: カナリア クラスタへのユーザー トラフィックをミラーリングします。ユーザー トラフィックの 100% がこのクラスタにコピーされ、カナリア クラスタの容量がテストされます。
  3. 90%10%: 10% のカナリア トラフィック分割で新しいクラスタをライブ トラフィックにゆっくり公開します。
  4. 0%100%: エラーが観察された場合は、元に戻すオプションを使用して、新しいクラスタに完全にカットオーバーします。

2 つの GKE クラスタ間での Blue/Green トラフィック分割

この例は前のものと似ていますが、内部マルチクラスタ Gateway をデプロイしている点が異なります。この例では、VPC 内からのみプライベートでのアクセスが可能な内部アプリケーション ロードバランサをデプロイしています。別の Gateway を介してデプロイする場合を除き、前の手順でデプロイしたクラスタとアプリケーションを使用します。

前提条件

次の例では、外部マルチクラスタ ゲートウェイのデプロイの一部の手順を使用しています。この例を続行する前に、次のことが完了していることを確認してください。

  1. マルチクラスタ Gateway の環境を準備する

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

    この例では、設定済みの gke-west-1 クラスタと gke-west-2 クラスタを使用します。gke-l7-rilb-mc GatewayClass はリージョンであり、同じリージョン内のクラスタ バックエンドのみをサポートしているため、これらのクラスタは同じリージョンにあります。

  3. 各クラスタに必要な Service と ServiceExport をデプロイします。前の例で Service と ServiceExports をデプロイした場合は、その一部がデプロイされています。

    kubectl apply --context gke-west-1 -f https://raw.githubusercontent.com/GoogleCloudPlatform/gke-networking-recipes/main/gateway/gke-gateway-controller/multi-cluster-gateway/store-west-1-service.yaml
    kubectl apply --context gke-west-2 -f https://raw.githubusercontent.com/GoogleCloudPlatform/gke-networking-recipes/main/gateway/gke-gateway-controller/multi-cluster-gateway/store-west-2-service.yaml
    

    各クラスタに同様のリソースセットをデプロイします。

    service/store created
    serviceexport.net.gke.io/store created
    service/store-west-2 created
    serviceexport.net.gke.io/store-west-2 created
    

プロキシ専用サブネットの構成

内部 Gateway をデプロイしているリージョンごとに、プロキシ専用サブネットを設定します(まだ設定していない場合)。このサブネットは、ロードバランサのプロキシに内部 IP アドレスを提供するために使用されます。--purposeREGIONAL_MANAGED_PROXY にのみ指定して設定する必要があります。

内部アプリケーション ロードバランサを管理する Gateway を作成する前に、プロキシ専用サブネットを作成する必要があります。内部アプリケーション ロードバランサを使用する Virtual Private Cloud(VPC)ネットワークの各リージョンに、プロキシ専用サブネットを作成する必要があります。

プロキシ専用サブネットを作成するには、gcloud compute networks subnets create コマンドを使用します。

gcloud compute networks subnets create SUBNET_NAME \
    --purpose=REGIONAL_MANAGED_PROXY \
    --role=ACTIVE \
    --region=REGION \
    --network=VPC_NETWORK_NAME \
    --range=CIDR_RANGE

次のように置き換えます。

  • SUBNET_NAME: プロキシ専用サブネットの名前。
  • REGION: プロキシ専用サブネットのリージョン。
  • VPC_NETWORK_NAME: サブネットを含む VPC ネットワークの名前。
  • CIDR_RANGE: サブネットのプライマリ IP アドレス範囲。サブネット マスクの長さは /26 以下にして、リージョン内のプロキシで 64 個以上の IP アドレスを使用できるようにします。推奨のサブネット マスクは /23 です。

Gateway のデプロイ

次の Gateway は gke-l7-rilb-mc GatewayClass から作成されます。これは、同じリージョン内の GKE クラスタのみをターゲットにできるリージョン内部 Gateway です。

  1. 次の Gateway マニフェストを構成クラスタに適用します(この例では gke-west-1)。

    cat << EOF | kubectl apply --context gke-west-1 -f -
    kind: Gateway
    apiVersion: gateway.networking.k8s.io/v1
    metadata:
      name: internal-http
      namespace: store
    spec:
      gatewayClassName: gke-l7-rilb-mc
      listeners:
      - name: http
        protocol: HTTP
        port: 80
        allowedRoutes:
          kinds:
          - kind: HTTPRoute
    EOF
    
  2. Gateway が正常に起動したことを確認します。次のコマンドを使用して、この Gateway のイベントのみを表示します。

    kubectl get events --field-selector involvedObject.kind=Gateway,involvedObject.name=internal-http --context=gke-west-1 --namespace store
    

    次のような出力の場合は、Gateway のデプロイに成功しています。

    LAST SEEN   TYPE     REASON   OBJECT                  MESSAGE
    5m18s       Normal   ADD      gateway/internal-http   store/internal-http
    3m44s       Normal   UPDATE   gateway/internal-http   store/internal-http
    3m9s        Normal   SYNC     gateway/internal-http   SYNC on store/internal-http was a success
    

ヘッダーに基づくカナリア

ヘッダーに基づくカナリアでは、サービス オーナーは実際のユーザー以外からの合成テスト トラフィックを照合できます。これは、ユーザーを直接公開することなく、アプリケーションの基本ネットワークが機能していることを簡単に検証できる方法です。

  1. 次の HTTPRoute マニフェストを構成クラスタに適用します(この例では gke-west-1)。

    cat << EOF | kubectl apply --context gke-west-1 -f -
    kind: HTTPRoute
    apiVersion: gateway.networking.k8s.io/v1
    metadata:
      name: internal-store-route
      namespace: store
      labels:
        gateway: internal-http
    spec:
      parentRefs:
      - kind: Gateway
        namespace: store
        name: internal-http
      hostnames:
      - "store.example.internal"
      rules:
      # Matches for env=canary and sends it to store-west-2 ServiceImport
      - matches:
        - headers:
          - name: env
            value: canary
        backendRefs:
          - group: net.gke.io
            kind: ServiceImport
            name: store-west-2
            port: 8080
      # All other traffic goes to store-west-1 ServiceImport
      - backendRefs:
        - group: net.gke.io
          kind: ServiceImport
          name: store-west-1
          port: 8080
    EOF
    

    デプロイされると、この HTTPRoute は次のルーティング動作を構成します。

    • env: canary HTTP ヘッダーを使用しない store.example.internal への内部リクエストは、gke-west-1 クラスタの store Pod に転送されます。
    • env: canary HTTP ヘッダーを使用する store.example.internal への内部リクエストは、gke-west-2 クラスタの store Pod に転送されます。

    HTTPRoute では、HTTP ヘッダーに基づいて異なるクラスタに転送できます。

    Gateway IP アドレスにトラフィックを送信し、HTTPRoute が正しく機能していることを確認します。

  2. internal-http から内部 IP アドレスを取得します。

    kubectl get gateways.gateway.networking.k8s.io internal-http -o=jsonpath="{.status.addresses[0].value}" --context gke-west-1 --namespace store
    

    次の手順の VIP は、出力として受け取る IP アドレスに置き換えます。

  3. env: canary HTTP ヘッダーを使用して、Gateway にリクエストを送信します。これにより、トラフィックが gke-west-2 に転送されていることを確認します。GKE クラスタと同じ VPC のプライベート クライアントを使用して、リクエストが正しく転送されていることを確認します。次のコマンドは、Gateway IP アドレスへのプライベート アクセスがあるマシンで実行する必要があります。そうしないと、機能しません。

    curl -H "host: store.example.internal" -H "env: canary" http://VIP
    

    この出力で、リクエストが gke-west-2 クラスタから Pod によって処理されたことを確認できます。

    {
        "cluster_name": "gke-west-2", 
        "host_header": "store.example.internal",
        "node_name": "gke-gke-west-2-default-pool-4cde1f72-m82p.c.agmsb-k8s.internal",
        "pod_name": "store-5f5b954888-9kdb5",
        "pod_name_emoji": "😂",
        "project_id": "agmsb-k8s",
        "timestamp": "2021-05-31T01:21:55",
        "zone": "us-west1-a"
    }
    

トラフィック ミラーリング

このステージでは、トラフィックを目的のクラスタに送信するだけでなく、カナリア クラスタにトラフィックをミラーリングします。

ミラーリングを使用すると、クライアントへのレスポンスに影響を与えることなく、トラフィックの負荷がアプリケーションのパフォーマンスに与える影響を判断するのに役立ちます。すべての種類のロールアウトで必要になるとは限りませんが、パフォーマンスや負荷に影響を及ぼす可能性のある大規模な変更をロールアウトする場合に役立ちます。

  1. 次の HTTPRoute マニフェストを構成クラスタに適用します(この例では gke-west-1)。

    cat << EOF | kubectl apply --context gke-west-1 -f -
    kind: HTTPRoute
    apiVersion: gateway.networking.k8s.io/v1
    metadata:
      name: internal-store-route
      namespace: store
      labels:
        gateway: internal-http
    spec:
      parentRefs:
      - kind: Gateway
        namespace: store
        name: internal-http
      hostnames:
      - "store.example.internal"
      rules:
      # Sends all traffic to store-west-1 ServiceImport
      - backendRefs:
        - name: store-west-1
          group: net.gke.io
          kind: ServiceImport
          port: 8080
        # Also mirrors all traffic to store-west-2 ServiceImport
        filters:
        - type: RequestMirror
          requestMirror:
            backendRef:
              group: net.gke.io
              kind: ServiceImport
              name: store-west-2
              port: 8080
    EOF
    
  2. プライベート クライアントを使用して、internal-http Gateway にリクエストを送信します。 /mirror パスを使用して、後の手順でアプリケーション ログを確認する際にこのリクエストを一意に識別できるようにします。

    curl -H "host: store.example.internal" http://VIP/mirror
    
  3. この出力で、クライアントが gke-west-1 クラスタの Pod からレスポンスを受信していることを確認します。

    {
        "cluster_name": "gke-west-1", 
        "host_header": "store.example.internal",
        "node_name": "gke-gke-west-1-default-pool-65059399-ssfq.c.agmsb-k8s.internal",
        "pod_name": "store-5f5b954888-brg5w",
        "pod_name_emoji": "🎖",
        "project_id": "agmsb-k8s",
        "timestamp": "2021-05-31T01:24:51",
        "zone": "us-west1-a"
    }
    

    これにより、プライマリ クラスタがトラフィックに応答していることを確認します。また、移行先のクラスタがミラーリング対象トラフィックを受信していることも確認する必要があります。

  4. gke-west-2 クラスタの store Pod のアプリケーション ログを確認します。このログは、Pod がロードバランサからミラーリングされたトラフィックを受け取ったことを確認するものです。

    kubectl logs deployment/store --context gke-west-2 -n store | grep /mirror
    
  5. この出力では、gke-west-2 クラスタの Pod も同じリクエストを受信していますが、これらのリクエストに対するレスポンスがクライアントに送信されていないことを確認できます。ログに記録された IP アドレスは、Pod と通信しているロードバランサの内部 IP アドレスになります。

    Found 2 pods, using pod/store-5c65bdf74f-vpqbs
    [2023-10-12 21:05:20,805] INFO in _internal: 192.168.21.3 - - [12/Oct/2023 21:05:20] "GET /mirror HTTP/1.1" 200 -
    [2023-10-12 21:05:27,158] INFO in _internal: 192.168.21.3 - - [12/Oct/2023 21:05:27] "GET /mirror HTTP/1.1" 200 -
    [2023-10-12 21:05:27,805] INFO in _internal: 192.168.21.3 - - [12/Oct/2023 21:05:27] "GET /mirror HTTP/1.1" 200 -
    

トラフィック分割

トラフィック分割は、新しいコードをロールアウトするか、新しい環境に安全にデプロイする最も一般的な方法の 1 つです。サービス オーナーは、カナリア バックエンドに送信されるトラフィックの明示的な割合を設定します。これは通常、トラフィック全体に対してかなり少ない量になります。実際のユーザー リクエストに対して許容可能なリスク量も判断できます。

ごく少量のトラフィックでトラフィック分割を行うと、サービス オーナーはアプリケーションの状態とレスポンスを検査できます。すべてのシグナルが正常と思われる場合は、カットオーバーに進みます。

  1. 次の HTTPRoute マニフェストを構成クラスタに適用します(この例では gke-west-1)。

    cat << EOF | kubectl apply --context gke-west-1 -f -
    kind: HTTPRoute
    apiVersion: gateway.networking.k8s.io/v1
    metadata:
      name: internal-store-route
      namespace: store
      labels:
        gateway: internal-http
    spec:
      parentRefs:
      - kind: Gateway
        namespace: store
        name: internal-http
      hostnames:
      - "store.example.internal"
      rules:
      - backendRefs:
        # 90% of traffic to store-west-1 ServiceImport
        - name: store-west-1
          group: net.gke.io
          kind: ServiceImport
          port: 8080
          weight: 90
        # 10% of traffic to store-west-2 ServiceImport
        - name: store-west-2
          group: net.gke.io
          kind: ServiceImport
          port: 8080
          weight: 10
    EOF
    
  2. プライベート クライアントを使用して、継続的 curl リクエストを internal- http Gateway に送信します。

    while true; do curl -H "host: store.example.internal" -s VIP | grep "cluster_name"; sleep 1; done
    

    出力は次のようになります。90 / 10 のトラフィック分割が発生していることがわかります。

    "cluster_name": "gke-west-1",
    "cluster_name": "gke-west-1",
    "cluster_name": "gke-west-1",
    "cluster_name": "gke-west-1",
    "cluster_name": "gke-west-1",
    "cluster_name": "gke-west-1",
    "cluster_name": "gke-west-1",
    "cluster_name": "gke-west-1",
    "cluster_name": "gke-west-2",
    "cluster_name": "gke-west-1",
    "cluster_name": "gke-west-1",
    ...
    

トラフィックのカットオーバー

Blue/Green 移行の最終ステージでは、新しいクラスタへの完全なカットオーバーを行い、古いクラスタを削除します。サービス オーナーが 2 つ目のクラスタを既存のクラスタにオンボーディングした場合、最後のステップは異なります。最後のステップでは、両方のクラスタにトラフィックが送信されます。そのシナリオでは、gke-west-1 クラスタと gke-west-2 クラスタの両方の Pod を持つ単一の store ServiceImport をおすすめします。これにより、ロードバランサは、近接性、健全性、容量に基づいて、アクティブ / アクティブなアプリケーションのトラフィックの送信先を決定できます。

  1. 次の HTTPRoute マニフェストを構成クラスタに適用します(この例では gke-west-1)。

    cat << EOF | kubectl apply --context gke-west-1 -f -
    kind: HTTPRoute
    apiVersion: gateway.networking.k8s.io/v1
    metadata:
      name: internal-store-route
      namespace: store
      labels:
        gateway: internal-http
    spec:
      parentRefs:
      - kind: Gateway
        namespace: store
        name: internal-http
      hostnames:
      - "store.example.internal"
      rules:
        - backendRefs:
          # No traffic to the store-west-1 ServiceImport
          - name: store-west-1
            group: net.gke.io
            kind: ServiceImport
            port: 8080
            weight: 0
          # All traffic to the store-west-2 ServiceImport
          - name: store-west-2
            group: net.gke.io
            kind: ServiceImport
            port: 8080
            weight: 100
    EOF
    
  2. プライベート クライアントを使用して、継続的 curl リクエストを internal- http Gateway に送信します。

    while true; do curl -H "host: store.example.internal" -s VIP | grep "cluster_name"; sleep 1; done
    

    出力は次のようになります。すべてのトラフィックが gke-west-2 に送信されるようになります。

    "cluster_name": "gke-west-2",
    "cluster_name": "gke-west-2",
    "cluster_name": "gke-west-2",
    "cluster_name": "gke-west-2",
    ...
    

この最後のステップで、1 つの GKE クラスタから別の GKE クラスタへの Blue/Green アプリケーションを完全に移行します。

クリーンアップ

このドキュメントの演習を完了したら、アカウントで不要な請求が発生しないように、以下の手順でリソースを削除します。

  1. クラスタを削除します

  2. 他の目的で登録する必要がない場合は、フリートからクラスタの登録を解除します。

  3. multiclusterservicediscovery 機能を無効にします。

    gcloud container fleet multi-cluster-services disable
    
  4. マルチクラスタ Ingress を無効にします。

    gcloud container fleet ingress disable
    
  5. API を無効にします。

    gcloud services disable \
        multiclusterservicediscovery.googleapis.com \
        multiclusteringress.googleapis.com \
        trafficdirector.googleapis.com \
        --project=PROJECT_ID
    

トラブルシューティング

正常なアップストリームがない

症状:

Gateway を作成してもバックエンド サービスにアクセスできない場合(503 レスポンス コード)、次の問題が発生している可能性があります。

no healthy upstream

理由:

このエラー メッセージは、ヘルスチェック プローバーが正常なバックエンド サービスを見つけられないことを示します。バックエンド サービスは正常である可能性がありますが、ヘルスチェックのカスタマイズが必要になる場合もあります。

回避策:

この問題を解決するには、HealthCheckPolicy を使用して、アプリケーションの要件(/health など)に基づいてヘルスチェックをカスタマイズします。

次のステップ