AWS再入門ブログリレー2022 AWS CodeDeploy 編

こんにちは、AWS事業本部コンサルティング部の枡川です。
当エントリは弊社コンサルティング部による『AWS再入門ブログリレー2022』の16日目のエントリです。
このブログリレーの企画は、普段AWSサービスについて最新のネタ・深い/細かいテーマを主に書き連ねてきたメンバーの手によって、今一度初心に返って、基本的な部分を見つめ直してみよう、解説してみようというコンセプトが含まれています。
AWSをこれから学ぼう!という方にとっては文字通りの入門記事として、またすでにAWSを活用されている方にとってもAWSサービスの再発見や2022年のサービスアップデートのキャッチアップの場となればと考えておりますので、ぜひ最後までお付合い頂ければ幸いです。
では、さっそくいってみましょう。16日目のテーマは『AWS CodeDeploy』です。

AWS CodeDeployとは

アプリケーションを構成するファイル群(アーティファクト)のデプロイを自動化するAWSのマネージドサービスです。
CodeDeployを使用することでデプロイにおけるダウンタイムを減らしたり、デプロイ時の切り戻しを簡単に行うことができたりします。
いろいろと作り込みをせずに、デプロイ時の面倒事をAWSに任せられるようなサービスとなります。
(引用)AWS Blackbelt Online Seminar AWS CodeDeploy

CodeDeployのコンポーネントについて

主なコンポーネントとしてアプリケーションデプロイグループデプロイ設定デプロイについて触れておきます。
アプリケーションはデプロイしたい対象を一意に識別する名前です。
アプリケーションで詳細なデプロイの方法を保持しているわけではないですが、デプロイ先のタイプを指定して作成します。
デプロイ先として下記3タイプのリソースがサポートされます。

  • EC2/オンプレミス
  • Lambda
  • ECS

アプリケーションの中にデプロイグループを複数作成可能であり、これがデプロイ対象の情報を持っています。
また、デプロイグループデプロイ設定と呼ばれるデプロイ時のトラフィックの切り替え方法等の設定を指定します。
良く使用されるであろう設定は既にAWS側で用意されていますが、細かく調整して設定を作成することも可能です。
一つのデプロイグループを使用して複数回デプロイが実施されるような構成となります。
ざっくりまとめると下図のようになります。(図のセンスが無くてすみません...)
デプロイ設定がデプロイグループとは別管理となっているため、デプロイ設定を使い回すことが可能です。
デプロイ設定の設定項目もEC2/Lambda/ECSで異なります。
EC2であればそもそもデプロイタイプをインプレースもしくはBlue/Greenで選択可能です。
LambdaとECSの場合はBlue/Greenのみになります。
(引用)AWS Blackbelt Online Seminar AWS CodeDeploy

※CodePipelineのDeployステージではローリングアップデートの設定をすることも可能ですが、CodeDeployを使用せずECSの機能でローリングアップデートしていることになります。
マネコンから設定する場合はこの辺りをあまり意識しなくても作成できるようになっていますが、CloudFormation等のテンプレートに落とし込む際は意識する必要があります。

CodeDeployの使い方

CodeDeployの使い方はデプロイ先のリソースによって大きく異なります。
下記チュートリアルが様々なパターンを網羅しているので非常に参考になります。

今回はCodeDeployが真価を発揮するBlue/Greenデプロイと、Blue/Greenデプロイに相性が良いだろうということでデプロイ対象はECSとして試してみます。
正確にはCodeDeploy単体ではなく、ECS、CodePipelineなどを含めた内容にはなりますが、細かいことは気にせず試してみようと思います。

やってみた

下図のようなパイプラインでBlue/Greenデプロイを使用してECSにデプロイしてみます。
この際、下記3つの設定ファイルが必要になります。

  • appspec.yml
  • taskdef.json
  • imageDetail.json

