AWS ECSにおけるローリングアップデートの安全性を向上させたい
こんにちは、ryoです。
ECSのローリングアップデートはデフォルトのデプロイ形式であり、多くのケースで利用されていると思います。
今回は、ECSのローリングアップデートの安全性を高める仕組みをご紹介します。
先にまとめ
- ローリングアップデート時のタスク起動の失敗やヘルスチェックの失敗を検知し、以前のバージョンへ自動ロールバックをしたい
- deployment circuit breakerを利用する
- ローリングアップデート時に何らかのメトリクスの閾値に応じて、以前のバージョンへ自動ロールバックをしたい
- CloudWatch Alarmsを利用する
- 両方のケースでは、どちらの方法を取り入れることも可能
今回紹介する方法は現時点(2023年2月)でローリングアップデートのみ対応です
ECSのローリングアップデート失敗時の挙動
ECSのタスクが正常に立ち上がらないなどの理由でDesiredCountに達しない場合、ECSはサービススロットリングロジックに基づきタスクの再起動を行います。
https://docs.aws.amazon.com/AmazonECS/latest/developerguide/service-throttle-logic.html
このロジックでは、タスクが正常に立ち上がりDesiredCountを満たすまで永久にタスクの再起動を行います。
そのためローリングアップデートの安全性を高めるためには、タスクの異常終了の早急な検知 +自動ロールバックの導入が役立ちます。
deployment circuit breakerの利用
概要
deployment circuit breakerはECSのローリングアップデートにおける異常を検知した際に、自動でロールバックをしてくれる機能です。
タスクの異常終了の検知はdeployment circuit breakerを使わずに実現が可能です。
例えば、タスクの終了イベントをEventBridgeでキャプチャすることが出来るので、EventBridgeから様々なAWSサービスに連携が出来ます。
しかし、タスクの異常終了の検知を行なってもロールバックなどの対応は自前で実装を行うか、その都度対応していく必要があります。
deployment circuit breakerを利用することで、ローリングアップデートにおける異常の検知 + 自動ロールバックが可能になります。
仕組み
詳しい仕組みはこちらのスライドに分かりやすくまとまっているので、ご一読いただければと思います。
https://d1.awsstatic.com/Developer%20Marketing/jp/developer-zone/ECS-Deployment-Circuit-Breaker-Rollback.pdf
基本的にはデプロイ時にタスクが立ち上がらない場合やヘルスチェックが通らない場合に、ある閾値まで再試行を行い、閾値を超えた際にロールバックを行うという挙動になります。
デプロイ時のチェックは主に2段階に分かれていて、簡単に図に表すと以下のようになります。
閾値は、10 <= Desired Count * 0.5 <= 200
という式で表されます。
最小の再試行回数は10で、最大の再試行回数は200になります。
例えばDesiredCountを2で設定している場合10 <= 2 * 0.5 <= 200
となり、最小試行回数である10が閾値となります。
DesiredCountが200の場合は、10 <= 200 * 0.5 <= 200
となり、閾値は100になります。
CloudWatch Alarmsの利用
概要
CloudWatch Alarmsを利用することでデプロイの最中や完了後に発生したCloudWatch Alarmsの発報に応じて、自動でロールバックを行うことが可能です。
CloudWatch Alarmsではメトリクスに対して、柔軟に設定が可能で、アプリケーションに応じて様々なケースが考えられます。
仕組み
公式ブログより引用
公式ブログの図を見ると、時間軸t2
から新バージョンのローリングアップデートが始まり、時間軸t3
でデプロイが完了すると想定します。
またローリングアップデートが始まった時間軸t2
と同時に、CloudWatch Alarmsの発報によるロールバック可能期間が開始されます。
時間軸t3 ~ t4
はbake time
と呼ばれる、デプロイ完了(DesiredCountを満たす)後の追加のロールバック可能期間です。
このbake time
はAlarmの設定に応じて、ECS側で自動で計算されます。
つまり、上図の時間軸t2 ~ t4
の間にCloudWatch Alarmsが発報した場合にタスクのロールバックが行われる形になります。
CloudWatch Alarmsに使うメトリクスの選定について、ドキュメントに推奨のものが記載されています。
https://docs.aws.amazon.com/AmazonECS/latest/userguide/deployment-alarm-failure.html#ecs-deployment-alarms
AWS CDKで設定を行う
deployment circuit breakerとCloudWatch Alarmsを組み込んだローリングアップデートをCDKで作成したECSに組み込む方法を紹介します。
今回はさくっと作りたいので、L3コンストラクタであるecs-patternsを利用して、ALB + Fargateのインフラ定義を行います。
各種バージョン
ツール | バージョン |
---|---|
Node.js | v18.12.0 |
aws-cdk | v2.64.0 |
コード
import { Stack, StackProps } from "aws-cdk-lib";
import { Cluster, ContainerImage, CfnService } from "aws-cdk-lib/aws-ecs";
import { Platform } from "aws-cdk-lib/aws-ecr-assets";
import { ApplicationLoadBalancedFargateService } from "aws-cdk-lib/aws-ecs-patterns";
import { Alarm } from "aws-cdk-lib/aws-cloudwatch";
import { Construct } from "constructs";
import { HttpCodeElb } from "aws-cdk-lib/aws-elasticloadbalancingv2";
import * as path from "path";
export class EcsDeployTestStack extends Stack {
constructor(scope: Construct, id: string, props?: StackProps) {
super(scope, id, props);
// ECS Cluster
const cluster = new Cluster(this, "ECSCluster");
// Fargate + ALB
const fargateService = new ApplicationLoadBalancedFargateService(
this,
"ECSService",
{
cluster,
taskImageOptions: {
image: ContainerImage.fromAsset(
path.resolve(__dirname, "../", "app"),
{
platform: Platform.LINUX_AMD64,
}
),
containerPort: 8000,
},
desiredCount: 2,
}
);
// ELB5xxに対してアラームの設定を行う
const elb5xxAlarm = new Alarm(this, "DeploymentAlarm", {
metric: fargateService.loadBalancer.metrics.httpCodeElb(
HttpCodeElb.ELB_5XX_COUNT
), // 適宜変更を行う
threshold: 3, // 適宜変更を行う
evaluationPeriods: 1, // 適宜変更を行う
});
// L1コンストラクタへのキャスト
const service = fargateService.service.node.defaultChild as CfnService;
// deployment circuit breakerとCloudWatch Alarmsの両方を利用する
service.deploymentConfiguration = {
deploymentCircuitBreaker: {
// deployment circuit breaker
enable: true,
rollback: true,
},
maximumPercent: 200,
minimumHealthyPercent: 50,
alarms: {
// CloudWatch Alarms統合
alarmNames: [elb5xxAlarm.alarmName],
enable: true,
rollback: true,
},
};
}
}
CDKコードの概要
ecs-patternsのApplicationLoadBalancedFargateService
とL2コンストラクタのFargateService
は、deployment circuit breakerには対応してますが、CloudWatch Alarmsとの統合にはまだ対応していないため、今回はエスケープハッチを利用して設定を行います。
CDKのGitHubを見るとPR出している方がいたので、もうすぐエスケープハッチを利用しなくても設定出来るようになりそうです。
https://github.com/aws/aws-cdk/pull/24182
エスケープハッチは高レベルコンストラクタからL1コンストラクタにアクセスするための方法で、L1コンストラクタはCloudFormationのリソースと1対1で紐づいているため、柔軟な設定が可能です。
詳細はこちらを参照ください。
https://docs.aws.amazon.com/cdk/v2/guide/cfn_layer.html
CloudFormationではDeploymentConfiguration
で該当の設定が可能なので、エスケープハッチからこちらの設定を行います。
https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ecs-service-deploymentconfiguration.html
deploymentCircuitBreaker
とalarms
にはenable
とrollback
があり、別々に設定可能です。
enable: true
、rollback: false
といった設定も可能で、この場合はデプロイ失敗時にEventBridgeへの通知のみ走ります。
// L1コンストラクタへのキャスト
const service = fargateService.service.node.defaultChild as CfnService;
// deployment circuit breakerとCloudWatch Alarmsの両方を利用する
service.deploymentConfiguration = {
deploymentCircuitBreaker: {
// deployment circuit breaker
enable: true,
rollback: true,
},
maximumPercent: 200,
minimumHealthyPercent: 50,
alarms: {
// CloudWatch Alarms統合
alarmNames: [elb5xxAlarm.alarmName],
enable: true,
rollback: true,
},
};
まとめ
今回はローリングアップデートをより安全に行う方法と、CDKでの設定方法を紹介させていただきました。
deployment circuit breakerとCloudWatch Alarmsを利用した場合では、bake time
を除くと評価期間が被りますが、それぞれの目的は全く異なるものです。
ぜひ取り入れて少しでも安全なデプロイにしていきましょう!
どなたかの参考になれば幸いでございます。
参考
- https://d1.awsstatic.com/Developer%20Marketing/jp/developer-zone/ECS-Deployment-Circuit-Breaker-Rollback.pdf
- https://aws.amazon.com/jp/blogs/news/announcing-amazon-ecs-deployment-circuit-breaker-jp/
- https://aws.amazon.com/jp/blogs/containers/automate-rollbacks-for-amazon-ecs-rolling-deployments-with-cloudwatch-alarms/