CloudFormationで作成しようとしているECS ServiceでCapacity is unavailable at this time.と言われ続けいつまで経ってもECS Serviceの起動が完了しない場合の対処法

# TL; DR

Graviton2 FargateでFargate Spotを使おうとして失敗した後に、テンプレートファイルを元に戻した状態で再度スタック更新しようとすると発生します。



# 内容

AWSにおけるCloudFormationで、ECSサービスとタスク定義のリソースをGraviton2 Fargateにより作成していました。
その際に発生したエラーと解決策です。


## 問題

次のようなCloudFormationテンプレートをデプロイしていました。
内容は、ECSサービスとタスク定義をGraviron2 Fargateで起動する、というものです。

```yaml:元のtemplate.yaml
Resources:
  # 説明に必要な項目のみ抜粋

  ECSTaskDefinition:
    Type: AWS::ECS::TaskDefinition
    Properties:
      RuntimePlatform:
        CpuArchitecture: ARM64  # Graviton2であることを表す
      RequiresCompatibilities:
      - FARGATE  # Fargateであることを表す
      NetworkMode: awsvpc

  MinecraftECSService:
    Type: AWS::ECS::Service
    Properties:
      LaunchType: FARGATE  # Fargateであることを表す
      TaskDefinition: !Ref ECSTaskDefinition
```

その後、Fargateの料金対策として全コンテナにFargate Spotを利用してみようと、次のように書換えて**デプロイ処理を実行**しました[^deploy-method]。

[^deploy-method]: AWS-SAMを利用していたため、 `sam deploy` コマンドを実行したというのが正確なのですが、今回の問題とは直接関係ないため省略しています。

```diff_yaml:FARGATE_SPOTキャパシティーユニットを設定したtemplate.yaml
Resources:
  # 説明に必要な項目のみ抜粋

  ECSTaskDefinition:
    Type: AWS::ECS::TaskDefinition
    Properties:
      RuntimePlatform:
        CpuArchitecture: ARM64  # Graviton2であることを表す
      RequiresCompatibilities:
      - FARGATE  # Fargateであることを表す
      NetworkMode: awsvpc

  MinecraftECSService:
    Type: AWS::ECS::Service
    Properties:
      LaunchType: FARGATE  # Fargateであることを表す
      TaskDefinition: !Ref ECSTaskDefinition
      CapacityProviderStrategy: 
+     - CapacityProvider: FARGATE_SPOT
```

しかし、デプロイがなんか時間がかかるなと思って調査した結果、Graviton2 (ARM64) ではFargate Spotの利用はできないということが判明しました。

> Fargate スポットキャパシティープロバイダーは ARM64 アーキテクチャを使用した Linux タスクではサポートされていません。Fargate スポットは X86_64 アーキテクチャの Linux タスクのみサポートします。
>
> [AWS Fargate キャパシティープロバイダー - Amazon Elastic Container Service](https://docs.aws.amazon.com/ja_jp/AmazonECS/latest/developerguide/fargate-capacity-providers.html)

ということで、デプロイ処理を中断し、最初のファイルに戻して再度デプロイ処理を実行したのですが、ECS Serviceリソースが変更中となったまま、いつまで経ってもデプロイ処理が完了しませんでした。

ECS Serviceの方を見ると、次のようなエラーメッセージが出ていました。

> service ${SERVICE_NAME} was unable to place a task.
> Reason: Capacity is unavailable at this time.
> Please try again later or in a different availability zone.
> For more information, see the [Troubleshooting](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/service-event-messages.html) section of the Amazon ECS Developer Guide.

なお、リンク先のページを見ても、ドンピシャな解決策はありません。

エラー文を訳せば「デプロイ先リージョンのAZ上で**キャパシティー**が一時的に確保できない」と読めるため、Fargate用リソースが枯渇したのかな？と判断しました（そんなことあるのか？とも思いましたが）。
しかし、翌日も時間をずらしても、同じエラー文が出てくるだけでした。

Fargate Spot挑戦前のテンプレートファイルとの差分は無く、デプロイ処理の中断後はテンプレートファイルも元に戻っています。
また、タグなどリソースの中身には殆ど関係のない設定を追加してデプロイしようとしても、同様のエラー文で前に進めませんでした。


## 原因

AWSコンソール上でのECSのページを確認すると、なんとキャパシティーユニットには `FARGATE_SPOT` と書かれていました。
なんと、CloudFormationでは消していたはずの設定が残ったままでした。

CloudFormationでは、既定値の設定を上書きしてデプロイ処理を実行した後、元に戻しても既定値が反映されず、上書きされた値でデプロイ処理を実行しようとする挙動が時々見られます（Elastic Beanstalkのオプションとか......）。
しかし今回については、デプロイ処理が完了していないにも関わらず、既にECS側ではリソース設定更新が完了されたために、既定値 (FARGATE) が上書き (FARGATE_SPOT) されてしまい、その後のデプロイも上書きされた値でデプロイ処理を実行しようとしていたのでした。


## 解決策

次のようにファイルを変更しデプロイしました。

```diff_yaml:FARGATEキャパシティーユニットを明記したtemplate.yaml
Resources:
  # 説明に必要な項目のみ抜粋

  ECSTaskDefinition:
    Type: AWS::ECS::TaskDefinition
    Properties:
      RuntimePlatform:
        CpuArchitecture: ARM64  # Graviton2であることを表す
      RequiresCompatibilities:
      - FARGATE  # Fargateであることを表す
      NetworkMode: awsvpc

  MinecraftECSService:
    Type: AWS::ECS::Service
    Properties:
      LaunchType: FARGATE  # Fargateであることを表す
      TaskDefinition: !Ref ECSTaskDefinition
      CapacityProviderStrategy: 
+     - CapacityProvider: FARGATE
```

これにより、全コンテナをFargateでデプロイするように設定を上書きしました。
その後、ファイルを元に戻して再度デプロイしたら、無事にデプロイが完了しました。



# まとめ

エラーにおける**キャパシティー**というのは、キャパシティーユニットのことだったのかもしれません。
どちらにせよ、エラー文だけでは判断できませんでしたね。

設定を変更したと思ったら、AWSコンソールで設定値を確認しておくと幸せになれることがあるかもしれません。