ステップスケーリングポリシーを使ってSQSキューの長さでECSサービスタスクを爆速オートスケーリングしてみた

課金が心配になるやつ
2021.03.22

はじめに

おはようございます、もきゅりんです。

ECSサービスのオートスケーリングしてますか?

ユースケースも少ないかとは思いますが、とにかく爆速でオートスケーリングしたくなることもありますよね? (春ですし)

ということで、本稿はステップスケーリングポリシーを使ってSQSキューの長さでECSサービスタスクを爆速オートスケーリングしてみました。

最初に注意

下記ご注意下さい。

この構成は、高速スケーリングにするために、高解像度のカスタムメトリクスを取得します。高解像度のメトリクス発行およびアラームの課金には注意して下さい。また、とりあえずやってみましたが、本当にこの構成を実装することが真なのかどうか、これで要件が満たせるのかどうか、他に適切な構成がないかどうかなどはよくよく検討する課題があるものだと考えます。

やりたいこと

AWS のサービスによって生成されたメトリクスは、デフォルトで標準解像度の1分です。

今回の対象となる、Amazon SQS キューの CloudWatch メトリクスも1 分間隔で CloudWatch に発行されます。そして、Auto Scaling アクションのアラームは 1 分間に 1 回アクションを呼び出します。

基準は分です。

上記の仕様から、メトリクスを検知してアラームが発砲されてからスケールアウトが完了するまでにどうしても数分間かかってきます。

それでもどうしてもその時間を短縮してみたい背景がある場合の一例として、本稿では以下のような構成を考えてみました。

rapid sqs autoscaling fargate

アイデア自体はそれほど新しいものではなく、2015年の正月に Amazon SQSとCloudWatchによる高速AutoScaling | DevelopersIO というブログもあります。やってることはこれと同じですが、実行環境がコンテナ (ECS/Fargate) になっただけです。

やること

  1. オートスケーリングするECSクラスターのメインサービスを設定する
  2. 高解像度のカスタムメトリクスを発行するサービスを設定する
  3. 1のサービスにステップスケーリングを設定する

順に進めていきます。

1 オートスケーリングするECSクラスターのメインサービスを設定する

タスクが増えれば何でも良いので、AWS Fargate での Amazon ECS の開始方法 - Amazon ECSECSの初回実行ウィザード で対応します。

手順は割愛します。

2 高解像度のカスタムメトリクスを発行するサービスを設定する

CloudWatch メトリクスには、標準解像度または高解像度というメトリクスの読み取りおよび取得期間の概念があります。

高解像度メトリクスを使用すれば、アプリケーションの 1 分未満のアクティビティをより迅速に把握できます。PutMetricData がカスタムメトリクスを呼び出す場合、課金されることに注意してください。そのため、高解像度で PutMetricData を頻繁に呼び出すと、高額な料金が発生する可能性があります。CloudWatch の料金の詳細については、「Amazon CloudWatch 料金表」を参照してください。

高解像度メトリクスでアラームを設定する場合、10 秒または 30 秒の期間で高解像度アラームを指定するか、60 秒の倍数の期間で通常のアラームを設定できます。10 秒または 30 秒の期間の高解像度アラームでは、料金が高くなります。

詳細は Amazon CloudWatch の概念 - Amazon CloudWatch の解像度および 料金 - Amazon CloudWatch | AWS を参照頂ければと思います。

EventBridge(CWE) のスケジュールの最小精度は 1 分です。 なので秒単位で利用することはできません。

本稿では、こちら を使ってDocker コンテナの cron を使って5秒ごとにメトリクスを発行します。

あくまで参考スクリプトなので、実際に利用する際は適宜、修正更新の上、ご利用下さい。

このイメージを使ってタスク定義、サービス登録します。

Docker Hub および ECR リポジトリへのイメージ登録については割愛します。

まずロググループの作成をしておきます。

aws logs create-log-group --log-group-name /ecs/sample-sqs-cron

タスク定義を登録します。

