Amazon Web Services ブログ

AWS App Mesh を使用した Amazon ECS でのカナリアデプロイパイプラインの作成

この記事は Create a pipeline with canary deployments for Amazon ECS using AWS App Mesh を翻訳したものです。

この記事では Amazon Elastic Container Service (Amazon ECS) で実行されるアプリケーションのカナリアデプロイ戦略を、AWS App Meshと組み合わせて実装する方法を説明します。ALB の加重ターゲットグループを使用した AWS CodeDeploy でのカナリアデプロイを行う場合はこちらの記事を参照してください。

Amazon ECS や Amazon EKS などのコンテナオーケストレータを利用することで、世界中のお客様は複雑なアプリケーションを大規模に実行できます。ただし、これらのツールの機能を活用する場合、お客様は高度に分散されたサービス群を実行するために、分散アーキテクチャにおけるマイクロサービス間の接続性、運用、およびセキュリティの管理について考える必要があります。

AWS App Mesh は、アプリケーションレベルのネットワーキングを提供するサービスメッシュで、様々なタイプのコンピューティングインフラストラクチャ上でのサービス同士の通信を容易にします。また、エンドツーエンドの可視性を提供し、アプリケーションの高可用性を確保するのに役立ちます。App Mesh はアプリケーション内のすべてのサービス間の一貫した可視性と、ネットワークトラフィック制御を提供します。

1. カナリアデプロイ

カナリアデプロイ/リリースでは、特定のアプリケーションの新しいバージョンをデプロイした後、ビジネスニーズに応じてユーザー定義のパーセント単位でトラフィックを切り替えられます。また、新しいバージョンの状態を監視できるため、問題が発生した場合でも自動的にトラフィックを古いバージョンに切り替えることができます。新しいバージョンのアプリケーションに混入したバグが、エンドカスタマーに与える影響を減らすことができます。このアプローチは、新しい機能の実装に役立つだけではなく、複雑な分散型マイクロサービスのテストのニーズにも対応しており、トラフィクの一部をコントロールしながら新しいバージョンに送ることができます。

2. アーキテクチャ概要

以下のアーキテクチャ図は、パイプラインの作成に使用される AWS サービスの概要を示しています。

このアーキテクチャをデモするために Yelb というアプリケーションを使用します。Yelb のアプリケーション上で、ユーザーがレストランなどの一連の選択肢に投票すると、投票に基づいて円グラフが動的に更新されます。さらに、Yelb はページビューの数を追跡し、 yelb-appserverインスタンスのホスト名を出力します。このホスト名は、投票やページ更新の API リクエストを受けるタスクの (Amazon ECS で awsvpc ネットワークモードを利用した際の) IP アドレスになります。Yelb には以下のコンポーネントが含まれます。

  • JS コードをブラウザに送信する役割を担う yelb-ui というフロントエンド
  • キャッシュサーバー (yelb-redisserver) への読み書きを行う Sinatra アプリケーションと、アプリケーションをホストする yelb-appserver というアプリケーションサーバー、Postgres のバックエンドデータベース (yelb-db)
  • Redis はページビュー数を格納し、Postgres は投票数を格納

Yelb のアーキテクチャは次のようになります。

Yelb の設定では、すべてのコンテナにエフェメラルディスクが使用されます。この方法でデータベースを実行するのは、あくまでデモのためであることにご注意ください。

3. インフラストラクチャのセットアップ

この記事では AWS のオレゴンリージョン (us-west-2) を使用します。

以降の手順では必要なツールが備わった環境が必要です。このチュートリアルを実行するために AWS Cloud9 インスタンスを使用します。AWS アカウント上に Cloud9 インスタンスを作成する場合、Amazon ECS Workshop の “Create a Workspace” の章の初めから “Validate the IAM role“ までの手順を参照してください。

3.1 要件

パイプラインを作成する前に、インストールや設定が必要なものがいくつかあります。まず GitHub リポジトリのクローンを作成することから始めます。

# ヘルパースクリプトと、ブログ投稿のリソース
git clone https://github.com/aws/aws-app-mesh-examples.git
cd aws-app-mesh-examples/blogs/ecs-canary-deployments-pipeline/

# サンプルアプリケーション
git clone https://github.com/tiagoReichert/yelb.git && mv yelb microservices

