ECSのタスク配置戦略でコストを最適化する

# はじめに
ECS on EC2 の構成でECSタスクのオートスケーリングの導入を行いました。
もともとはLambda等を使った自前のオートスケーリングの構成だったのですが、マネージドなオートスケーリングへ移行しました。

スケジュールでスケーリングする設定等は上手く動いたのですが、EC2インスタンスのコストが移行前より増加傾向となってしまいました。その増加したコストを最適化するためにやったことをTipsとしてまとめます。

ある程度 ECS について理解がある前提の記事となっていることをご了承ください。
まず、移行に際して導入したサービスについて、その後にコスト最適化のためのTipsという流れで記載します。

# マネージドなオートスケーリング
ECS on EC2 を使用している環境では結構当たり前のことかもしれないですが、今まではFargate環境に触れることが多くEC2のスケーリングを意識する機会が少なかったので勉強になりました。

## キャパシティープロパイダー
キャパシティープロパイダーは EC2(AutoScallingグループ)とFargateでそれぞれ対応しており、プロパイダーは複数設定することが可能です。
しかし今回は1つのEC2のAutoScallingグループをプロパイダーとする構成としています。

AutoScallingグループのキャパシティープロパイダーはAutoScallingグループで設定された最大インスタンス数までECSタスクに合わせてEC2インスタンスをスケーリングすることが可能です。

例えば、以下のような動作が可能になります。

- EC2インスタンスが1台稼働しており、AutoScallingグループの最大インスタンス数は2
- 起動しているインスタンス上にECSサービス(ECSタスク)が1つ実行されている
- ECSのデプロイ（ECSローリングアップデート）が実行されたが、起動しているEC2インスタンスには2つ目のタスクを実行できるほどスペック(メモリ等)に余裕がない
- EC2インスタンスのAutoScallingグループが上記を検知して2台目のインスタンスを起動する
- 正常にデプロイできる
- デプロイ終了後、古いタスクは終了し、1台のインスタンスはタスクが実行されていないアイドル状態となる
- アイドル状態となったEC2インスタンスを終了させる

このように、自動で必要なECSタスク数にEC2インスタンスの台数を変動してくれる便利な機能です。
他にも後述のようにサービスの自動スケーリングと組み合わせることで、様々なケースでEC2インスタンスをスケーリングしてくれます。

このサービスがリリースされるまではスケーリングのターゲットとなるメトリクスを監視し、監視からEC2インスタンスをスケーリングさせ、そのEC2インスタンスのスケーリングのイベントをトリガーにLambda等でECSサービス(タスク)を更新する必要がありました。
このような運用と比較するとかなりAWSマネージドなものとなり、運用負荷がだいぶ抑えられるかと思います。

### Fargateでよくない？
実際、このようなインスタンスの運用から脱却する意味でもFargateの利用は有効だと思います。しかし、以下のような理由でEC2インスタンスの利用を継続することもあります。

- Savings Plan, Reserved Instanceを購入している
    - これらを活用することでコスト自体はFargateと比較すると安価になる
- I/Oなど厳しい要件があり、EC2インスタンスをチューニングする必要がある