タスクに充てる IAMロールにはSQSキューの長さを取得できる AmazonSQSReadOnlyAccess ポリシーおよび CloudWatch にメトリクスを発行するための CloudWatchFullAccess を付与しておきます。(実利用時はもう少し権限を限定するのが適切でしょう)

aws ecs register-task-definition --cli-input-json file://sqs-cron-task.json
## sqs-cron-task.json
{
  "family": "sample-sqs-cron",
  "networkMode": "awsvpc",
  "taskRoleArn": "arn:aws:iam::<AWS_ACCOUNT_ID>:role/<IAMROLE_NAME>",
  "containerDefinitions": [
    {
      "name": "sample-sqs-cron",
      "image": "<IMAGE_URL>",
      "essential": true,
      "logConfiguration": {
        "logDriver": "awslogs",
        "options": {
          "awslogs-group": "/ecs/sample-sqs-cron",
          "awslogs-region": "ap-northeast-1",
          "awslogs-stream-prefix": "fargate"
        }
      }
    }
  ],
  "cpu": "512",
  "memory": "1024",
  "requiresCompatibilities": ["FARGATE"],
  "executionRoleArn": "arn:aws:iam::<AWS_ACCOUNT_ID>:role/ecsTaskExecutionRole"
}

レスポンスされた Task ARN を取得してサービスの json に反映させて登録します。

aws ecs create-service --cli-input-json file://sqs-cron-service.json
## sqs-cron-service.json
{
  "cluster": "default",
  "serviceName": "sample-sqs-cron-service",
  "taskDefinition": "arn:aws:ecs:ap-northeast-1:<AWS_ACCOUNT_ID>:task-definition/sample-sqs-cron:1",
  "desiredCount": 1,
  "launchType": "FARGATE",
  "networkConfiguration": {
    "awsvpcConfiguration": {
      "subnets": ["subnet-xxxxxxxxx", "subnet-xxxxxxxxx"],
      "securityGroups": ["sg-xxxxxxxxxxxx"],
      "assignPublicIp": "ENABLED"
    }
  }
}

これで、カスタムメトリクス発行は対応完了です。

3 1のサービスにステップスケーリングを設定する

ECS サービスのオートスケーリングには以下3つのタイプがあります。

  • ターゲット追跡スケーリングポリシー
  • ステップスケーリングポリシー
  • スケジュールに基づくスケーリング

ターゲット追跡スケーリングは、指定したメトリクスを指定したターゲット値に、またはそれに近い値に維持するよう、自動的に CloudWatch アラームの作成・管理および ECS サービスのタスクの追加・削除を実施する方式です。

そして、ターゲット追跡スケーリングポリシー用に サービスの Auto Scaling によって管理されている CloudWatch アラームを編集または削除しないでください。 と記載されています。

AWS ではターゲット追跡スケーリングポリシーの使用を推奨されていますが、手動でのしきい値の設定が必要な場合などには、ステップスケーリングポリシーを使用することが可能です。

詳細は、サービスの Auto Scaling - Amazon Elastic Container Service を参照下さい。

ステップスケーリングポリシー - Amazon Elastic Container ServiceAWS CLI を使用して ECS サービスのスケーリングポリシーを設定するには で記載されている通り下記で設定します。

SQSのオートスケーリングは現在(2021/03/22)、コンソールでは対応しておらず、AWS CLIで作成します。

  1. register-scalable-target コマンドを使用して、スケーラブルなターゲットとして ECS サービスを登録します。
  2. put-scaling-policy コマンドを使用してスケーリングポリシーを作成します。
  3. [ステップスケーリング] put-metric-alarm コマンドを使用してスケーリングポリシーをトリガーするアラームを作成します。

1 を対応します。

aws application-autoscaling register-scalable-target \
--service-namespace ecs \
--scalable-dimension ecs:service:DesiredCount \
--resource-id service/default/sample-app-service \
--min-capacity 1 \
--max-capacity 5

2 と 3 については若干分かりにくいので説明を加えておきます。

CloudWatchのアラームとステップスケーリングについて

