ECS on Fargateで構築したWEBアプリの環境に、Terraformを使用してAuto Scalingの機能を導入する

# 概要
表題の通り、`Terraform`を用いて自動スケーリング機能を追加しました。本記事では、その具体的な実装方法について記述します。

# Auto Scalingの種類
- Auto Scalingは、主に3種類の設定項目がある為、各機能について説明する

## ターゲット追跡ポリシ
- システムのパフォーマンスを安定させる為に、`CPU`使用率を目標値に保つ様、コンテナ数を自動的に調整する
- 指定したメトリクス（例：`CPU`使用率、メモリ使用量）が設定した目標値を超えるか下回った際、自動的にスケールイン（リソースを減らす）またはスケールアウト（リソースを増やす）を行う
- `AWS`が提供するマネージドサービスであり、導入が比較的容易
- `AWS`が推奨するスケーリング方法


## ステップスケーリングポリシ
- リソースの使用率の変化に対してより細かく閾値を決めてスケールイン、アウトを行う
- `CloudWatch`アラームをトリガーとして、`CPU`使用率が特定の閾値を超過するか、閾値未満になるとアラームが発生し、それに応じたスケーリングアクションが実行される
- スケールアウトはリソースを追加するだけなので比較的容易に設定出来るが、スケールインはリソース削減に伴うパフォーマンス低下やコスト増加のリスクがある為、設定が複雑になる
- 閾値を適切に設定をする必要がある為、導入が難しいが、詳細にスケーリングを調整したい場合に便利

## スケジュールに基づくスケーリング
- 指定した日時に基づいて、タスク数を増減させる
- 他のスケーリングポリシと組み合わせて利用可能