### Appendix
- [EC2 起動タイプ用の Amazon ECS キャパシティープロバイダー](https://docs.aws.amazon.com/ja_jp/AmazonECS/latest/developerguide/asg-capacity-providers.html)
- [Capacity Providerとは？ECSの次世代スケーリング戦略を解説する #reinvent #cmregrowth](https://dev.classmethod.jp/articles/regrwoth-capacity-provider/)


## ECSサービスの自動スケーリング
ECSサービスのタスクを自動的に増減してくれる機能です。これは[Application Auto Scalling](https://docs.aws.amazon.com/ja_jp/autoscaling/application/userguide/what-is-application-auto-scaling.html)というサービスに含まれます。

特定のメトリクスやスケジュールに応じて自動的にECSタスクのスケーリングを実施してくれます。特定のメトリクスはALBへのリクエスト数や、ECSサービスのCPUとメモリの使用率などがあたります。
このスケーリングによってECSサービスの必要タスク数が変動すると、上述のキャパシティープロパイダーが検知してEC2インスタンスを増減してくれます。

### Appendix
- [Amazon ECS サービスを自動的にスケールする](https://docs.aws.amazon.com/ja_jp/AmazonECS/latest/developerguide/service-auto-scaling.html)

# コスト最適化のTips
## 起こったこと
上述のサービスの自動スケーリングを使用して、スケジュールでECSサービスのタスク数のスケーリングを実施しました。なお、このスケジュールによるスケーリングも移行前の自前の実装でも運用されていました。

移行後、移行前より稼働するインスタンス数が増えていることに気づきました。

このECSサービスは以下のようなスケジュールでタスク数を変動させています。
以下のような想定です。

1. 00:00になったら必要タスク数を増加する
    - キャパシティープロパイダーによってEC2インスタンスがスケーリングし、EC2インスタンスが追加
1. 01:00になったら必要タスク数をもとに戻す
    - 不要となったECSタスクが終了する
    - インスタンス数が元に戻る

しかし、インスタンス数が減少はしたものの、スケーリング前の台数に戻りませんでした。

以下のようなイメージです。
```
※EC2インスタンス1台に対して、サービスAのタスクは2台実行可能

サービスA: タスク数2(EC2インスタンス数1台)
↓
↓ 特定時刻で必要タスク数を5に変更
↓
サービスA: タスク数5(EC2インスタンス数3台)
↓
↓ 特定時刻で必要タスク数を2に戻す
↓
サービスA: タスク数2(EC2インスタンス数2台)
```

調査したところ、EC2インスタンスは1台で2タスク稼働できるにもかかわらず、2台で2タスク稼働するような形になっていました。
以下のようなイメージです。
```
※aタスクがスケーリング対象のタスク
※bタスクがスケーリング対象ではないが常時稼働のサービス

Aインスタンス: aタスク,aタスク,bタスク
↓
↓ 特定時刻で必要タスク数を5に変更
↓
Aインスタンス: aタスク,aタスク,bタスク
Bインスタンス: aタスク,aタスク
Cインスタンス: aタスク
↓
↓ 特定時刻で必要タスク数を2に戻す
↓
Aインスタンス: aタスク,bタスク
Bインスタンス: aタスク

※ タスク数変更前の Aインスタンス: aタスク,aタスク,bタスク が望ましい形となる
```

※補足
イメージのため1台のインスタンスで運用しているように見えますが、実際は複数インスタンスでさらに複数のECSタスクを実行しています。

## タスク配置戦略の見直し
調査の結果、ECSタスクの配置戦略の見直しが有効でした。
対象のECSサービスの配置戦略は `spread` となっていました。この戦略を設定しているとAZ間で分散することを優先してタスクを配置します。可用性を優先した戦略と言えます。

対して `binpack` という戦略にすると、未使用のCPUやメモリを最小にしようとします。スケーリングでタスク数が減少する際もEC2インスタンスのリソース量に基づいて判断されます。コスト最適化を優先した戦略と言えます。

配置戦略を `spead` から `binpack` に変更することで、意図したようにCPUやメモリを最大限利用するようなタスク配置とすることができます。

上記のイメージですと、最終的に
```
Aインスタンス: aタスク,bタスク
Bインスタンス: aタスク
```
ではなく、
```
Aインスタンス: aタスク,aタスク,bタスク
```
の状態になります。

### 可用性について
上記のように配置戦略を `binpack` にすると可用性が損なわれるのではないか、という懸念があると思います。
今回の構成は ECS on EC2 なのでEC2インスタンス上にECSタスクを実行します。したがって、まずEC2インスタンスをAZに分散して配置する必要があります。これはEC2 AutoScallingの設定で可能です。

ECSタスク配置戦略は複数設定可能（優先度はある）なので、 `binpack` -> `spread(instance事に分散)` といった設定にすれば、可用性も担保できるのではないかと考えます。

### Appendix
[戦略を使用して Amazon ECS タスク配置を定義する](https://docs.aws.amazon.com/ja_jp/AmazonECS/latest/developerguide/task-placement-strategies.html)

# おわりに
ECSタスク配置戦略を見直すことで、可用性を担保しつつコストも適切な構成とすることができました。
Fargateの運用コストが低くて良い、というのは周知の事実かと思います。
それでも様々な理由でEC2を利用する必要がある環境は少なからずあります。今回でECS on EC2の運用について知見が深まり、良い経験ができました。