賢明な皆さんはそうではないと思いますが、 自分は put-metric-alarm で設定する際に、いつもどれがどれだっけと戸惑うので、Amazon CloudWatch でのアラームの使用 - Amazon CloudWatch から設定値をまとめておきます。(ドキュメントに記載されているものです)

項目 内容
Period(期間) アラームの各データポイントを作成するためにメトリクスや式を評価する期間。秒単位で表される。1分とした場合、アラームはメトリクスを1分あたり1回評価する。
EvaluationPeriods(評価期間) アラームの状態を決定するまでに要する最新の期間またはデータポイントの数。
Datapoints to Alarm(アラームを実行するデータポイント) アラームが ALARM 状態に移るためにしきい値を超過する必要がある評価期間内のデータポイントの数。データポイントは必ずしも連続している必要はない。
Threshold(しきい値) アラームを実行するかどうかの基準となるしきい値

上の表と見比べると、この図は分かりやすいです。

cloudwatch_alarm_setting

ステップスケーリングは、アラームのしきい値を基準となるスケーリングポリシーを設定します。

アラームに対して定義したしきい値との差に基づく調整になります。

例えば、しきい値が50のアラームとします。

メトリックが50以上60未満のときに調整をトリガーするには、下限0と上限10を指定します。また、メトリックが40より大きく50以下の場合に調整をトリガーするには、下限を-10、上限を0と指定します。

詳細は、Amazon EC2 AutoScalingのステップおよびシンプルなスケーリングポリシー-AmazonEC2 Auto Scaling を参照すると良いです。

本稿のスケールアウトのステップスケーリングは、アラート発砲されたタイミングでキュー数が2より大きくなったら1タスク、3以上で2タスク、4以上で3タスク増やすことにします。ただし、上限は5なので5より大きくなることはありません。スケーリングが実行されるまでの待機時間を1分とします。

aws application-autoscaling put-scaling-policy --service-namespace ecs \
--scalable-dimension ecs:service:DesiredCount \
--resource-id service/default/sample-app-service \
--policy-name sqs-queue-step-high-scaling-policy \
--policy-type StepScaling \
--step-scaling-policy-configuration file://high-policy-config.json
## high-policy-config.json
{
  "AdjustmentType": "ChangeInCapacity",
  "StepAdjustments": [
    {
      "MetricIntervalLowerBound": 1,
      "MetricIntervalUpperBound": 2,
      "ScalingAdjustment": 1
    },
    {
      "MetricIntervalLowerBound": 2,
      "MetricIntervalUpperBound": 3,
      "ScalingAdjustment": 2
    },
    {
      "MetricIntervalLowerBound": 3,
      "ScalingAdjustment": 3
    }
  ],
  "Cooldown": 60,
  "MetricAggregationType": "Average"
}

レスポンスされた ARN は手元に控えておき、下記の Config.json に反映させます。

## high-alarm-config.json
{
  "AlarmName": "demo-sqs-rapid-high-alarm",
  "AlarmDescription": "demo high alarm policy for sqs queue number of messages",
  "TreatMissingData": "notBreaching",
  "ActionsEnabled": true,
  "OKActions": [],
  "AlarmActions": [
    "arn:aws:autoscaling:ap-northeast-1:<AWS_ACCOUNT_ID>:scalingPolicy:xxxxxxxxxx:resource/ecs/service/default/sample-app-service:policyName/sqs-queue-step-high-scaling-policy"
  ],
  "MetricName": "FiveSecondsApproximateNumberOfMessages",
  "Namespace": "<NAME_SPACE>",
  "Statistic": "Average",
  "Dimensions": [
    {
      "Name": "QueueName",
      "Value": "<QUEUE_NAME>"
    }
  ],
  "Period": 10,
  "Unit": "Count",
  "DatapointsToAlarm": 1,
  "EvaluationPeriods": 1,
  "Threshold": 1,
  "ComparisonOperator": "GreaterThanThreshold"
}

ポリシーをもう1つ作成します。