appspec.ymlがデプロイ設定を規定するファイルです。
残り2つはBlue/Greenデプロイを使用してECSにデプロイする際に必要になるファイルとなります。
taskedef.jsonはECSのタスク定義の内容を記載したファイルとなります。
CI/CDパイプラインでコンテナイメージをビルドしている際は、タスク定義で参照するイメージは毎回新しくビルドしたイメージを使用することになると思います。
しかし、そのイメージのURIはビルド時にはじめて使用可能になります。
taskdef.jsonの中ではIMAGE1_NAMEのようなプレースホルダを設定しておき、imageDetail.jsonで指定したURIに自動で置き換えることができます。
latestタグを使用している場合は正直このような仕組みは必要ないです。
コミット時のIDをイメージのタグに使用する場合等に、imageDetail.jsonにイメージのURIを書き込むことでCodePipelineが良い感じにしてくれます。
latest運用をやめようという話は下記エントリに詳しく記載されています。

appspec.ymlは下記のようなファイルを使用します(ECSの場合)。
versionはAppSpecファイルのバージョンです。
2022年2月時点では0.0一択です。

version: 0.0
Resources:
  - TargetService:
      Type: AWS::ECS::Service
      Properties:
        TaskDefinition: <TASK_DEFINITION>
        LoadBalancerInfo:
          ContainerName: "sample-container"
          ContainerPort: 80
        PlatformVersion: "1.4.0"

TASK_DEFINITIONについてもtaskdef.jsonを使用してAWS側でよしなにしてくれます。
また、CodeDeployでECSにデプロイする際、テストポートを指定できます。
これはALBのリスナーを余分に用意しておき、本番用に公開しているポートとは別ポートでデプロイした内容を確認するために使用します。
サービスを公開したまま別ポートで確認を行い、OKならトラフィックを新規タスクセットに切り替えることが簡単にできます。
BlackBeltに良い図があったので引用します。
(引用)AWS Blackbelt Online Seminar AWS CodeDeploy

ECSの事前定義されたデプロイ設定は下記が存在します。
今回はテスト用リスナーできちんと確認するということでCodeDeployDefault.ECSAllAtOnceを使用してみます。

CodeDeployDefault.ECSLinear10PercentEvery1Minutes

すべてのトラフィックが移行されるまで、毎分トラフィックの10パーセントを移行します。

CodeDeployDefault.ECSLinear10PercentEvery3Minutes

すべてのトラフィックが移行されるまで、3分ごとにトラフィックの10パーセントを移行します。

CodeDeployDefault.ECSCanary10Percent5Minutes

最初の増分でトラフィックの10パーセントを移行します。残りの90パーセントは5分後にデプロイされます。

CodeDeployDefault.ECSCanary10Percent15Minutes

最初の増分でトラフィックの10パーセントを移行します。残りの90パーセントは15分後にデプロイされます。

CodeDeployDefault.ECSAllAtOnce

すべてのトラフィックを同時に更新済み AmazonECSコンテナに移行します。

実際にやってみると下記のような画面になります。
テストリスナーを設定している場合、この状態で別ポートで状態を確認できます。
確認が済んだらトラフィックの再ルーティングをクリックします。
トラフィックが新しいタスクセットに移行したことがグラフィカルに示されて楽しいですね。
ここでトラフィックの切り替えはダウンタイムを感じることなく、あっという間にトラフィックを切り替えることができます。

ライフサイクルイベントについて

CodeDeployではデプロイが開始されるとライフサイクルイベントが順に実行されます。
ECSの場合のライフサイクルイベントは下図のようになります。

(引用)AppSpecの「hooks」セクション

各ライフサイクルイベントの特定のタイミングでLambda関数を実行することが可能です。
上の図だと青い四角のタイミングになり、下記が相当します。