手順を簡略化するために Makefileを追加しています。この Makefile は、ダウンロードしたリソースの setup フォルダの下に配置されたヘルパースクリプト実行します。 これらのヘルパースクリプトは AWS Cloud9 インスタンスで作成およびテストされており、次の手順でリソースの作成を容易にするために使用されます。ヘルパースクリプトの機能を理解するために、スクリプトの中身を確認してみてください。

それでは、いくつかの環境変数をエクスポートすることから始めましょう (これらの変数は自由にカスタマイズしてください) 。

make set-env

注: 上記のコマンドは、/setup/scripts/export_environment_variables.sh を実行します。

また、AWS クラウド上で、定義された仮想ネットワークで AWS リソースを起動する論理的に分離されたセクションをプロビジョニングするためには、Amazon Virtual Private Cloud (VPC) が必要です。VPC では自身で定義した仮想ネットワーク内で AWS リソースを起動することができます。独自の IP アドレス範囲の選択、サブネットの作成、ルートテーブルとネットワークゲートウェイの設定など、仮想ネットワーク環境を完全に制御できます。

make create-vpc

注:上記のコマンドは、/setup/scripts/create_vpc.sh を実行します。

3.2 Amazon ECS クラスターと AWS App Mesh でサービスメッシュを作成

Amazon Elastic Container Service (Amazon ECS) は、クラスター上のコンテナの実行、停止、管理を容易にするスケーラブルで高速なコンテナ管理サービスです。ここでは、ECS サービスを起動するための前提条件を備えた空の Amazon ECS クラスターと AWS App Mesh でのサービスメッシュを作成し、マイクロサービスでカナリアデプロイを実行するために使用します。

make create-ecs-mesh

注:上記のコマンドは、/setup/scripts/create_ecs_mesh.sh を実行します。

3.3 Amazon ECS クラスターでの Prometheus メトリクスのコレクション

CloudWatch Container Insights の Prometheus メトリクスのモニタリング は、コンテナ化されたシステムとワークロードからの Prometheus メトリクスの検出を自動化します。Prometheus は、オープンソースのシステム監視およびアラートのためのツールキットです。詳細は Prometheus のドキュメントにある “What is Prometheus?” を参照してください。動的なサービスディスカバリメカニズムの概念を活用し、正規表現ベースのサービスディスカバリに基づいて、ECS タスク定義の ARN をベースに ECS タスク定義を検出します。

make create-monitoring-resources

注: 上記のコマンドは、/setup/scripts/create_prometheus_env.sh を実行します。

4. パイプラインの作成

これですべての要件がインストール、設定されたので、マイクロサービスごとに AWS CodePipeline を利用したパイプラインの作成を進めることができます。

4.1 共有の AWS CloudFormation スタック

共有の AWS CloudFormation スタックには、パイプラインのデプロイステージを担当する複数の AWS Lambda 関数と 1 つの AWS Step Functions ステートマシンが含まれています。すべての設定パラメータを入力として受け入れるため、すべてのデプロイで同じリソースを使用できます。

AWS Step Functions のステートマシンは、マイクロサービス単位のリポジトリで利用可能な CloudFormation テンプレートのヘルパーを使用して、カナリアデプロイを実行する複数の Lambda 関数をオーケストレーションします。これは、ユーザーが定義した待機時間を考慮して、アプリケーションの複数バージョン間でトラフィックの切り替えを行います。

