電通総研 テックブログ

電通総研が運営する技術ブログ

CodeDeploy によるECS でのBlue/Greenデプロイの話

皆さん、こんにちは。電通国際情報サービスXイノベーション本部ソフトウェアデザインセンターの陳 欣瑩です。

業務でAmazon ECSとCodeDeployを使ってBlue/Greenデプロイを実施してみたのでその方法をご紹介します。
CodeDeployを使ったECSのBlue/Greenデプロイの手順を紹介する記事は少なくありませんが、実際にやってみると意外とハマったことが多かったです。
本記事では、ハマったことや使ってみた感想についてもご紹介したいと思います。

CodeDeployを使ったBlue/Greenデプロイについて

CodeDeployを使ってBlue/Greenデプロイを実現するには、ターゲットグループを2つ用意する必要があります。
ALBのルーティング制御によって、トラフィックを流すターゲットグループを切り替えることで、ダウンタイムなしで環境を切り替えることを実現できます。

図から詳しく説明します。

本番環境はユーザーがアクセス可能な環境で、テスト環境は開発者のみアクセス可能な、動作確認のための環境だと想像してください。
Target Group1とTarget Group2がありますが、初期状態では、本番環境とテスト環境のトラフィックは両方ともTarget Group1に流します。

CodeDeployを利用し、新しいコンテナイメージをデプロイすると、テスト環境のトラフィックの転送先は新しいコンテナーを持つTarget Group2に切り替えます。
本番環境のトラフィックはそのまま古いコンテナーを持つTarget Group1に流すため、デプロイ期間においても本番環境へのアクセスは可能です。

テスト環境での動作確認が完了後、CodeDeployを利用してALBの本番環境へのトラフィック転送を制御できます。本番環境もテスト環境も最新のコンテナーを持つTarget Group2にトラフィックを転送します。トラフィックの転送の切り替えはほとんど時間がかからないため、ダウンタイムほぼ0でデプロイできます。
Blue/Greenデプロイは本番環境とテスト環境の入れ替えのイメージがありますが、CodeDeployの場合はデプロイ完了後、本番環境もテスト環境も同じターゲットグループにトラフィックを転送します。

次のデプロイを実行すると、Target Group1は最新のコンテナーを持つようになリます。
デプロイ実行後、テスト環境のリスナーは自動的にTarget Group1にトラフィックを転送し、本番環境のリスナーはそのままTarget Group2にトラフィックを転送します。
本番環境のトラフィックの転送を制御することで、テスト環境と本番環境は両方ともTarget Group1にトラフィックを転送します。

方法

AWSマネジメントコンソールから以下のリソースを作成しました。

  • VPCなどネットワークインフラ
  • CodeDeployにECSを更新するアクセス許可を付与するIAMロール
  • ALB
  • ECRレポジトリ
  • ECSクラスタ
  • ECSタスク定義
  • ECSサービス
  • Route53を利用する場合は、Aレコードを作成し、ルーティング先をALBにする

リソースの作成、およびBlue/Greenデプロイの実行手順は以下の記事を参考にしました。

今回は本番環境でHTTPSを利用したいため、本番環境のリスナーポートを443、テスト環境のリスナーポートを8443にしました。

手順の自動化

AWS for GitHub Actionsを組み合わせれば、GitHub Actionsワークフローを利用してCodeDeployの実行までの手順を自動化できます。

  1. Dockerfileをbuildし、タグをつけてECRにpushする
    amazon-ecr-login

  2. ECSタスク定義を更新する
    amazon-ecs-render-task-definition

  3. 更新されたタスク定義を使ってCodeDeployのデプロイを実行させる
    amazon-ecs-deploy-task-definition

GitHubにタグをプッシュすることで、既存のECSリソースにCodeDeployを使用したBlue/Greenデプロイができます。ただし、デプロイ後のトラフィック転送先の切り替えなど、CodeDeployに対する操作はAWSマネジメントコンソールから手動で操作する必要があります。

CodeDeployの設定

AWSマネジメントコンソールから、CodeDeployの以下の設定を変更できます。(アプリケーション>デプロイグループを選択>編集)

トラフィックの再ルーティング

トラフィックの再ルーティングとは、ALBで本番トラフィックの転送先のターゲットグループを切り替えることを指します。
デフォルトでは、トラフィックが直ちに置き換え先環境に再ルーティングされるように設定されています。
トラフィックが再ルーティングされる前に、動作確認したい場合は再ルーティングするタイミング、つまり待機する日数、時間、分数を指定できます。動作確認のために再ルーティングを保留できる時間は最短5分、最長時1日間23時間55分まで指定できます。
Lambda関数の検証テストをAppSpecファイルに追加すれば、設定した待機時間内に検証テストが自動的に実行されます。

テスト環境のポートにアクセスすれば動作確認ができます。動作確認が完了後、再ルーティングを実行するために、CodeDeployの「トラフィックの再ルーティング」ボタンを指定した待機時間内に押下する必要があります。(CodeDeployのコンソールでデプロイIDをクリックした後の画面にあります)
待機時間が経過しても、再ルーティングが実行されない場合は、デプロイの状態は失敗になります。

