Step FunctionsとECS fargateを使ってバッチ処理を構成した
ポートのSREを担当している @taiki.noda です。
少し前にJenkinsやEC2上で実行していたcronを別環境に移行しました。
バッチ処理の構成パターンも様々あると思いますが、今回は
EventBridge + Step Functionsステートマシン + ECS Fargate
で定期実行・タスクの実行環境を構築したので、解説していこうと思います。
選定理由
まず、移行するにあたってなぜこの構成を選定したのかについて。
以下のようないくつかの要件がありました。
[MUST]
- cron 的な構文で、定期実行が可能である
- タスクランナーとして利用できる
- 実行結果のログが cloudwatch に残る
- タスクの成功と失敗がわかる
- 実行時間に制約がない
- メモリや CPU 等のリソースを柔軟に設定できる
[SHOULD]
- 新規で作成する時に周辺リソースや追加の設定が多くない
- シェルスクリプト等から任意のコマンドを実行できる
- 失敗時に再実行できる
- ジョブの並列化が行える
- 既存のジョブを一覧で確認できる
[WANT]
- タスクの依存関係が定義できる
- Web 上の UI が用意されている
- 実行結果が slack に通知できる
ECS Fargateであれば、Lambdaのように実行時間の制約はありませんし、
Step Functionsを組み込むことで失敗時のリトライも可能です。
詳細は省きますが、おおよそ要件を満たすことができたのでこの構成を採用しました。
構成の解説
構成について解説していこうと思います。
大きく分けて以下の3つのフローがあります。
- タスクの単発実行、定時処理の登録
- 定時処理の実行
- 実行結果の通知
構成図
タスクの単発実行、定時処理の登録
それぞれコンポーネントで表すと、以下のような構成です。
- Engineer → GitHub Actions → Step Functionsステートマシン → ECS Fargate
- Engineer → GitHub Actions → EventBridge
GitHub Actionsからタスクの実行、定時処理を登録できるようにしています。
もちろんAWSのコンソール上からも実行できるのですが、アプリケーションエンジニアの実行し易さ、権限管理上の都合からこの構成にしています。
具体的にみていきます。
ステートマシンの定義は以下のようになっていて、実行したいコマンドをECS taskの実行時にinputでoverrideできるようにしています。
resource "aws_sfn_state_machine" "prod_batch" {
・
・
・
definition = <<EOF
{
"Comment": "A description of my State Machine",
"StartAt": "ECS RunTask",
"States": {
"ECS RunTask": {
・
・
・
"Overrides": {
"ContainerOverrides": [
{
"Name": "rails",
"Command.$": "$.commands"
}
]
}
・
・
・
冪等性が考慮されたバッチ処理であれば、以下のようにRetryを定義に入れることで、失敗時に再実行してくれるようになります。
"Retry": [
{
"ErrorEquals": [
"States.ALL"
],
"BackoffRate": 1,
"IntervalSeconds": 10,
"MaxAttempts": 2
}
],
ステートマシン定義でワークフローを定義すると、AWSコンソール上から可視化することができます。
タスクの単発実行
- name: Execute Command
env:
ENVIRONMENT: ${{ github.event.inputs.environment }}
NAME: ${{ github.event.inputs.name }}
COMMAND: ${{ github.event.inputs.command }}
run: |
./run_task.sh "$ENVIRONMENT" "$NAME" "$COMMAND"
working-directory: scripts/batch
aws stepfunctions start-execution \
--state-machine-arn "$STATE_MACHINE_ARN" \
--name "$NAME" \
--input "$INPUT_JSON" \
・
・
workflow上で、shellscriptを実行します。
AWS CLIを叩いて、Step Functionsに実行したいコマンドを渡しています。
定時処理の登録
- name: Add Event and Target to EventBridge
env:
ENVIRONMENT: ${{ github.event.inputs.environment }}
NAME: ${{ github.event.inputs.name }}
SCHEDULE: ${{ github.event.inputs.schedule }}
COMMAND: ${{ github.event.inputs.command }}
run: |
./add_scheduled_task.sh "$ENVIRONMENT" "$NAME" "$SCHEDULE" "$COMMAND"
working-directory: scripts/batch
aws events put-targets \
--rule "$RULE_NAME" \
--targets "Id"="$TARGET_NAME","Arn"="$STATE_MACHINE_ARN","Input"="$INPUT_JSON","RoleArn"="$EVENTBRIDGE_ROLE_ARN"
こちらも同様にAWS CLIからEventBridgeルールを追加できるようになっています。
GitHub Actions上からは以下のように、情報を入力して実行できるような形となっています。
定時処理の実行
- EventBridge → Step Functionsステートマシン → ECS Fargate
登録されたEventBridgeルールは、スケジュールでStep Functionsステートマシンを実行し、
実行時にJSONでコマンドを渡すようにしています。
{
"commands": ["bash", "-c", "bundle exec rake xxx"]
}
ここのEventBridgeルールは追加したりdisabledにしたりすることが多いため、terraformとの相性が悪いと考え、管理外にしています。
実行結果の通知
- EventBridge → SNS → ChatBot → Slack
Step Functionsステートマシンの実行ステータスの変化をトリガーに、SNSでChatBotに知らせてSlackに通知を送るようにしています。
resource "aws_cloudwatch_event_rule" "prod_batch_statemachine_succeed_rule" {
・
・
event_pattern = <<EOF
{
"source": ["aws.states"],
"detail-type": ["Step Functions Execution Status Change"],
"detail": {
"status": ["SUCCEEDED"],
"stateMachineArn": ["${aws_sfn_state_machine.prod_batch.arn}"]
}
}
EOF
}
resource "aws_cloudwatch_event_rule" "prod_batch_statemachine_failure_rule" {
・
・
event_pattern = <<EOF
{
"source": ["aws.states"],
"detail-type": ["Step Functions Execution Status Change"],
"detail": {
"status": ["FAILED", "TIMED_OUT", "ABORTED"],
"stateMachineArn": ["${aws_sfn_state_machine.prod_batch.arn}"]
}
}
EOF
}
成功時と失敗時でルールを別々で作成して、別々のチャンネルに通知をするようにしています。
終わりに
参考になれば幸いです。
Discussion