コンテナワークロードでは CodeDeploy などで継続的なデリバリを行い、Auto Scaling を利用して負荷に応じて、動的にコンテナを増減させて運用されているのではないかと思います。特に ECS では CodeDeploy を利用して、Blue/Green デプロイメントを行うことができます。また、Auto Scaling ではスケーリングを判断する指標の1つとして Application Load Balancer(ALB)ターゲットグループ内のターゲットごとに完了したリクエストの数(ALBRequestCountPerTarget
)を利用することができます。が、実は現時点ではそれらを一緒に使うと、そのままでは期待通りに動作してくれません。それぞれをうまく利用するために検討する機会がありましたので、考え方のベースとして記録に残しておきたいと思います。
本記事の内容をそのまま本番環境に適用せず、十二分に検証を行った上でご判断ください。
2024/03/20 更新
2023年3月に「Application Auto Scaling がターゲット追跡ポリシーに対する Metric Math に対応」というアップデートがありました。これにより、本記事のようにオートスケーリングポリシーの付け替えを行わずに (CodeDeploy のライフサイクルイベントフックを使わずに) 実現する案も可能になりました。 詳しくは以下の記事を参照ください。
[ECS Blue/Green デプロイでもリクエストカウント追跡のオートスケーリングを利用したい (Metric Math 編)]
https://blog.msysh.me/posts/2024/03/ecs-blue-green-deploy-with-request-count-per-target-tracking-autoscaling-with-metric-math.html
はじめに
冒頭のとおり、CodeDeploy による Blue/Green デプロイメントと、ECS の Auto Scaling においてスケーリングポリシーに「ターゲットの追跡」で ALB のターゲットごとのリクエストカウントをメトリクスとして利用すると、そのままでは期待した動作となりません。まずはその辺りの理由について書いておきたいと思います。
CodeDeploy による ECS Blue/Green の動作
CodeDeploy による ECS の Blue/Green はどのように実現されているか、簡単にいうと ALB に関連付けられる Target Group を切り替えることで実現されています。マネジメントコンソールなどから設定した場合、Target Group に -1
と -2
がついているかと思います。
ターゲット追跡ポリシーでの ALBRequestCountPerTarget
一方、Auto Scaling の方を見てみます。ECS で利用できる Auto Scaling ポリシーのうち「ターゲット追跡スケーリングポリシー」があり、利用できるメトリクスの一つに ALBRequestCountPerTarget
があります。ALBRequestCountPerTarget
はざっくりいうとターゲット毎(タスク毎)のリクエスト数が指定した数になるようにタスク数を増減させていく動作となります(公式ドキュメント)。ALBRequestCountPerTarget
はリクエストが増えてもコンテナの CPU やメモリの増加があまり変化がなく、ECSServiceAverageCPUUtilization
や ECSServiceAverageMemoryUtilization
といったメトリクスではうまくスケールさせられない場合に利用できる選択肢の1つかと思います。
ECS Blue/Green と ALBRequestCountPerTarget を一緒に使うと期待通りにならない
CodeDeploy の Blue/Green の動作と、ALBRequestCountPerTarget
による Auto Scaling の図を見てもらえればなんとなく想像いただけそうですが、Blue/Green により ALB に関連付けられる Target Group が変わってしまいながらも、Target Group に設定された Auto Scaling の設定までは付け変わってくれないため、実質的には Green 環境に変わったことで Auto Scaling の設定が外れてしまうような動作になってしまいます。
Green 環境への切り替え後、マネジメントコンソールから「サービス」-「Auto Scaling」タブを見るとポリシーは残っているのように見えますが、後述する CLI で確認すると Blue 環境側の Target Group と関連付けられていることがわかります。
CLI で確認すると Auto Scaling の設定は Blue 側の Target Group(仮に Blue 側が Target Group 1、Green 側が Target Group 2 だったとします)に関連付けられたままになっていることがわかります。以下の出力例では説得力ないですが、CloudWatch Alarm からも辿ってみるとわかるのではないかと思います。
aws application-autoscaling describe-scaling-policies --service-namespace ecs --policy-names <Policy_Name>
|
|
Target Group 2 の方にも Auto Scaling の設定を…
じゃ Target Group 2 の方にも Auto Scaling の設定を入れれば… と思ってしまいますが、Auto Scaling が ECS サービスに関連付けられているからでしょうか、すでに存在しているとのことで設定できません。
検討方針
ではどうしようか、となりますが、今回はデプロイが完了したタイミングで、Auto Scaling の設定を新しい Target Group 向けに設定し直す方針で検討してみました。幸い、CodeDeploy の「ライフサイクルイベントフック」で ECS デプロイにおける各イベントのタイミングで Lambda を実行することができますのでこちらを利用します。
ライフサイクルイベントフックについての公式ドキュメントは以下です。
- AppSpec ‘hooks’ section
CodeDeploy のライフサイクルイベントフックとは(ECS デプロイの場合)
ECS デプロイのライフサイクルイベントフックですが、詳細はドキュメントを見てもらえればと思いますので、ここでは各タイミングについて簡単に。
BeforeInstall
- Green 環境のタスクセットが作成される前、Blue 環境しかない状態です。この時点ではロールバックはできません。
AfterInstall
- Green 環境のタスクセットが作成され、Target Group に関連付けられた後のタイミングです。
これ以降で、フックする Lambda の結果からロールバックさせることができます。
- Green 環境のタスクセットが作成され、Target Group に関連付けられた後のタイミングです。
AfterAllowTestTraffic
- Green 環境のタスクセットにテスト用トラフィックが流せるようになった後のタイミングです。
BeforeAllowTraffic
- Green 環境のタスクセットに本番用トラフィックを流す前のタイミングです。
AfterAllowTraffic
- Green 環境のタスクセットに本番用トラフィックを流した後のタイミングです。
表にしてみました(テストトラフィックはテストリスナーが設定されている場合に有効です)。
タイミング | 本番トラフィックは? | テストトラフィックは? | ロールバック |
---|---|---|---|
BeforeInstall |
Blue 環境へ | まだ流せない (Green 環境がまだない) |
不可 |
AfterInstall |
Blue 環境へ | まだ流れない (Green 環境はできた) |
可能 |
AfterAllowTestTraffic |
Blue 環境へ | Green 環境へ | 可能 |
BeforeAllowTraffic |
Blue 環境へ | Green 環境へ | 可能 |
AfterAllowTraffic |
Green 環境へ | もう流せない | 可能 |
補足
ちなみに ALBRequestCountPerTarget
ではなく、ECSServiceAverageCPUUtilization
や ECSServiceAverageMemoryUtilization
をターゲット追跡のメトリクスに利用する場合は、ECS サービス全体でのメトリクスに基づいて判断してくれますので、今回のような「Auto Scaling ポリシーを付け替える」といった事をする必要はありません。
検討に際しての具体的なポイントなど
具体的に検討した上でのポイントなどを。
1. Blue/Green デプロイにおけるそもそもの考慮事項
公式ドキュメントにも記載されている通り、そもそも Blue/Green と Auto Scaling を併用するとデプロイに失敗する可能性があります。
- Blue/Green デプロイの考慮事項
- サービス Auto Scaling と blue/green デプロイタイプを使用するように設定されたサービスでは、Auto Scaling はデプロイ中にブロックされませんが、状況によってはデプロイが失敗する場合があります。以下では、この動作について詳しく説明します。
- サービスがスケーリング中の状態でデプロイが開始されると、グリーンタスクセットが作成され、CodeDeploy は Green タスクセットが定常状態になるまで最大 1 時間待機し、完了するまでトラフィックをシフトしません。
- サービスが Blue/Green デプロイのプロセス中で、スケーリングイベントが発生した場合、トラフィックは 5 分間シフトし続けます。サービスが 5 分以内に定常状態にならない場合、CodeDeploy はデプロイを停止し、失敗としてマークします。
デプロイ失敗のリスク低減のため、デプロイ中は Auto Scaling を止めることにしようと思います。
2. ECS の Auto Scaling を操作する API は?
マネジメントコンソールからは「サービスの更新」により Auto Scaling を再設定することができるので、ECS の UpdateService APIとか使えばいけそうな雰囲気がありますが、ECS サービスの Auto Scaling は設定変更できません。
先ほども少し触れましたが、ECS サービスの Auto Scaling 設定は Application Auto Scaling を利用します。Application Auto Scaling はマネジメントコンソールからは利用できず、CLI、SDK などから操作できます。
例えば CLI などから以下のように実行すると ECS に設定されている Auto Scaling 設定が見れます。
$ aws application-autoscaling describe-scalable-targets --service-namespace ecs
また再掲になりますが以下の CLI で、設定されている Auto Scaling ポリシーが見れます。
$ aws application-autoscaling describe-scaling-policies --service-namespace ecs --policy-names <Policy_Name>
実際の Auto Scaling ポリシーの操作は CodeDeploy のライフサイクルイベントで
- デプロイ前に
DeleteScalingPolicy
API で Auto Scaling ポリシー を削除し - デプロイ完了後に
PutScalingPolicy
API で新しい Auto Scaling ポリシー を入れ直す
ということをしてやろうと思います。
3. デプロイ失敗(ロールバック)時の対応
CodeDeploy のライフサイクルイベントで Auto Scaling ポリシーを削除/再設定してあげるわけですが、デプロイの途中でアプリの不具合などでロールバックする可能性も考えられます。デプロイのロールバックや、キャンセル(即時停止)をした場合は、以降のライフサイクルイベントがスキップされフック関数(Lambda)も呼び出されなくなりますので、Auto Scaling ポリシーが削除されたままになってしまうことも想定されます。
そのため、CodePipeline からの通知の仕組みを使って、デプロイの失敗時には SNS に通知し、Lambda を使って Auto Scaling ポリシーを入れ直す、ということをしてみたいと思います。ただし、ロールバックされることを前提としており、即時停止のケースは今回は検討の対象外にしました。
まとめると…
BeforeInstall
のタイミングで Application Auto Scaling の DeleteScalingPolicy を利用して Auto Scaling ポリシー を削除。AfterAllowTraffic
のタイミングで Application Auto Scaling の PutScalingPolicy を利用して ALB に関連付けられた新しい Target Group で Auto Scaling ポリシーを設定。- ロールバックした場合は、通知を使って Auto Scaling ポリシーが外れたままにならないよう再設定する。
図にすると以下のような感じです。
BeforeInstall
時に Auto Scaling ポリシーを削除
AfterAllowTraffic
時に Auto Scaling ポリシーを設定
ロールバックした場合は、通知を使って Auto Scaling ポリシーを再設定
サンプルコード
せっかくなので試した Lambda のコードもサンプルとして紹介します。見易さ重視でエラー処理など省略しているとこもあるので参考程度にとどめてくださいませ。
appspec.yaml での Hooks セクション
CodeDeploy の appspec.yaml
では Hooks
セクションに以下のように設定します。ハイライト部のように各イベントで Lambda 関数名を指定します。
|
|
BeforeInstall に設定する Lambda
Auto Scaling ポリシーを削除してやります。CodeDeploy 向けのお作法(codedeploy.put_lifecycle_event_hook_execution_status
)はドキュメントのサンプルで確認ください。
環境変数には以下を設定してください。
POLICY_NAME
: Auto Scaling Policy の名前ECS_CLUSTER_NAME
: ECS クラスタ名ECS_SERVICE_NAME
: ECS サービス名LOG_LEVEL
: ログレベル
|
|
AfterAllowTraffic に設定する Lambda
Lambda 実行時の ALB に関連付けられているターゲットグループをもとに Auto Scaling ポリシーを設定します。
(ちなみに、BeforeInstall でポリシーを削除してから、AfterAllowTraffic でポリシーを再設定していますが、削除しなくても PutScalingPolicy
API で上書きできます。)
環境変数は以下を設定してします。
POLICY_NAME
: Auto Scaling Policy の名前ECS_CLUSTER_NAME
: ECS クラスタ名ECS_SERVICE_NAME
: ECS サービス名ALB_NAME
: ALB の名前TARGET_GROUP_NAME_1
: ターゲットグループ 1 の名前TARGET_GROUP_NAME_2
: ターゲットグループ 2 の名前REQUEST_COUNT_TARGET_VALUE
ターゲット追跡として ALBRequestCountPerTarget に設定したいリクエスト数SCALE_OUT_COOL_DOWN
: スケールアウトのクールダウン期間(秒数)SCALE_IN_COOL_DOWN
: スケールインのクールダウン期間(秒数)DISABLE_SCALE_IN
: スケールインの無効化(True
: スケールインしない /False
: スケールインする)LOG_LEVEL
: ログレベル
|
|
デプロイ失敗(ロールバック)時に SNS 経由で実行される Lambda
ちょっと横着ですが、デプロイ失敗時に SNS 経由で起動する Lambda は AfterAllowTraffic
のものをそのまま利用します。ロールバックされた後に Lambda が呼び出され、ALB に関連付けられているターゲットグループをもとに Auto Scaling ポリシーを再設定するというわけです。
注意点として、上記コード内のコメントにも書いていますが、本番トラフィックとテストトラフィックが有効な状態( AfterAllowTestTraffic
から BeforeAllowTraffic
までの間)でロールバックを伴わない「デプロイの停止」(即時停止)を行うと、2つのターゲットグループが ALB に関連付けされたままになるので、どちらのターゲットグループが本番トラフィック用かというのが elbv2.describe_target_groups
だけでは判断できません。そういったケースにも対応する場合は、DynamoDB なども使ってステートを管理するとか、ALB リスナーのポートから判断しながら Auto Scaling ポリシーをリカバリする必要があるでしょう。
まとめ
CodeDeploy による Blue/Green デプロイメント利用時であっても、Auto Scaling で ALB のリクエストカウントのメトリクスを利用する方法について検討してみました。
最後に・・・
この投稿は個人的なものであり、所属組織を代表するものではありません。ご了承ください。
※本サンプルは、自己責任の範囲でご利用ください。