ステートマシンのワークフローの概要:

  • Step 1: Check Deployment Version は、AWS Systems Manager (AWS SSM) パラメータストアからデプロイされたバージョンを確認する、ステートマシンワークフローの Lambda のステップです。最初のデプロイ時に、関数はバージョンを 1 として返し、その後のデプロイ時には、デプロイされた対応するバージョンを返します。
  • Step 2: Deploy New Version/CFN は、ヘルパーとなるCloudFormationテンプレートをデプロイする Lambda のステップです。このテンプレートでは新バージョンに必要なカナリアリソースをさらにデプロイし、リソース名の前にバージョンコントロールから取得した Git のコミットハッシュ を付けます。
  • Step 3: Canary & Switch Traffic は、AWS App Mesh の仮想ルーターにおける仮想ルート間のトラフィック割合をシフトさせ、ユーザーが定義した Wait time を尊重する Lambda のステップです。そして、Gather Health Check Status は、直近デプロイメントの状態を表示する Lambda のステップです。
  • デプロイが SUCCESSFUL になると何が起きますか?
    • デプロイが正常であると判断された場合、トラフィックの新バージョンへの移行率が 100% になるまで Step 3が繰り返されます。その後、ワークフローはデプロイが初回かどうかを確認します。初回のデプロイの場合、クリーンアップすべき古いバージョン (以前のカナリアコンポーネント) はありません。2回目以降のデプロイの場合は、Remove Old Version Lambda のステップで古いバージョンをクリーンアップし、AWS SSM パラメータストアのデプロイバージョンを更新します。
  • デプロイが FAILED になるとどうなりますか?
    • デプロイが正常でないと判断された場合、ワークフローは RollBack Lambda のステップを起動し、現在のデプロイされたバージョン (不具合のあるバージョン) をクリーンアップし、以前にデプロイされた稼働中のバージョンを保持します。

では、共有のCloudFormationスタックを作成してみましょう。

make create-shared-cloudformation-stack

注: 上記のコマンドは、/setup/scripts/create_shared_cloudformation_stack.sh を実行します。

最後のコマンドが CREATE_COMPLETE を返した場合のみ、次のステップに進みます。

4.2 パイプラインの AWS CloudFormation スタック

パイプラインの AWS CloudFormation スタックは、マイクロサービス単位でデプロイするように設計されています。1つのAWS CodeCommit リポジトリと、そのリポジトリの master ブランチへの新規コミットをトリガーとするパイプラインを作成します。

デモ用に Yelb マイクロサービスで 4 つのスタックを作成します。USE_SAMPLE_MICROSERVICES という環境変数を「False」に変更すると、パイプラインに必要なすべてのリソースと空の AWS Code Commit リポジトリが作成され、独自のソースコードで使用できるようになります。

Yelb アーキテクチャには4つのマイクロサービスが含まれているため、必要なすべての AWS CloudFormation スタックを作成するスクリプトを作成しました。

make create-sample-microservice-pipeline-stack

注: 上記のコマンドは、/setup/scripts/create_sample_microservice_pipeline_stack.sh を実行します。

最後のコマンドで「All CloudFormation stacks created successfully!」と返ってきた場合のみ、次のステップに進みます。

5. パイプラインのテスト

これでアーキテクチャがすべて作成されたので、このアーキテクチャがどのように機能するのか見ていきましょう。

5.1 設定ファイル

まず、マイクロサービス固有のコードリポジトリがある AWS CodeCommit リポジトリを見てみましょう。 そこには 3 つのファイルがある specfiles というフォルダがあります。これら 3 つのファイルに Dive Deep していきましょう。

  • build.yml には、マイクロサービス毎の Docker イメージをビルドし、Amazon ECR リポジトリに保存する手順が書かれています。また deploy.json に 2 つのパラメータを追加しています。
    • Sha: コミットハッシュ の先頭が deploy.json ファイルに追加されます。
    • ContainerImage: docker イメージが Amazon ECR にプッシュされた後の Image URI が deploy.jsonファイルに追加されます。
    • CanaryTemplate: base64 でエンコードされた canary-helper.yaml の中身が deploy.json ファイルに追加されます
  • canary-helper.yaml は AWS CloudFormation テンプレートで、デプロイ時に Amazon ECS のサービス、AWS Cloud Map のサービスディスカバリーレコード、AWS App Mesh の仮想ノードの作成に使用されます。デプロイ時に設定できるいくつかの変数が入っていることがわかります。
    • MicroserviceName: deploy.json ファイルの MicroserviceName の値で置き換えられます。
    • Sha: deploy.json ファイルの commit Sha の値で置き換えられます。
    • EnvironmentName: deploy.jsonファイルの EnvironmentName の値で置き換えられます。
    • MeshName: deploy.jsonファイルの MeshName の値で置き換えられます。
  • deploy.json には、マイクロサービスをデプロイするためのコンフィグパラメータが含まれています。
    • EnvironmentName: AWS CloudFormation の クロススタック 参照する際に、AWS リソースの物理名のプレフィックスとして使用される文字列です。

