🔰

【自分用】【備忘録】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

https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/TemplateReference/aws-resource-stepfunctions-statemachine.html#aws-resource-stepfunctions-statemachine--examples--Including_Tags

プロパティ名 項目説明 設定根拠
Type リソース種別(Step Functionsステートマシン) CloudFormationでステートマシンを作成するには AWS::StepFunctions::StateMachine を指定する必要がある
Definition ステートマシンの定義全体 ステートの開始点やフロー、タスク処理を記述するブロック
Definition > StartAt ステートマシン開始ステートの名前 ステートマシンがどのステートから処理を開始するかを指定
Definition > States ステート群の定義 複数のタスクや条件分岐など、処理の流れを明示する
Type(内側) 個々のステートの種別(例:Task) ECSや他のStep Functionsの呼び出しに対応するため Task を指定
Resource ステートが実行するリソースのARN ecs:runTask.syncstates: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時間)が適用されるため明示指定が必要

・公式ドキュメント
再試行ポリシー
https://docs.aws.amazon.com/ja_jp/scheduler/latest/APIReference/API_RetryPolicy.html

When retry policy is not specified, the scheduler uses a default MaximumEventAgeInSeconds of 86400 seconds (24 hours).
https://docs.aws.amazon.com/AWSCloudFormation/latest/TemplateReference/aws-resource-scheduler-schedulegroup.html
https://docs.aws.amazon.com/AWSCloudFormation/latest/TemplateReference/aws-resource-scheduler-schedule.html

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

https://docs.aws.amazon.com/cli/latest/reference/scheduler/list-schedules.html

・特定のスケジュールグループの定義情報をテーブル形式で抽出するコマンド

aws scheduler list-schedule-groups \
  --region ap-northeast-1 \
  --query "ScheduleGroups[?Name=='my-schedule-group'].[Name, Arn, CreationDate, LastModificationDate, State]" \
  --output table

https://docs.aws.amazon.com/cli/latest/reference/scheduler/list-schedule-groups.html

・タグ確認コマンド

aws scheduler list-tags-for-resource \
  --resource-arn arn:aws:scheduler:ap-northeast-1:{アカウントID}:schedule-group/my-schedule-group

https://docs.aws.amazon.com/cli/latest/reference/scheduler/list-tags-for-resource.html

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ルールで検知します。

デフォルトで開く:イベントが作成されている期間中は、タスク実行がブロックされる。
デフォルトで閉じる:イベントが作成されている期間中に、タスクが実行可能。

https://docs.aws.amazon.com/AWSCloudFormation/latest/TemplateReference/aws-resource-ssm-document.html

https://qiita.com/SawaShuya/items/b8542939b4ac8b992c66

https://docs.aws.amazon.com/ja_jp/systems-manager/latest/userguide/systems-manager-change-calendar.html

https://techblog.ap-com.co.jp/entry/2024/05/17/093000_1

https://dev.classmethod.jp/articles/linking-aws-step-functions-and-aws-systems-manager-change-calendar-to-handle-irregularities-in-periodic-execution-processes/

https://dev.classmethod.jp/articles/tips-for-migrating-job-management-systems-to-aws-step-functions/

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サポートされていない
https://icalendar.org/

セクション 項目 内容・説明
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で取得)
ExportName 2413882-job-control-calendar:他テンプレートから参照できる名前
観点 内容
タイムゾーン DTSTARTDTENDUTC (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

  1. 概念の整理
    ●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