Amazon Web Services ブログ

AWS Load Balancer Controller を使った Blue/Green デプロイメント、カナリアデプロイメント、A/B テスト

これまで AWS をお使いのみなさまは、一般的に Flagger やサービスメッシュ、CI/CD などのソリューションを使用して、Blue/Green デプロイメント、A/B テスト、トラフィック管理をしてきました。AWS Load Balancer Controller ( 旧称:ALB Ingress Controller ) がリリースされたことで、EKS ユーザーは AWS Application Load Balancer をネイティブサポートした Kubernetes Ingress リソースを介して Blue/Green デプロイメント、A/B テスト、カナリアデプロイをすることが可能になりました。

このブログでは、AWS Application Load Balancer の加重ターゲットグループの概念、高度なリクエストルーティング、そして Kubernetes Ingress リソースを介してそれらの設定を管理する方法を紹介します。

ソリューション概要

加重ターゲットグループ

AWS ユーザーが Blue/Green デプロイやカナリアデプロイ、A/B テスト戦略を採用できるように、AWS は 2019/11 に Application Load Balancer の加重ターゲットグループを発表しました。複数のターゲットグループが、あるリスナールールの同じ転送アクションに関連づけられ、各ターゲットグループの重みを指定できます。これにより、開発者がアプリケーションの異なるバージョンに対して通信をどのように分散するかコントロールできます。例えば、8 対 2 の重み付けがされた 2 つのターゲットグループを持つリスナールールを定義した場合、ロードバランサーは通信を 80 %対 20 %の割合で各ターゲットグループにルーティングします。

高度なリクエストルーティング

加えて、加重ターゲットグループに関して、AWS は 2019 年に高度なリクエストルーティング機能を発表しています。高度なリクエストルーティングでは、開発者が標準 及び カスタム HTTP ヘッダーやメソッド、リクエストパス、クエリ文字列、ソース IP アドレスに基づいてルール ( 及びトラフィックルーティング ) を作成することができます。この新機能により、ルーティングのためのプロキシフリートが不要になるためアプリケーションを簡素化し、ロードバランサーで不要なトラフィックをブロックし、A/B テストの実装が可能になります。

AWS Load Balancer Controller

AWS Load Balancer Controller は Kubernetes クラスターが Elastic Load Balancing を管理するのを助けるコントローラーです。Kubernetes Ingress リソースに従い、Application Load Balancer のプロビジョニングします。プロビジョニングされた Application Load Balancer の動作をカスタマイズするため、アノテーションを Kubernetes Ingress オブジェクトに追加します。これにより、開発者は Application Load Balancer を構成し、Kubernetes のネイティブセマンティックスを用い、Blue/Green デプロイメント、カナリアデプロイメント、A/B デプロイメントを実現します。例えば、下記の Ingress アノテーションは、2 つのアプリケーションバージョン間で通信を分散するための Application Load Balancer の設定です。

annotations:
   ...
  alb.ingress.kubernetes.io/actions.blue-green: |
    {
      "type":"forward",
      "forwardConfig":{
        "targetGroups":[
          {
            "serviceName":"hello-kubernetes-v1",
            "servicePort":"80",
            "weight":50
          },
          {
            "serviceName":"hello-kubernetes-v2",
            "servicePort":"80",
            "weight":50
          }
        ]
      }
    }

ウォークスルー

前提条件

  • AWS Application Load Balancer、Amazon EKS、Kubernetes に対する十分な理解
  • Amazon EKS コマンドラインツールである eksctl
  • Kubernetes コマンドラインツールである kubectl 及び helm

eksclt を用いたEKSクラスターの作成

下記のコマンドを利用して Amazon EKS クラスターを作成します。クラスター名 dev を独自の名前に書き換えることは可能です。region-code を Amazon EKS がサポートされる他のリージョンに書き換えることもできます。

より詳細は、こちらを参照ください。