## 参考資料
- [[AWS公式] Amazon ECS と Application Auto Scaling](https://docs.aws.amazon.com/ja_jp/autoscaling/application/userguide/services-that-can-integrate-ecs.html)
- [[AWS公式] Amazon ECS サービスを自動的にスケールする](https://docs.aws.amazon.com/ja_jp/AmazonECS/latest/developerguide/service-auto-scaling.html)
- [[AWS公式] ターゲット追跡スケーリングポリシー](https://docs.aws.amazon.com/ja_jp/AmazonECS/latest/developerguide/service-autoscaling-targettracking.html)
- [[AWS公式] ステップスケーリングポリシ](https://docs.aws.amazon.com/ja_jp/AmazonECS/latest/developerguide/service-autoscaling-stepscaling.html)
- [[AWS公式] スケジュールに基づくスケーリング](https://docs.aws.amazon.com/ja_jp/AmazonECS/latest/developerguide/service-autoscaling-schedulescaling.html)
- [ECSにおけるAuto Scaling Policyの「ターゲットスケーリング」と「ステップスケーリング」について](https://zenn.dev/techno_koki/articles/082e7bbe1a3605)
- [Auto Scaling ターゲット追跡ポリシー VS ステップスケーリングポリシー](https://zenn.dev/techno_koki/scraps/5f7cc375adba01)
- [ECSで通常時とスパイク時のオートスケールを運用する](https://tech.timee.co.jp/entry/2020/08/31/191612)
- [[Terraform]ECS Fargateのオートスケーリング設定](https://zenn.dev/spacemarket/articles/4eb067a734cacd)


# 構築手法

- 下記の構築内容は、ターゲット追跡ポリシ、スケーリングポリシのどちらでも使用する

```tf
resource "aws_appautoscaling_target" "auto_scale_target" {
  service_namespace  = "ecs" # 固定値
  resource_id        = "service/サービス名/クラスター名"
  scalable_dimension = "ecs:service:DesiredCount" # 固定値
  min_capacity       = # 最小のタスク数
  max_capacity       = # 最大のタスク数
}
```

## 参考資料
### Terraform
- [[HashiCorp]aws_appautoscaling_policy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/appautoscaling_policy)

### 構築時の参考資料
- [[AWS 公式]スケーラブルターゲットとしての ECS サービスの Application Auto Scaling への登録](https://docs.aws.amazon.com/ja_jp/autoscaling/application/userguide/services-that-can-integrate-ecs.html#integrate-register-ecs)
- [[Terraform]ECS Fargateのオートスケーリング設定](https://zenn.dev/spacemarket/articles/4eb067a734cacd)


## ターゲット追跡ポリシの構築
```tf
resource "aws_appautoscaling_policy" "scale_policy" {
  name               = "任意の名前"
  policy_type        = "TargetTrackingScaling"
  service_namespace  = "ecs"
  resource_id        = "service/クラスタ名/サービス名"
  scalable_dimension = "ecs:service:DesiredCount"
  
  # policy_typeがTargetTrackingScalingの為、target_tracking_scaling_policy_configurationを使用して設定する
  target_tracking_scaling_policy_configuration {
    predefined_metric_specification {
      # ECSサービスの平均CPU使用率をターゲットメトリクスとして指定
      predefined_metric_type = "ECSServiceAverageCPUUtilization"
    }    
    # スケールイン(リソースの減少)を無効にするかどうかを指定する。スケールインは必要な為、false に設定
    disable_scale_in   = false
    # 目標CPU使用率のパーセンテージ
    target_value       = 50
    # スケールインが完了してから次のスケールインが開始出来るまでの時間(秒)を指定
    scale_in_cooldown  = 300
    # スケールアウト(リソースの増加)が完了してから次のスケールアウトが開始出来るまでの時間(秒)を指定
    scale_out_cooldown = 300
  }
  
  depends_on = [aws_appautoscaling_target.auto_scale_target]
}
```

| メソッド | 説明 |
| :- | :- |
| policy_type | ターゲット追跡ポリシーで構築する為、`TargetTrackingScaling`を選択。これにより、特定のメトリクス(`CPU`使用率やメモリ使用率など)に基づいて、リソースの自動スケーリングが実行される |
| service_namespace | どの`AWS`リソースに対して、`Auto Scaling`を適用するか指定する。今回は、`ECS`のサービスに対して適用する為、`namespace`には`ecs`を指定 |
| scalable_dimension | `ECS`サービスのタスク数をスケーリングする為の`AWS Auto Scaling`の設定。この設定は、「タスク数」を指定するのではなく、スケーリングするリソース（ここではタスク数）の「スケーリング可能な属性」を指定する |
| resource_id | `Auto Scaling`を追加する為、`ECS`のサービスを指定する。リソースIDの形式は、`service/クラスタ名/サービス名` |
| predefined_metric_type | `AWS Auto Scaling`でサポートされているサービスに対して適用する。今回は、ECSサービスの平均CPU使用率をターゲットメトリクスとして指定 |
| depends_on | `aws_appautoscaling_target`が作成された後にスケーリングポリシーが適用される様に、明示的に依存関係を設定する為に使用。依存関係を管理する事で、リソースの作成順序が保証される |


## 参考資料
### Terraform
- [[HashiCorp]aws_appautoscaling_policy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/appautoscaling_policy)

### 構築時の参考資料
- [RegisterScalableTarget(スケーラブルを登録する)](https://docs.aws.amazon.com/ja_jp/autoscaling/application/APIReference/API_RegisterScalableTarget.html)
- [PredefinedScalingMetricSpecification](https://docs.aws.amazon.com/ja_jp/autoscaling/application/APIReference/API_RegisterScalableTarget.html)
- [ECS Fargate Terraform オートスケーリングの２つの方法について、まとめてみた(コードあり)](https://qiita.com/katsuryo68/items/e9fda91306ed7561d26e)
- [terraform import: ECS / Application Auto Scaling](https://qiita.com/sasaki_hir/items/2892811d46e5e36acb75)
- [Terraformでターゲット追跡スケーリングポリシーのAuto Scalingを実装する](https://radiochemical.hatenablog.com/entry/2020/04/27/180000)




## スケーリングポリシの構築
- `Terraform`の公式から提供されている構築例をもとに、AWS ECSサービスの自動スケーリングを設定
- 実際の設定については、以下の`GitHub`リポジトリのモジュールを活用
- [[GitHub] AWS ECS Service Autoscaling Module](https://github.com/cn-terraform/terraform-aws-ecs-service-autoscaling?tab=readme-ov-file)

***

#### CloudWatch Alarmの設定内容
```tf
#------------------------------------------------------------------------------
# AWS Auto Scaling - CloudWatch Alarm CPU High
#------------------------------------------------------------------------------
resource "aws_cloudwatch_metric_alarm" "cpu_high" {
  alarm_name          = "任意の名前"
  comparison_operator = "GreaterThanOrEqualToThreshold"
  period              = 60
  evaluation_periods  = 1
  threshold           = 80
  namespace           = "AWS/ECS"
  metric_name         = "CPUUtilization"
  statistic           = "Maximum"
  dimensions = {
    ClusterName = # クラスタ名を指定
    ServiceName = # サービス名を指定
  }
  alarm_actions = [
    aws_appautoscaling_policy.scale_up_policy.arn
  ]
  tags = # 任意で設定
}


#------------------------------------------------------------------------------
# AWS Auto Scaling - CloudWatch Alarm CPU Low
#------------------------------------------------------------------------------
resource "aws_cloudwatch_metric_alarm" "cpu_low" {
  alarm_name          = "任意の名前"
  comparison_operator = "LessThanOrEqualToThreshold"
  evaluation_periods  = 1
  period              = 60
  metric_name         = "CPUUtilization"
  namespace           = "AWS/ECS"
  statistic           = "Average"
  threshold           = 50
  dimensions = {
    ClusterName = # クラスタ名を指定
    ServiceName = # サービス名を指定
  }
  alarm_actions = [
    aws_appautoscaling_policy.scale_down_policy.arn
  ]
  tags = # 任意で設定
}

```

| メソッド | 説明 |
| - | - |
| comparison_operator | `CloudWatch`アラームで、メトリクスの値がしきい値とどのような条件で比較されるかを決める設定。今回は、上限に`GreaterThanOrEqualToThreshold`、下限に`LessThanOrEqualToThreshold`を設定 |
| period | メトリクスデータを収集する時間間隔を秒単位で指定 |
| evaluation_periods | アラームが発動する条件が連続で何回満たされたら反応するかを指定 |
| evaluation_periods | 今回は`ECS`のスケールイン、スケールアウトを行う為、`ECS`を指定 |
| metric_name | 何のメトリクスを閾値の基準するか指定する。今回は、`CPUUtilization`を指定。変更をする場合は、[[AWS公式] Amazon ECS CloudWatch メトリクス](https://docs.aws.amazon.com/ja_jp/AmazonECS/latest/developerguide/available-metrics.html)を読んで選定する |
| namespace | 何のリソースに対してスケーリングを行うか指定する。今回は、`ECS`に適用する為、`AWS/ECS`を記載 |
| threshold | 閾値の設定を設定する |
| statistic | `CloudWatch`アラームで、メトリクスの値をどう計算して判断するかを指定する |
| dimensions | スケーリングさせたいタスクを指定する必要がある為、クラスタとサービスを指定する |
| alarm_actions | `CloudWatch`アラームの値を参照して、その値が基準値を超えたらコンテナの`Auto Scaling`機能が動作する。トリガーにするリソースは、構築内容によって変わる |

### 補足説明
<details><summary>ComparisonOperatorの設定項目</summary>

> ComparisonOperator
The arithmetic operation to use when comparing the specified statistic and threshold. The specified statistic value is used as the first operand.
Required: Yes
Type: String
GreaterThanOrEqualToThreshold（しきい値以上）
GreaterThanThreshold（しきい値より大きい）
LessThanThreshold（しきい値より小さい）
LessThanOrEqualToThreshold（しきい値以下）
LessThanLowerOrGreaterThanUpperThreshold（下限未満または上限超過）
LessThanLowerThreshold（下限未満）
GreaterThanUpperThreshold（上限超過）

- [AWS::CloudWatch::Alarm](https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-resource-cloudwatch-alarm.html)
</details>

</details>

<details><summary>statisticの設定の仕方</summary>

- 上限に関しては、設定したCPUの閾値を超えた場合にスケーリングさせる必要がある為、`Maximum`の設定にする必要がある
- 下限に関しては、タスク(コンテナ)のCPU値で判断する必要がある。`Minimum`にすると考える事が多いが、`Minimum`にするとその設定を下回らない限りスケーリング処理が発生したない為、設定が難しい
- 下限の設定は、CPUの平均で判断した方が、スケールリング作業が適切に行える為、`Average`を設定するのが無難な選択と言える

</details>

*** 

### スケーリングの設定内容
```tf
#------------------------------------------------------------------------------
# AWS Auto Scaling - Scaling Up Policy
#------------------------------------------------------------------------------
resource "aws_appautoscaling_policy" "scale_up_policy" {
  name               = "test-scale-up-policy" # 任意の名前
  service_namespace  = "ecs"
  resource_id        = "service/サービス名/クラスター名"
  scalable_dimension = "ecs:service:DesiredCount"
  
  step_scaling_policy_configuration {
    adjustment_type         = "ChangeInCapacity"
    cooldown                = # 任意の数値を記載
    metric_aggregation_type = # "Maximum" or "Average"
    step_adjustment {
      metric_interval_lower_bound = 0
      scaling_adjustment          = 1
    }
  }
  depends_on = [aws_appautoscaling_target.auto_scale_target]
}

#------------------------------------------------------------------------------
# AWS Auto Scaling - Scaling Down Policy
#------------------------------------------------------------------------------
resource "aws_appautoscaling_policy" "scale_down_policy" {
  name               = "test-scale-down-policy" # 任意の名前
  service_namespace  = "ecs"
  resource_id        = "service/サービス名/クラスター名"
  scalable_dimension = "ecs:service:DesiredCount"
  step_scaling_policy_configuration {
    adjustment_type         = "ChangeInCapacity"
    cooldown                = # 任意の数値を記載
    metric_aggregation_type = # "Maximum" or "Average"
    step_adjustment {
      metric_interval_upper_bound = 0
      scaling_adjustment          = -1
    }
  }
  depends_on = [aws_appautoscaling_target.auto_scale_target]
}
```

| メソッド | 説明 |
| - | - |
| step_scaling_policy_configuration | テップスケーリングポリシーの設定を行うブロック。スケーリングの方法(どのようにリソースを増減させるか)を設定 |
| adjustment_type | スケーリング時にリソースの変更をどのように行うかを指定する。`ChangeInCapacity `はリソースの容量(ここではタスク数)を直接増減させる設定 |
| cooldown | スケーリングが行われた後、次のスケーリングアクションが実行されるまでの待機時間を設定 |
| metric_aggregation_type | メトリクスの集計方法を指定 |
| step_adjustment | スケーリングの調整を行う設定ブロックです。どの程度のリソースを増減させるかを指定 |
| metric_interval_lower_bound | スケーリングを行うためのメトリクスの下限値を指定。今回は、`CPU`が0以上の値でスケールアップするように設定 |
| metric_interval_upper_bound | スケーリングを行うためのメトリクスの上限値を指定。今回は、`CPU`が0以下の値でスケールダウンするように設定 |
| scaling_adjustment | スケーリング時にリソース(タスク)の数をどれだけ増減させるかを指定。スケールアップでは**1**、スケールダウンでは** -1** |

:::note warn
下記のメソッドは、[cloudwatch-alarmの設定内容](https://qiita.com/drafts/d4eef937477a527e9559/edit#cloudwatch-alarm%E3%81%AE%E8%A8%AD%E5%AE%9A%E5%86%85%E5%AE%B9)で説明している為、省略する
- service_namespace
- resource_id 
- scalable_dimension
- depends_on
:::

### 補足
<details><summary>metric_aggregation_typeの設定</summary>

- `metric_aggregation_type`の選択は、スケーリングの目的やメトリクスの特性によって決まる

| 項目 | Maximum | Average |
| :-: | - | - |
| 用途 | 負荷のピーク時に迅速にスケールアップ/ダウンを実行する | 全体的な平均負荷に基づいて安定したスケーリングを行う |
| 適している状況 | タスクが瞬間的に高負荷を受けた際、即座にリソースを追加して対応するケース。例えば、CPU使用率が急激に上昇した場合、迅速にリソースを拡張して負荷を分散させる際に有効 | 複数タスクの平均負荷をもとにリソースを調整する。全体の負荷が徐々に増加する際、急激なスケールアップを避け、スムーズに対応するのに最適 |
| 例 | 特定のタスクで非常に高いCPU負荷が発生した際に、即座にスケールアップして対応する | 短期間の負荷ピークに反応せず、全体の平均使用率が高くなってきた時にスケールアップし、低下した際にスケールダウンする |


**選択の仕方**
- `Maximum` ; 瞬間的な負荷に対して対応したい場合に選択する
- `Average` : 平均的な負荷に基づいて安定したスケーリングを行いたい場合に選択する


</details>

## 参考資料
### Terraform
- [[HashiCorp]ECS Service Autoscaling[aws_appautoscaling_policy]](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/appautoscaling_policy#ecs-service-autoscaling)

### 構築時の参考資料
- [[AWS公式] Amazon ECS CloudWatch メトリクス](https://docs.aws.amazon.com/ja_jp/AmazonECS/latest/developerguide/available-metrics.html)
- [[AWS公式] CloudWatch 統計定義](https://docs.aws.amazon.com/ja_jp/AmazonCloudWatch/latest/monitoring/Statistics-definitions.html)
- [[AWS公式] Amazon EC2 Auto Scaling のスケーリングのクールダウン](https://docs.aws.amazon.com/ja_jp/autoscaling/ec2/userguide/ec2-auto-scaling-scaling-cooldowns.html)
- [Amazon EC2 Auto Scaling のステップおよびシンプルなスケーリングポリシー](https://docs.aws.amazon.com/autoscaling/ec2/userguide/as-scaling-simple-step.html#as-scaling-adjustment)
- [[Terraform]ECS Fargateのオートスケーリング設定](https://zenn.dev/spacemarket/articles/4eb067a734cacd)



# CPU使用率の確認方法
ClowdWatchから確認可能(手順は下記に記載)

1. `CloudWatch`の画面を開く
2. 左側のナビゲーションパネルで**メトリクス**を選択する
3. **すべてのメトリクス**タブを選択する
4. `ECS`を展開し、`ClusterName`を選択する
5. クラスター名を選択する
6. `CPUUtilization`メトリクスを選択する

メモリの使用率等も確認可能
## 参考資料
- [CloudWatch メトリクスで ECS のCPU使用率が実際の値より低く見えるときの対処方法](https://dev.classmethod.jp/articles/tsnote-why-do-the-cpu-utilization-metrics-in-ecs-appear-lower-than-they-actually-are/)


# まとめ
今回は、`Terraform`を使用して、`ECS`に対して`Auto Scaling`を組み込みました。`Auto Scaling`は、複数の実装方法が存在する為、とても勉強になりました。今回学んだ事を活かして、要件に合わせた`Auto Scaling`を組み込んでいきたいと思います。