こんにちは。AWS CLIが好きな福島です。
今回は、Codeシリーズを活用したECSのCI/CDパイプラインの動きを触って理解してみたいと思います。 特にBlue/Greenデプロイがどう動いているのか確認してみたいと思います。
概要図
CodePipelineの動きは以下のイメージです。
- ①CodeCommitにソースコードをPush
- ②EvnetBridgeによりCodeCommitの変更が検知され、CodePipelineを起動
- ③CodePipelineのSourceステージでCodeCommitのソースコードをS3に出力
- ④CodePipelineのBuildステージでS3に出力されたソースコードを基にDockerイメージを作成し、ECRに出力
- ⑤CodePipelineのDeployステージでBlue/Greenデプロイを実施
詳細な動きを以下のブログにもまとめているため、ご興味があれば参照ください。
Blue/Greenデプロイの流れ
Blue/Greenデプロイの詳細を記載します。 前提として今回の構成では、ALBのターゲットグループとリスナーの設定が2つ必要となります。
今回はBlueのポートを80、Greenのポートを81で設定しています。
Blue/Greenデプロイ前
ALBは、片方のターゲットグループに紐づいています。 また、Blue用、Green用、どちらのリスナーも同一のターゲットグループに紐づいています。
Blue/Greenデプロイ中
Blue/Greenデプロイ中は以下の処理が行われます。
- CodeDeployにより、ECSに新しいコンテナが起動
- Green用のターゲットグループに新しく起動したコンテナが紐づけられる
- Green用のリスナー設定のターゲットグループが更新される
この時点では、Blue(本番)のトラフィックには影響が出ないため、気軽にアプリの更新をチェックできます。
Blue/Greenデプロイ後
Blue/Greenが実行された後は、以下の処理が行われます。
- Blue用のリスナー設定のターゲットグループが更新される
- 既存のコンテナが削除される
ここでポイントなのが、Blue/Greenデプロイする度にBlue用/Green用のターゲットグループが入れ替わる点です。 (リスナーの設定はBlue用/Green用で固定)
上記の図で言うとTargetGroup1,2がどちらもBlue用、Green用として利用されることがあると言う事です。
そこまで意識する必要はないかも知れませんが、上記の動きになるため、ターゲットグループにはBlueやGreenという文言は入れない方が良いかも知れません。
手順
では、ここからCI/CDパイプラインを構築して、動きを見ていきたいと思います。 リソースの作成は大変なので、CloudFormationでサクッと作成します。
①リソースのデプロイ
- ソースコードのダウンロード
git clone https://github.com/kazuya9831/blog-sample.git
- ディレクトリの移動
cd blog-sample/cicd-for-ecs
- リソースのデプロイ
aws cloudformation create-stack \ --stack-name "cicd-for-ecs" \ --template-body file://template.yml \ --capabilities CAPABILITY_NAMED_IAM
②CodeCommitにソースコードをPush
- デフォルトブランチをmainに変更
git config --global init.defaultBranch main
- CodeCommitのクローンを作成
git clone codecommit::ap-northeast-1://sample-repository
上手くクローンをできなかった場合は、以下をご確認ください。 https://docs.aws.amazon.com/ja_jp/codecommit/latest/userguide/setting-up-git-remote-codecommit.html?icmpid=docs_acc_console_connect
- サンプルアプリをCodeCommitに複製
cp -rp sample-app/* sample-repository/
- CodeCommitのリポジトリのルートに移動
cd sample-repository/
- 既存のタスク定義をエクスポート
aws ecs describe-task-definition \ --task-definition sample-task-definition \ --query taskDefinition > taskdef_original.json
- アカウントIDの取得
account_id=$(aws sts get-caller-identity --query Account --output text)
- エクスポートしたタスク定義のimageの書き換え
sed "s/${account_id}.dkr.ecr.ap-northeast-1.amazonaws.com\/sample-ecr-repository:latest/<IMAGE1_NAME>/g" taskdef_original.json > taskdef.json
- 差分の確認
diff taskdef_original.json taskdef.json
正しく置換ができれば、imageの値を<IMAGE1_NAME>に変換できていると思います。
$ diff taskdef_original.json taskdef.json 6c6 < "image": "123456789012.dkr.ecr.ap-northeast-1.amazonaws.com/sample-ecr-repository:latest", --- > "image": "<IMAGE1_NAME>", $
- 不要なファイルの削除
rm taskdef_original.json
- CodeCommitにソースコードをPush
git add ./
git commit -m "first commit"
git push
※パイプラインは、mainブランチの変更をトリガーに実行されるため、mainブランチにpushされていることを確認してください。
③ECRにコンテナイメージがPushされているか確認
CodeCommitのmainブランチが変更されると、CodePipelineが起動します。 そして、CodePipelineが起動されると、Buildステージでコンテナイメージが作成され、ECRにPushされます。
そのため、Buildステージが正常終了したら、ECRにコンテナイメージがPushされているかを確認します。
このパイプラインの中でDeployステージの処理も開始されますが、 ECSのサービスが0の状態でCloudFormationをデプロイしているため、 置き換えるサービスがなく空振りするので、デプロイを停止します。
まずは、「詳細を表示」を押下します。
実行の詳細を押下します。
「デプロイを停止」を押下します。
「即時停止」を押下します。
④ECSのサービス追加
上述した通り、ECSのサービスが0の状態でCloudFormationをデプロイしているため、 パラメーターを修正し、CloudFormationのスタックを更新します。
補足ですが、なぜ0の状態でデプロイしているのかというと理由は簡単でCloudFormation実行時には、 ECRにコンテナイメージが存在していなく、ECSのサービス起動(CloudFormationのデプロイ)が失敗するためです。
なので、1回CodePipelineを走らせ、BuildステージによりECRにコンテナイメージを配置してからサービスを更新する流れにしています。
- サービス更新
blog-sample/cicd-for-ecs直下で以下のコマンドを実行します。
aws cloudformation deploy \ --stack-name "cicd-for-ecs" \ --template-file template.yml \ --capabilities CAPABILITY_NAMED_IAM \ --parameter-overrides NumberOfECSService=1
- ALBのFQDN確認
aws cloudformation describe-stacks \ --stack-name "cicd-for-ecs" \ --query "Stacks[].Outputs[?OutputKey=='ALBDNSName'].[OutputValue]" \ --output text
- サービスの動作確認
以下のように表示されればOKです。
⑤アプリの更新
- Blue/Greenデプロイ前のALBのリスナー設定の確認
アプリの更新前にALBのリスナーの設定がどのターゲットグループを指しているか確認しておきます。 おそらく、両方のリスナーの設定がsample-tg-1を指しているかと思います。
- ソースコードの更新
echo 'st.subheader("Blue/Green Deployment Test")' >> src/main.py
- CodeCommitにソースコードをPush
git add ./
git commit -m "second commit"
git push
⑥Blue/Greenデプロイの確認
CodeCommitを更新することでCodePipelineが実行されます。 Deployステージが進行中になったら、「詳細を表示」を押下します。
実行の詳細を押下します。
進行中のデプロイの詳細を確認することができます。
「ステップ2: テストトラフィックルーティングのセットアップ」が完了したら、ALBのFQDNの80番(本番)、81番(テスト)ポートにアクセスします。 テストトラフィックのみ更新予定の変更が反映されていることが分かります。
またこの段階でALBのリスナー設定を確認すると、80番がsample-tg-1、81番がsample-tg-2に紐づいていることが分かります。
ECSのタスクも2つ起動されていることが分かります。
今回、「ステップ4: 本稼働トラフィックを置き換えタスクセットに再ルーティング中」が行われる前に30分待機する処理を入れています。 そのため、30分経過すれば自動でステップ4に進むのですが、今回はテストトラフィックの確認が取れたため、「トラフィックの再ルーティング」を押下し、処理を進めます。
トラフィックの再ルーティングを押下後、ステップ4が実行されます。
ステップ4が完了すると、ALBのFQDNの80番(本番)にもアプリの更新が反映されます。
その後、先ほどと同様に「ステップ 6:元のタスクセットの終了」が行われる前に30分待機する処理を入れているため、 本番トラフィックの確認が取れたら、「元のタスクセットの終了」を押下します。
しばらくすると、「元のタスクセットの終了」も完了すると思います。
CodePipelineの画面に戻るとDeployステージも正常終了していることが分かると思います。
最後にALBのリスナーの設定を確認するとどちらもsample-tg-2がターゲットになっていることが分かると思います。
また、ECSのタスクも1つになっていることが分かると思います。
終わりに
Codeシリーズを活用したECSのCI/CDパイプラインの動きについて、実際に触ることで理解が深まったのではないでしょうか。 このブログがどなたかのお役に立てれば幸いです。