問題点
-
ECS Scheduled Tasks の実態である EventBridge では、スケジュールされた期間に対して同じルールを複数回トリガーする可能性がある
-
弊社ではユーザーへのメール送信など複数回実行されては困るケースがあるので、これをなんとかしたい
-
対策として DynamoDB を使って実行中タスクのロックを実装するという手段があるが、EventBridge ではタスク実行中だけでなく終了後にも起動されるパターンがあるので、そのケースに対応できない
-
そもそもこの手法は運用フレンドリーなのか?という疑問
対応策
-
2023/11/13 から CLIのECS RunTaskでタスク起動の冪等性がサポート可能となったのでこれを使う
-
具体的には、以下のようにECS RunTask 実行時のclient-tokenパラメータでトークンを仕込むことにより、タスクロックの仕組みを自前で用意しなくても、同じtokenが指定されている場合は冪等性を担保した状態でタスクが処理される
aws ecs run-task \ --cluster MyCluster \ --task-definition MyTaskDefinition:2 \ --client-token 550e8400-e29b-41d4-a716-446655440000
-
正常に完了した後で、同じクライアントトークンと同じリクエストパラメータを使用して API リクエストを再試行すると、元のリクエストの結果が返される
-
クライアント・トークンの TTL は以下の小さい方なので、タスク終了後に再実行されるケースにも対応できる
- 24 時間
- タスクが STOPPED になってから+1 時間
-
ただし、CloudFormation のプロパティで直接的にこれを定義する方法はないので、ワークフローの中で aws ecs run-task コマンドを実行する形である必要がある
-
また、1時間以内の間隔でタスクを実行したい場合でも、tokenのTTLによって実行が阻害されてしまいます。なのでその場合だけtokenの指定を外して重複実行の可能性を許容する、もしくは違うジョブスケジューリングツール(Jenkinsやdigdagなど)をEC2にホスティングして動かすことになると思います
ワークフローの選択肢
-
EventBridge Scheduler で lambda 関数を実行し、その中で ECS RunTask を実行する。
- シンプルに実装可能だが、15分の実行時間上限がある
- lambda内でECS RunTaskを実行した場合、タスクの起動を完了とみなす(起動したタスクの完了まで待つわけではない)ので、ECS RunTaskだけ実行したい場合はこちらの方法でこと足りる
-
EventBridge Scheduler で AWS Batch を起動し、ECS RunTask を実行する。
- 15分の実行時間上限を取り払いたい場合はこちら
-
EventBridge Scheduler で StepFunctions を起動し、ECS RunTask を起動する。
- より複雑なワークフローを定義可能
-
docker コンテナで cron を動かす
- ある意味わかりやすい
- 理論的には可能だが、1 コンテナ 1 プロセスという docker のベストプラクティスと乖離するため極力避けたい
弊社の場合は複雑なワークフローは不要かつ、ジョブの実行時間は15分を超えることはないので①の選択肢としました
また、定期実行の選択肢としてEventBridge RuleとEventBridge Schedulerがありますが、EventBridge Schedulerの方がタイムゾーンの指定やタイム ウィンドウ スケジュールなど、よりスケジューリングに最適化されているのでこちらを使った方が便利です
参考
最終的な構成
①CircleCIからaws cloudformation deploy
を実行する
②CloudFormationがEventBridge Schedulerの定義を最新化する
③EventBridge Schedulerで定義されているスケジュールでLambda Functionが実行される
④Lambda FunctionでECS RunTaskを実行し、ECSタスクが実行される
一難去ってまた一難
上記の方法で1時間以上の間隔のジョブについては対応できるようになった。
しかし、1時間以内の間隔の場合、トークンによって重複実行と見なされてしまうため、トークンを指定せず重複実行を許容しなくてはならない点に注意。
これが気になるなら、もはやEventBridgeからdigdagのようなワークフローエンジンに移行を検討した方が良さそう。(筆者は1時間以内のジョブに関してはジョブサーバーで運用する方針としました)