Fargate Spot の安全な運用方法を考える キャパシティ編

# はじめに

[前回の記事](https://qiita.com/sugimount-a/items/1b64e1f2eb544e059371)では、Fargate Spot の中断について、考慮すべきポイントを整理しました。今回の記事では、キャパシティについて考えていきたいと思います。

Fargate Spot は AWS の空きキャパシティを利用することで、通常より最大 70% OFF の価格で利用できます。一方、空きキャパシティが確保できなくなった場合、いつでも中断される可能性があります。中断される際には、AWS から 2 分前に通知として `SIGTERM` を受け取ることが出来るため、アプリケーションを安全にシャットダウンできます。

中断については、2 分前の通知で安全にシャットダウンが出来ますが、合わせてパフォーマンス面の考慮も必要です。Fargate Spot の空きキャパシティが不足している時でも、システムを稼働し続けるために、キャパシティをどのように確保するか考慮が必要です。キャパシティ確保の考え方について、以下の 2 パターンを紹介します。この名前は、個人的にわかりやすく付けた名前なので、AWS 公式の Document などの用語ではありません。ご留意ください。これ以外にも、素敵なパターンがある場合は、ぜひコメントで教えて頂けるとうれしいです。

- バランス型 : Capacity Provider を使って、最低限必要なリソースを通常の Fargate で準備する
- ガンガン活用型 : Fargate Spot が確保できなくなった時に、通常の Fargate を増やす



それぞれのパターンについてみていきます。



# バランス型

バランス型の話に移る前に、先に Capacity Provider を説明します。

Capacity Provider とは、ECS クラスターで動かすタスクのリソースを管理するものです。Fargate を利用する場合は、通常価格の「FARGATE」と、スポット価格の「FARGATE_SPOT」を組み合わせて、どのような配分で利用するかを定義できます。Capacity Provider には、base と weight という考え方があります。

- base : 最小実行タスク数
- weight : 「FARGATE」と「FARGATE_SPOT」間の相対的な割合



base と weight を理解するために、次のシナリオを考えてみましょう。

- ECS を使ってサービスを提供する際に、コストを最適化を考えて「FARGATE_SPOT」 を使う
- 「FARGATE_SPOT」のキャパシティ不足が発生したときに、最低限必要なパフォーマンスを「FARGATE」で確保する
- 最低限必要なパフォーマンスは、2 個のタスクとする
- 3個目以降のタスクは、「FARGATE」と「FARGATE_SPOT」を混在させる

この場合、次のように Capacity Provider を構成できます

![image-20220503144524208.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/7f9d0c3b-5ac7-9a45-50c0-64fc549a48bb.png)



Base は、最小実行タスク数を指定する概念です。上の例では、「FARGATE」を Base 2 としているので、ECS Service で稼働する最初の 2 個のタスクは「FARGATE」から確保されます。次の画像のイメージです。

![image-20220503144905509.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/a76672f2-1d58-8efe-7fcb-4f0279cfd0c9.png)



　

Base を超えるタスクは、Weight で指定されている数の割合を利用してタスクが生成されます。例えば、ECS Service で動かすタスクの数を 6 と指定すると、Weight で指定した割合に合わせて、タスクが増えていきます。「FARGATE」と「FARGATE_SPOT」が混在して増えていく形です。

![image-20220503145236281.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/c820d79c-fdec-f5ed-5fb5-916499aea143.png)



これにより、仮に「FARGATE_SPOT」のリソースが確保できなくなっても、「FARGATE」で動かしているタスク分は確保できているため、サービスの提供を継続できます。パフォーマンス面では低下する可能性があり、いわゆる縮退運航というやつです。

Capacity Provider をより知りたい方は、Classmethod さんの記事がわかりやすいので、URL を紹介します。

- Capacity Providerとは？ECSの次世代スケーリング戦略を解説する #reinvent #cmregrowth
  - https://dev.classmethod.jp/articles/regrwoth-capacity-provider/

- Fargate Spotにおける各Provider毎のタスク数起動推移を検証してみた #reinvent
  - https://dev.classmethod.jp/articles/fargate-spot-task-count/



# ガンガン活用型

次のパターンのガンガン活用型について説明をします。ガンガン活用型は、より積極的に「FARGATE_SPOT」を利用するパターンになります。バランス型は、リソースを確保できる「FARGATE」を利用するためキャパシティ面で安心できますが、一方コスト効率もバランスをとっている形です。より多くの 「FARGATE_SPOT」を活用する方がコスト効率が良いです。

ガンガン活用型は、普段は「FARGATE_SPOT」を多く活用しながら、キャパシティが確保できなくなった際に、「FARGATE」を増やしていく方法です。通常時はコスト効率の良いリソースを、より多く活用していくので、ガンガン活用型と名付けています。

**ガンガン活用型の注意点があります。「FARGATE_SPOT」のキャパシティが確保できなくなった時に「FARGATE」を増やしていく考え方ですが、「FARGATE」を必ず増やせられるわけではありません。AWS はキャパシティ不足が発生しないように運用していますが、物理的なリソースに依存する分、枯渇してしまう可能性はゼロではありません。万が一の枯渇が許されない場合は、「バランス型」や、Savings Plans、他リージョンも活用など、別の方法を検討されると良いと思います。**

ガンガン活用型の実現方法についてです。Capacity Provider を利用しているため、「FARGATE_SPOT」のキャパシティが確保できなくなったときに「FARGATE」の Base を増やす方法を思いつきますが、これは実現が難しい点があります。既存の Capacity Provider の base や weight の数値を変更する場合、「Force new deployment」が必要です。これは、既に稼働している Task を全て削除して、新たに再作成する動きになります。キャパシティ不足が発生している状況で全タスクを再作成するため、リソースが確保できない懸念があります。

この懸念を解決するために次の実現アイデアが考えられます

- 「FARGATE_SPOT」と「FARGATE」の 2 つの ECS サービス作り、Application Auto Scaling で切り替える
- 「FARGATE_SPOT」と「FARGATE」の 2 つの ECS サービス作り、「FARGATE_SPOT」の「desiredCount」 と 「runningCount」 の差分を監視して、差分が有る場合は 「FARGATE」を増やす Lambda Function を動かす

1点目のパターンがシンプルで実装負担が少ないため、これを取り上げていきます。2点目のパターンが気になる方がいれば Qiita にコメントを残して頂ければ幸いです。追加の記事を作成します。



まず、「FARGATE_SPOT」と「FARGATE」の 2 つの ECS サービス作ります。異なる ECS サービスでも、同一の Target Group を共有することが出来ます。構成図にすると、次のような感じです。

ユーザー側の視点から紐解いていくと、ALB と Target Group は通常通り 1 対 1 で紐づいています。また、1 個の Target Group に対して、「FARGATE_SPOT」と「FARGATE」の Capacity Provider を持つ 2つの Service を紐づけることが出来ます。「FARGATE」側のオートスケーリングの条件を厳しくすることで、普段は「FARGATE_SPOT」がより多く使われる構成になります。

![image-20220503161900914.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/083cf51e-e7a2-f93e-3e49-72efe47b3e51.png)



ここで、「FARGATE_SPOT」のキャパシティが確保できなくなった場合はどうなるのか説明していきます。キャパシティを確保できなくなったため、黄色い「FATGATE_SPOT」のタスクが、中断通知を受けながら中断されます。すると、青色の「FARGATE」のタスクの CPU 利用率が上がります。わかりやすく 100% になるとしましょう。その結果、「FARGATE」側のオートスケーリングの条件に引っかかります。

![image-20220503163806750.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/76d19f2d-382b-4015-0e4f-996ef89b9c77.png)





オートスケーリングが発動して「FARGATE」のタスクが増えます。Application Auto Scaling の仕組みに乗っかることで、負荷に合わせて自動的に「FARGATE」のタスクを増やすことが出来ます。

![image-20220503164109966.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/87d26fe6-1c10-edc4-4cd6-6dfdc497706d.png)



その後、「FARGATE_SPOT」側が復旧する場面を見てみましょう。「FARGATE_SPOT」側は ECS Service なので、定期的に再作成を試みています。空きキャパシティが確保出来たら、「FARGATE_SPOT」のタスクが増えます。その結果、CPU 使用率が分散され、青色の「FARGATE」側のスケールインが発生します。

![image-20220503164426742.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/9c4abf5f-957c-902b-f574-011cd9fc41e7.png)



スケールインの結果、元のシステム構成に戻ります。

![image-20220503164446671.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/77c0bda9-b9e5-beea-929e-922b1654e4ac.png)



# ガンガン活用型を構築してみる

ガンガン活用型 の Application Auto Scaling パターンが、どういった構成なのかを説明してきました。実際にこれを構築するための手順を紹介していきます。

## Target Group の作成

まず、2つの ECS Service で共有する Target Group を作成します。

![image-20220503170047363.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/d3efbae1-498e-d80c-b405-6b8453161bd1.png)



IP address を指定します。

![image-20220502132005232.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/be95d5f9-b5f1-216d-c923-5b8ccf2a9d7f.png)



Target Group 名を適当に指定します。

![image-20220503170255781.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/a32c1717-e607-e063-825e-32a4f6e44ed1.png)



Next を押します

![image-20220503170325838.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/8927106b-d7f3-77e8-8939-54d409351a0b.png)



Target Group に紐づける IP アドレスは、いったん無しで作成します

![image-20220503173044927.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/3243164e-35c6-b65e-38b6-416567c8dab8.png)



Deregistration delay は2分未満にした方が良いので、今回は115秒にしておきます。この詳細が気になる方は、[前回の記事の中断編](https://qiita.com/sugimount-a/items/1b64e1f2eb544e059371)を参照してください。

![image-20220503173142742.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/44836212-4eae-a1f3-ec94-5e60fc3d59d2.png)



## ALB の作成

Create Load Balancer を押します

![image-20220503170803983.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/3f8d24d0-9492-df92-1e0c-cf33e8a850e6.png)



ALB を Create します

![image-20220503170838745.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/63023e88-de61-2969-3906-a44c8516c40b.png)



ALB の名前を付けます

![image-20220503170917338.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/cb4d2583-270e-f899-c49c-7fc8a3b68afe.png)



VPC や Subnet を指定します

![image-20220503171025643.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/b0d56a00-13f8-73d0-28f3-9297d22898a4.png)



作成した Target Group を指定します

![image-20220503171106791.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/1e8c1218-b750-5dad-fae5-2365c613482b.png)



Create を押します

![image-20220503171119798.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/0927be51-3990-a861-ef3e-ff0e0e891b27.png)



作成されました

![image-20220503171330277.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/cd1b74b0-7fd6-34c6-6629-a9c6dbe7ead5.png)



## ECS Service の作成

1個の Target Group を共有する設定は、AWS マネージメントコンソールでは出来ません。AWS CLI や CloudFormation などで指定できます。今回は AWS CLI で作業をしていきます。

Capacity Provider「FARGATE_SPOT」で、1個目のサービスを起動 

```
aws ecs create-service \
  --cluster test-cluster01 \
  --service-name fargate-spot-gangan-service01 \
  --task-definition fargate-spot-testapp-def \
  --load-balancers '[{"targetGroupArn": "arn:aws:elasticloadbalancing:ap-northeast-1:xxxxxxxxxxxx:targetgroup/fargate-spot-gangan-targetgroup/5c371daa599850bd", "containerName":"testapp", "containerPort": 8080 }]' \
  --desired-count 1 \
  --capacity-provider-strategy '[{"capacityProvider": "FARGATE_SPOT", "base": 1, "weight": 1 }]' \
  --network-configuration "awsvpcConfiguration={subnets=[subnet-0c414bf2d11f17bba,subnet-09023225144fa1c62,subnet-0cf48d891b1bea31c],securityGroups=[sg-0fbfac9c258b61337],assignPublicIp=DISABLED}"
```



Capacity Provider「FARGATE」で、2個目のサービスを起動 

```
aws ecs create-service \
  --cluster test-cluster01 \
  --service-name fargate-spot-gangan-service02 \
  --task-definition fargate-spot-testapp-def \
  --load-balancers '[{"targetGroupArn": "arn:aws:elasticloadbalancing:ap-northeast-1:xxxxxxxxxxxx:targetgroup/fargate-spot-gangan-targetgroup/5c371daa599850bd", "containerName":"testapp", "containerPort": 8080 }]' \
  --desired-count 1 \
  --capacity-provider-strategy '[{"capacityProvider": "FARGATE", "base": 1, "weight": 1 }]' \
  --network-configuration "awsvpcConfiguration={subnets=[subnet-0c414bf2d11f17bba,subnet-09023225144fa1c62,subnet-0cf48d891b1bea31c],securityGroups=[sg-0fbfac9c258b61337],assignPublicIp=DISABLED}"
```



上記コマンドで、2つの ECS Service が作成されました。

![image-20220503180841779.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/49314d80-c343-bb66-ca21-6de165b9b81b.png)



2つの Task が起動しており、それぞれ、「FARGATE_SPOT」と「FARGATE」で稼働しています。

![image-20220503181031399.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/42064a4f-3eca-e2ce-d549-fda4f56cad73.png)



Target Group に2個の Task が作成されています。

![image-20220503181159740.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/ba68c80d-7e29-4714-46cc-960b02247af1.png)



## Application Auto Scaling の設定

Application Auto Scaling は、引き続き AWS CLI でも設定できますが、AWS マネージメントコンソール上からも設定可能です。ECS の古いページから設定できます。

「**FARGATE_SPOT**」を利用しているService 選択して、Update を押します

![image-20220503182252375.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/8b1d238a-c58f-a363-fea8-c59f4b4c620b.png)



Auto Scaling の設定ページへ移動します。この設定画面の裏側では、Application Auto Scaling が使われています。以下のパラメータを押したあとに、Add scaling policy を選択します。

- 最小タスク数
- 最大タスク数

![image-20220503182454774.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/5af7da98-a66f-5074-68eb-030b58293a52.png)



Auto Scaling の条件を入れたうえで、Save を押します。

- CPU 使用率の平均を 50% をターゲットにして Auto Scaling を行う
- CPU 以外にも、メモリ使用率、ALB のリクエストカウントをスケーリングの条件に設定可能

![image-20220503182647181.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/94117a9b-0ca7-f65f-5632-7a15d9804c61.png)



Update を押す

![image-20220503182831912.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/ffa266b5-2784-1b19-e6e7-52665c3dd915.png)



設定されました。設定されたことを、AWS CLI でも確認可能です。Application Auto Scaling のカテゴリで確認できます。

- 最小タスク、最大タスクが確認できる

```
> aws application-autoscaling describe-scalable-targets --service-namespace ecs
{
    "ScalableTargets": [
        {
            "ServiceNamespace": "ecs",
            "ResourceId": "service/test-cluster01/fargate-spot-gangan-service01",
            "ScalableDimension": "ecs:service:DesiredCount",
            "MinCapacity": 1,
            "MaxCapacity": 10,
            "RoleARN": "arn:aws:iam::xxxxxxxxxxxx:role/aws-service-role/ecs.application-autoscaling.amazonaws.com/AWSServiceRoleForApplicationAutoScaling_ECSService",
            "CreationTime": "2022-05-03T18:28:20.357000+09:00",
            "SuspendedState": {
                "DynamicScalingInSuspended": false,
                "DynamicScalingOutSuspended": false,
                "ScheduledScalingSuspended": false
            }
        }
    ]
}
```



- CPU 使用率を 50% にする旨が定義されている

```
> aws application-autoscaling describe-scaling-policies --service-namespace ecs
{
    "ScalingPolicies": [
        {
            "PolicyARN": "arn:aws:autoscaling:ap-northeast-1:xxxxxxxxxxxx:scalingPolicy:1aa137bb-06f5-4a1c-bd3f-72d817090513:resource/ecs/service/test-cluster01/fargate-spot-gangan-service01:policyName/fargate-spot-gangan-policy01",
            "PolicyName": "fargate-spot-gangan-policy01",
            "ServiceNamespace": "ecs",
            "ResourceId": "service/test-cluster01/fargate-spot-gangan-service01",
            "ScalableDimension": "ecs:service:DesiredCount",
            "PolicyType": "TargetTrackingScaling",
            "TargetTrackingScalingPolicyConfiguration": {
                "TargetValue": 50.0,
                "PredefinedMetricSpecification": {
                    "PredefinedMetricType": "ECSServiceAverageCPUUtilization"
                },
                "ScaleOutCooldown": 300,
                "ScaleInCooldown": 300
            },
            "Alarms": [
                {
                    "AlarmName": "TargetTracking-service/test-cluster01/fargate-spot-gangan-service01-AlarmHigh-66934f79-b0f3-41d6-a7e4-81b3cd40db5e",
                    "AlarmARN": "arn:aws:cloudwatch:ap-northeast-1:xxxxxxxxxxxx:alarm:TargetTracking-service/test-cluster01/fargate-spot-gangan-service01-AlarmHigh-66934f79-b0f3-41d6-a7e4-81b3cd40db5e"
                },
                {
                    "AlarmName": "TargetTracking-service/test-cluster01/fargate-spot-gangan-service01-AlarmLow-8979e557-e315-46ab-b804-14460a892409",
                    "AlarmARN": "arn:aws:cloudwatch:ap-northeast-1:xxxxxxxxxxxx:alarm:TargetTracking-service/test-cluster01/fargate-spot-gangan-service01-AlarmLow-8979e557-e315-46ab-b804-14460a892409"
                }
            ],
            "CreationTime": "2022-05-03T18:28:20.567000+09:00"
        }
    ]
}
```



同様に、「FARGATE」側の Service に Auto Scaling の設定を行います。**<u>「FARGATE」側は、「FARGATE_SPOT」より高めの目標を設定します。これにより、先に「FARGATE_SPOT」がスケーリングすることを期待できます。</u>**

![image-20220503183742540.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/ff7de73b-d83a-0dc9-219f-cc86a9574b8a.png)



これで、以下のような構成ができました。

![image-20220503184000399.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/819016dc-2ceb-e9b0-4af4-53340d5d40fc.png)



# 検証を通じてわかったこと

- 同一の Target Group に、複数の ECS Service を紐づけることが出来る
  - AWS マネージメントコンソールからは設定できないので、AWS CLI などを利用する
- Auto Scaling の条件には、CPU 使用率やメモリ使用率に加えて、ALB のリクエスト数が利用できる。CPU やメモリに表れてこない負荷の場合には、リクエスト数の利用を検討すると良さそう
- ガンガン活用型は、Blue/Green Deployment は基本的に利用は難しい。ECS にデフォルトで組み込まれているものは利用ができなくなるので、外部デプロイの選択肢になってくるが、比較的複雑な方法になる。Rolling Update も検討したほうがいい
  - https://blog.msysh.me/posts/2023/07/demo_app_for_blue_green_deploy_with_ecs_external_deploy_controller_using_step_functions.html



# 参考URL

サービスに複数のターゲットグループを登録する
https://docs.aws.amazon.com/ja_jp/AmazonECS/latest/developerguide/register-multiple-targetgroups.html

202109 AWS Black Belt Online Seminar Amazon Elastic Container Service − EC2 スポットインスタンス / Fargate Spot ことはじめ
https://www.slideshare.net/AmazonWebServicesJapan/202109-aws-black-belt-online-seminar-amazon-elastic-container-service-ec-fargate-spot