😎

スケジュールされたスケーリングとターゲット追跡スケーリングを組み合わせて万全のAutoScalingを実現する

2023/11/01に公開

こんにちは。
ご機嫌いかがでしょうか。
"No human labor is no human error" が大好きな吉井 亮です。

今回は Application Auto Scaling について考える〜
吉井亮の吉井亮的こころです。

あるアプリケーションがありまして、こちらピークタイムが決まっています。仮にお昼時とします。
10時〜14時は EC2 8台で運用、それ以外の時間帯は 2台で運用しています。ただし、2台運用時にも予測できないアクセス増があり、そのときには4台運用へと増やしたいと考えています。

img

このケースを Application Auto Scaling にて解決したいと思います。

Application Auto Scaling とは

Application Auto Scaling は、AWS リソースの自動的なスケーリングを提供するサービスです。サポートしているサービスは以下です。

  • AppStream 2.0 fleets
  • Aurora replicas
  • Amazon Comprehend document classification and entity recognizer endpoints
  • DynamoDB tables and global secondary indexes
  • Amazon Elastic Container Service (ECS) services
  • ElastiCache for Redis clusters (replication groups)
  • Amazon EMR clusters
  • Amazon Keyspaces (for Apache Cassandra) tables
  • Lambda function provisioned concurrency
  • Amazon Managed Streaming for Apache Kafka (MSK) broker storage
  • Amazon Neptune clusters
  • SageMaker endpoint variants
  • SageMaker Serverless provisioned concurrency
  • Spot Fleet requests
  • Custom resources provided by your own applications or services. For more information, see the GitHub repository.

スケーリング条件は3通り対応しています。
今回は、「スケジュールされたスケーリング」と「ターゲット追跡スケーリング」を組み合わせて使います。

  • ターゲット追跡スケーリング
  • ステップスケーリング
  • スケジュールされたスケーリング

やってみた

Application Auto Scaling は様々なサービスをサポートしています。今回は検証のしやすさと値段の安さを考慮して EC2 スポットフリートを使います。
細かい違いはあれど基本的な設定は他のサービスも同じはずです。(という気持ちで検証しました)

Application Auto Scaling はマネジメントコンソール画面がありません。(執筆日時点)
ですので、AWS CLI または CloudShell を使って設定を行います。

スケーラブルターゲットに登録

スポットフリートは作ってある前提です。

Application Auto Scaling でスケールさせるにはスケーラブルターゲットに登録が必要です。

繰り返しですが、EC2 スポットフリートで検証した例です。
何度かフリート ID を使います。ID が長目の文字列なのでミスを減らすため、スポットフリートのリクエスト ID を変数に入れます。

ASRESOURCE=spot-fleet-request/your_request_id

スポットフリートを Auto Scaling 対象にするため、スケーラブルターゲットに登録します。
この時点では最小キャパシティも最大キャパシティも0で大丈夫です。

aws application-autoscaling register-scalable-target \
  --service-namespace ec2 \
  --scalable-dimension ec2:spot-fleet-request:TargetCapacity \
  --resource-id ${ASRESOURCE} \
  --min-capacity 0 \
  --max-capacity 0

以下のような出力が確認できれば成功です。

{
    "ScalableTargetARN": "arn:aws:application-autoscaling:us-west-2:AWS_ACCOUNT_ID:scalable-target/0ecc0a813d295aa94475ae736666f9bfdbdc"
}

スケジュールされたポリシーを追加

スケールアウトポリシーを追加します。
先に示した図でいうところのピーク時間帯のスケーリングです。

10時〜14時は8台固定にしています。

aws application-autoscaling put-scheduled-action \
  --service-namespace ec2 \
  --schedule "cron(0 10 ? * MON-FRI *)" \
  --timezone "Asia/Tokyo" \
  --scheduled-action-name "Scale-Out by cron" \
  --resource-id ${ASRESOURCE} \
  --scalable-dimension ec2:spot-fleet-request:TargetCapacity \
  --scalable-target-action MinCapacity=8,MaxCapacity=8

こちらはスケールインです。最低2台、最高4台にしています。最低の2台で運用し、予期せぬアクセス増に4台へと増やしたい意図です。