$ eksctl create cluster --name dev --region ap-southeast-2
......
2021-12-31 16:15:04 [ℹ]  using region ap-southeast-2
2021-12-31 16:15:05 [ℹ]  setting availability zones to [ap-southeast-2a ap-southeast-2c ap-southeast-2b]
2021-12-31 16:15:05 [ℹ]  subnets for ap-southeast-2a - public:192.168.0.0/19 private:192.168.96.0/19
2021-12-31 16:15:05 [ℹ]  subnets for ap-southeast-2c - public:192.168.32.0/19 private:192.168.128.0/19
2021-12-31 16:15:05 [ℹ]  subnets for ap-southeast-2b - public:192.168.64.0/19 private:192.168.160.0/19
......
2021-12-31 16:31:36 [ℹ]  node "ip-192-168-23-165.ap-southeast-2.compute.internal" is ready
2021-12-31 16:31:36 [ℹ]  node "ip-192-168-79-33.ap-southeast-2.compute.internal" is ready
2021-12-31 16:31:38 [ℹ]  kubectl command should work with "/Users/<username>/.kube/config", try 'kubectl get nodes'
2021-12-31 16:31:38 [✔]  EKS cluster "dev" in "ap-southeast-2" region is ready

AWS Load Balancer Controller のインストール

本ドキュメントに記載されているように、最新バージョンの AWS Load Balancer Controller をインストールします。

AWS Load Balancer Controller がデプロイされたことを確認してください。

$ kubectl get deployment -n kube-system aws-load-balancer-controller
NAME                           READY   UP-TO-DATE   AVAILABLE   AGE
aws-load-balancer-controller   2/2     2            2           5m34s

サンプルアプリケーションバージョン 1 とバージョン 2 のデプロイ

使用するサンプルアプリケーションは、hello-kubernetes です。カスタムメッセージを含む 2 つのバージョンのアプリケーションをデプロイし、サービスタイプを ClusterIP に設定します。

$ helm install --create-namespace --namespace hello-kubernetes v1 \
  ./hello-kubernetes/deploy/helm/hello-kubernetes \
  --set message="You are reaching hello-kubernetes version 1" \
  --set ingress.configured=true \
  --set service.type="ClusterIP"
NAME: v1
LAST DEPLOYED: Sat Jan  1 15:12:57 2022
NAMESPACE: hello-kubernetes
STATUS: deployed
REVISION: 1
TEST SUITE: None

$ helm install --create-namespace --namespace hello-kubernetes v2 \
  ./hello-kubernetes/deploy/helm/hello-kubernetes \
  --set message="You are reaching hello-kubernetes version 2" \
  --set ingress.configured=true \
  --set service.type="ClusterIP"
NAME: v2
LAST DEPLOYED: Sat Jan  1 15:13:26 2022
NAMESPACE: hello-kubernetes
STATUS: deployed
REVISION: 1
TEST SUITE: None

Ingress のデプロイと Blue/Green デプロイメントのテスト

Ingress のアノテーション(alb.ingress.kubernetes.io/actions.${action-name}) は、Application Load Balancer のリスナーに対するカスタムアクションを構成する方法を提供します。例えば、リダイレクトアクションやフォワードアクションなどです。フォワードアクションの場合、異なる重みを持つ複数のターゲットグループがアノテーションで定義されます。AWS Load Balancer Controller は、ターゲットグループをプロビジョニングし、通信を転送するためにアノテーションに従ってリスナールールを設定します。例えば、下記の Ingress リソースは 全通信を hello-kubernetes-v1 サービス ( 重み: 100 対 0 ) にフォワードする Application Load Balancer を構成します。

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: "hello-kubernetes"
  namespace: "hello-kubernetes"
  annotations:
    kubernetes.io/ingress.class: alb
    alb.ingress.kubernetes.io/scheme: internet-facing
    alb.ingress.kubernetes.io/target-type: ip
    alb.ingress.kubernetes.io/actions.blue-green: |
      {
        "type":"forward",
        "forwardConfig":{
          "targetGroups":[
            {
              "serviceName":"hello-kubernetes-v1",
              "servicePort":"80",
              "weight":100
            },
            {
              "serviceName":"hello-kubernetes-v2",
              "servicePort":"80",
              "weight":0
            }
          ]
        }
      }
  labels:
    app: hello-kubernetes
spec:
  rules:
    - http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: blue-green
                port:
                  name: use-annotation

アノテーションの action-name は Ingress ルール内の serviceName に一致する必要があり、servicePort は前のコードスニペットと同様に use-annotation と記載する必要がある点に注意してください。