BeforeInstall — 置き換えタスクセットが作成される前にタスクを実行するために使用します。1 つのターゲットグループが元のタスクセットに関連付けられています。オプションのテストリスナーが指定されている場合、それは元のタスクセットに関連付けられます。この時点で、ロールバックはできません。
AfterInstall — 置き換えタスクセットが作成され、ターゲットグループの1つがそれに関連付けられた後、タスクを実行するために使用します。オプションのテストリスナーが指定されている場合、それは元のタスクセットに関連付けられます。このライフサイクルイベントでのフック関数の結果により、ロールバックをトリガーできます。
AfterAllowTestTraffic — テストリスナーが置き換えタスクセットにトラフィックを提供した後、タスクを実行するために使用します。この時点でのフック関数の結果により、ロールバックをトリガーできます。
BeforeAllowTraffic — 2番目のターゲットグループが置き換えタスクセットに関連付けられた後、かつ、トラフィックが置き換えタスクセットに移行される前に、タスクを実行するために使用します。このライフサイクルイベントでのフック関数の結果により、ロールバックをトリガーできます。
AfterAllowTraffic — 2番目のターゲットグループが置き換えタスクセットにトラフィックを提供した後、タスクを実行するために使用します。このライフサイクルイベントでのフック関数の結果により、ロールバックをトリガーできます。
(引用)AppSpecの「hooks」セクション

ライフサイクルイベントの流れはEC2/オンプレ、Lambda、ECSのそれぞれで存在しますが、特定イベントでLambdaをフックできるという点では同じです。
イベントにフックしてLambda関数を実行することで、検証テストを実行したり、AutoScailingグループを一時停止したりといった処理を実施することが可能です。
Lambdaを呼び出すシンプルな機能なので、Lambdaを作成する必要がありますが柔軟に処理を実行することが可能です。
使用するためにはLambda関数を作成した後、appspec.ymlのHooks欄に関数のArnを記載します。

Hooks:
  - AfterAllowTestTraffic: "arn:aws:lambda:aws-region-id:aws-account-id:function:AfterAllowTestTraffic"

ECSのBlue/GreenデプロイとAutoScalingの併用について

ECSのBlue/GreenデプロイとAutoScalingを併用する際には下記を考慮する必要があります。

・サービスがスケーリング中の状態でデプロイが開始されると、グリーンタスクセットが作成され、CodeDeployはGreenタスクセットが定常状態になるまで最大1時間待機し、完了するまでトラフィックをシフトしません。
・サービスがBlue/Greenデプロイのプロセス中で、スケーリングイベントが発生した場合、トラフィックは5分間シフトし続けます。サービスが5分以内に定常状態にならない場合、CodeDeployはデプロイを停止し、失敗としてマークします。
(引用)CodeDeployによるBlue/Green デプロイ

AutoScailingが設定されていてもCodeDeployのデプロイ先に指定することが可能ですが、併用が原因でビルドが失敗することがあります。
失敗したらやり直せば良いという考え方もとれますが、デプロイ失敗の可能性を減らすためにはデプロイ時にAutoScalingを停止することも考えられます。
ライフサイクルフックのBeforeInstallでAutoScalingを停止するLambdaを呼び出し、AfterAllowTrafficでAutoScalingを再開するLambdaを呼び出せば楽に実現することが可能です。

オンプレミスサーバを対象とする場合

CodeDeployエージェントをインストールして、AWSサービスエンドポイントにアクセスできるようにすることでオンプレミスインスタンスをデプロイ対象とすることが可能になります。
AWSサービスエンドポイントへのネットワークアクセスの確保やIAMの設定等が必要になります。
手順について記載したブログを紹介しておきます。

最後に

以上、『AWS再入門ブログリレー2022』の16日目のエントリ『AWS CodeDeploy』編でした。
デプロイ先のサービスによって大分勝手が異なるので覚えることはたくさんありますが、少しでもCodeDeployに入門する方の役に立つと嬉しいです。
明日2/25(金)はスジェさんの「Amazon EC2」の予定です。お楽しみに!!