概要
- ECSタスクを起動するStepFunctionsのステートマシンを作りたい
- ステートマシンは任意のコマンドをECSタスクに渡せるものとする
- CDKでステートマシンを作成する際に、ECSタスクを同時に作成するか既に作成しているかで実装コードが違う
作りたいもの
StepFunctionsのステートマシンからECSタスクを起動したい。
ここではCDKを使って構築する。
ECSタスク定義もCDKで生成した場合
以下コードのECSタスク関連のプロパティは別途CDKで生成したリソースのインスタンスを設定しているとする。
ステートマシンのIAMロールとポリシーは自動で作られるのでコードを書く必要はない。
// コンテナの実行コマンド上書きに使う、taskDefinitionはリソースをCDKで作成済み
const containerDefinition = taskDefinition.findContainer('hoge')
if (!containerDefinition) {
throw new Error(`Not Found container hoge`)
}
// ステートマシンのECSタスクを実行するステート
const state = new tasks.EcsRunTask(this, 'hogefuga-runFargate', {
integrationPattern: sfn.IntegrationPattern.RUN_JOB,
cluster, // リソースをCDKで作成済み
taskDefinition, // リソースをCDKで作成済み
securityGroups, // リソースをCDKで作成済み
containerOverrides: [ // 入力値によりコンテナの実行コマンドを切り替えている
{
containerDefinition,
command: sfn.JsonPath.listAt('$.command')
}
],
launchTarget: new tasks.EcsFargateLaunchTarget()
})
const sf = new sfn.StateMachine(this, 'hogefuga-StateMachine', {
definition: state
})
生成したステートマシンのIAMロールには以下のポリシーが付与される。
{
"Version": "2012-10-17",
"Statement": [
{
"Action": "ecs:RunTask",
"Resource": "arn:aws:ecs:ap-northeast-1:XXXXXXXXXXXX:task-definition/hogefuga",
"Effect": "Allow"
},
{
"Action": [
"ecs:StopTask",
"ecs:DescribeTasks"
],
"Resource": "*",
"Effect": "Allow"
},
{
"Action": "iam:PassRole",
"Resource": [
"arn:aws:iam::XXXXXXXXXXXX:role/hogefuga-taskRole",
"arn:aws:iam::XXXXXXXXXXXX:role/hogefuga-executionRole"
],
"Effect": "Allow"
},
{
"Action": [
"events:PutTargets",
"events:PutRule",
"events:DescribeRule"
],
"Resource": "arn:aws:events:ap-northeast-1:XXXXXXXXXXXX:rule/StepFunctionsGetEventsForECSTaskRule",
"Effect": "Allow"
}
]
}
余談
StateMachinePropsの definition
はaws-cdkの v2.85.0
からdeprecatedになった。
新しいプロパティ definitionBody
により文字列とファイルでステートマシンの定義を設定できるようになった。
今までの State
(上のコードの new tasks.EcsRunTask()
)を使う場合は definitionBody: sfn.DefinitionBody.fromChainable(state)
とすればそのまま使える。
また、文字列とファイルによる定義の場合は自動的にIAMロールにポリシーが付与されないらしい。
ECSタスク定義はCDKで生成していない場合
以前から運用しているシステムではECSタスク定義をCDK管理していないケースがままある。
こういった場合はクラスターやタスク定義は既存のリソースから読み込む作業が必要になる。
ITaskDefinitionが使用できない
タスク定義を既存のリソースから読み込むのに使用する ecs.TaskDefinition.fromTaskDefinitionArn()
などの型は ITaskDefinition
になる。
今回のコードではこのインターフェースにないプロパティやメソッドを必要とするのでエラーになってしまう。
先ほどのコードのタスク定義のみ fromTaskDefinitionArn()
を使ったものに変更したコードが以下のもの。
// taskDefinitionArnからタスク定義を読み込む
const iTaskDefinition = ecs.TaskDefinition.fromTaskDefinitionArn(this, 'hoge-taskDefinition-arn', `${taskDefinition.taskDefinitionArn}`)
// コンテナの実行コマンド上書きに使う
const containerDefinition = iTaskDefinition.findContainer('hoge')
if (!containerDefinition) {
throw new Error(`Not Found container hoge`)
}
// ステートマシンのECSタスクを実行するステート
const ecsRunTask = new tasks.EcsRunTask(this, 'hogefuga-runFargate', {
integrationPattern: sfn.IntegrationPattern.RUN_JOB,
cluster: ecscluster,
taskDefinition: iTaskDefinition,
securityGroups,
containerOverrides: [ // 入力値によりコンテナの実行コマンドを切り替えている
{
containerDefinition,
command: sfn.JsonPath.listAt('$.command')
}
],
launchTarget: new tasks.EcsFargateLaunchTarget()
})
このコードをcdk diffすると、 findContainer()
で以下のようなエラーが発生する。
Type 'ITaskDefinition' is missing the following properties from type 'TaskDefinition': family, containers, volumes, placementConstraints, and 32 more.
findContainer()
をコメントアウトした場合でも、 new tasks.EcsRunTask()
で同様のエラーになるのだが、こちらのドキュメントには以下のように書かれている。
Note: this must be TaskDefinition, and not ITaskDefinition, as it requires properties that are not known for imported task definitions If you want to run a RunTask with an imported task definition, consider using CustomState
つまり、既存のECSタスク定義を参照するStepFunctionsのステートマシンではCustomStateを使う必要がある。
大抵のConstruct Propsはインターフェースのプロパティとメソッドを使っているが、これはその例外らしい。
CustomStateを使う
CustomStateを使うと Amazon States Language (ASL)
を使った形式でステートマシンを記述できる。
要はAWSコンソールでステートマシンの定義にアクセスしたときに表示される形式で記述する。
CustomStateを使ったコードは以下の通り。
クラスターでもタスク定義でもARNはインターフェースから取得できる、サブネットやセキュリティグループも同様。
const stateJson = {
Type: 'Task',
Resource: 'arn:aws:states:::ecs:runTask.sync',
Parameters: {
Cluster: 'arn:aws:ecs:ap-northeast-1:XXXXXXXXXXXX:cluster/hogefuga-cluster',
TaskDefinition: 'arn:aws:ecs:ap-northeast-1:XXXXXXXXXXXX:task-definition/hogefuga',
NetworkConfiguration: {
AwsvpcConfiguration: {
Subnets: Subnets.map((r) => r.subnetId),
SecurityGroups: SecurityGroups.map((r) => r.securityGroupId)
}
},
Overrides: {
ContainerOverrides: [
{
Name: 'hoge',
'Command.$': '$.command'
}
]
},
LaunchType: 'FARGATE'
}
}
const state = new sfn.CustomState(this, 'hogefuga-runFargate', {
stateJson,
// role を設定しないとステートマシンのIAMロールにポリシーが付与されない
})
付与すべきIAMポリシーについて
CustomStateであっても必要なIAMポリシーはtasks.EcsRunTaskを使った場合のポリシー一覧と同じになる。
今回作成しているStandardのステートマシンではステートが終了するまで処理を待つことができ、そのためにはEventBridgeを使用しているらしい。
そしてその為には以下の部分が必要になる。
{
"Action": [
"events:PutTargets",
"events:PutRule",
"events:DescribeRule"
],
"Resource": "arn:aws:events:ap-northeast-1:XXXXXXXXXXXX:rule/StepFunctionsGetEventsForECSTaskRule",
"Effect": "Allow"
}
rule/StepFunctionsGetEventsForECSTaskRule
の部分はターゲットのリソースによって異なる。
例えば AWS Batch
をターゲットにする場合は rule/StepFunctionsGetEventsForBatchJobsRule
にする必要がある。
どのポリシーを設定すべきかは、上のドキュメントの IAM ロールの作成に使用されるポリシーテンプレート
の トピック
のリンク先で確認できる。
ここの設定を間違えると以下のようなエラーが発生することがある。
Resource handler returned message: "'arn:aws:iam::XXXXXXXXXX:role/hogefuga-role' is not a
uthorized to create managed-rule.
(Service: AWSStepFunctions; Status Code: 400; Error Code: AccessDeniedException;
Request ID: XXXXXXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX;
Proxy: null)"
(RequestToken: XXXXXXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX, HandlerErrorCode: AccessDenied)
まとめ
StepFunctionsでECSタスクを呼び出す場合、インターフェースが使えないのでターゲットのECSタスク定義のCDK管理状態によって対応する方法が大きく異なってしまう。