aws application-autoscaling put-scheduled-action \
  --service-namespace ec2 \
  --schedule "cron(0 14 ? * MON-FRI *)" \
  --timezone "Asia/Tokyo" \
  --scheduled-action-name "Scale-In by cron" \
  --resource-id ${ASRESOURCE} \
  --scalable-dimension ec2:spot-fleet-request:TargetCapacity \
  --scalable-target-action MinCapacity=2,MaxCapacity=4

describe-scheduled-actions で設定内容を確認できます。

$ aws application-autoscaling describe-scheduled-actions  --service-namespace ec2

ターゲット追跡スケーリングポリシーを作成

ポリシー作成用 JSON ファイルを作成します。

事前定義された変数を使うと簡単にポリシーを作成できます。
どのような変数が使えるかは PredefinedMetricSpecification を参照ください。
ほとんどのケースを網羅していると思います。

cpu50-target-tracking-scaling-policy.json というファイルを作り、以下を貼り付けます。
スポットフリートの CPU 使用率平均 50% をしきい値にする設定です。

cpu50-target-tracking-scaling-policy.json
{
  "TargetValue": 50.0,
  "PredefinedMetricSpecification": 
    {
      "PredefinedMetricType": "EC2SpotFleetRequestAverageCPUUtilization"
    }
}

put-scaling-policy でスケーリングポリシーを登録します。

aws application-autoscaling put-scaling-policy --service-namespace ec2 \
  --scalable-dimension ec2:spot-fleet-request:TargetCapacity \
  --resource-id ${ASRESOURCE}  \
  --policy-name cpu50-target-tracking-scaling-policy \
  --policy-type TargetTrackingScaling \
  --target-tracking-scaling-policy-configuration file://cpu50-target-tracking-scaling-policy.json

以下のような出力を確認します。ポイントはアラームが2つ作成されることです。
CloudWatch アラーム画面でも2つのアラームが確認可能です。(これは削除してはいけません)

{
    "PolicyARN": "arn:aws:autoscaling:us-west-2:AWS_ACCOUNT_ID:scalingPolicy:6eb96339-8f71-40f1-9c02-e42fd6c1238f:resource/ec2/spot-fleet-request/sfr-535b6fd5-638b-4d34-a330-bc4df1fcf09b:policyName/cpu50-target-tracking-scaling-policy",
    "Alarms": [
        {
            "AlarmName": "TargetTracking-spot-fleet-request/sfr-535b6fd5-638b-4d34-a330-bc4df1fcf09b-AlarmHigh-20fb2620-b86a-4cae-b0bc-12f4db6aae5e",
            "AlarmARN": "arn:aws:cloudwatch:us-west-2:AWS_ACCOUNT_ID:alarm:TargetTracking-spot-fleet-request/sfr-535b6fd5-638b-4d34-a330-bc4df1fcf09b-AlarmHigh-20fb2620-b86a-4cae-b0bc-12f4db6aae5e"
        },
        {
            "AlarmName": "TargetTracking-spot-fleet-request/sfr-535b6fd5-638b-4d34-a330-bc4df1fcf09b-AlarmLow-74611c07-f278-4c1b-85bd-5f8916f395fa",
            "AlarmARN": "arn:aws:cloudwatch:us-west-2:AWS_ACCOUNT_ID:alarm:TargetTracking-spot-fleet-request/sfr-535b6fd5-638b-4d34-a330-bc4df1fcf09b-AlarmLow-74611c07-f278-4c1b-85bd-5f8916f395fa"
        }
    ]
}

おまけ

自身でメトリクスをカスタマイズして定義することも可能です。以下の例はスポットフリートの CPU 使用率平均を指定しています。
指定できる値は CustomizedMetricSpecification を参照ください。

{
   "TargetValue":50.0,
   "CustomizedMetricSpecification":{
      "MetricName":"CPUUtilization",
      "Namespace":"AWS/EC2Spot",
      "Dimensions":[
         {
            "Name":"FleetRequestId",
            "Value":"your_fleet_id"
         }
      ],
      "Statistic":"Average",
      "Unit":"Percent"
   }
}

EC2インスタンスに負荷をかける