注: この記事では、EnvironmentName を使用して ECS クラスターおよび App Mesh の MeshName リソースの名前を指定します。

    • Namespace: デプロイ先のサービスディスカバリのネームスペースの名前です。
    • MicroserviceName: アプリケーションのマイクロサービス名です。

注: 値の例としては yelb-uiyelb-appserver などがあります。この変数は、AWS App Mesh の仮想ノード、Amazon ECS のサービス、タスク定義、サービスディスカバリレコードなど、アプリケーション固有のカナリアリソースの命名に使用されます。

    • Protocol: AWS App Mesh の仮想ノードのリスナーが使用するマイクロサービスと通信するためのプロトコルです。

注: 値の例としては、httptcpgrpc などがあります。

    • Port: AWS App Mesh の仮想ノードのリスナーが使用するマイクロサービスと通信するためのポートです。
    • PercentageStep: 新バージョンに切り替わったトラフィックの割合を段階的に示したものです。

注: 値の例としては、10、25、50 などがあります。例えば、パーセンテージが 25 に設定されている場合、ステートマシンのワークフローは、パーセンテージが 100% になるまで、25% のトラフィックを旧バージョンから新バージョンへと増分してシフトします。

    • WaitTime: カナリアが旧バージョンから新バージョンに切り替わる際に,各トラフィックが切り替わるのを待つ時間を秒単位で表します.このパラメータは、待機時間中に実行されるテストを完了させるために役立ちます。

注: 値の例としては、60、300 などがあります。

    • FailureThresholdValue: (オプション) 自動ロールバックが発生するまでに許容される 5xx HTTP レスポンスコードの量の最大値を指定するために使用します。(デフォルト = 0)
    • FailureThresholdTime: (オプション) 5xx HTTP レスポンスコードの量をカウントする時間範囲を秒単位で指定するために使用します。CloudWatch メトリクスフィルタは 1 分ごとに集計・報告するため、推奨値は最小で 60 です。(デフォルト = 600)

以下は、マイクロサービスのリポジトリのディレクトリ構造のレイアウト例です。AWS CodeCommit リポジトリ内の他のすべてのものは、それぞれの Yelb アプリケーションのソースコードです。

5.2 パイプラインリソース

作成されたリソースを確認する最も簡単な方法は、AWS CodePipeline を開くことです。AWS コンソール上に先ほど作成した 4 つのパイプラインが表示されています。

その中の一つを開くと、「Source」「Build」「Deploy」のステップが表示されます。これらのステップはすべてカスタマイズ可能です。例えば、デプロイの前にセキュリティチェックや手動承認などを追加することができます。

1. Source ステップは、AWS CodeCommit リポジトリの master ブランチに変更があるかどうかを監視します。Source ステップ内の AWS CodeCommit のリンクをクリックすると、リポジトリにアクセスできます。

2. Build ステップでは、AWS CodeBuild 内で Docker イメージをビルドし、Amazon ECR に保存します。Build ステップから「Details」リンクをクリックすると、ビルドログを見ることができます。

3. Deploy ステップは、共有された AWS CloudFormation スタックによって作成された AWS Step Functions のステートマシンをトリガーします。「Details」リンクをクリックすると、そのステータスが表示されます。

 

 

 

 

Deploy ステップから「Details」タブをクリックすると、実行時のインプットアウトプット、各ステップごとの AWS Lambda 関数実行ログを確認することができます。

トラフィックの 100% が新バージョンに切り替わり、すべてのヘルスチェックに合格したデプロイメントの成功例は、以下の画像のようになります。

CloudWatch メトリクス の ECS/ContainerInsights/Prometheus ネームスペースを見ると、新バージョン (yelb-appserver-<gitsha>)の 5xx レスポンスコードが表示されていないはずです。

5.3 新バージョンのデプロイ

では、Yelb アプリケーションが動作しているのを確認し、パイプラインの動作を確認するためにいくつかの変更を加えてみましょう。

source ~/.bash_profile && echo http://$APP_URI

上記のコマンドで返された URL をお好みのブラウザで開きます。以下のようなページが表示されるはずです (DNS 名が伝播するのに数分かかる場合があります)。

いくつかの投票ボタンをクリックすると値がどのように更新されるかを確認できます。投票中に何かお気づきになりましたか?

