「え？！バッチプログラムが動かない？」CloudFormationテンプレートを使用したECS失敗タスクの再実行

## プロローグ
ぼく  「よしバッチプログラム完成、このバッチ処理は **最重要** だから絶対に動かしたいな。」

ぼく 「もし動かなかったら…。。。」

ぼく 「…	(´・ω・｀)」

ぼく 「今は考えないでおこう…、ECSの[スケジュールされたタスク](https://docs.aws.amazon.com/ja_jp/AmazonECS/latest/userguide/scheduled_tasks.html)に登録して…ﾎﾟﾁﾎﾟﾁ」
![スクリーンショット 2023-06-25 20.01.53.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/759541/25a742d3-dfc6-20f6-0906-1ba491c7ef93.png)

ぼく 「完了！」

ぼく 「仮にプログラムが失敗しても、プログラムのエラーやタスクの失敗は監視しているし、大丈夫なはず…！」

**数ヶ月後**

上司 「おい！バッチプログラム動いないみたいだぞ！」

ぼく 「え、何ですって！？エラーやタスクの失敗の通知は来ていないんですが！」

上司 「そんなことはこっちではわからん！何とかしろ！」

ぼく 「は、はい！とりあえず本日分は手動で実行しておきました！」」

**終業後**

ぼく 「なにが原因で動いてなかったんだろう…。とりあえず、[CloudTrail](https://aws.amazon.com/jp/cloudtrail/)を見てみるか・・・。ECSのタスクは[RunTask](https://docs.aws.amazon.com/AmazonECS/latest/APIReference/API_RunTask.html) APIを介して起動されるからEvent名 RunTaskで検索して…。」
![スクリーンショット 2023-06-25 20.12.46.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/759541/224cc08f-f31e-41b9-e325-2a8bb8234c30.png)

ぼく 「あったあった。ええと、失敗の原因は…」

```json
"failures": [
    {
        "arn": "arn:aws:ecs:ap-northeast-1:xxx:container-instance/xxx",
        "reason": "AGENT"
    }
]
```

ぼく 「reason…、AGENT？」


## 目的

ECS on EC2環境で、タスクの起動が AGENT エラーで失敗することがあります。本稿では、その原因と対策について解説します。

## そもそもの原因

コンテナインスタンスとして稼働しているEC2では、ECSとの通信を行うためにECS Agentが常に起動しています。しかしこのAgentが数時間に1度更新されることがあり、そのタイミングとECSタスク起動が重なってしまうと実行がエラーになります。
そもそもEC2の乗り込むことが出来ていないため、プログラムのエラーでもなく、ECSタスクの異常終了でもないわけです。

ではどのように対策すればいいのでしょうか？

## 対策

まず１つ、StepFunctionsを挟むことが考えられます。
例えば、[[AWS] EventBridge Rules による ECS Scheduled Task はエラー時リトライできない](https://engineering.mobalab.net/2022/05/18/use-aws-step-functions-to-retry-ecs-scheduled-task/)では、EventBridge Rules とECSの間にStepFunctionsを挟むことでリトライを可能にしています。
この方法は素晴らしく、StepFunctionsによるRunTaskであればリトライ可能ですし、変更点も少ないです。（EventBridge Rulesでもリトライは可能ですが、同記事にあるように今回のケースは検知してくれないそうです。）

しかし、多くのタスクが実働してる場合にそれら全てを移行するには少々コストがかかってしまいます。

そのため、起動の失敗を検知し再実行できるインフラは実現できないかと考え以下のようなCloudFormation Templateを作成しました🎉

https://github.com/iisyos/ecs_rerun_cfn

![名称未設定ファイル-ページ1.drawio (3).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/759541/e8e7b46b-e134-2d5d-8feb-f78687f013e1.png)

（アーキテクチャ図右側のリソースを作成します。Repositoryには左側のリソースを作成するためのTerraformもあります。）

このテンプレートは、EventBridgeを利用してECSタスクの起動失敗を検出し、Step Functionsを使ってタスクを再実行します。

前述のインフラとは違い、既存のECSタスクをすべてこのテンプレートに移行する必要はありません。必要に応じて、タスクの起動が失敗した際に再実行を試みます。

クイック作成リンクはこちら💁‍♂️
https://console.aws.amazon.com/cloudformation/home?region=ap-northeast-1#/stacks/create/review?templateURL=https://s3.ap-northeast-1.amazonaws.com/cd-template-okamos/ecs_rerun_cfn/main.yml


### 詳細

アーキテクチャ図に示した通り、Templateは以下のリソースを作成します。

1. EventBridge (and rules)
2. Step Functions
    a. SNS Topic
    b. Lambda Function

（とそれに付随するIAM RoleとPolicy。）

#### EventBridge

まずは、失敗イベントを検知する必要があります。そのためのEventBridgeです。
具体的なルールはこんな感じです。

```json
{
  "detail-type": ["AWS API Call via CloudTrail"],
  "source": ["aws.ecs"],
  "detail": {
    "responseElements": {
      "failures": {
        "reason": [{
          "exists": true
        }]
      }
    },
    "requestParameters": {
      "startedBy": [{
        "anything-but": "AWS Step Functions"
      }]
    },
    "eventSource": ["ecs.amazonaws.com"],
    "eventName": ["RunTask"]
  }
}
```

簡単にこのルールの説明をします。
`anything-but`は否定であり、StepFunctionsがキックしたECSタスクのイベントは含めないことを意味しています。なぜなら無限ループしてしまう恐れがあるからです。
実はテスト中に無限ループしてしまって、大慌てでリソースを削除しました。。。
![スクリーンショット 2023-06-25 0.42.14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/759541/b7d3cc87-9778-d0af-b9a8-8e4409bbe25d.png)

`exists`は存在の条件で、今回で言うとfailuresが空配列ではない場合に起動するようにしています。

詳しくは公式を参照。
[Amazon EventBridge のイベントパターン](https://docs.aws.amazon.com/ja_jp/eventbridge/latest/userguide/eb-event-patterns.html)

#### Step Functions

![stepfunctions_graph (1).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/759541/79e42cd1-e737-8ca7-776c-6e36e321cd2a.png)

SNSはLambdaと連携し、Slack通知のために使用しています。
`Check Reason`で失敗の原因を確認し、`AGENT`であれば再実行するようにしています。



## まとめ

バッチプログラムは多くのアプリケーションで重要な役割を果たしています。その実行が中断されると、結果として大きな問題を引き起こす可能性があります。この記事では、Amazon ECSでのタスク実行が `AGENT` エラーにより失敗した場合の対処法を提案しました。

その解決策として、ECSの起動失敗を検出し再実行するためのCloudFormationテンプレートを作成しました。EventBridgeを使用して失敗イベントを検知し、Step Functionsを利用してタスクを再起動するという方法を用いました。

また、この方法は既存のECSタスクをすべて移行する必要はなく、タスクの起動が失敗した場合にのみ再実行を試みるという外付けのリソースであるため、実装が楽です。

この記事が、皆さんがAmazon ECSでの信頼性の高いバッチプログラムを実現するための一助となれば幸いです。