ターゲット追跡スケーリングが正しく設定されていることを確認するためにインスタンスに負荷をかけ CPU 使用率を上げます。
みんな大好き yes コマンドです。

$ yes >/dev/null &
$ yes >/dev/null &
$ yes >/dev/null &
$ yes >/dev/null &

4発くらい打てば100%までいくはずです。

しばらく待ち、スケーリングアクティビティを確認すると正しくスケールされました。
"Setting target capacity to 4." が出力されています。

$ aws application-autoscaling describe-scaling-activities --service-namespace ec2

{
    "ScalingActivities": [
        {
            "ActivityId": "843924cd-fb6f-4d4a-bebf-e86741c65138",
            "ServiceNamespace": "ec2",
            "ResourceId": "spot-fleet-request/sfr-343d8e85-38d0-4681-9f1c-b4a4adff52d4",
            "ScalableDimension": "ec2:spot-fleet-request:TargetCapacity",
            "Description": "Setting target capacity to 4.",
            "Cause": "monitor alarm TargetTracking-spot-fleet-request/sfr-343d8e85-38d0-4681-9f1c-b4a4adff52d4-AlarmHigh-ce43e93b-2df3-4cc7-aca6-8d6c28fc16b9 in state ALARM triggered policy cpu50-target-tracking-scaling-policy",
            "StartTime": "2023-10-31T15:44:34.353000+09:00",
            "EndTime": "2023-10-31T15:46:42.658000+09:00",
            "StatusCode": "Successful",
            "StatusMessage": "Successfully set target capacity to 4. Change successfully fulfilled by ec2."
        },

気になったこと

14時のタイミング、8台から2台へスケールインする際の挙動が気になりましたので挙動を見ていました。
8台起動した状態からスケジュールされたポリシーを使って10時45分にスケールイン (最小2台、最大4台) しています。

履歴を抜粋しました。(タイムスタンプが14時ではないですが、気にしないでください。)

No. タイムスタンプ イベントタイプ ステータス 説明
(1) 11/01/2023 10:45:27 AM fleetRequestChange modify_in_progress Modify request received. Requested targetCapacity: 4
(2) 11/01/2023 11:09:10 AM fleetRequestChange modify_in_progress Modify request received. Requested targetCapacity: 3
(3) 11/01/2023 11:15:10 AM fleetRequestChange modify_in_progress Modify request received. Requested targetCapacity: 2

(1) は cron により最大に指定した 4台へとスケールインしています。

(2) はおそらくですが、ターゲット追跡スケーリングポリシー作成時にできた CloudWatch アラームのうちの1つ、これがスケールイン用アラームです。
1分ごとに計測し15回連続で CPU 平均使用率が45%を下回ったらスケールインというルールになっているためだと思われます。
なぜ45%かというと、ターゲット追跡スケーリングポリシーはしきい値を10%以上下回るとスケールインする挙動になっています。
(1) で4台に減ってからきっちり15分ではない、というツッコミがあるかもしれません。が、(1) は4台にしなさいよというイベントを受け取っただけであり、EC2 の終了がなんやかんや時間がかかっていたりします。
ユーザーガイドには Application Auto Scaling はスケールイン/スケールアウト時に保守的な動作をすると記述されており、
可用性を重視して遅めのスケールインになっているのかもしれません。
また、CloudWatch アラームもそれほど厳密に1分1秒刻んでいるわけではないと個人的には考えています。

(3) の段階で最小に指定した2台へとスケールインしています。これも可用性や再スケールアウトを考慮して、一気にスケールインしないようにしているものと考えます。
(2)と(3)の約6分が何を意味しているか、これも推測になってしまうのですが、スケールインのクールダウン期間である300秒 (サービスによって異なる) は必ず保護し、EC2 シャットダウン時間や保守的な動作時間が含まれているものと考えています。

可用性や再スケールを考慮して保守的になっているのは合理的な設計だと感じます。
もしかしたら、狙った時間にスケールアウト&インしてくれないと予算が・・・みたいなケースはあるかもしれません。その場合は lambda 等で作り込みになると思います。作り込むより考え方を Application Auto Scaling に合わせたほうが幸せそうな気がしますが。

参考

Application Auto Scaling User Guide
AWS CLI Command Reference application-autoscaling

Discussion