実は、yelb-appserver のマイクロサービスに意図的に問題点を追加し、投票が 1 件あるたびに 2 件ずつインクリメントするようにしました。さて、その問題点を修正するとどのように適用されるか見てみましょう。

AWS CodeCommit のコンソールを開きyelb-appserver のリポジトリに移動しましょう。modules/restaurantsdbupdate.rb というファイルを開き、Edit ボタンをクリックして問題点を修正します。以下の行を変更します。

con.prepare('statement1', 'UPDATE restaurants SET count = count +2 WHERE name = $1')
から
con.prepare('statement1', 'UPDATE restaurants SET count = count +1 WHERE name = $1')
へ変更し、コミットします

AWS CodePipeline のコンソールを開くと、数秒後に yelb-appserver-pipelineIn progress と表示されます。それを開いてデプロイの進捗を確認します。Deploy stage になるまで待ち、Yelb アプリケーションのブラウザタブを数回更新します。以下の画像のように App Server のホスト名が元の名前から切り替わっているのを確認できますが、これはカナリアのデプロイが行われているためです。

AWS App Mesh のコンソールで yelb-appserver マイクロサービスの仮想ルーターの詳細を開くと、現時点でのウェイトがどのようになっているかを確認することができます。また AWS CodeCommit の yelb-appserver リポジトリにある specfiles/deploy.json ファイルを開くと percent_stepwait_time パラメータが表示され、すべてのトラフィックを切り替えるのにかかる時間を知ることができます。この例では percent_step: 10wait_time: 60 という値が使われており、すべてのトラフィックを切り替えるのに合計で 10 分かかります。

デプロイが完了した後、再び Yelb アプリケーションで投票してみると 投票が 1 件ずつ増えていることがわかります。

5.4 ロールバックのトリガーとなるバージョンのデプロイ

AWS CodeCommit リポジトリ の yelb-appserver から yelb-appserver.rb ファイルを開きます。本番設定のアプリケーションのポート (33行目) を 4567 から 4568 (または 4567 以外のポート番号) に変更し、変更をコミットします。

すると yelb-appserver-pipeline がトリガーされ、動作しない yelb-appserver マイクロサービスの新バージョンがデプロイされます。AWS CodePipeline で yelb-appserver-pipeline を開き、Deploy ステージが「In Progress」になるまで待ちます。その後、Deploy ステージの下にある Details をクリックします。このページではデプロイ中のビジュアルワークフローが表示されます。

Yelb アプリケーションを開いているブラウザを何度か更新してみてください。リロードしても投票データが返ってこず、投票できない状態であるのがわかります。これは動作していない新バージョンにリダイレクトされているためです。

数分待つと、新しいバージョンがヘルスチェックを通過しなかったため、ロールバックが発生したことがビジュアルワークフローで確認できます。ここで重要なのは、今回のワークフローにおけるヘルスチェックの責務は AWS Lambda 関数であり、必要に応じてカスタマイズすることができるということです。

CloudWatch メトリクスの ECS/ContainerInsights/Prometheus 名前空間を見ると、古いバージョンはまだレスポンスコード 2xx を返しているのに対し、新しいバージョンは 5xx を返しています。しばらくすると、新しいバージョンのメトリクスは削除されたため (自動ロールバックされて)、存在しなくなることがわかります。

Yelbアプリケーションを開いているブラウザを何度か更新すると、以前のバージョンで正常に動作するようになります。

6. 後片付け

以下のコマンドで、前のステップで作成したリソースを削除することができます。

make delete-blog-contents

注: 上記のコマンドは、/setup/scripts/delete_blog_contents.py というシェルスクリプトを実行します。

7. まとめ

この記事では AWS App Mesh を活用し、AWS Code Pipeline や AWS Step Functions などの他の AWS サービスと連携してカナリア式のデプロイ戦略を実施する方法を紹介しました。

さらに、AWS App Mesh をより深く知りたい場合は、以下の便利なリンクをご参照ください。

App Mesh のロードマップで今後の機能を確認したり、App Mesh のプレビューチャンネルで新機能を試したりすることができます。また、App Mesh の Slack コミュニティに参加して、チームや仲間と経験を共有したり議論したりすることもできます。

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