👍
Step FunctionsでMapを使用して配列を順次ループする
AWS Step Functionsでループ処理は簡単に書けないものと思っていましたが、Map
を使用して順番にループ処理することができました。Map
ステートは「動的並列処理」のために使用されるステートですが、直列(?)の反復処理にも普通に使えました。
簡単なサンプル
入力としてJSON配列を受け取り、要素をループし、それぞれ10秒待つだけの簡単なステートマシンを作成してみます。
CloudFormationテンプレート
配列を1つずつ処理するには、Map
ステートで"MaxConcurrency": 1
と指定します。
AWSTemplateFormatVersion: 2010-09-09
Description: Create a simple loop state machine
Resources:
LoopMachine:
Type: AWS::StepFunctions::StateMachine
Properties:
StateMachineName: LoopMachine
StateMachineType: STANDARD
RoleArn: !GetAtt LoopMachineRole.Arn
LoggingConfiguration:
Level: 'OFF'
TracingConfiguration:
Enabled: false
EncryptionConfiguration:
Type: AWS_OWNED_KEY
Definition:
{
"Comment": "A simple loop state machine",
"StartAt": "IterateJsonArray",
"States": {
"IterateJsonArray": {
"Type": "Map",
"ItemProcessor": {
"ProcessorConfig": {
"Mode": "INLINE"
},
"StartAt": "Wait10Seconds",
"States": {
"Wait10Seconds": {
"Type": "Wait",
"Seconds": 10,
"End": true
}
}
},
"End": true,
"MaxConcurrency": 1
}
}
}
LoopMachineRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Principal:
Service:
- states.amazonaws.com
Action:
- sts:AssumeRole
実行結果
以下の要素数4の配列を入力として実行してみます。
[1, "two", {"number": 3}, "四"]
実行結果は以下のとおりとなりました。配列の順番どおりに1つずつ実行され、各イテレーションで10秒ずつ待つため全体で40秒掛かったことが確認できました。
少し実践的なサンプル
より実践的な例を次に示します。
最初のステートで起動中のEC2インスタンス一覧を取得し、それをループ処理して各インスタンスを停止してみます。
CloudFormationテンプレート
AWSTemplateFormatVersion: 2010-09-09
Description: Create a simple loop state machine
Resources:
LoopMachine:
Type: AWS::StepFunctions::StateMachine
Properties:
StateMachineName: LoopMachine
StateMachineType: STANDARD
RoleArn: !GetAtt LoopMachineRole.Arn
LoggingConfiguration:
Level: 'OFF'
TracingConfiguration:
Enabled: false
EncryptionConfiguration:
Type: AWS_OWNED_KEY
Definition:
{
"Comment": "A simple loop state machine",
"StartAt": "DescribeInstances",
"States": {
"DescribeInstances": {
"Type": "Task",
"Resource": "arn:aws:states:::aws-sdk:ec2:describeInstances",
"Parameters": {
"Filters": [
{
"Name": "instance-state-name",
"Values": [
"running"
]
}
]
},
"OutputPath": "$.Reservations[*].Instances[*]",
"Next": "ForInstanceInInstances"
},
"ForInstanceInInstances": {
"Type": "Map",
"ItemProcessor": {
"ProcessorConfig": {
"Mode": "INLINE"
},
"StartAt": "StopInstance",
"States": {
"StopInstance": {
"Type": "Task",
"Resource": "arn:aws:states:::aws-sdk:ec2:stopInstances",
"Parameters": {
"InstanceIds.$": "States.Array($.InstanceId)"
},
"End": true
}
}
},
"End": true,
"MaxConcurrency": 1
}
}
}
LoopMachineRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Principal:
Service:
- states.amazonaws.com
Action:
- sts:AssumeRole
Policies:
- PolicyName: EC2AccessPolicy
PolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Action:
- ec2:DescribeInstances
- ec2:StopInstances
Resource:
- '*'
解説(メモ)
-
Parameters
のFilters
、InstanceIds
:-
describeInstances
とstopInstances
のAPIリファレンスを見ても、それぞれFilter
とInstanceId
というパラメーターしか記載されていませんが、配列を渡すパラメーターは複数形にすることになっています。参考:Step Functions APIのサービスへのパラメータの受け渡し
-
-
"OutputPath": "$.Reservations[*].Instances[*]"
:-
describeInstances
アクションで以下のような入れ子の配列が返されるため、これを平坦化します。{ "Reservations": [ { "Instances": [ ... ], ... }, ... ] }
-
該当するインスタンスがないときはタスク結果が
{"Reservations": []}
になりますが、その場合でもエラーにならず[]
という期待する出力が得られました。
-
-
"InstanceIds.$": "States.Array($.InstanceId)"
:-
InstanceIds.$
は、InstanceIds
の値にJSONPathが使用されることを示します。参考:パスを使用して状態入力をパラメータとして渡す -
"InstanceIds.$": ["$.InstanceId"]
と書くと「値がJSONPathでない」と怒られてしまい、"InstanceIds.$": "$.InstanceId"
と書くと「値が配列でない」と怒られてしまうため、States.Array()
を使用します。参考:配列の組み込み関数
-
実行結果
省略。
想像どおり、起動中のインスタンスがすべて停止されました。
次のステップ
本記事で示した例では、簡単のためリトライやエラー処理を省略しています。例えば、ループの途中で失敗があった場合、既定ではその時点でステートマシン全体が失敗となるため、これを防ぎたい場合はエラーのキャッチを実装する必要があります。
Discussion