【自分用】【備忘録】AWS Step Functions+ECSタスクのワークフロー
【備忘録】
Step functionsステートマシンでのECS(Fargate)タスク実行について
並列・直列実行ワークフロー定義を作成した。
※学習内容を備忘録として残す
■Step Functions ステートマシーン定義
直列実行定義(ECSタスク×2) 警告判定
{
"StartAt": "ECS_TaskA",
"States": {
"ECS_TaskA": {
"Type": "Task",
"Resource": "arn:aws:states:::ecs:runTask.sync",
"Parameters": {
"LaunchType": "FARGATE",
"Cluster": "",
"TaskDefinition": "",
"NetworkConfiguration": {
"AwsvpcConfiguration": {
"Subnets": [
"subnet-020bfb42a62070fd0",
"subnet-0f76b85fd4ec253be"
],
"SecurityGroups": [
"sg-0a952161376b08a81"
],
"AssignPublicIp": "ENABLED"
}
}
},
"Catch": [
{
"ErrorEquals": [
"States.ALL"
],
"ResultPath": "$.error",
"Next": "警告判定(A)"
}
],
"Next": "ECS_TaskB"
},
"警告判定(A)": {
"Type": "Choice",
"Choices": [
{
"Variable": "$.error.Cause",
"StringMatches": "*\"ExitCode\":9*",
"Next": "ECS_TaskB"
}
],
"Default": "異常終了"
},
"ECS_TaskB": {
"Type": "Task",
"Resource": "arn:aws:states:::ecs:runTask.sync",
"Parameters": {
"LaunchType": "FARGATE",
"Cluster": "",
"TaskDefinition": "",
"NetworkConfiguration": {
"AwsvpcConfiguration": {
"Subnets": [
"subnet-020bfb42a62070fd0",
"subnet-0f76b85fd4ec253be"
],
"SecurityGroups": [
"sg-0a952161376b08a81"
],
"AssignPublicIp": "ENABLED"
}
}
},
"Catch": [
{
"ErrorEquals": [
"States.ALL"
],
"Next": "警告判定(B)",
"ResultPath": "$.error"
}
],
"End": true
},
"警告判定(B)": {
"Type": "Choice",
"Choices": [
{
"Variable": "$.error.Cause",
"StringMatches": "*\"ExitCode\":9*",
"Next": "警告判定(ExitCode:9)"
}
],
"Default": "異常終了"
},
"警告判定(ExitCode:9)": {
"Type": "Succeed"
},
"異常終了": {
"Type": "Fail",
"Error": "UnexpectedFailure",
"Cause": "ExitCode が 0, 9 以外の異常終了"
}
}
}
ジョブスキップ
{
"StartAt": "ECS_Task1",
"States": {
"ECS_Task1": {
"Type": "Task",
"Resource": "arn:aws:states:::ecs:runTask.sync",
"Parameters": {
"LaunchType": "FARGATE",
"Cluster": "",
"TaskDefinition": "",
"NetworkConfiguration": {
"AwsvpcConfiguration": {
"Subnets": [
"subnet-020bfb42a62070fd0",
"subnet-0f76b85fd4ec253be"
],
"SecurityGroups": [
"sg-0a952161376b08a81"
],
"AssignPublicIp": "ENABLED"
}
}
},
"Next": "Choice"
},
"Choice": {
"Type": "Choice",
"Choices": [
{
"Variable": "$.skipJob2",
"BooleanEquals": true,
"Next": "ECS_RunTask3"
},
{
"Variable": "$.skipJob2",
"BooleanEquals": false,
"Next": "ECS_Task2"
}
]
},
"ECS_Task2": {
"Type": "Task",
"Resource": "arn:aws:states:::ecs:runTask.sync",
"Parameters": {
"LaunchType": "FARGATE",
"Cluster": "",
"TaskDefinition": "",
"NetworkConfiguration": {
"AwsvpcConfiguration": {
"Subnets": [
"subnet-020bfb42a62070fd0",
"subnet-0f76b85fd4ec253be"
],
"SecurityGroups": [
"sg-0a952161376b08a81"
],
"AssignPublicIp": "ENABLED"
}
}
},
"Next": "ECS_RunTask4"
},
"ECS_RunTask3": {
"Type": "Task",
"Resource": "arn:aws:states:::ecs:runTask",
"Parameters": {
"LaunchType": "FARGATE",
"Cluster": "arn:aws:ecs:REGION:ACCOUNT_ID:cluster/MyECSCluster",
"TaskDefinition": "arn:aws:ecs:REGION:ACCOUNT_ID:task-definition/MyTaskDefinition:1"
},
"Next": "ECS_RunTask4"
},
"ECS_RunTask4": {
"Type": "Task",
"Resource": "arn:aws:states:::ecs:runTask",
"Parameters": {
"LaunchType": "FARGATE",
"Cluster": "arn:aws:ecs:REGION:ACCOUNT_ID:cluster/MyECSCluster",
"TaskDefinition": "arn:aws:ecs:REGION:ACCOUNT_ID:task-definition/MyTaskDefinition:1"
},
"End": true
}
}
}
直列実行定義(ECSタスク×2) 警告判定 CFn
AWSTemplateFormatVersion: '2010-09-09'
Resources:
StateMachinec7bf56b8:
Type: AWS::StepFunctions::StateMachine
Properties:
Definition:
StartAt: ECS_TaskA
States:
ECS_TaskA:
Type: Task
Resource: arn:aws:states:::ecs:runTask.sync
Parameters:
LaunchType: FARGATE
Cluster: [クラスターARN]
TaskDefinition: [タスク定義ARN]
NetworkConfiguration:
AwsvpcConfiguration:
Subnets:
- subnet-020bfb42a62070fd0
- subnet-0f76b85fd4ec253be
SecurityGroups:
- sg-0a952161376b08a81
AssignPublicIp: ENABLED
Catch:
- ErrorEquals:
- States.ALL
ResultPath: $.error
Next: 警告判定(A)
Next: ECS_TaskB
警告判定(A):
Type: Choice
Choices:
- Variable: $.error.Cause
StringMatches: '*"ExitCode":9*'
Next: ECS_TaskB
Default: 異常終了
ECS_TaskB:
Type: Task
Resource: arn:aws:states:::ecs:runTask.sync
Parameters:
LaunchType: FARGATE
Cluster: [クラスターARN]
TaskDefinition: [タスク定義ARN]
NetworkConfiguration:
AwsvpcConfiguration:
Subnets:
- subnet-020bfb42a62070fd0
- subnet-0f76b85fd4ec253be
SecurityGroups:
- sg-0a952161376b08a81
AssignPublicIp: ENABLED
Catch:
- ErrorEquals:
- States.ALL
ResultPath: $.error
Next: 警告判定(B)
End: true
警告判定(B):
Type: Choice
Choices:
- Variable: $.error.Cause
StringMatches: '*"ExitCode":9*'
Next: 警告判定(ExitCode:9)
Default: 異常終了
警告判定(ExitCode:9):
Type: Succeed
異常終了:
Type: Fail
Error: UnexpectedFailure
Cause: ExitCode が 0, 9 以外の異常終了
RoleArn: [IAMのARN]
StateMachineName: StateMachinec7bf56b8
StateMachineType: STANDARD
EncryptionConfiguration:
Type: AWS_OWNED_KEY
直列実行警告(ECSタスク×2) CFn定義詳細
プロパティ名 | 項目説明 | 設定根拠・意味 |
---|---|---|
StateMachineName |
ステートマシンの名前 | コンソールやCLI上で識別する名前として使用されます。 |
StateMachineType |
ステートマシンの種別(STANDARD or EXPRESS) |
STANDARD は長時間実行・高信頼、EXPRESS は高頻度/短時間向け。今回は標準型。 |
RoleArn |
ステートマシンが AWS サービスを操作するための IAM ロール | ECS タスクの実行、ログ出力などを許可するために必要です。 |
EncryptionConfiguration.Type |
実行履歴の暗号化方式 |
AWS_OWNED_KEY は AWS が自動管理するキーで暗号化します(デフォルト)。 |
♦︎定義内の各ステート(状態)
プロパティ名 | 項目説明 | 設定根拠・意味 |
---|---|---|
StartAt |
ステートマシンの最初のステート名 | 実行が ECS_TaskA から始まることを指定します。 |
ECS_TaskA.Type / ECS_TaskB.Type
|
タスク実行の型 |
Task 型で ECS タスクを同期実行(runTask.sync )します。 |
Resource |
実行リソースのARN |
arn:aws:states:::ecs:runTask.sync は Step Functions から ECS タスクを同期実行するリソース指定です。 |
Parameters.LaunchType |
実行タイプ |
FARGATE を指定することで、Fargateでの実行になります。 |
Parameters.Cluster |
実行する ECS クラスターの ARN | ECS タスクがどのクラスターで動くかを指定します。 |
Parameters.TaskDefinition |
実行する ECS タスク定義 | 実行されるアプリケーション(Dockerコンテナ構成)を定義したリソースです。 |
NetworkConfiguration.AwsvpcConfiguration |
ネットワーク設定(VPC) | 実行環境のサブネットやセキュリティグループを指定します。 |
Catch |
エラー発生時の処理先ステート |
States.ALL によりすべての例外をキャッチし、次の判断(Choice)に進めます。 |
警告判定(A/B).Type |
条件分岐ステート | エラー内容に "ExitCode":9 が含まれているかを判定します。 |
警告判定(ExitCode:9).Type |
警告とみなして正常終了扱いにするステート |
Succeed を返して全体の処理を成功として終了させます。 |
異常終了.Type |
異常終了を明示するステート |
Fail 型は明示的に失敗とするため、モニタリングや通知に活用できます。 |
<注記>
・StringMatches: '"ExitCode":9' の書き方は文字列として "ExitCode":9 が含まれているかを見るパターンマッチです。
・ResultPath: $.error によりエラー情報が $.error に格納され、Choice ステートで参照されます。
直列実行定義
{
"Comment": "ECSタスク実行結果に基づいて異常終了原因を明示する構成 (ExitCode をエラー判断に活用)",
"StartAt": "【先行】ECS RunTask ①",
"States": {
"【先行】ECS RunTask ①": {
"Type": "Task",
"Comment": "最初のECSタスク(正常系)を同期実行する",
"Resource": "arn:aws:states:::ecs:runTask.sync",
"Parameters": {
"LaunchType": "FARGATE",
"Cluster": "arn:aws:ecs:ap-northeast-1:{アカウントID}:cluster/2413882_test",
"TaskDefinition": "arn:aws:ecs:ap-northeast-1:{アカウントID}:task-definition/2413882_Helloworld:7",
"NetworkConfiguration": {
"AwsvpcConfiguration": {
"Subnets": [
"subnet-020bfb42a62070fd0",
"subnet-0f76b85fd4ec253be"
],
"SecurityGroups": [
"sg-0a952161376b08a81"
],
"AssignPublicIp": "ENABLED"
}
}
},
"Retry": [
{
"ErrorEquals": [
"States.ALL"
],
"IntervalSeconds": 2,
"MaxAttempts": 1,
"BackoffRate": 1
}
],
"Catch": [
{
"ErrorEquals": [
"States.ALL"
],
"ResultPath": "$.error",
"Next": "異常終了"
}
],
"Next": "【先行】ECS RunTask ②"
},
"【先行】ECS RunTask ②": {
"Type": "Task",
"Comment": "2つ目のECSタスク(正常/異常含む)を同期実行する",
"Resource": "arn:aws:states:::ecs:runTask.sync",
"Parameters": {
"LaunchType": "FARGATE",
"Cluster": "arn:aws:ecs:ap-northeast-1:{アカウントID}:cluster/2413882_test",
"TaskDefinition": "arn:aws:ecs:ap-northeast-1:{アカウントID}:task-definition/2413882_Helloworld:6",
"NetworkConfiguration": {
"AwsvpcConfiguration": {
"Subnets": [
"subnet-020bfb42a62070fd0",
"subnet-0f76b85fd4ec253be"
],
"SecurityGroups": [
"sg-0a952161376b08a81"
],
"AssignPublicIp": "ENABLED"
}
}
},
"Retry": [
{
"ErrorEquals": [
"States.ALL"
],
"IntervalSeconds": 2,
"MaxAttempts": 1,
"BackoffRate": 1
}
],
"Catch": [
{
"ErrorEquals": [
"States.ALL"
],
"ResultPath": "$.error",
"Next": "異常終了"
}
],
"Next": "成功"
},
"成功": {
"Type": "Succeed",
"Comment": "すべての処理が正常に終了した"
},
"異常終了": {
"Type": "Fail",
"Comment": "いずれかのECSタスクが異常終了した",
"Cause": "ECSタスクが異常終了しました",
"Error": "ECS.Fargate.ExitCode"
}
}
}
Overridesを用いてECSタスクにデータを渡す
{
"StartAt": "【先行】ECS RunTask ①",
"States": {
"【後行】ECS RunTask ①": {
"Type": "Task",
"Resource": "arn:aws:states:::ecs:runTask.sync",
"Parameters": {
"LaunchType": "FARGATE",
"Cluster": "[クラスターARN]",
"TaskDefinition": "[タスク定義ARN]",
"NetworkConfiguration": {
"AwsvpcConfiguration": {
"Subnets": [
"subnet-020bfb42a62070fd0",
"subnet-0f76b85fd4ec253be"
],
"SecurityGroups": [
"sg-0a952161376b08a81"
],
"AssignPublicIp": "ENABLED"
}
},
"Overrides": {
"ContainerOverrides": [
{
"Name": "[コンテナ名]",
"Command": [
"sh",
"-c",
"echo Hello-world; exit 100"
]
}
]
}
},
"End": true
}
}
}
並列実行定義=Prallelが失敗したら、失敗とみなす
{
"Comment": "並列ECSジョブ + 条件分岐",
"StartAt": "並列ジョブ開始",
"States": {
"並列ジョブ開始": {
"Type": "Parallel",
"Branches": [
{
"StartAt": "ECS RunTask (Helloworld)",
"States": {
"ECS RunTask (Helloworld)": {
"Type": "Task",
"Resource": "arn:aws:states:::ecs:runTask.sync",
"Parameters": {
"LaunchType": "FARGATE",
"Cluster": "arn:aws:ecs:ap-northeast-1:{アカウントID}:cluster/2413882_test",
"TaskDefinition": "arn:aws:ecs:ap-northeast-1:{アカウントID}:task-definition/2413882_Helloworld:7",
"NetworkConfiguration": {
"AwsvpcConfiguration": {
"Subnets": [
"subnet-020bfb42a62070fd0",
"subnet-0f76b85fd4ec253be"
],
"SecurityGroups": [
"sg-0a952161376b08a81"
],
"AssignPublicIp": "ENABLED"
}
}
},
"End": true
}
}
},
{
"StartAt": "Run Task (Helloworld:202)",
"States": {
"Run Task (Helloworld:202)": {
"Type": "Task",
"Resource": "arn:aws:states:::ecs:runTask.sync",
"Parameters": {
"LaunchType": "FARGATE",
"Cluster": "arn:aws:ecs:ap-northeast-1:{アカウントID}:cluster/2413882_test",
"TaskDefinition": "arn:aws:ecs:ap-northeast-1:{アカウントID}:task-definition/2413882_Helloworld:8",
"NetworkConfiguration": {
"AwsvpcConfiguration": {
"Subnets": [
"subnet-020bfb42a62070fd0",
"subnet-0f76b85fd4ec253be"
],
"SecurityGroups": [
"sg-0a952161376b08a81"
],
"AssignPublicIp": "ENABLED"
}
}
},
"Retry": [
{
"ErrorEquals": [
"States.ALL"
],
"IntervalSeconds": 2,
"MaxAttempts": 1,
"BackoffRate": 1
}
],
"End": true
}
}
}
],
"Next": "Check If Success"
},
"Check If Success": {
"Type": "Choice",
"Choices": [
{
"Variable": "$[1].Containers[0].ExitCode",
"NumericEquals": 0,
"Next": "Final ECS RunTask"
}
],
"Default": "異常終了"
},
"Final ECS RunTask": {
"Type": "Task",
"Resource": "arn:aws:states:::ecs:runTask.sync",
"Parameters": {
"LaunchType": "FARGATE",
"Cluster": "arn:aws:ecs:ap-northeast-1:{アカウントID}:cluster/2413882_test",
"TaskDefinition": "arn:aws:ecs:ap-northeast-1:{アカウントID}:task-definition/2413882_Helloworld:7",
"NetworkConfiguration": {
"AwsvpcConfiguration": {
"Subnets": [
"subnet-020bfb42a62070fd0",
"subnet-0f76b85fd4ec253be"
],
"SecurityGroups": [
"sg-0a952161376b08a81"
],
"AssignPublicIp": "ENABLED"
}
}
},
"Next": "成功"
},
"成功": {
"Type": "Succeed"
},
"異常終了": {
"Type": "Fail",
"Error": "TaskFailed",
"Cause": "One or more parallel jobs failed"
}
}
}
並列実行定義=条件分岐(片方失敗しても、Prallel突破) ※後行ジョブは実行しない
{
"Comment": "並列ECSジョブ + 条件分岐",
"StartAt": "並列ジョブ開始",
"States": {
"並列ジョブ開始": {
"Type": "Parallel",
"Branches": [
{
"StartAt": "ECS RunTask (Helloworld①)",
"States": {
"ECS RunTask (Helloworld①)": {
"Type": "Task",
"Resource": "arn:aws:states:::ecs:runTask.sync",
"Parameters": {
"LaunchType": "FARGATE",
"Cluster": "arn:aws:ecs:ap-northeast-1:{アカウントID}:cluster/2413882_test",
"TaskDefinition": "arn:aws:ecs:ap-northeast-1:{アカウントID}:task-definition/2413882_Helloworld:7",
"NetworkConfiguration": {
"AwsvpcConfiguration": {
"Subnets": [
"subnet-020bfb42a62070fd0",
"subnet-0f76b85fd4ec253be"
],
"SecurityGroups": [
"sg-0a952161376b08a81"
],
"AssignPublicIp": "ENABLED"
}
}
},
"Catch": [
{
"ErrorEquals": [
"States.ALL"
],
"ResultPath": "$.error",
"Next": "SetExitCode①"
}
],
"End": true
},
"SetExitCode①": {
"Type": "Pass",
"Result": {
"ExitCode": "エラー"
},
"ResultPath": "$.Containers[0]",
"End": true
}
}
},
{
"StartAt": "Run Task (Helloworld②)",
"States": {
"Run Task (Helloworld②)": {
"Type": "Task",
"Resource": "arn:aws:states:::ecs:runTask.sync",
"Parameters": {
"LaunchType": "FARGATE",
"Cluster": "arn:aws:ecs:ap-northeast-1:{アカウントID}:cluster/2413882_test",
"TaskDefinition": "arn:aws:ecs:ap-northeast-1:{アカウントID}:task-definition/2413882_Helloworld:9",
"NetworkConfiguration": {
"AwsvpcConfiguration": {
"Subnets": [
"subnet-020bfb42a62070fd0",
"subnet-0f76b85fd4ec253be"
],
"SecurityGroups": [
"sg-0a952161376b08a81"
],
"AssignPublicIp": "ENABLED"
}
}
},
"Catch": [
{
"ErrorEquals": [
"States.ALL"
],
"ResultPath": "$.error",
"Next": "SetExitCode②"
}
],
"End": true
},
"SetExitCode②": {
"Type": "Pass",
"Result": {
"ExitCode": "エラー"
},
"ResultPath": "$.Containers[0]",
"End": true
}
}
}
],
"Next": "Check If Success"
},
"Check If Success": {
"Type": "Choice",
"Choices": [
{
"Variable": "$[0].Containers[0].ExitCode",
"NumericEquals": 0,
"Next": "Check Branch 2"
}
],
"Default": "異常終了"
},
"Check Branch 2": {
"Type": "Choice",
"Choices": [
{
"Variable": "$[1].Containers[0].ExitCode",
"NumericEquals": 0,
"Next": "Final ECS RunTask"
}
],
"Default": "異常終了"
},
"Final ECS RunTask": {
"Type": "Task",
"Resource": "arn:aws:states:::ecs:runTask.sync",
"Parameters": {
"LaunchType": "FARGATE",
"Cluster": "arn:aws:ecs:ap-northeast-1:{アカウントID}:cluster/2413882_test",
"TaskDefinition": "arn:aws:ecs:ap-northeast-1:{アカウントID}:task-definition/2413882_Helloworld:7",
"NetworkConfiguration": {
"AwsvpcConfiguration": {
"Subnets": [
"subnet-020bfb42a62070fd0",
"subnet-0f76b85fd4ec253be"
],
"SecurityGroups": [
"sg-0a952161376b08a81"
],
"AssignPublicIp": "ENABLED"
}
}
},
"Next": "成功"
},
"成功": {
"Type": "Succeed"
},
"異常終了": {
"Type": "Fail",
"Error": "TaskFailed",
"Cause": "One or more parallel jobs failed"
}
}
}
並列実行定義=条件分岐(片方失敗しても、Prallel突破、※後行ジョブも実行
{
"Comment": "並列ECSジョブ + 条件分岐(OR 条件)",
"StartAt": "並列ジョブ開始",
"States": {
"並列ジョブ開始": {
"Type": "Parallel",
"Branches": [
{
"StartAt": "【先行】ECS RunTask ①",
"States": {
"【先行】ECS RunTask ①": {
"Type": "Task",
"Resource": "arn:aws:states:::ecs:runTask.sync",
"Parameters": {
"LaunchType": "FARGATE",
"Cluster": "arn:aws:ecs:ap-northeast-1:{アカウントID}:cluster/2413882_test",
"TaskDefinition": "arn:aws:ecs:ap-northeast-1:{アカウントID}:task-definition/2413882_Helloworld:8",
"NetworkConfiguration": {
"AwsvpcConfiguration": {
"Subnets": [
"subnet-020bfb42a62070fd0",
"subnet-0f76b85fd4ec253be"
],
"SecurityGroups": [
"sg-0a952161376b08a81"
],
"AssignPublicIp": "ENABLED"
}
}
},
"Catch": [
{
"ErrorEquals": [
"States.ALL"
],
"ResultPath": "$.error",
"Next": "①エラーコードをChoiceへ渡す"
}
],
"End": true
},
"①エラーコードをChoiceへ渡す": {
"Type": "Pass",
"Result": {
"ExitCode": 999
},
"ResultPath": "$.Containers[0]",
"End": true
}
}
},
{
"StartAt": "【先行】ECS RunTask ②",
"States": {
"【先行】ECS RunTask ②": {
"Type": "Task",
"Resource": "arn:aws:states:::ecs:runTask.sync",
"Parameters": {
"LaunchType": "FARGATE",
"Cluster": "arn:aws:ecs:ap-northeast-1:{アカウントID}:cluster/2413882_test",
"TaskDefinition": "arn:aws:ecs:ap-northeast-1:{アカウントID}task-definition/2413882_Helloworld:7",
"NetworkConfiguration": {
"AwsvpcConfiguration": {
"Subnets": [
"subnet-020bfb42a62070fd0",
"subnet-0f76b85fd4ec253be"
],
"SecurityGroups": [
"sg-0a952161376b08a81"
],
"AssignPublicIp": "ENABLED"
}
}
},
"Catch": [
{
"ErrorEquals": [
"States.ALL"
],
"ResultPath": "$.error",
"Next": "②エラーコードをChoiceへ渡す"
}
],
"End": true
},
"②エラーコードをChoiceへ渡す": {
"Type": "Pass",
"Result": {
"ExitCode": 999
},
"ResultPath": "$.Containers[0]",
"End": true
}
}
}
],
"Next": "判定"
},
"判定": {
"Type": "Choice",
"Choices": [
{
"Or": [
{
"Variable": "$[0].Containers[0].ExitCode",
"NumericEquals": 0
},
{
"Variable": "$[1].Containers[0].ExitCode",
"NumericEquals": 0
}
],
"Next": "【後行】ECS RunTask③"
}
],
"Default": "異常終了"
},
"【後行】ECS RunTask③": {
"Type": "Task",
"Resource": "arn:aws:states:::ecs:runTask.sync",
"Parameters": {
"LaunchType": "FARGATE",
"Cluster": "arn:aws:ecs:ap-northeast-1:{アカウントID}:cluster/2413882_test",
"TaskDefinition": "arn:aws:ecs:ap-northeast-1:{アカウントID}:task-definition/2413882_Helloworld:8",
"NetworkConfiguration": {
"AwsvpcConfiguration": {
"Subnets": [
"subnet-020bfb42a62070fd0",
"subnet-0f76b85fd4ec253be"
],
"SecurityGroups": [
"sg-0a952161376b08a81"
],
"AssignPublicIp": "ENABLED"
}
}
},
"Next": "成功",
"Catch": [
{
"ErrorEquals": [
"States.ALL"
],
"Comment": "エラーキャッチ",
"Next": "異常終了"
}
]
},
"成功": {
"Type": "Succeed"
},
"異常終了": {
"Type": "Fail",
"Cause": "ECSタスクが異常終了しました"
}
}
}
並列実行定義=条件分岐(片方失敗しても、Prallel突破、※後行ジョブも実行するが、各ジョブどれかエラーの場合は、ステートマシーンは失敗
{
"Comment": "並列ジョブ → 最終ジョブは常に実行 → 成否で分岐",
"StartAt": "並列ジョブ開始",
"States": {
"並列ジョブ開始": {
"Type": "Parallel",
"Branches": [
{
"StartAt": "ECS RunTask (Helloworld1)",
"States": {
"ECS RunTask (Helloworld1)": {
"Type": "Task",
"Resource": "arn:aws:states:::ecs:runTask.sync",
"Parameters": {
"LaunchType": "FARGATE",
"Cluster": "arn:aws:ecs:ap-northeast-1:{アカウントID}:cluster/2413882_test",
"TaskDefinition": "arn:aws:ecs:ap-northeast-1:{アカウントID}:task-definition/2413882_Helloworld:7",
"NetworkConfiguration": {
"AwsvpcConfiguration": {
"Subnets": [
"subnet-020bfb42a62070fd0",
"subnet-0f76b85fd4ec253be"
],
"SecurityGroups": [
"sg-0a952161376b08a81"
],
"AssignPublicIp": "ENABLED"
}
}
},
"Catch": [
{
"ErrorEquals": [
"States.ALL"
],
"ResultPath": "$.Containers[0]",
"Next": "SetError1"
}
],
"End": true
},
"SetError1": {
"Type": "Pass",
"Result": {
"ExitCode": 999
},
"ResultPath": "$.Containers[0]",
"End": true
}
}
},
{
"StartAt": "ECS RunTask (Helloworld2)",
"States": {
"ECS RunTask (Helloworld2)": {
"Type": "Task",
"Resource": "arn:aws:states:::ecs:runTask.sync",
"Parameters": {
"LaunchType": "FARGATE",
"Cluster": "arn:aws:ecs:ap-northeast-1:{アカウントID}:cluster/2413882_test",
"TaskDefinition": "arn:aws:ecs:ap-northeast-1:{アカウントID}4:task-definition/2413882_Helloworld:9",
"NetworkConfiguration": {
"AwsvpcConfiguration": {
"Subnets": [
"subnet-020bfb42a62070fd0",
"subnet-0f76b85fd4ec253be"
],
"SecurityGroups": [
"sg-0a952161376b08a81"
],
"AssignPublicIp": "ENABLED"
}
}
},
"Catch": [
{
"ErrorEquals": [
"States.ALL"
],
"ResultPath": "$.Containers[0]",
"Next": "SetError2"
}
],
"End": true
},
"SetError2": {
"Type": "Pass",
"Result": {
"ExitCode": 999
},
"ResultPath": "$.Containers[0]",
"End": true
}
}
}
],
"Next": "後行ジョブ"
},
"後行ジョブ": {
"Type": "Task",
"Resource": "arn:aws:states:::ecs:runTask.sync",
"Parameters": {
"LaunchType": "FARGATE",
"Cluster": "arn:aws:ecs:ap-northeast-1:{アカウントID}:cluster/2413882_test",
"TaskDefinition": "arn:aws:ecs:ap-northeast-1:{アカウントID}:task-definition/2413882_Helloworld:7",
"NetworkConfiguration": {
"AwsvpcConfiguration": {
"Subnets": [
"subnet-020bfb42a62070fd0",
"subnet-0f76b85fd4ec253be"
],
"SecurityGroups": [
"sg-0a952161376b08a81"
],
"AssignPublicIp": "ENABLED"
}
}
},
"Next": "判定"
},
"判定": {
"Type": "Choice",
"Choices": [
{
"Or": [
{
"Variable": "$[0].Containers[0].ExitCode",
"NumericEquals": 999
},
{
"Variable": "$[1].Containers[0].ExitCode",
"NumericEquals": 999
}
],
"Next": "異常終了"
}
],
"Default": "成功"
},
"成功": {
"Type": "Succeed"
},
"異常終了": {
"Type": "Fail",
"Cause": "1つ以上の並列ジョブが異常終了しました",
"Error": "ParallelJobFailed"
}
}
}
■直列・並列定義詳細を表で整理
直列実行定義詳細
項目 | 定義 | 詳細 | 設定根拠 |
---|---|---|---|
処理構成 | StartAt |
"StartAt": "[対象ECSタスク]" 最初に実行されるステートを定義 |
ステートマシンの開始地点を明示的に指定する必要があるため |
ECSタスクの同期実行 | Resource |
"Resource": "arn:aws:states:::ecs:runTask.sync" ECSタスクの同期型起動 |
タスクの完了を待ち、終了コードに基づく次の処理を制御するため |
Fargate実行設定 | Parameters |
LaunchType , Cluster , TaskDefinition , NetworkConfiguration 等を定義 |
ECSタスクの実行環境(クラスタ、定義、NW設定)を指定するため |
AwsvpcConfiguration |
Subnets , SecurityGroups , AssignPublicIp を指定 |
タスクのENI生成に必要であるため | |
再試行 | Retry |
ErrorEquals: States.ALL IntervalSeconds: 2 MaxAttempts: 1 BackoffRate: 1
|
一時的な失敗に対して自動再試行することで、ワークフローの耐久性を高めるため |
エラーハンドリング | Catch |
ErrorEquals: States.ALL ResultPath: "$.error"
|
タスクが失敗した際、異常終了に制御を移すため |
正常終了定義 | 成功状態 | "Type": "Succeed" |
成功時のステートマシン終了状態を明示するため |
異常終了定義 | 異常終了状態 |
"Type": "Fail" "Cause": "ECSタスクが異常終了しました" "Error": "ECS.Exit"
|
失敗時のステートマシン終了状態を明示するため |
並列実行定義詳細
大項目 | 中項目 | 詳細 | 設定根拠 |
---|---|---|---|
処理構成 | StartAt |
"StartAt": "[対象ECSタスク]" 最初に実行されるステートを定義 |
ステートマシンの開始地点を明示的に指定する必要があるため |
ECSタスクの同期実行 | Resource |
"Resource": "arn:aws:states:::ecs:runTask.sync" ECSタスクの同期型起動 |
タスクの完了を待ち、終了コードに基づく次の処理を制御するため |
Fargate実行設定 | Parameters |
LaunchType , Cluster , TaskDefinition , NetworkConfiguration 等を定義 |
ECSタスクの実行環境(クラスタ、定義、NW設定)を指定するため |
AwsvpcConfiguration |
Subnets , SecurityGroups , AssignPublicIp を指定 |
タスクのENI(Elastic Network Interface)生成に必要であるため | |
再試行 | Retry |
ErrorEquals: States.ALL IntervalSeconds: 2 MaxAttempts: 1 BackoffRate: 1
|
一時的な失敗に対して自動再試行することで、ワークフローの耐久性を高めるため |
エラーハンドリング | Catch |
ErrorEquals: States.ALL ResultPath: "$.error" |
タスクが失敗した際、”Pass”へ遷移させるため |
エラーコードを"Choice"へ渡す | Pass |
"Pass" で"ExitCode: 999" を Containers[0] に格納し、Choiceに使用できるようにする Result で固定値 "ExitCode: 999" を生成 ResultPath で出力データ構造を他と統一する |
並列ジョブいずれか一方が失敗しても、後行ジョブを実行させるため |
判定(正常 or 異常) | Choice |
"Choice" 下記条件を設定することで、後行ジョブが実行する ※並列ジョブ両方失敗は、後行ジョブ実行しない "Variable": "$[0].Containers[0].ExitCode" 並列ジョブ両方成功"Variable":"$[1].Containers[0].ExitCode" 並列ジョブいずれか一方が失敗 |
後行ジョブ実行後、ステートマシーン全体が正常 or 異常と判定させるため |
正常終了定義 | 成功状態 | "Type": "Succeed" |
成功時のステートマシン終了状態を明示するため |
異常終了定義 | 異常終了状態 |
"Type": "Fail" "Cause": "失敗したジョブがあります" "Error": "ParallelJobFailed"
|
終了時のステートマシン終了状態を明示するため |
■親,子ステートマシン CloudFormation(yml)で作成
親,子ステートマシンCFn
AWSTemplateFormatVersion: '2010-09-09'
Resources:
Child1StateMachine:
Type: AWS::StepFunctions::StateMachine
Properties:
Definition:
StartAt: 子1ECSタスク
States:
子1ECSタスク:
Type: Task
Resource: arn:aws:states:::ecs:runTask.sync
Parameters:
LaunchType: FARGATE
Cluster: !ImportValue MyECSClusterArn
TaskDefinition: !ImportValue MyTaskDefinitionArn
NetworkConfiguration:
AwsvpcConfiguration:
Subnets:
- !ImportValue MySubnet1Id
- !ImportValue MySubnet2Id
SecurityGroups:
- !ImportValue MySecurityGroupId
AssignPublicIp: ENABLED
Overrides:
ContainerOverrides:
- Name: my-container-name
Command:
- sh
- '-c'
- echo Hello-world; exit 0
End: true
RoleArn: !ImportValue MyStepFunctionsRoleArn
StateMachineName: Child1
StateMachineType: STANDARD
EncryptionConfiguration:
Type: AWS_OWNED_KEY
LoggingConfiguration:
Level: 'OFF'
IncludeExecutionData: false
Tags:
- Key: Name
Value: テスト学習
Child2StateMachine:
Type: AWS::StepFunctions::StateMachine
Properties:
Definition:
StartAt: 子2ECSタスク
States:
子2ECSタスク:
Type: Task
Resource: arn:aws:states:::ecs:runTask.sync
Parameters:
LaunchType: FARGATE
Cluster: !ImportValue MyECSClusterArn
TaskDefinition: !ImportValue MyTaskDefinitionArn
NetworkConfiguration:
AwsvpcConfiguration:
Subnets:
- !ImportValue MySubnet1Id
- !ImportValue MySubnet2Id
SecurityGroups:
- !ImportValue MySecurityGroupId
AssignPublicIp: ENABLED
Overrides:
ContainerOverrides:
- Name: my-container-name
Command:
- sh
- '-c'
- echo Hello-world; exit 0
End: true
RoleArn: !ImportValue MyStepFunctionsRoleArn
StateMachineName: Child2
StateMachineType: STANDARD
EncryptionConfiguration:
Type: AWS_OWNED_KEY
LoggingConfiguration:
Level: 'OFF'
IncludeExecutionData: false
Tags:
- Key: Name
Value: テスト学習
PareStateMachine:
Type: AWS::StepFunctions::StateMachine
Properties:
Definition:
Comment: Parent state machine
StartAt: '[先行]StartExecution'
States:
'[先行]StartExecution':
Type: Task
Resource: arn:aws:states:::states:startExecution.sync:2
Parameters:
StateMachineArn: !Ref Child1StateMachine
Next: '[後行]StartExecution'
'[後行]StartExecution':
Type: Task
Resource: arn:aws:states:::states:startExecution.sync:2
Parameters:
StateMachineArn: !Ref Child2StateMachine
Next: '[先行]ECS RunTask'
'[先行]ECS RunTask':
Type: Task
Resource: arn:aws:states:::ecs:runTask.sync
Parameters:
LaunchType: FARGATE
Cluster: !ImportValue MyECSClusterArn
TaskDefinition: !ImportValue MyTaskDefinitionArn
NetworkConfiguration:
AwsvpcConfiguration:
Subnets:
- !ImportValue MySubnet1Id
- !ImportValue MySubnet2Id
SecurityGroups:
- !ImportValue MySecurityGroupId
AssignPublicIp: ENABLED
Overrides:
ContainerOverrides:
- Name: my-container-name
Command:
- sh
- '-c'
- echo Hello-world; exit 0
Next: '[後行]ECS RunTask'
'[後行]ECS RunTask':
Type: Task
Resource: arn:aws:states:::ecs:runTask.sync
Parameters:
LaunchType: FARGATE
Cluster: !ImportValue MyECSClusterArn
TaskDefinition: !ImportValue MyTaskDefinitionArn
NetworkConfiguration:
AwsvpcConfiguration:
Subnets:
- !ImportValue MySubnet1Id
- !ImportValue MySubnet2Id
SecurityGroups:
- !ImportValue MySecurityGroupId
AssignPublicIp: ENABLED
Overrides:
ContainerOverrides:
- Name: my-container-name
Command:
- sh
- '-c'
- echo Hello-world; exit 0
End: true
QueryLanguage: JSONPath
RoleArn: !ImportValue MyStepFunctionsRoleArn
StateMachineName: Pare
StateMachineType: STANDARD
EncryptionConfiguration:
Type: AWS_OWNED_KEY
LoggingConfiguration:
Level: 'OFF'
IncludeExecutionData: false
Tags:
- Key: Name
Value: テスト学習
Outputs:
Pare:
Description: ARN of the parent Step Functions state machine
Value: !Ref PareStateMachine
Export:
Name: Pare
Child1:
Description: ARN of the first child Step Functions state machine
Value: !Ref Child1StateMachine
Export:
Name: Child1
Child2:
Description: ARN of the second child Step Functions state machine
Value: !Ref Child2StateMachine
Export:
Name: Child2
プロパティ名 | 項目説明 | 設定根拠 |
---|---|---|
Type | リソース種別(Step Functionsステートマシン) | CloudFormationでステートマシンを作成するには AWS::StepFunctions::StateMachine を指定する必要がある |
Definition | ステートマシンの定義全体 | ステートの開始点やフロー、タスク処理を記述するブロック |
Definition > StartAt | ステートマシン開始ステートの名前 | ステートマシンがどのステートから処理を開始するかを指定 |
Definition > States | ステート群の定義 | 複数のタスクや条件分岐など、処理の流れを明示する |
Type(内側) | 個々のステートの種別(例:Task) | ECSや他のStep Functionsの呼び出しに対応するため Task を指定 |
Resource | ステートが実行するリソースのARN |
ecs:runTask.sync や states:startExecution.sync:2 で各種タスクの同期実行を行う |
Parameters > LaunchType | ECSタスクの起動タイプ | FARGATEを指定することでサーバーレスに実行可能 |
Parameters > Cluster | ECSクラスターのARN | タスク実行対象となるクラスターを指定 |
Parameters > TaskDefinition | ECSタスク定義のARN | 実行するタスク内容を定義 |
Parameters > NetworkConfiguration | VPC内で実行するためのネットワーク設定 | FargateでVPC起動するにはSubnet/SecurityGroupが必須 |
Parameters > Overrides | コンテナごとの上書き設定 | 実行時にコマンドなどを上書きする際に使用 |
RoleArn | ステートマシンが使用するIAMロールのARN | ECS実行やStep Functions連携に必要なIAMロールを指定 |
StateMachineName | ステートマシン名(任意名) | 識別可能かつ管理しやすい名称を設定 |
StateMachineType | ステートマシンの種類(STANDARDまたはEXPRESS) | バッチ処理やワークフローにはSTANDARDが適している |
EncryptionConfiguration > Type | 暗号化設定の種類 | AWS管理キー(AWS_OWNED_KEY)を使い簡易にセキュリティを担保 |
LoggingConfiguration > Level | ログの詳細レベル | 開発・運用方針に応じて OFF(今回はログを出さない)を指定 |
LoggingConfiguration > IncludeExecutionData | 実行データをログに含めるかどうか | OFFとすることでデータ出力を抑えセキュリティとコスト削減 |
Outputs | スタック出力の定義 | 他リソース(EventBridgeなど)から参照できるようにARNを出力 |
Outputs > Value: !Ref リソース名 | リソースのARNを返却 | !RefはStep Functionsリソースに対してARNを返すため、参照が簡易 |
Outputs > Export > Name | 出力値の外部スタック向け識別名 | !ImportValueで他テンプレートから参照可能にするために必要 |
EventBridgeスケジュール,スケジュールグループ
AWSTemplateFormatVersion: '2010-09-09'
Resources:
# EventBridgeスケジュールグループ作成
MyScheduleGroup:
Type: AWS::Scheduler::ScheduleGroup
Properties:
Name: my-schedule-group
Tags:
- Key: Name
Value: 計画操作
# EventBridgeスケジュール作成
MySchedule:
Type: AWS::Scheduler::Schedule
Properties:
Name: my-schedule-job
GroupName: !Ref MyScheduleGroup
Description: stepfunctions-test
ScheduleExpression: 'cron(0 10 * * ? *)'
ScheduleExpressionTimezone: Asia/Tokyo
StartDate: '2025-06-23T01:00:00.000Z'
FlexibleTimeWindow:
Mode: "OFF"
Target:
Arn: !ImportValue Pare # ← 親ステートマシンの出力名 'Pare' を参照
RoleArn: [IAM]
Input: '{}'
RetryPolicy:
MaximumRetryAttempts: 0
MaximumEventAgeInSeconds: 60
Outputs:
ScheduleGroupArn:
Description: ARN of the EventBridge schedule group
Value: !Sub arn:aws:scheduler:${AWS::Region}:${AWS::AccountId}:schedule-group/my-schedule-group
Export:
Name: ScheduleGroupArn
ScheduleArn:
Description: ARN of the EventBridge schedule
Value: !Sub arn:aws:scheduler:${AWS::Region}:${AWS::AccountId}:schedule/my-schedule-group/my-schedule-job
Export:
Name: ScheduleArn
・DLQを使いたいときだけ DeadLetterConfig を明示
・[MaximumEventAgeInSeconds: 60]について
たとえ GUI 上で「再試行ポリシー」をオフにしても、
AWS EventBridge Scheduler の裏側では イベント自体の「最大生存期間(Event Age)」として 24時間(= 86400秒) がデフォルトとして使われる
機能 | 明記しなかった場合のデフォルト | OFFにしたいときに必要な書き方 |
---|---|---|
再試行ポリシー(RetryPolicy) | ON(再試行される) | RetryPolicy: { MaximumRetryAttempts: 0 } |
デッドレターキュー(DLQ) | OFF(使用されない) |
DeadLetterConfig: { Arn: ... } を記載しなければOFFのまま
|
プロパティ名 | 項目説明 | 設定根拠 |
---|---|---|
Name |
スケジュールの一意な名前 | GUIの「スケジュール名」欄に相当 |
GroupName |
紐づくスケジュールグループの名前(上記の Name を参照) |
GUIで「スケジュールグループを選択」で設定 |
Description |
スケジュールの説明テキスト | GUIの「説明」欄に入力される |
ScheduleExpression |
実行スケジュール(cron形式)cron(0 1 * * ? *) は JSTで毎日10:00 |
GUIの「スケジュール(cron/rate)」に相当。UTCベースのためJST換算で 10:00実行になる |
ScheduleExpressionTimezone |
Asia/Tokyo | 日本標準時間 |
StartDate |
2025-06-23T01:00:00.000Z | UTC(協定世界時)で定義する必要があるため |
FlexibleTimeWindow.Mode |
実行時間の柔軟性を許容するか"OFF" = 固定時間に実行 |
GUIの「柔軟な時間ウィンドウ」をオフにした状態 |
Target.Arn |
実行先の Step Functions の ARN | GUIで「ターゲット」→ Step Functions 選択で指定 |
Target.RoleArn |
EventBridge スケジューラがターゲットを実行するための IAM ロール | GUIで指定する「IAM 実行ロール」に相当 |
Target.Input |
ターゲットへ渡す入力 JSON | GUIで「入力パラメータ(JSON形式)」に相当。ここでは空の {} を指定 |
RetryPolicy.MaximumRetryAttempts |
ターゲットが失敗した場合の最大再試行回数(ここでは再試行なし) | GUIで「再試行ポリシー」→ オフにしたときと同様 |
RetryPolicy.MaximumEventAgeInSeconds |
最大でイベントを保持する時間(秒) 60秒=1分以内に実行できなければ中断 |
GUIでは非表示だが、CLI/IaCで明示しないとデフォルト(86400秒=24時間)が適用されるため明示指定が必要 |
・公式ドキュメント
再試行ポリシー
When retry policy is not specified, the scheduler uses a default MaximumEventAgeInSeconds of 86400 seconds (24 hours).
IAMポリシー
events:PutEvents
カスタムイベントを EventBridge に送信するための権限
Step Functions が ECS タスクの終了を検知するために使う EventBridge 機能では、PutEvents は使わない
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowRunAndMonitorECSTask",
"Effect": "Allow",
"Action": [
"ecs:RunTask",
"ecs:StopTask",
"ecs:DescribeTasks"
],
"Resource": "*"
},
{
"Sid": "AllowPassTaskRole",
"Effect": "Allow",
"Action": "iam:PassRole",
"Resource": "arn:aws:iam::{アカウントID}:role/ecsTaskExecutionRole"
},
{
"Sid": "AllowEventBridgeSyncForECS",
"Effect": "Allow",
"Action": [
"events:PutTargets",
"events:PutRule",
"events:DescribeRule"
],
"Resource": "*"
},
{
"Sid": "AllowStartChildStepFunction",
"Effect": "Allow",
"Action": [
"states:StartExecution",
"states:DescribeExecution"
],
"Resource": "*"
},
{
"Sid": "AllowCloudWatchLogsAccess",
"Effect": "Allow",
"Action": [
"logs:CreateLogDelivery",
"logs:GetLogDelivery",
"logs:UpdateLogDelivery",
"logs:DeleteLogDelivery",
"logs:ListLogDeliveries",
"logs:PutResourcePolicy",
"logs:DescribeResourcePolicies",
"logs:DescribeLogGroups"
],
"Resource": "*"
}
]
}
Sid | Action一覧 | Resourceの範囲 | 説明 |
---|---|---|---|
AllowRunAndMonitorECSTask | - ecs:RunTask - ecs:StopTask - ecs:DescribeTasks |
*(全てのECSタスク) | Step Functions から ECS タスクを起動・停止・状態確認するために必要な権限 |
AllowPassTaskRole | - iam:PassRole | arn:aws:iam::{アカウントID}:role/ecsTaskExecutionRole | ECS タスクに割り当てる実行ロール(Task Execution Role)を許可。ARN指定で最小権限を遵守 |
AllowEventBridgeSyncForECS | - events:PutTargets - events:PutRule - events:DescribeRule |
*(イベントルール一式) | runTask.sync の内部処理で必要な EventBridge の同期ルール作成と管理に必要 |
AllowStartChildStepFunction | - states:StartExecution - states:DescribeExecution |
*(すべての Step Functions) | 子ステートマシン(サブステートマシン)を起動・状態確認するための権限 |
AllowCloudWatchLogsAccess | - logs:CreateLogDelivery - logs:GetLogDelivery - logs:UpdateLogDelivery - logs:DeleteLogDelivery - logs:ListLogDeliveries - logs:PutResourcePolicy - logs:DescribeResourcePolicies - logs:DescribeLogGroups |
*(CloudWatch Logs 関連リソース) | ステートマシン実行ログを CloudWatch Logs に出力するために必要な一連の操作 |
iam:PassRole の Resource はセキュリティ観点から 明示された1つのロールに限定
events:* 系のアクションは、Step Functions から ECS を同期実行 (runTask.sync) する場合に AWS が 内部的に EventBridge ルールを作成するため必要
states:* の権限で、Step Functions の中から 子ステートマシンを起動できる
logs:* は、Step Functions の 実行ログやトレースを CloudWatch に出力するために必要
CLIリソース抽出 EventBridge
・特定のスケジュール名の定義情報をテーブル形式で抽出するコマンド
例
aws scheduler get-schedule \ --region ap-northeast-1 \ --name my-schedule-job \ --group-name my-schedule-group
・特定のスケジュールグループの定義情報をテーブル形式で抽出するコマンド
例
aws scheduler list-schedule-groups \
--region ap-northeast-1 \
--query "ScheduleGroups[?Name=='my-schedule-group'].[Name, Arn, CreationDate, LastModificationDate, State]" \
--output table
・タグ確認コマンド
例
aws scheduler list-tags-for-resource \
--resource-arn arn:aws:scheduler:ap-northeast-1:{アカウントID}:schedule-group/my-schedule-group
CLIリソース抽出 StepFunctions
・ステートマシン定義の取得コマンド
aws stepfunctions describe-state-machine
--state-machine-arn arn:aws:states:<リージョン>:<アカウントID>:stateMachine:<ステートマシン名>
--output table
・ステートマシンの定義以外の項目を出力するコマンド
aws stepfunctions describe-state-machine
--state-machine-arn arn:aws:states:<リージョン>:<アカウントID>:stateMachine:<ステートマシン名>
--query '{Name: name, StateMachineArn: stateMachineArn, RoleArn: roleArn, Type: type, Logging: loggingConfiguration, Tracing: tracingConfiguration, CreatedAt: creationDate}'
--output table
ジョブ管理システムををStep Functionsへ代替
SSM カレンダー+EventBridgeルール+Step Functions ステートマシン実行
AWS Systems Manager Change Calendar とは、
特定期間におけるアクションの実行可否(許可: Open / 禁止: Closed)を制御するカレンダー機能である。
本構成では、カレンダーが「Closed → Open」へ変わるタイミングを EventBridgeルールで検知し、Step Functionsを実行させる。
Change Calendarを使用することで、「平日は定期ジョブを実行」「特定日(祝日・メンテ期間など)はジョブを停止」といった柔軟なスケジュール制御が可能になる。
Systems Manager Change Calendarとは
カレンダーを作成し、指定した期間中の特定のアクションの実行を許可または禁止するためのカレンダー機能です。作成したカレンダーに対して、イベント(アクション実行条件)を設定することができ、それに沿ってカレンダーのステータスが変更されます。変更されるタイミングでイベントを発するため、これをEventBridgeルールで検知します。
デフォルトで開く:イベントが作成されている期間中は、タスク実行がブロックされる。
デフォルトで閉じる:イベントが作成されている期間中に、タスクが実行可能。
EventBridge Rule
AWSTemplateFormatVersion: '2010-09-09'
Description: Create EventBridge Rule
Resources:
Rule:
Type: AWS::Events::Rule
Properties:
Name: test01
EventPattern: >-
{
"source": ["aws.ssm"],
"detail-type": ["Calendar State Change"],
"resources": ["arn:aws:ssm:ap-northeast-1:{アカウントID}:document/test"],
"detail": {
"state": ["OPEN"],
"event": ["test"]
}
}
State: ENABLED
Description: 日次
EventBusName: default
Targets:
- Id: [ID]
Arn:
[ARN]
RoleArn:
[RoleArn]
Outputs:
Rule:
Description: ARN EventBridge Rule
Value: !Ref Rule
Export:
Name: test01
aws events tag-resource \
--resource-arn arn:aws:events:ap-northeast-1:{アカウントID}:rule/{イベントバス名}/{ルール名} \
--tags Key=Name,Value=テスト
・UTC表示
aws ssm get-calendar-state --calendar-names "arn:aws:ssm:ap-northeast-1:{アカウントID}:document/{ルール名}" --at-time "2025-07-15T09:00:00Z"
・JST表示
aws ssm get-calendar-state --calendar-names "arn:aws:ssm:ap-northeast-1:{アカウントID}:document/{ルール名}" --at-time "2025-07-15T18:00:00+09:00"
AWSTemplateFormatVersion: '2010-09-09'
Resources:
ChangeCalendarDocument:
Type: AWS::SSM::Document
Properties:
Name: 2413882_job_control_calendar
DocumentType: ChangeCalendar
DocumentFormat: TEXT
Content: |
BEGIN:VCALENDAR
PRODID:-//AWS//Change Calendar 1.0//EN
VERSION:2.0
X-CALENDAR-TYPE:DEFAULT_CLOSED
X-WR-CALDESC:Job control calendar for step functions
BEGIN:VEVENT
UID:event-20250718
DTSTAMP:20250718T010000Z
DTSTART:20250718T010000Z
DTEND:20250718T013000Z
SUMMARY:Allow Job Execution
X-CHG-TYPE:OPEN
END:VEVENT
BEGIN:VEVENT
UID:event-20250722
DTSTAMP:20250722T010000Z
DTSTART:20250722T010000Z
DTEND:20250722T013000Z
SUMMARY:Allow Job Execution
X-CHG-TYPE:OPEN
END:VEVENT
END:VCALENDAR
AWSTemplateFormatVersion: '2010-09-09'
Resources:
ChangeCalendarDocument:
Type: AWS::SSM::Document
Properties:
Name: 2413882_job_control_calendar
DocumentType: ChangeCalendar
DocumentFormat: TEXT
Content: |
BEGIN:VCALENDAR
PRODID:-//AWS//Change Calendar 1.0//EN
VERSION:2.0
X-CALENDAR-TYPE:DEFAULT_CLOSED
X-WR-CALDESC:Job control calendar for step functions
BEGIN:VEVENT
UID:event-20250718
DTSTAMP:20250715T142000Z
DTSTART:20250715T143000Z
DTEND:20250715T144000Z
SUMMARY:Allow
X-CHG-TYPE:OPEN
END:VEVENT
END:VCALENDAR
Outputs:
ChangeCalendarDocument:
Description: ARN of Change Calendar
Value: !Ref ChangeCalendarDocument
Export:
Name: 2413882-job-control-calendar
RRULサポートされていない
セクション | 項目 | 内容・説明 |
---|---|---|
AWSTemplateFormatVersion |
'2010-09-09' |
CloudFormationのテンプレートバージョン(固定) |
Resources |
ChangeCalendarDocument |
作成するリソース名(任意の識別子) |
Type |
AWS::SSM::Document :SSMドキュメントを作成する指定 |
|
Name |
2413882_job_control_calendar :カレンダー名 |
|
DocumentType |
ChangeCalendar :ジョブ制御用カレンダーとして機能 |
|
DocumentFormat |
TEXT :テキスト形式(iCalendar .ics 互換形式) |
|
Content |
iCalendar構文でカレンダー定義(下記表に詳細) |
行 | 意味・内容 |
---|---|
BEGIN:VCALENDAR |
カレンダーの開始を示す |
PRODID |
カレンダーの識別子(AWS標準) |
VERSION:2.0 |
iCalendarのバージョン(2.0固定) |
X-CALENDAR-TYPE |
DEFAULT_CLOSED :デフォルト状態を「実行不可(CLOSED)」に設定 |
X-WR-CALDESC |
カレンダーの説明文(GUIにも表示される) |
BEGIN:VEVENT |
イベント(ジョブ実行許可期間)の開始 |
UID |
イベントの一意識別子(重複不可) |
DTSTAMP |
イベントが定義された時刻(UTC) |
DTSTART |
イベントの開始時間(ジョブ許可開始時刻/UTC) |
DTEND |
イベントの終了時間(ジョブ許可終了時刻/UTC) |
SUMMARY |
イベントの概要(任意) |
X-CHG-TYPE |
OPEN :この期間だけ「実行可能(OPEN)」とする |
END:VEVENT |
イベントの終了 |
END:VCALENDAR |
カレンダーの終了 |
項目 | 説明 |
---|---|
Outputs |
CloudFormationスタックの出力定義セクション |
ChangeCalendarDocument |
出力の名前(任意) |
Description |
出力の説明文(例:ARN of Change Calendar) |
Value: !Ref ChangeCalendarDocument |
作成したSSMドキュメントの名前(Refで取得) |
Export → Name
|
2413882-job-control-calendar :他テンプレートから参照できる名前 |
観点 | 内容 |
---|---|
タイムゾーン |
DTSTART やDTEND は UTC (Z付き) で記述が必須 |
デフォルト状態の意味 |
DEFAULT_CLOSED → 明示的なOPEN イベント期間以外はすべて実行禁止 |
実行許可イベント |
X-CHG-TYPE: OPEN によって特定の時間帯にジョブ実行を許可する |
◆繰り返し設定 |
AWSTemplateFormatVersion: '2010-09-09'
Resources:
ChangeCalendarDocument:
Type: AWS::SSM::Document
Properties:
Name: 2413882_job_control_calendar
DocumentType: ChangeCalendar
DocumentFormat: TEXT
Content: |
BEGIN:VCALENDAR
PRODID:-//AWS//Change Calendar 1.0//EN
VERSION:2.0
X-CALENDAR-TYPE:DEFAULT_CLOSED
X-WR-CALDESC:Job control calendar for step functions
BEGIN:VEVENT
DTSTAMP:20250716T112000Z
UID:event-20250716test
SEQUENCE:0
SUMMARY:isf
DTSTART:20250716T113000Z
DTEND:20250716T114000Z
RRULE:FREQ=DAILY;INTERYAL=1
X-CHG-TYPE:OPEN
END:VEVENT
END:VCALENDAR
Outputs:
ChangeCalendarDocument:
Description: ARN of Change Calendar
Value: !Ref ChangeCalendarDocument
Export:
Name: 2413882-job-control-calendar
初期構築後、イベントのみ日時修正、新規イベント作成する場合
DocumentType: ChangeCalendar
DocumentFormat: TEXT
UpdateMethod: NewVersion # 必ず追加する
Content: |
BEGIN:VCALENDAR
PRODID:-//AWS//Change Calendar 1.0//EN
VERSION:2.0
Pythonスクリプトで .ics(iCalendar形式)を生成
対象期間:2025年〜2026年の1年間
除外条件:土日・日本の祝日
生成した .ics テキストを CloudFormation テンプレートに埋め込む or 外部テンプレートと連携
AWS::SSM::Document (ChangeCalendar) としてデプロイ
ステップ1:Pythonスクリプトで .ics 生成(土日・祝日除外)
以下のPythonコードは、jpholiday ライブラリを使用して、365日の日次スケジュールを祝日・土日除外で .ics フォーマットに変換。
import datetime
import jpholiday
def generate_ics(start_date, end_date, timezone="Asia/Tokyo"):
ics_lines = [
"BEGIN:VCALENDAR",
"PRODID:-//AWS//Change Calendar 1.0//EN",
"VERSION:2.0",
"X-CALENDAR-TYPE:DEFAULT_CLOSED",
f"X-CALENDAR-TIMEZONE:{timezone}",
"X-WR-CALDESC:Exclude weekends and holidays"
]
uid_index = 1
dt = start_date
while dt <= end_date:
if dt.weekday() < 5 and not jpholiday.is_holiday(dt):
dt_start = dt.replace(hour=1, minute=0)
dt_end = dt.replace(hour=1, minute=30)
dtstamp = datetime.datetime.utcnow().strftime("%Y%m%dT%H%M%SZ")
dt_start_str = dt_start.strftime("%Y%m%dT%H%M%SZ")
dt_end_str = dt_end.strftime("%Y%m%dT%H%M%SZ")
ics_lines.extend([
"BEGIN:VEVENT",
f"UID:open-job-{uid_index}",
f"DTSTAMP:{dtstamp}",
f"DTSTART:{dt_start_str}",
f"DTEND:{dt_end_str}",
"SUMMARY:Allow Job Execution",
"X-CHG-TYPE:OPEN",
"END:VEVENT"
])
uid_index += 1
dt += datetime.timedelta(days=1)
ics_lines.append("END:VCALENDAR")
return "\n".join(ics_lines)
# 例: 2025年7月18日〜2026年7月17日
ics_content = generate_ics(datetime.datetime(2025, 7, 18), datetime.datetime(2026, 7, 17))
# 出力
with open("change-calendar.ics", "w") as f:
f.write(ics_content)
ステップ2:CFn テンプレートに .ics を組み込む
Pythonで生成された .ics を CFN に組み込むには、以下のように DocumentFormat: TEXT として設定。
Resources:
ChangeCalendarDocument:
Type: AWS::SSM::Document
Properties:
Name: exclude-holiday-calendar
DocumentType: ChangeCalendar
DocumentFormat: TEXT
Content: |
BEGIN:VCALENDAR
PRODID:-//AWS//Change Calendar 1.0//EN
VERSION:2.0
X-CALENDAR-TYPE:DEFAULT_CLOSED
X-CALENDAR-TIMEZONE:Asia/Tokyo
...
END:VEVENT
END:VCALENDAR
・ステータス状態変更不可設定(Advisory)
X-EVENT-TYPE:ADVISORY
- 概念の整理
●DEFAULT_OPEN
何もイベントを作らなければ常に OPEN(実行可)
特定の期間だけ CLOSED イベントを作成して「その間だけ止める」
●DEFAULT_CLOSED
何もイベントを作らなければ常に CLOSED(実行不可)
特定の期間だけ OPEN イベントを作成して「その間だけ動かす」
理屈:「状態検知を反転すれば、DEFAULT_CLOSED+祝日OPENでも同じ挙動は“作れます”。」
現実:「しかし、OPEN=実行可という一般原則に反するため、人間の理解・レビュー・将来拡張で必ず負債になります。」
差分:「DEFAULT_OPEN+祝日CLOSEDなら、祝日ICSの流用・Enable/Disable自動切替・通知表現が自然に一致し、保守が最小です。」
結論:「作れることと運用しやすいことは別。将来の変更コストと事故リスクを考えると、デフォルトOPENが実務最適です。」
■前提
平日は日次1日1イベント
5時から23時の間は1時間ごとに処理実行
一時間ごとのスケジュール実行は絶対時刻としたい
緊急で止めたい場合は、カレンダステータス検知用のEventBridge無効化とする
1ステートマシン、1EventBridge構成とするため
✗EventBridgeスケジュール実行(cron)
◆GetCalendarState
{
"Comment": "A description of my state machine",
"StartAt": "GetCalendarState",
"States": {
"GetCalendarState": {
"Type": "Task",
"Parameters": {
"CalendarNames": [
"arn:aws:ssm:ap-northeast-1:{アカウントID}:document/testholiday"
]
},
"Resource": "arn:aws:states:::aws-sdk:ssm:getCalendarState",
"Next": "Choice"
},
"Choice": {
"Type": "Choice",
"Choices": [
{
"Variable": "$.State",
"StringEquals": "OPEN",
"Next": "Lambda Invoke1"
}
],
"Default": "Pass"
},
"Lambda Invoke1": {
"Type": "Task",
"Resource": "arn:aws:states:::lambda:invoke",
"Parameters": {
"FunctionName": "hello-from-lambda",
"Payload": {
"inputKey1": "value1",
"inputKey2": "value2"
}
},
"ResultSelector": {
"lambdaResult.$": "$.Payload"
},
"Next": "Lambda Invoke 2"
},
"Lambda Invoke 2": {
"Type": "Task",
"Resource": "arn:aws:states:::lambda:invoke",
"Parameters": {
"FunctionName": "hello-from-lambda",
"Payload": {
"inputKey1": "value1",
"inputKey2": "value2"
}
},
"ResultSelector": {
"lambdaResult.$": "$.Payload"
},
"End": true
},
"Pass": {
"Type": "Pass",
"End": true
}
}
}
観点 | ① CloudWatch Logs → Kinesis Data Firehose(※必要時のみ Lambda変換)→ S3 | ② CloudWatch Logs → Lambda → S3 |
---|---|---|
送信元での絞り込み | どちらもサブスクリプションフィルターで ERROR 等のみ抽出可能(同じ) | 同左 |
変換(CSV化 等) | Firehoseに組み込み(Lambdaデータ変換をONにすると“バッチ単位”で変換)。不要ならOFFでLambda不使用 | Lambdaで自由に実装(1行→CSV、複数行まとめ、分岐出力など完全自由) |
バッファリング/小オブジェクト対策 | Firehoseが自動(サイズ/時間でバッチ→S3。小ファイル乱立を防ぐ) | 自前実装(メモリ/一時ストア/フラッシュ条件/重複排除/再実行時の整合性など) |
再試行/失敗時処理 | マネージド(バックオフ再試行、失敗時はerrorプレフィックスへ退避可) | 自前(リトライ/バックオフ/失敗オブジェクトの退避設計が必要) |
圧縮/暗号化/日付ローテ | 設定だけでON/OFF(GZIP/UNCOMPRESSED、KMS、接頭辞ローテ) | コード or 設計で対応(PutObject時のヘッダ、キー命名) |
スケーリング/スパイク耐性 | 高い(Firehoseが吸収) | Lambda同時実行・S3スロットリング等に設計配慮必要(SQSバッファ等) |
レイテンシ | 数十秒〜(Firehoseのバッファ条件依存) | 低レイテンシ(基本は即時、バッチ実装すると増える) |
宛先の拡張性 | S3/Redshift/OpenSearch/HTTP 等へ柔軟に切替可 | 自由だが都度コード拡張と運用設計が必要 |
運用負荷 | 低い(“変換が必要な時だけ”薄いLambda) | 高め(集約/リトライ/命名/監視/再処理を自前) |
コスト構成(概念) | CloudWatch配信(GB) + Firehose(GB) + S3 PUT/Storage + (変換Lambdaの実行コスト※ON時のみ) | CloudWatch配信(GB) + Lambda 起動回数×実行時間 + S3 PUT/Storage |
小オブジェクトのS3コスト | 抑制されやすい(Firehoseがまとめ書き) | 嵩みやすい(1イベント単位PutだとPUT課金・リスト操作が増える) |
セキュリティ/IAM | Firehose→S3、CW Logs→Firehose のロール付与で定型 | Lambda→S3、DLQ/SQS/KDS 等を併用するなら権限が増えやすい |
使いどころ | “安定にためる”が主目的(CSV化や非圧縮はオプションで選択) | “高度な分岐や外部連携”が主目的(通知/複数バケット/動的キー分岐 等) |
・JSON式バイナリデータ
・CloudWatch logs[エクスポートタスク]にてS3転送
{
"Comment": "CloudWatch Logs Export Task Flow (lowercase taskId unified)",
"StartAt": "CreateExportTask",
"States": {
"CreateExportTask": {
"Type": "Task",
"Resource": "arn:aws:states:::aws-sdk:cloudwatchlogs:createExportTask",
"Parameters": {
"Destination": "S3バケット名",
"DestinationPrefix": "フォルダ名",
"From": 1756554400000, #10分以前のログ全て
"To": 1756555000000,
"LogGroupName": "ロググループ名",
"TaskName": "export-test"
},
"ResultSelector": {
"taskId.$": "$.TaskId"
},
"ResultPath": "$.exportTask",
"Next": "Wait",
"Catch": [
{
"ErrorEquals": [
"States.ALL"
],
"Next": "Fail"
}
]
},
"Wait": {
"Type": "Wait",
"Seconds": 3,
"Next": "DescribeExportTasks"
},
"DescribeExportTasks": {
"Type": "Task",
"Resource": "arn:aws:states:::aws-sdk:cloudwatchlogs:describeExportTasks",
"Parameters": {
"TaskId.$": "$.exportTask.taskId"
},
"ResultSelector": {
"statusCode.$": "$.ExportTasks[0].Status.Code"
},
"ResultPath": "$.describe",
"Next": "Choice"
},
"Choice": {
"Type": "Choice",
"Choices": [
{
"Variable": "$.describe.statusCode",
"StringEquals": "COMPLETED",
"Next": "成功"
},
{
"Variable": "$.describe.statusCode",
"StringEquals": "RUNNING",
"Next": "Wait2秒"
},
{
"Variable": "$.describe.statusCode",
"StringEquals": "PENDING",
"Next": "Fail"
}
]
},
"Wait2秒": {
"Type": "Wait",
"Seconds": 2,
"Next": "DescribeExportTasks"
},
"Fail": {
"Type": "Fail",
"Cause": "Export task failed or unknown status"
},
"成功": {
"Type": "Succeed"
}
}
}
Discussion