本稿のスケールインのステップスケーリングは、2より小さいとアラート発砲します。そのタイミングでキュー数が2より小さかったら1ずつ減らしていきます。ただし、タスクの下限数は1なので1より小さくなることはありません。スケーリングが実行されるまでの待機時間を1分とします。

スケールアウトよりもスケールインはステップを緩やかにしています。

aws application-autoscaling put-scaling-policy --service-namespace ecs \
--scalable-dimension ecs:service:DesiredCount \
--resource-id service/default/sample-app-service \
--policy-name sqs-queue-step-low-scaling-policy \
--policy-type StepScaling \
--step-scaling-policy-configuration file://low-policy-config.json
## low-policy-config.json
{
  "AdjustmentType": "ChangeInCapacity",
  "StepAdjustments": [
    {
      "MetricIntervalUpperBound": 0,
      "MetricIntervalLowerBound": -1,
      "ScalingAdjustment": -1
    },
    {
      "MetricIntervalUpperBound": -1,
      "MetricIntervalLowerBound": -2,
      "ScalingAdjustment": -1
    },
    {
      "MetricIntervalUpperBound": -2,
      "ScalingAdjustment": -1
    }
  ],
  "Cooldown": 60,
  "MetricAggregationType": "Average"
}

レスポンスされた ARN は手元に控えておき、下記の Config.json に反映させます。

## low-alarm-config.json
{
  "AlarmName": "demo-sqs-low-alarm",
  "AlarmDescription": "demo low alarm policy for sqs queue number of messages",
  "TreatMissingData": "notBreaching",
  "ActionsEnabled": true,
  "OKActions": [],
  "AlarmActions": [
    "arn:aws:autoscaling:ap-northeast-1:<AWS_ACCOUNT_ID>:scalingPolicy:xxxxxxxxxx:resource/ecs/service/default/sample-app-service:policyName/sqs-queue-step-low-scaling-policy"
  ],
  "MetricName": "FiveSecondsApproximateNumberOfMessages",
  "Namespace": "<NAME_SPACE>",
  "Statistic": "Average",
  "Dimensions": [
    {
      "Name": "QueueName",
      "Value": "<QUEUE_NAME>"
    }
  ],
  "Period": 10,
  "Unit": "Count",
  "DatapointsToAlarm": 1,
  "EvaluationPeriods": 1,
  "Threshold": 2,
  "ComparisonOperator": "LessThanThreshold"
}

アラームを high と low とでそれぞれ1つずつ作成します。

aws cloudwatch put-metric-alarm --cli-input-json file://high-alarm-config.json
aws cloudwatch put-metric-alarm --cli-input-json file://low-alarm-config.json

スケーリングポリシーおよびアラーム条件の詳細内容は各パラメータをご確認頂ければと思います。

実際に利用する際は、繰り返しテスト検証して想定している挙動になるかどうかパラメータを微調整していく必要があります。

これでオートスケーリングの設定も完了です。

検証

コンソールでメッセージをポコポコ送信してみます。

sqs messages

経過30,40秒くらいでオートスケーリングが開始されます。

だいたい1.5分で RUNNING ステータス完了です。

こんな感じです。

autoscaling tasks

現状、5秒の解像度、10秒の期間なので、より短くすることでさらにスケールアウトの速度は増すでしょう。(課金も増しますが)

そして、さらにさらにスピードを欲しがる本格派は、こちらでしょうか。

爆速でFargateをスケールさせる「aws-fargate-fast-autoscaler」を試してみた | DevelopersIO

なお、作成したリソースはクラスターからサービス、タスク、ポリシーおよびアラームも忘れず全て削除しましょう。

最後に

繰り返しますが、実際にこの仕組みを使う必要性があるかどうか、コストについてはよくよくご検討下さい。

本稿のDocker コンテナ内の調査のために早速、[アップデート] 実行中のコンテナに乗り込んでコマンドを実行できる「ECS Exec」が公開されました | DevelopersIO を使ってみました。

非常にタイムリーで助かりました!

こちらは重宝できる機能だと感じられました。

以上です。

どなたかのお役に立てば幸いです。

参考