下記の Ingress リソースをデプロイし、AWS Load Balancer Controller によって Application Load Balancer が作成と設定がされるまで 2 分程度待ちます。

ロードバランサーエンドポイントからのレスポンスが、常にアプリケーションバージョン 1 からであることを確認してください。

$ kubectl apply -f ingress.yaml
ingress.networking.k8s.io/hello-kubernetes configured

$ while true; do curl -s k8s-hellokub-hellokub-1c21b68bea-597504338.ap-southeast-2.elb.amazonaws.com | grep version; sleep 1; done
  You are reaching hello-kubernetes version 1
  You are reaching hello-kubernetes version 1
  You are reaching hello-kubernetes version 1

Blue/Green デプロイメント

Blue/Green デプロイメントをするために、すべての通信がバージョン 2 に流れるように Ingress のアノテーションをアップデートします。

下記の Ingress リソースをデプロイします。

alb.ingress.kubernetes.io/actions.blue-green: |
  {
     "type":"forward",
     "forwardConfig":{
       "targetGroups":[
         {
           "serviceName":"hello-kubernetes-v1",
           "servicePort":"80",
           "weight":0
         },
         {
           "serviceName":"hello-kubernetes-v2",
           "servicePort":"80",
           "weight":100
         }
       ]
     }
   }

ロードバランサーエンドポイントからのレスポンスが、常にアプリケーションバージョン 2 からであることを確認してください。

