これは、なにをしたくて書いたもの?
こちらのエントリーでAWS Step Functions Localを試してみました。
AWS Step Functionsをローカルで試す(AWS Step Functions Localを動かしてみる) - CLOVER🍀
この時はとりあえず動かしただけなので、今度はAWS Lambdaと組み合わせてみましょう。AWS Step Functionsのチュートリアルのうち、
AWS Lambdaと組み合わせているものを少し選んでみます。
AWS Lambdaの関数はAWS SAMで作成することにします。
対象とするチュートリアル
AWS Step Functionsには慣れていないので、今回はドキュメントに沿って試してみるのがよいでしょう。
対象はこちらにします。
チュートリアル: Step Functions と を使用したワークフローのテスト AWS SAM CLI ローカル - AWS Step Functions
Lambda を使用する Step Functions ステートマシン状態の作成 - AWS Step Functions
Step Functions で Lambda 関数を使用してループを反復する - AWS Step Functions
これらをAWS Step Functions LocalとAWS SAMで試していこうと思います。
AWS Lambda関数はPythonで作成することにします。
環境
今回の環境はこちら。
$ java --version openjdk 21.0.4 2024-07-16 OpenJDK Runtime Environment (build 21.0.4+7-Ubuntu-1ubuntu222.04) OpenJDK 64-Bit Server VM (build 21.0.4+7-Ubuntu-1ubuntu222.04, mixed mode, sharing) $ python3 --version Python 3.10.12 $ pip3 --version pip 22.0.2 from /usr/lib/python3/dist-packages/pip (python 3.10) $ aws --version aws-cli/2.19.4 Python/3.12.6 Linux/5.15.0-125-generic exe/x86_64.ubuntu.22 $ sam --version SAM CLI, version 1.127.0
AWS CLI、AWS SAM用のクレデンシャルは以下で設定。
$ export AWS_ACCESS_KEY_ID=test $ export AWS_SECRET_ACCESS_KEY=test $ export AWS_DEFAULT_REGION=us-east-1
AWS Step Functions Local。
$ java -jar StepFunctionsLocal.jar --version Step Functions Local Version: 2.0.0 Build: 2024-05-18 Step Functions Local Version: 2.0.0 Build: 2024-05-18
クレデンシャルはこうしておきます。
$ export AWS_ACCESS_KEY_ID=test $ export AWS_SECRET_ACCESS_KEY=test
AWS Step Functions Localのチュートリアルを試す
まずはこちらから試していこうと思います。
チュートリアル: Step Functions と を使用したワークフローのテスト AWS SAM CLI ローカル - AWS Step Functions
AWS SAMのPython 3.10向けのテンプレートを確認。
$ curl -s https://raw.githubusercontent.com/aws/aws-sam-cli-app-templates/master/manifest-v2.json | jq '."python3.10"[]'
{
"directory": "python3.10/hello",
"displayName": "Hello World Example",
"dependencyManager": "pip",
"appTemplate": "hello-world",
"packageType": "Zip",
"useCaseName": "Hello World Example"
}
{
"directory": "python3.10/hello-pt",
"displayName": "Hello World Example with Powertools for AWS Lambda",
"dependencyManager": "pip",
"appTemplate": "hello-world-powertools-python",
"packageType": "Zip",
"useCaseName": "Hello World Example with Powertools for AWS Lambda"
}
{
"directory": "python3.10/event-bridge",
"displayName": "EventBridge Hello World",
"dependencyManager": "pip",
"appTemplate": "eventBridge-hello-world",
"packageType": "Zip",
"useCaseName": "Infrastructure event management"
}
{
"directory": "python3.10/event-bridge-schema",
"displayName": "EventBridge App from scratch (100+ Event Schemas)",
"dependencyManager": "pip",
"appTemplate": "eventBridge-schema-app",
"isDynamicTemplate": "True",
"packageType": "Zip",
"useCaseName": "Infrastructure event management"
}
{
"directory": "python3.10/step-func",
"displayName": "Step Functions Sample App (Stock Trader)",
"dependencyManager": "pip",
"appTemplate": "step-functions-sample-app",
"packageType": "Zip",
"useCaseName": "Multi-step workflow"
}
{
"directory": "python3.10/efs",
"displayName": "Elastic File System Sample App",
"dependencyManager": "pip",
"appTemplate": "efs-sample-app",
"packageType": "Zip",
"useCaseName": "Lambda EFS example"
}
{
"directory": "python3.10/web-conn",
"displayName": "Quick Start: Web Backend With Connectors",
"dependencyManager": "pip",
"appTemplate": "hello-world-connector",
"packageType": "Zip",
"useCaseName": "Serverless Connector Hello World Example"
}
{
"directory": "python3.10/step-func-conn",
"displayName": "Step Functions Sample App (Stock Trader) With Connectors",
"dependencyManager": "pip",
"appTemplate": "step-functions-with-connectors",
"packageType": "Zip",
"useCaseName": "Multi-step workflow with Connectors"
}
プロジェクトを作成。
$ sam init --name sfn-local-sam-getting-started --runtime python3.10 --app-template hello-world --package-type Zip --no-tracing --no-application-insights --structured-logging
プロジェクト内に移動。
$ cd sfn-local-sam-getting-started
プロジェクト内はこんな感じですね。
$ tree
.
├── README.md
├── __init__.py
├── events
│ └── event.json
├── hello_world
│ ├── __init__.py
│ ├── app.py
│ └── requirements.txt
├── samconfig.toml
├── template.yaml
└── tests
├── __init__.py
├── integration
│ ├── __init__.py
│ └── test_api_gateway.py
├── requirements.txt
└── unit
├── __init__.py
└── test_handler.py
5 directories, 14 files
今回はあまりAWS SAMの説明はせず、AWS Lambda関数の部分だけにフォーカスしてみていきましょう。
用意されているAWS SAMのテンプレートおよびAWS Lambda関数のスケルトン実装を見てみます。
template.yaml
AWSTemplateFormatVersion: '2010-09-09' Transform: AWS::Serverless-2016-10-31 Description: > sfn-local-sam-getting-started Sample SAM Template for sfn-local-sam-getting-started # More info about Globals: https://github.com/awslabs/serverless-application-model/blob/master/docs/globals.rst Globals: Function: Timeout: 3 # You can add LoggingConfig parameters such as the Logformat, Log Group, and SystemLogLevel or ApplicationLogLevel. Learn more here https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-resource-function.html#sam-function-loggingconfig. LoggingConfig: LogFormat: JSON Resources: HelloWorldFunction: Type: AWS::Serverless::Function # More info about Function Resource: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction Properties: CodeUri: hello_world/ Handler: app.lambda_handler Runtime: python3.10 Architectures: - x86_64 Events: HelloWorld: Type: Api # More info about API Event Source: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#api Properties: Path: /hello Method: get Outputs: # ServerlessRestApi is an implicit API created out of Events key under Serverless::Function # Find out more about other implicit resources you can reference within SAM # https://github.com/awslabs/serverless-application-model/blob/master/docs/internals/generated_resources.rst#api HelloWorldApi: Description: API Gateway endpoint URL for Prod stage for Hello World function Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/hello/" HelloWorldFunction: Description: Hello World Lambda Function ARN Value: !GetAtt HelloWorldFunction.Arn HelloWorldFunctionIamRole: Description: Implicit IAM Role created for Hello World function Value: !GetAtt HelloWorldFunctionRole.Arn
hello_world/app.py
import json # import requests def lambda_handler(event, context): """Sample pure Lambda function Parameters ---------- event: dict, required API Gateway Lambda Proxy Input Format Event doc: https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html#api-gateway-simple-proxy-for-lambda-input-format context: object, required Lambda Context runtime methods and attributes Context doc: https://docs.aws.amazon.com/lambda/latest/dg/python-context-object.html Returns ------ API Gateway Lambda Proxy Output Format: dict Return doc: https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html """ # try: # ip = requests.get("http://checkip.amazonaws.com/") # except requests.RequestException as e: # # Send some context about this error to Lambda Logs # print(e) # raise e return { "statusCode": 200, "body": json.dumps({ "message": "hello world", # "location": ip.text.replace("\n", "") }), }
どう見てもAmazon API Gateway向けのものですね。
ローカルでAmazon API GatewayのエミュレーションをするHTTPサーバーを起動。
$ sam local start-api
3000ポートでリッスンします。
確認。
$ curl localhost:3000/hello
{"message": "hello world"}
1度停止して、AWS Lambdaの実行環境を起動。
$ sam local start-lambda
AWS Lambdaのエンドポイントを指定して、AWS Step Functions Localを起動。
$ java -jar StepFunctionsLocal.jar --lambda-endpoint http://localhost:3001
ログはこんな感じになりました。
Step Functions Local Version: 2.0.0 Build: 2024-05-18 2024-11-09 18:31:04.541: Configure [Lambda Endpoint] to [http://localhost:3001] 2024-11-09 18:31:04.545: Loaded credentials from environment 2024-11-09 18:31:05.221: Starting server on port 8083 with account 123456789012, region us-east-1 SLF4J: No SLF4J providers were found. SLF4J: Defaulting to no-operation (NOP) logger implementation SLF4J: See https://www.slf4j.org/codes.html#noProviders for further details.
ステートマシンを作成します。
$ aws --endpoint http://localhost:8083 stepfunctions create-state-machine --definition "{\
\"Comment\": \"A Hello World example of the Amazon States Language using an AWS Lambda Local function\",\
\"StartAt\": \"HelloWorld\",\
\"States\": {\
\"HelloWorld\": {\
\"Type\": \"Task\",\
\"Resource\": \"arn:aws:lambda:us-east-1:123456789012:function:HelloWorldFunction\",\
\"End\": true\
}\
}\
}" --name 'HelloWorld' --role-arn 'arn:aws:iam::012345678901:role/DummyRole'
作成できました。
{
"stateMachineArn": "arn:aws:states:us-east-1:123456789012:stateMachine:HelloWorld",
"creationDate": "2024-11-09T18:33:46.330000+09:00"
}
文字列ですが、ステートマシンの定義を見てみましょう。
{\
\"Comment\": \"A Hello World example of the Amazon States Language using an AWS Lambda Local function\",\
\"StartAt\": \"HelloWorld\",\
\"States\": {\
\"HelloWorld\": {\
\"Type\": \"Task\",\
\"Resource\": \"arn:aws:lambda:us-east-1:123456789012:function:HelloWorldFunction\",\
\"End\": true\
}\
}\
}
Amazon States Language(ASL)のリファレンスはこちら。
Step Functions ワークフローの Amazon States Language のステートマシン構造 - AWS Step Functions
Commentは文字通りコメント、StartAtはステートマシンが開始した際に最初に実行するステートを指定します。
ステートはStatesで定義されていて、「HelloWorld」というひとつのステートがあります。
\"States\": {\
\"HelloWorld\": {\
\"Type\": \"Task\",\
\"Resource\": \"arn:aws:lambda:us-east-1:123456789012:function:HelloWorldFunction\",\
\"End\": true\
}\
}\
これはAWS Lambdaなどを呼び出せるステートですね。
タスクワークフローの状態 - AWS Step Functions
Endがtrueになっているので、このステートが終了するとこのステートマシンが終了します。
Resourceは呼び出すAWSリソースのARNを指定します。今回はAWS Lambda関数ですね。
今回はAWS Lambda関数をデプロイしているわけではありませんが、sam local start-lambdaで起動したAWS Lambda関数環境内に含まれる
リソース名と一致しているので、これで呼び出せるんだと思います。
Resources: HelloWorldFunction: Type: AWS::Serverless::Function # More info about Function Resource: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction
つまり、こういう構図になるわけですね。
flowchart LR
A[AWS CLI] --> |start-execution| B[AWS Step Functions Local]
B --> |invoke| C[AWS SAM start-lambda]
C --> B
B --> A
では、ステートマシンを実行してみましょう。
$ aws --endpoint http://localhost:8083 stepfunctions start-execution --state-machine arn:aws:states:us-east-1:123456789012:stateMachine:HelloWorld --name test
AWS Step Functions Localにはこんなログが出力されました。
2024-11-09 18:43:54.121: StartExecution => {"requestClientOptions":{"readLimit":131073,"skipAppendUriPath":false},"requestMetricCollector":null,"customRequestHeaders":null,"customQueryParameters":null,"cloneSource":null,"sdkRequestTimeout":null,"sdkClientExecutionTimeout":null,"stateMachineArn":"arn:aws:states:us-east-1:123456789012:stateMachine:HelloWorld","name":"test","input":null,"traceHeader":null,"requestCredentialsProvider":null,"requestCredentials":null,"generalProgressListener":{"syncCallSafe":true},"readLimit":131073,"cloneRoot":null}
2024-11-09 18:43:54.470: arn:aws:states:us-east-1:123456789012:execution:HelloWorld:test : {"Type":"ExecutionStarted","PreviousEventId":0,"ExecutionStartedEventDetails":{"Input":"{}","RoleArn":"arn:aws:iam::012345678901:role/DummyRole"}}
2024-11-09 18:43:54.471: arn:aws:states:us-east-1:123456789012:execution:HelloWorld:test : {"Type":"TaskStateEntered","PreviousEventId":0,"StateEnteredEventDetails":{"Name":"HelloWorld","Input":"{}"}}
2024-11-09 18:43:54.475: [200] StartExecution <= {"sdkResponseMetadata":null,"sdkHttpMetadata":null,"executionArn":"arn:aws:states:us-east-1:123456789012:execution:HelloWorld:test","startDate":1731145434441}
2024-11-09 18:43:54.507: arn:aws:states:us-east-1:123456789012:execution:HelloWorld:test : {"Type":"LambdaFunctionScheduled","PreviousEventId":2,"LambdaFunctionScheduledEventDetails":{"Resource":"arn:aws:lambda:us-east-1:123456789012:function:HelloWorldFunction","Input":"{}"}}
2024-11-09 18:43:54.508: arn:aws:states:us-east-1:123456789012:execution:HelloWorld:test : {"Type":"LambdaFunctionStarted","PreviousEventId":3}
2024-11-09 18:43:55.919: arn:aws:states:us-east-1:123456789012:execution:HelloWorld:test : {"Type":"LambdaFunctionSucceeded","PreviousEventId":4,"LambdaFunctionSucceededEventDetails":{"Output":"{\"statusCode\": 200, \"body\": \"{\\\"message\\\": \\\"hello world\\\"}\"}"}}
2024-11-09 18:43:55.920: arn:aws:states:us-east-1:123456789012:execution:HelloWorld:test : {"Type":"TaskStateExited","PreviousEventId":5,"StateExitedEventDetails":{"Name":"HelloWorld","Output":"{\"statusCode\": 200, \"body\": \"{\\\"message\\\": \\\"hello world\\\"}\"}"}}
2024-11-09 18:43:55.922: arn:aws:states:us-east-1:123456789012:execution:HelloWorld:test : {"Type":"ExecutionSucceeded","PreviousEventId":6,"ExecutionSucceededEventDetails":{"Output":"{\"statusCode\": 200, \"body\": \"{\\\"message\\\": \\\"hello world\\\"}\"}"}}
AWS SAM側にはこんなログが出力されました。
2024-11-09 18:43:55 127.0.0.1 - - [09/Nov/2024 18:43:55] "POST /2015-03-31/functions/HelloWorldFunction/invocations HTTP/1.1" 200 -
動いたようです。
結果を確認。
$ aws --endpoint http://localhost:8083 stepfunctions describe-execution --execution-arn arn:aws:states:us-east-1:123456789012:execution:HelloWorld:test
OKですね。
{ "executionArn": "arn:aws:states:us-east-1:123456789012:execution:HelloWorld:test", "stateMachineArn": "arn:aws:states:us-east-1:123456789012:stateMachine:HelloWorld", "name": "test", "status": "SUCCEEDED", "startDate": "2024-11-09T18:43:54.441000+09:00", "stopDate": "2024-11-09T18:43:55.923000+09:00", "input": "{}", "inputDetails": { "included": true }, "output": "{\"statusCode\": 200, \"body\": \"{\\\"message\\\": \\\"hello world\\\"}\"}", "outputDetails": { "included": true } }
outputを見ると、AWS Lambda関数の戻り値が含まれていることが確認できます。
これで最初のチュートリアルは確認できました。
AWS Step FunctionsのAWS Lambdaのチュートリアルを試す
次は、こちらのチュートリアルを試してみます。
Lambda を使用する Step Functions ステートマシン状態の作成 - AWS Step Functions
パッと見、先程のAWS Lambdaの例と変わらないのではと思いましたが、呼び出し時にペイロードを取るパターンですね。
簡単に試しておきましょう。
先程のAWS Step Functions LocalとAWS SAMのプロセスは終了し、新しく作り直します。ここから先は、すべて別々にアプリケーションを
作成して行うことにします。
AWS SAMプロジェクトを作成。
$ sam init --name sfn-local-invoke-lambda --runtime python3.10 --app-template hello-world --package-type Zip --no-tracing --no-application-insights --structured-logging $ cd sfn-local-invoke-lambda
AWS Lambda関数は、こんな感じに修正。
hello_world/app.py
def lambda_handler(event, context): return f'Hello from {event["who"]}'
AWS SAMでAWS Lambda環境を、そしてAWS Step Functions Localを起動します。
$ sam local start-lambda $ java -jar StepFunctionsLocal.jar --lambda-endpoint http://localhost:3001
ステートマシンを作成します。定義自体は、先ほどと同じですね。
$ aws --endpoint http://localhost:8083 stepfunctions create-state-machine --definition "{\
\"Comment\": \"A Hello World example of the Amazon States Language using an AWS Lambda Local function\",\
\"StartAt\": \"HelloWorld\",\
\"States\": {\
\"HelloWorld\": {\
\"Type\": \"Task\",\
\"Resource\": \"arn:aws:lambda:us-east-1:123456789012:function:HelloWorldFunction\",\
\"End\": true\
}\
}\
}" --name 'HelloWorld' --role-arn 'arn:aws:iam::012345678901:role/DummyRole'
ステートマシンを実行します。
$ aws --endpoint http://localhost:8083 stepfunctions start-execution --state-machine arn:aws:states:us-east-1:123456789012:stateMachine:HelloWorld --name test --input '{"who": "AWS Step Functions"}'
{
"executionArn": "arn:aws:states:us-east-1:123456789012:execution:HelloWorld:test",
"startDate": "2024-11-09T19:05:56.723000+09:00"
}
この時、--input '{"who": "AWS Step Functions"}'で入力を与えます。
実行結果を確認。
$ aws --endpoint http://localhost:8083 stepfunctions describe-execution --execution-arn arn:aws:states:us-east-1:123456789012:execution:HelloWorld:test
{
"executionArn": "arn:aws:states:us-east-1:123456789012:execution:HelloWorld:test",
"stateMachineArn": "arn:aws:states:us-east-1:123456789012:stateMachine:HelloWorld",
"name": "test",
"status": "SUCCEEDED",
"startDate": "2024-11-09T19:05:56.723000+09:00",
"stopDate": "2024-11-09T19:05:57.284000+09:00",
"input": "{\"who\": \"AWS Step Functions\"}",
"inputDetails": {
"included": true
},
"output": "\"Hello from AWS Step Functions\"",
"outputDetails": {
"included": true
}
}
outputを見ると、AWS Lambdaに--inputで渡した値が引き継がれていることがわかります。
--inputを変えて実行してみましょう。
$ aws --endpoint http://localhost:8083 stepfunctions start-execution --state-machine arn:aws:states:us-east-1:123456789012:stateMachine:HelloWorld --name test-name --input '{"who": "AWS Lambda"}'
{
"executionArn": "arn:aws:states:us-east-1:123456789012:execution:HelloWorld:test-name",
"startDate": "2024-11-09T19:09:53.368000+09:00"
}
この時、--nameの値を変えてあげないとすでに同じ実行が存在するのでエラーになります。
$ aws --endpoint http://localhost:8083 stepfunctions start-execution --state-machine arn:aws:states:us-east-1:123456789012:stateMachine:HelloWorld --name test --input '{"who": "AWS Lambda"}'
An error occurred (ExecutionAlreadyExists) when calling the StartExecution operation: Execution Already Exists: 'arn:aws:states:us-east-1:123456789012:execution:HelloWorld:test'
結果の確認。
$ aws --endpoint http://localhost:8083 stepfunctions describe-execution --execution-arn arn:aws:states:us-east-1:123456789012:execution:HelloWorld:test-name
{
"executionArn": "arn:aws:states:us-east-1:123456789012:execution:HelloWorld:test-name",
"stateMachineArn": "arn:aws:states:us-east-1:123456789012:stateMachine:HelloWorld",
"name": "test-name",
"status": "SUCCEEDED",
"startDate": "2024-11-09T19:09:53.368000+09:00",
"stopDate": "2024-11-09T19:09:53.399000+09:00",
"input": "{\"who\": \"AWS Lambda\"}",
"inputDetails": {
"included": true
},
"output": "\"Hello from AWS Lambda\"",
"outputDetails": {
"included": true
}
}
OKですね。
AWS Step FunctionsのAWS Lambdaのループのチュートリアルを試す
次は、AWS Step FunctionsとAWS Lambda関数でループを試してみます。
Step Functions で Lambda 関数を使用してループを反復する - AWS Step Functions
まずはAWS SAMプロジェクトを作成。
$ sam init --name sfn-local-loop-lambda --runtime python3.10 --app-template hello-world --package-type Zip --no-tracing --no-application-insights --structured-logging $ cd sfn-local-loop-lambda
AWS Lambda関数は、こうしました。
hello_world/app.py
def lambda_handler(event, context): iterator = event["iterator"] index = iterator["index"] step = iterator["step"] count = iterator["count"] print(f"index = {index}, step = {step}, count = {count}, event = {event}") index = index + step return { "index": index, "step": step, "count": count, "continue": count > index }
AWS SAMのAWS Lambda環境と、AWS Step Functions Localを起動。
$ sam local start-lambda $ java -jar StepFunctionsLocal.jar --lambda-endpoint http://localhost:3001
ステートマシンを作成します。定義はこちらです。
{ "Comment": "Iterator State Machine Example", "StartAt": "ConfigureCount", "States": { "ConfigureCount": { "Type": "Pass", "Result": { "count": 10, "index": 0, "step": 1 }, "ResultPath": "$.iterator", "Next": "Iterator" }, "Iterator": { "Type": "Task", "Resource": "arn:aws:lambda:us-east-1:123456789012:function:Iterate", "ResultPath": "$.iterator", "Next": "IsCountReached" }, "IsCountReached": { "Type": "Choice", "Choices": [ { "Variable": "$.iterator.continue", "BooleanEquals": true, "Next": "ExampleWork" } ], "Default": "Done" }, "ExampleWork": { "Comment": "Your application logic, to run a specific number of times", "Type": "Pass", "Result": { "success": true }, "ResultPath": "$.result", "Next": "Iterator" }, "Done": { "Type": "Pass", "End": true } } }
ここまでとだいぶ定義が違います。
スタートになるステートは「ConfigureCount」のようです。
"StartAt": "ConfigureCount",
こちらですね。
"ConfigureCount": { "Type": "Pass", "Result": { "count": 10, "index": 0, "step": 1 }, "ResultPath": "$.iterator", "Next": "Iterator" },
TypeがPassのステートは、なにもせずに入力を出力に渡すステートのようです。
ワークフロー状態を渡す - AWS Step Functions
Resultはこのステートの出力です。
"Result": { "count": 10, "index": 0, "step": 1 },
これをResultPathで出力位置を指定します。
"ResultPath": "$.iterator",
つまり、このステートの出力は以下になるというわけですね。作成したAWS Lambda関数が入力として期待している構造になっています。
{ "iterator": { "count": 10, "index": 0, "step": 1 } }
Step Functions での入力と出力の処理 - AWS Step Functions
NextはIteratorなので、次はIteratorのステート定義を見てみましょう。
こちらはTaskステートです。先ほど作成したAWS Lambdaを呼び出すようにARNを調整すればよさそうですね。
"Iterator": { "Type": "Task", "Resource": "arn:aws:lambda:us-east-1:123456789012:function:Iterate", "ResultPath": "$.iterator", "Next": "IsCountReached" },
ResultPathでは、関数の結果を指定のパスに入れます。つまり、こうなるわけですね。
{ "iterator": { "count": 10, "index": 0, "step": 1 } }
やっぱり作成したAWS Lambda関数が期待する構造になっています。
NextはIsCountReachedです。
"IsCountReached": { "Type": "Choice", "Choices": [ { "Variable": "$.iterator.continue", "BooleanEquals": true, "Next": "ExampleWork" } ], "Default": "Done" },
今度はChoiceというステートになりました。これは、条件によって分岐を行うステートのようですね。
選択ワークフローの状態 - AWS Step Functions
こちらを見ると、$.iterator.continueの値がtrueであればExampleWorkステートに進むようです。
"Choices": [ { "Variable": "$.iterator.continue", "BooleanEquals": true, "Next": "ExampleWork" } ],
DefaultはDoneになっていて、Choicesの条件に一致しなかった場合はこちらの実行に移ります。
"Done": { "Type": "Pass", "End": true }
こちらはPassステートで終了ですね。
ExampleWorkステートはこちらもPassステートで、Iteratorステートに遷移します。
"ExampleWork": { "Comment": "Your application logic, to run a specific number of times", "Type": "Pass", "Result": { "success": true }, "ResultPath": "$.result", "Next": "Iterator" },
つまり、これでループを行っているわけですね。

では、AWS SAMでAWS Lambda環境と、AWS Step Functions Localを起動します。
$ sam local start-lambda $ java -jar StepFunctionsLocal.jar --lambda-endpoint http://localhost:3001
ステートマシンを作成。
$ aws --endpoint http://localhost:8083 stepfunctions create-state-machine --definition "{\
\"Comment\": \"Iterator State Machine Example\",\
\"StartAt\": \"ConfigureCount\",\
\"States\": {\
\
\"ConfigureCount\": {\
\"Type\": \"Pass\",\
\"Result\": {\
\"count\": 10,\
\"index\": 0,\
\"step\": 1\
},\
\"ResultPath\": \"$.iterator\",\
\"Next\": \"Iterator\"\
},\
\"Iterator\": {\
\"Type\": \"Task\",\
\"Resource\": \"arn:aws:lambda:us-east-1:123456789012:function:HelloWorldFunction\",\
\"ResultPath\": \"$.iterator\",\
\"Next\": \"IsCountReached\"\
},\
\"IsCountReached\": {\
\"Type\": \"Choice\",\
\"Choices\": [\
{\
\"Variable\": \"$.iterator.continue\",\
\"BooleanEquals\": true,\
\"Next\": \"ExampleWork\"\
}\
],\
\"Default\": \"Done\"\
},\
\"ExampleWork\": {\
\"Comment\": \"Your application logic, to run a specific number of times\",\
\"Type\": \"Pass\",\
\"Result\": {\
\"success\": true\
},\
\"ResultPath\": \"$.result\",\
\"Next\": \"Iterator\"\
},\
\"Done\": {\
\"Type\": \"Pass\",\
\"End\": true\
}\
}\
}" --name "Loop" --role-arn "arn:aws:iam::012345678901:role/DummyRole"
AWS Lambda関数のARNはHelloWorldFunctionに合わせています。
\"Iterator\": {\ \"Type\": \"Task\",\ \"Resource\": \"arn:aws:lambda:us-east-1:123456789012:function:HelloWorldFunction\",\ \"ResultPath\": \"$.iterator\",\ \"Next\": \"IsCountReached\"\ },\
作成したステートマシンのARN。
{
"stateMachineArn": "arn:aws:states:us-east-1:123456789012:stateMachine:Loop",
"creationDate": "2024-11-09T20:49:08.180000+09:00"
}
ステートマシンを実行します。
$ aws --endpoint http://localhost:8083 stepfunctions start-execution --state-machine arn:aws:states:us-east-1:123456789012:stateMachine:Loop --name loop
{
"executionArn": "arn:aws:states:us-east-1:123456789012:execution:Loop:loop",
"startDate": "2024-11-09T20:51:18.239000+09:00"
}
この時のAWS SAM側のログ。AWS Lambdaの実行ログですね。
Invoking app.lambda_handler (python3.10)
Reuse the created warm container for Lambda function 'HelloWorldFunction'
Lambda function 'HelloWorldFunction' is already running
index = 0, step = 1, count = 10, event = {'iterator': {'count': 10, 'index': 0, 'step': 1}}
END RequestId: eca096a4-56e0-4f08-a14b-b1b54c814ee2
REPORT RequestId: eca096a4-56e0-4f08-a14b-b1b54c814ee2 Init Duration: 0.05 ms Duration: 52.59 ms Billed Duration: 53 ms Memory Size: 128 MB Max Memory Used: 128 MB
2024-11-09 20:51:18 127.0.0.1 - - [09/Nov/2024 20:51:18] "POST /2015-03-31/functions/HelloWorldFunction/invocations HTTP/1.1" 200 -
Invoking app.lambda_handler (python3.10)
Reuse the created warm container for Lambda function 'HelloWorldFunction'
Lambda function 'HelloWorldFunction' is already running
START RequestId: d4fb2a1b-6e96-4464-9f23-854dbe5ec213 Version: $LATEST
index = 1, step = 1, count = 10, event = {'iterator': {'index': 1, 'step': 1, 'count': 10, 'continue': True}, 'result': {'success': True}}
END RequestId: 35f21479-50c7-4329-b446-565303fb48a0
REPORT RequestId: 35f21479-50c7-4329-b446-565303fb48a0 Duration: 1.33 ms Billed Duration: 2 ms Memory Size: 128 MB Max Memory Used: 128 MB
2024-11-09 20:51:18 127.0.0.1 - - [09/Nov/2024 20:51:18] "POST /2015-03-31/functions/HelloWorldFunction/invocations HTTP/1.1" 200 -
Invoking app.lambda_handler (python3.10)
Reuse the created warm container for Lambda function 'HelloWorldFunction'
Lambda function 'HelloWorldFunction' is already running
START RequestId: 1d3a97af-6df5-4693-b97b-666e461c4183 Version: $LATEST
index = 2, step = 1, count = 10, event = {'iterator': {'index': 2, 'step': 1, 'count': 10, 'continue': True}, 'result': {'success': True}}
END RequestId: d79f784a-e872-40ff-9a46-49e281dc53c3
REPORT RequestId: d79f784a-e872-40ff-9a46-49e281dc53c3 Duration: 1.27 ms Billed Duration: 2 ms Memory Size: 128 MB Max Memory Used: 128 MB
2024-11-09 20:51:18 127.0.0.1 - - [09/Nov/2024 20:51:18] "POST /2015-03-31/functions/HelloWorldFunction/invocations HTTP/1.1" 200 -
Invoking app.lambda_handler (python3.10)
Reuse the created warm container for Lambda function 'HelloWorldFunction'
Lambda function 'HelloWorldFunction' is already running
START RequestId: 53d19d9c-9dae-41e5-9243-84a3c96cd418 Version: $LATEST
index = 3, step = 1, count = 10, event = {'iterator': {'index': 3, 'step': 1, 'count': 10, 'continue': True}, 'result': {'success': True}}
END RequestId: c117cc83-dfb7-4bb6-8092-776ac05ab64c
REPORT RequestId: c117cc83-dfb7-4bb6-8092-776ac05ab64c Duration: 0.93 ms Billed Duration: 1 ms Memory Size: 128 MB Max Memory Used: 128 MB
2024-11-09 20:51:18 127.0.0.1 - - [09/Nov/2024 20:51:18] "POST /2015-03-31/functions/HelloWorldFunction/invocations HTTP/1.1" 200 -
Invoking app.lambda_handler (python3.10)
Reuse the created warm container for Lambda function 'HelloWorldFunction'
Lambda function 'HelloWorldFunction' is already running
START RequestId: 4be613d6-1ffd-400c-951d-00a32603dbcf Version: $LATEST
index = 4, step = 1, count = 10, event = {'iterator': {'index': 4, 'step': 1, 'count': 10, 'continue': True}, 'result': {'success': True}}
END RequestId: 140797b6-8754-42e1-be5d-81b049f1f34a
REPORT RequestId: 140797b6-8754-42e1-be5d-81b049f1f34a Duration: 0.94 ms Billed Duration: 1 ms Memory Size: 128 MB Max Memory Used: 128 MB
2024-11-09 20:51:18 127.0.0.1 - - [09/Nov/2024 20:51:18] "POST /2015-03-31/functions/HelloWorldFunction/invocations HTTP/1.1" 200 -
Invoking app.lambda_handler (python3.10)
Reuse the created warm container for Lambda function 'HelloWorldFunction'
Lambda function 'HelloWorldFunction' is already running
START RequestId: b96f049a-360d-4382-a2b9-9415f5423e02 Version: $LATEST
index = 5, step = 1, count = 10, event = {'iterator': {'index': 5, 'step': 1, 'count': 10, 'continue': True}, 'result': {'success': True}}
END RequestId: 634cf741-c6d9-4908-b79d-36661b0a4563
REPORT RequestId: 634cf741-c6d9-4908-b79d-36661b0a4563 Duration: 0.90 ms Billed Duration: 1 ms Memory Size: 128 MB Max Memory Used: 128 MB
2024-11-09 20:51:18 127.0.0.1 - - [09/Nov/2024 20:51:18] "POST /2015-03-31/functions/HelloWorldFunction/invocations HTTP/1.1" 200 -
Invoking app.lambda_handler (python3.10)
Reuse the created warm container for Lambda function 'HelloWorldFunction'
Lambda function 'HelloWorldFunction' is already running
START RequestId: 93121bb9-6d50-429c-9483-cb05041aca1e Version: $LATEST
index = 6, step = 1, count = 10, event = {'iterator': {'index': 6, 'step': 1, 'count': 10, 'continue': True}, 'result': {'success': True}}
END RequestId: e7434c8b-efd8-4b45-b987-34c1f77b0b08
REPORT RequestId: e7434c8b-efd8-4b45-b987-34c1f77b0b08 Duration: 0.86 ms Billed Duration: 1 ms Memory Size: 128 MB Max Memory Used: 128 MB
2024-11-09 20:51:18 127.0.0.1 - - [09/Nov/2024 20:51:18] "POST /2015-03-31/functions/HelloWorldFunction/invocations HTTP/1.1" 200 -
Invoking app.lambda_handler (python3.10)
Reuse the created warm container for Lambda function 'HelloWorldFunction'
Lambda function 'HelloWorldFunction' is already running
START RequestId: c84f02a3-a8a1-47e8-b64a-b164e731d9d7 Version: $LATEST
index = 7, step = 1, count = 10, event = {'iterator': {'index': 7, 'step': 1, 'count': 10, 'continue': True}, 'result': {'success': True}}
END RequestId: bea5aae8-8018-4047-9895-6b44c34c6ae7
REPORT RequestId: bea5aae8-8018-4047-9895-6b44c34c6ae7 Duration: 1.07 ms Billed Duration: 2 ms Memory Size: 128 MB Max Memory Used: 128 MB
2024-11-09 20:51:18 127.0.0.1 - - [09/Nov/2024 20:51:18] "POST /2015-03-31/functions/HelloWorldFunction/invocations HTTP/1.1" 200 -
Invoking app.lambda_handler (python3.10)
Reuse the created warm container for Lambda function 'HelloWorldFunction'
Lambda function 'HelloWorldFunction' is already running
START RequestId: dbbd2fcb-37fa-4ff3-b241-283a22289a8b Version: $LATEST
index = 8, step = 1, count = 10, event = {'iterator': {'index': 8, 'step': 1, 'count': 10, 'continue': True}, 'result': {'success': True}}
END RequestId: c83ef130-f1f8-4713-be6d-1f5a86ac7721
REPORT RequestId: c83ef130-f1f8-4713-be6d-1f5a86ac7721 Duration: 0.86 ms Billed Duration: 1 ms Memory Size: 128 MB Max Memory Used: 128 MB
2024-11-09 20:51:18 127.0.0.1 - - [09/Nov/2024 20:51:18] "POST /2015-03-31/functions/HelloWorldFunction/invocations HTTP/1.1" 200 -
Invoking app.lambda_handler (python3.10)
Reuse the created warm container for Lambda function 'HelloWorldFunction'
Lambda function 'HelloWorldFunction' is already running
START RequestId: 7b47866f-4986-41b1-8cfc-b143f17c6d49 Version: $LATEST
index = 9, step = 1, count = 10, event = {'iterator': {'index': 9, 'step': 1, 'count': 10, 'continue': True}, 'result': {'success': True}}
END RequestId: 27fb39f7-169e-4256-9ff9-9a4ecbd99584
REPORT RequestId: 27fb39f7-169e-4256-9ff9-9a4ecbd99584 Duration: 0.92 ms Billed Duration: 1 ms Memory Size: 128 MB Max Memory Used: 128 MB
2024-11-09 20:51:18 127.0.0.1 - - [09/Nov/2024 20:51:18] "POST /2015-03-31/functions/HelloWorldFunction/invocations HTTP/1.1" 200 -
このあたりを見ると、実行の様子やExampleWorkステートのResult.successが一緒に入力に渡ってきていることが確認できますね。
# 1回目
index = 0, step = 1, count = 10, event = {'iterator': {'count': 10, 'index': 0, 'step': 1}}
# 9回目
index = 8, step = 1, count = 10, event = {'iterator': {'index': 8, 'step': 1, 'count': 10, 'continue': True}, 'result': {'success': True}}
実行結果の確認。
$ aws --endpoint http://localhost:8083 stepfunctions describe-execution --execution-arn arn:aws:states:us-east-1:123456789012:execution:Loop:loop
{
"executionArn": "arn:aws:states:us-east-1:123456789012:execution:Loop:loop",
"stateMachineArn": "arn:aws:states:us-east-1:123456789012:stateMachine:Loop",
"name": "loop",
"status": "SUCCEEDED",
"startDate": "2024-11-09T20:51:18.239000+09:00",
"stopDate": "2024-11-09T20:51:18.821000+09:00",
"input": "{}",
"inputDetails": {
"included": true
},
"output": "{\"iterator\":{\"index\":10,\"step\":1,\"count\":10,\"continue\":false},\"result\":{\"success\":true}}",
"outputDetails": {
"included": true
}
}
OKですね。
おわりに
AWS Step Functions LocalとAWS SAMを使って、AWS Lambdaを使ったチュートリアルを動かしてみました。
前回はただ実行しただけだったので、ステートマシンの定義の基礎みたいなところは学べたかなと思います。