GitHub Actions → Step Functions → ECS Run Task するのにハマったポイント
はじめに
マイグレーションのようなワンショットのタスクを実行するのに、Step Functions は非常に便利なのですが、GitHub Actions から実行するのに若干ハマりポイントがあったので記事にしてみます。
Step Functions 使う必要あります?
というツッコミが当然あると思いますが、直接、ECS Run Task しようとすると、サブネットIDやセキュリティグループIDなど複数のパラメータが必要となります。
GitHub Actions のシークレットにそれらを登録したくないので、いい方法はないかと試行錯誤していました。
ECSサービスを設定して、タスク数 1 で起動して、ECS Exec してたこともあるのですが、色々面倒なので、最近では、Step Functions を使うようにしています。
尚、下記のブログにも下記の記述があり、共感しました。
Step Functions から ECS Run Task を実行するのにハマったポイント
下記は、CloudFormation テンプレートの抜粋なのですが、ポイントが複数ありますので1つずつ解説していきます。
AWSTemplateFormatVersion: 2010-09-09
Resources:
StateMachineMigrate:
Type: AWS::StepFunctions::StateMachine
Properties:
StateMachineName: ${STATE_MACHINE_NAME}
RoleArn: ${STATE_MACHINE_ROLE_ARN}
Definition:
StartAt: Migrate
States:
Migrate:
Type: Task
Resource: arn:aws:states:::ecs:runTask.sync
Parameters:
Cluster: ${CLUSTER_ARN}
TaskDefinition: ${TASKDEF_ARN}
Overrides:
ContainerOverrides:
- Name: "migrate"
"Command.$": "States.Array('ls','-la')"
LaunchType: FARGATE
NetworkConfiguration:
AwsvpcConfiguration:
Subnets:
- ${SUBNETS}
SecurityGroups:
- ${SECURITY_GROUP}
Next: PrintLogs
PrintLogs:
Type: Task
Resource: arn:aws:states:::aws-sdk:cloudwatchlogs:filterLogEvents
Parameters:
LogGroupName: ${LOG_GROUP_NAME}
LogStreamNamePrefix: ${LOG_GROUP_NAME_PREFIX}
"StartTime.$": "$.StartedAt"
End: true
Step Functions から arn:aws:states:::ecs:runTask.sync するには、StepFunctionsGetEventsForECSTaskRule が必要
下記の公式ドキュメントを参考に権限を追加します。
- Effect: Allow
Action:
- events:PutTargets
- events:PutRule
- events:DescribeRule
Resource:
- !Sub "arn:aws:events:${AWS::Region}:${AWS::AccountId}:rule/StepFunctionsGetEventsForECSTaskRule"
タスク定義にはリビジョンが必要
タスク定義を更新するたびにリビジョンは上がっていくわけですが、それを呼び出す Step Functions も更新が必要になります。
Parameters にて外部からリビジョンを更新するようにするなど工夫が必要です。
AWS CLI であれば、以下のようにして最新のリビジョンを取得できます。
aws ecs describe-task-definition \
--task-definition $(taskdef) \
--query 'taskDefinition.revision' \
--no-cli-pager
これを利用して、Makefile で Step Functions を更新できるようにしておきます。
--parameter-overrides TaskdefRevision="$(revision)" がポイントですね。
# Makefile
.PHONY: deploy-step-functions
deploy-step-functions: cache-taskdef-revision
$(eval revision := $(shell cat $(cache_dir)/taskdef-revision.txt))
aws --profile "$(profile)" cloudformation deploy \
--template ./templates/step-functions.yml \
--stack-name $(stack_name) \
--parameter-overrides TaskdefRevision="$(revision)" \
--capabilities CAPABILITY_NAMED_IAM \
--no-fail-on-empty-changeset
cache-taskdef-revision:
mkdir -p $(cache_dir)
aws --profile "$(profile)" ecs describe-task-definition \
--task-definition $(taskdef) \
--query 'taskDefinition.revision' \
--no-cli-pager | tr -d '"' > $(cache_dir)/taskdef-revision.txt
タスクの実行結果は CloudWatch Logs を参照するしかない
色々試したのですが、arn:aws:states:::ecs:runTask.sync する限りは、実行結果は、ログを確認する以外になさそうです。
なので、前述のとおり、arn:aws:states:::aws-sdk:cloudwatchlogs:filterLogEvents でタスク定義で指定しているロググループを検索・出力します。
その際、"StartTime.$": "$.StartedAt" と開始時刻を指定しておくと、直前の Step Functions が開始して以降のログが取得できます。
PrintLogs:
Type: Task
Resource: arn:aws:states:::aws-sdk:cloudwatchlogs:filterLogEvents
Parameters:
LogGroupName: ${LOG_GROUP_NAME}
LogStreamNamePrefix: ${LOG_GROUP_NAME_PREFIX}
"StartTime.$": "$.StartedAt"
End: true
出力が256Kを超えると aws-sdk:cloudwatchlogs' returned a result with a size exceeding the maximum number of bytes service limit になる
これは、Step Functions の Payload サイズにデフォルトで 256K の上限が設けられているのが原因っぽいです。
FilterPattern を設定することで、ログのサイズを絞ると良いかと思います。
GitHub Actions から Step Functions を実行するのにハマったポイント
Step Functions 自体をデプロイしないといけない
アプリのデプロイのタイミングで、タスク定義が更新されるとすると、それを利用する Step Functions も更新しないといけません。
デプロイ自体は前述の make コマンドを使用して、以下のようにすると良いです。
- name: Deploy Step Functions
shell: bash
run: |
make deploy-step-functions
aws stepfunctions start-execution は非同期なので、完了を待たないといけない / aws stepfunctions start-sync-execution は Express ワークフローのみ
Step Functions を実行しようと、AWS CLI を使って、aws stepfunctions start-execution とやるわけですが、非同期なので実行結果を待たずに正常終了してしまいます。
start-execution が非同期だからといって、start-sync-execution を選択しようとしますが、標準ワークフローでは使えない、つまり、Express ワークフローのみ使用可能ということに気付きます。
標準ワークフロー と Express ワークフロー の違いについては、下記のリンクを参照ください。
Express ワークフロー は 5分以内という制約がありますが、これで十分な場合は、Express ワークフロー に変更してしまうのもいいかもしれません。
5分以上かかる可能性がある場合は、以下のように完了を待つようにすると良いです。
// .github/workflows/deploy.yml
- name: Execute Step Functions
shell: bash
run: |
EXECUTION_ARN=$(aws stepfunctions start-execution \
--state-machine-arn ${STATE_MACHINE} | jq '.executionArn
echo $EXECUTION_ARN
STATUS=RUNNING
OUTPUT=""
while [ "${STATUS}" == "RUNNING" ]
do
sleep 5
OUTPUT=$(aws stepfunctions describe-execution \
--execution-arn ${EXECUTION_ARN} \
--no-cli-pager)
STATUS=$(echo $OUTPUT | jq '.status' | tr -d '"')
done
echo $OUTPUT | jq --raw-output '.output' | jq '.Events[]
おわりに
ECS Run Task して結果を確認したいだけなのに、こんなにハマることあるの、という感じだったので、備忘録的に記事にしてみました。
参考になれば幸いです。
ではでは。