デプロイ設定

デプロイの速度や、デプロイ成功または失敗の条件など、ルールを設定できます。

元のリビジョンの終了

新しいタスクのデプロイが完了後、元のタスクセットを終了するまでの時間を設定できます。デフォルトは1時間です。

設定した時間内に、デプロイしたものに問題があったら、いつでも元のタスクセットにロールバックできます。
再ルーティングの実行後でもロールバックはできます。

終了処理が開始するとロールバックすることはできません。

ハマったこと

CodeDeployのinstallイベントが一向に終わらない

CodeDeployのコンソールでは詳しいログ情報やエラーメッセージが表示されないため、最初は原因がよくわからず困りました。
今回は、Dockerfileに不備があり、起動できない状態のコンテナーをデプロイしようとしているのが原因でした。他の記事を読むと、IAMロールに必要な権限がない場合や、task-definition.jsonにミスがあった場合でもCodeDeoloyのinstallが延々と続くらしいです。
何かの不備があるとCodeDeployでのinstallイベントは一向に終わらなくなります。しかし、Dockerfileやtask-definition.jsonは変更を頻繁に加えるものではないため、デプロイが成功したものを使い続ければこのような問題は発生しないでしょう。

テストトラフィックの動作確認ができない

ECSとCodeDeployを使用してBlue/Greenデプロイを実施すると、設定した待機時間内に、テスト環境のリスナーポートにアクセスし動作確認ができます。しかし、リスナーポートの設定が不適切な場合やWebアプリケーションのURLがハードコードされる場合は、動作確認できないこともあります。

リスナーポートの設定

最初は、本番環境のリスナーポートをHTTPS/443、テスト環境のリスナーポートをHTTP/8080にしましたが、テスト環境のリスナーポートにアクセスできませんでした。
理由としては、検証で使っていた.devドメインではHTTPが使えないからです。他にも、HTTPをHTTPSにリダイレクトする要件があれば、両方ともHTTPSのポートにしたほう望ましいでしょう。

どのドメインからテスト環境にアクセスすべきかがわからなかった

最初は、本番環境と同じドメインでなくALBのドメインを使ってテスト環境にアクセスしてしまいました。
本番環境と異なるドメインからアクセスすると、場合によってログインできなくなったり、APIリクエストを投げられなくなったりすることもあります。
本番環境のドメインhttps://example.comであれば、それと同じドメインのテストポートhttps://example.com:8443からテスト環境にアクセスするのが正解です。

Next.jsを利用する場合のAPI URL問題

Next.jsのgetServerSidePropsを利用し、APIリクエストを送信するには、APIエンドポイントのURLを次のように指定する必要があります。axios.get(https://example.com/api/hello)
https://example.comの部分を環境変数として設定してしまう場合は、異なるポートからアクセスしAPIにリクエストを送信すると、CORSエラーが発生します。
それを回避するために、URLのハードコードをやめて、以下のようにgetServerSidePropsのcontextからホスト名とポート番号を取得しました。
axios.get(https://${ctx.req.headers.host!}/api/hello)
ただし、プロトコルはcontextから取得できないため、httpsを固定するまたは環境変数に入れる必要があります。

使ってみた感想

デプロイの所要時間はとても短い

今回はGitHub Actionsを利用すると、ほとんどのワークフローの処理時間は1秒程度で完結できました。DockerfileのbuildおよびECRへのpushは、相対的に時間かかりましたが、Dockerfileの改善により処理速度の短縮が期待できます。
GitHub Actionsのワークフローが成功した後、CodeDeployでのデプロイ(置き換えタスクセットのデプロイ、テストトラフィックルーティングのセットアップ)は約2、3分間くらいかかります。動作確認を含め、デプロイの全体を数分程度に抑えることができたのですごいと思いました。

本番と同じ環境で動作確認できる

テストトラフィックで動作確認し、問題がなければ本番トラフィックの転送先を切り替えればデプロイが完了しますので、テストトラフィックの転送先のターゲットグループは本番に昇格するみたいなイメージですね。万が一動作確認でバグを見つけても、本番環境に影響せずロールバックができます。

何よりダウンタイム0はすごい

また、トラフィックの再ルーティングはほとんど時間がかからないため、デプロイで発生するダウンタイムは0に近いです。テストトラフィックで動作確認できるため、ユーザーはデプロイ作業に影響されなくシステムを使い続けられます。

まとめ

本記事では、Amazon ECSとCodeDeployを使ってBlue/Greenデプロイメンを実施する方法、ハマったところや使ってみた感想についてご紹介しました。デプロイの時間を短縮し、ダウンタイムなしでデプロイできるので、本当に便利だと思いました。AWSによくまとめてある設定手順がありますので、皆さんご機会がありましたらぜひ使ってみてください。

最後までお読みいただきありがとうございました。

参考にした情報

執筆:@chen.xinying、レビュー:@sato.taichiShodoで執筆されました