$ kubectl apply -f ingress.yaml
ingress.networking.k8s.io/hello-kubernetes (http://ingress.networking.k8s.io/hello-kubernetes) configured
 
$ while true; do curl -s k8s-hellokub-hellokub-1c21b68bea-597504338.ap-southeast-2.elb.amazonaws.com (http://k8s-hellokub-hellokub-1c21b68bea-597504338.ap-southeast-2.elb.amazonaws.com/) | grep version; sleep 1; done
  You are reaching hello-kubernetes version 2
  You are reaching hello-kubernetes version 2
  You are reaching hello-kubernetes version 2

Ingress のデプロイとカナリアデプロイメントのテスト

すべての通信をバージョン 2 へ向ける代わりに、バージョン 2 の重みを段階的に増やすことで、通信を緩やかにバージョン 2 へシフトできます。これにより、より多くの通信をバージョン 2 に流す前に、プロダクションの通信の一部でバージョン 2 を検証できます。下記の例では、通信の 10 %がバージョン 2 へ、残りの 90 %がバージョン1へ流れています。

下記の Ingress リソースをデプロイします。

alb.ingress.kubernetes.io/actions.blue-green: |
  {
     "type":"forward",
     "forwardConfig":{
       "targetGroups":[
         {
           "serviceName":"hello-kubernetes-v1",
           "servicePort":"80",
           "weight":90
         },
         {
           "serviceName":"hello-kubernetes-v2",
           "servicePort":"80",
           "weight":10
         }
       ]
     }
   }

ロードバランサーからのレスポンスを確認してください。

$ kubectl apply -f ingress.yaml
ingress.networking.k8s.io/hello-kubernetes configured

$ while true; do curl -s k8s-hellokub-hellokub-1c21b68bea-597504338.ap-southeast-2.elb.amazonaws.com | grep version; sleep 1; done
  You are reaching hello-kubernetes version 1
  You are reaching hello-kubernetes version 2
  You are reaching hello-kubernetes version 1
  You are reaching hello-kubernetes version 1
  You are reaching hello-kubernetes version 1

Argo Rolluots

プロダクション環境でカナリアデプロイメントをする時、通常、少しずつ通信を切り替えます。その際、ある程度の自動化が施されます。このプロセスには、さまざまなパフォーマンス監視システムも組み込まれ、各ステップでエラーが発生してないか、またエラーが許容閾値以下であるか確認します。これを実現するためには、Argo Rollouts などのプログレッシブデリバリーの仕組みが非常に有益です。
Argo Rollouts は、AWS Load Balancer Controller のアノテーションベースのトラフィックシェーピング機能を利用して、更新中に徐々に通信を新しいバージョンのアプリケーションにシフトするためのファーストクラスのサポートを提供します。加えて、Argo Rollouts は、主要な KPI を検証するためにさまざまなプロバイダーからのメトリックを照会して解釈し、更新中の自動プロモーションまたはロールバックを促進できます。より詳細な情報は、こちらをご参照ください。

Ingress のデプロイとA/Bテスト

Ingress の spec で設定するホストやパスのルーティング条件に加え、Ingress アノテーション alb.ingress.kubernetes.io/conditions.${conditions-name} を用い、ルーティング条件を設定できます。また、http-header , http-request-method , query-string 及び source-ip を基準としたルーティングを設定することもできます。これによって開発者は、サービスメッシュ等の個別のルーティング機構をセットアップ 及び 管理する必要なく、A/B テストを実装するためにこれらのルーティングオプションを利用することができます。
AWS Load Balancer Controller は、特定のバックエンドへの通信の一部を転送するために、アノテーションに従ってリスナールールを設定します。下記の例では、全てのリクエストがデフォルトでアプリケーションバージョン 1 へ転送されます。リクエストのカスタム HTTP ヘッダーに HeaderName=HeaderValue1 が含まれる場合、下記の Ingress リソースは、アプリケーションバージョン 2 へ通信を転送します。

下記の Ingress リソースをデプロイしてください。

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: "hello-kubernetes"
  namespace: "hello-kubernetes"
  annotations:
    kubernetes.io/ingress.class: alb
    alb.ingress.kubernetes.io/scheme: internet-facing
    alb.ingress.kubernetes.io/target-type: ip
    alb.ingress.kubernetes.io/conditions.ab-testing: >
      [{"field":"http-header","httpHeaderConfig":{"httpHeaderName": "HeaderName", "values":["HeaderValue1"]}}]
    alb.ingress.kubernetes.io/actions.ab-testing: >
      {"type":"forward","forwardConfig":{"targetGroups":[{"serviceName":"hello-kubernetes-v2","servicePort":80}]}}
  labels:
    app: hello-kubernetes
spec:
  rules:
    - http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: ab-testing
                port:
                  name: use-annotation
          - path: /
            pathType: Prefix
            backend:
              service:
                name: hello-kubernetes-v1
                port:
                  name: http

ロードバランサーエンドポイントからのレスポンスを確認してください。

$ kubectl apply -f ingress-ab.yaml
ingress.networking.k8s.io/hello-kubernetes configured

$ while true; do curl -s k8s-hellokub-hellokub-1c21b68bea-597504338.ap-southeast-2.elb.amazonaws.com | grep version; sleep 1; done
  You are reaching hello-kubernetes version 1
  You are reaching hello-kubernetes version 1
  You are reaching hello-kubernetes version 1

$ while true; do curl -s -H "HeaderName: HeaderValue1" k8s-hellokub-hellokub-1c21b68bea-597504338.ap-southeast-2.elb.amazonaws.com | grep version; sleep 1; done
  You are reaching hello-kubernetes version 2
  You are reaching hello-kubernetes version 2
  You are reaching hello-kubernetes version 2

クリーンアップ

VPC 削除を妨げる VPC 内の孤立したリソースが発生しない様、Ingress リソースを削除する必要があります。これにより最初に Application Load Balancer が削除されます。

$ kubectl delete ing hello-kubernetes -n hello-kubernetes
ingress.extensions "hello-kubernetes" deleted

$ eksctl delete cluster --name dev
2022-01-03 19:33:48 [ℹ]  eksctl version 0.79.0
2022-01-03 19:33:48 [ℹ]  using region ap-southeast-2
2022-01-03 19:33:48 [ℹ]  deleting EKS cluster "dev"
......
2022-01-03 19:37:59 [ℹ]  will delete stack "eksctl-dev-cluster"
2022-01-03 19:37:59 [✔]  all cluster resources were deleted

まとめ

Blue/Green デプロイメント、カナリアデプロイメント、A/B テストを採用するためのさまざまな方法があります。このブログでは、AWS Load Balancer Controller を使用して Kubernetes Ingress リソースを管理することにより、AWS Application Load Balancer で同様のデプロイメント戦略の実現する方法にを示しました。

翻訳はソリューションアーキテクト祖父江が担当しました。原文はこちらです。