CLOVER🍀

That was when it all began.

LocalStack × AWS SAMでAWS Step Functionsを構築して動かしてみる

これは、なにをしたくて書いたもの?

前に、LocalStackとTerraform、AWS SAMでAWS Step Functionsを作成してみました。

LocalStack × Terraform × AWS SAMでAWS Step Functionsを動かしてみる - CLOVER🍀

ただ、AWS SAMでもAWS Step Functionsのステートマシンを扱えるらしく、AWS Step Functionsを主体のテーマに扱う場合はAWS SAMで
一気に作ってしまうのもよいかなということで試してみます。

AWS SAMのAWS Step Functionsのサポート

全然気にしていませんでしたが、かなり前から使えるようです。

AWS SAM が新たに AWS Step Functions をサポート

よくよく見ると、AWS SAMのテンプレート内にもAWS Step Functionsのものが含まれています。

https://github.com/aws/aws-sam-cli-app-templates/blob/master/manifest-v2.json

AWS SAMでのAWS Step Functionsのステートマシンのリソース定義はこちら。

AWS::Serverless::StateMachine - AWS Serverless Application Model

DefinitionSubstitutionsプレースホルダーを使うことで、ステートマシンの定義(Definition)の一部を変数化できるようです。

ステートマシン定義のプレースホルダー変数のマッピングを指定する string-to-string マップ。これは、実行時に取得した値 (組み込み関数からの値など) をステートマシン定義に挿入できるようにします。

AWS::Serverless::StateMachine / プロパティ / DefinitionSubstitutions

また、AWS CloudFormationのリソース定義と異なり、DefinitionUriというプロパティが増えているのでローカルファイルを指定できます。

AWS::StepFunctions::StateMachine - AWS CloudFormation

便利そうですね。

というか、AWS SAMのテンプレートにもAWS Step Functions向けのものが含まれていますね。こちらはPython 3.10のテンプレートです。

$ curl -s https://raw.githubusercontent.com/aws/aws-sam-cli-app-templates/master/manifest-v2.json | jq '."python3.10"[] | select(.appTemplate | test("step"))'
{
  "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/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"
}

GitHubリポジトリーを見ると、もっといろいろありそうですけどね…?

https://github.com/aws/aws-sam-cli-app-templates/tree/master/python3.10

今回は、こちらの内容をLocalStackとAWS SAMのみに置き換えていきます。

LocalStack × Terraform × AWS SAMでAWS Step Functionsを動かしてみる - CLOVER🍀

環境

今回の環境はこちら。

$ python3 --version
Python 3.10.12


$ pip3 --version
pip 22.0.2 from /usr/lib/python3/dist-packages/pip (python 3.10)


$ awslocal --version
aws-cli/2.19.4 Python/3.12.6 Linux/5.15.0-125-generic exe/x86_64.ubuntu.22


$ samlocal --version
SAM CLI, version 1.127.0

AWS CLIなどのクレデンシャル。

$ export AWS_ACCESS_KEY_ID=dummy
$ export AWS_SECRET_ACCESS_KEY=dummy
$ export AWS_DEFAULT_REGION=us-east-1

LocalStack。

$ localstack --version
3.8.1

起動。

$ localstack start

AWS SAMでAWS LambdaからAWS Step Functionsまでデプロイする

では、AWS SAMプロジェクトを作成します。

$ samlocal init --name sfn-localstack-sam --runtime python3.10 --app-template hello-world --package-type Zip --no-tracing --no-application-insights --structured-logging
$ cd sfn-localstack-sam

テンプレートは「hello-world」にして、このあたりを参考にしつつ自分で作成することにしました。

https://github.com/aws/aws-sam-cli-app-templates/tree/master/python3.10/step-func

AWS Lambda関数。

loop/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
    }

ステートマシンの定義。

statemachine/loop-state-machine.asl.json

{
    "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": "${LoopFunctionArn}",
            "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
        }
    }
}

ここがプレースホルダーになっています。

            "Resource": "${LoopFunctionArn}",

AWS SAMテンプレート。

template.yaml

AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: >
  sfn-localstack-sam

  Sample SAM Template for sfn-localstack-sam

Globals:
  Function:
    Timeout: 3
    LoggingConfig:
      LogFormat: JSON

Resources:
  LoopFunction:
    Type: AWS::Serverless::Function
    Properties:
      FunctionName: LoopFunction
      CodeUri: loop/
      Handler: app.lambda_handler
      Runtime: python3.10
      Architectures:
      - x86_64
  LoopStateMachine:
    Type: AWS::Serverless::StateMachine
    Properties:
      Name: LoopStateMachine
      DefinitionUri: statemachine/loop-state-machine.asl.json
      DefinitionSubstitutions:
        LoopFunctionArn: !GetAtt LoopFunction.Arn

Outputs:
  LoopFunction:
    Description: Loop Lambda Function ARN
    Value: !GetAtt LoopFunction.Arn
  LoopFunctionIamRole:
    Description: Implicit IAM Role created for Loop function
    Value: !GetAtt LoopFunctionRole.Arn
  LoopStateMachineArn:
    Description: "Loop State machine ARN"
    Value: !Ref LoopStateMachine
  LoopStateMachineRoleArn:
    Description: "IAM Role created for Loop State machine based on the specified SAM Policy Templates"
    Value: !GetAtt LoopStateMachineRole.Arn

ステートマシンのリソース定義はここですね。

  LoopStateMachine:
    Type: AWS::Serverless::StateMachine
    Properties:
      Name: LoopStateMachine
      DefinitionUri: statemachine/loop-state-machine.asl.json
      DefinitionSubstitutions:
        LoopFunctionArn: !GetAtt LoopFunction.Arn

DefinitionUriでステートマシンのASLを、DefinitionSubstitutionsプレースホルダーを指定しています。

AWS Lambdaを作成しつつ、そのARNをプレースホルダーの値に指定できるのでよいですね。

デプロイ。

$ samlocal deploy --region us-east-1

できました。

CloudFormation events from stack operations (refresh every 5.0 seconds)
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
ResourceStatus                                  ResourceType                                    LogicalResourceId                               ResourceStatusReason
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
CREATE_IN_PROGRESS                              AWS::CloudFormation::Stack                      sfn-localstack-sam                              -
CREATE_IN_PROGRESS                              AWS::IAM::Role                                  LoopFunctionRole                                -
CREATE_COMPLETE                                 AWS::IAM::Role                                  LoopFunctionRole                                -
CREATE_IN_PROGRESS                              AWS::IAM::Role                                  LoopStateMachineRole                            -
CREATE_COMPLETE                                 AWS::IAM::Role                                  LoopStateMachineRole                            -
CREATE_IN_PROGRESS                              AWS::Lambda::Function                           LoopFunction                                    -
CREATE_COMPLETE                                 AWS::Lambda::Function                           LoopFunction                                    -
CREATE_IN_PROGRESS                              AWS::StepFunctions::StateMachine                LoopStateMachine                                -
CREATE_COMPLETE                                 AWS::StepFunctions::StateMachine                LoopStateMachine                                -
CREATE_COMPLETE                                 AWS::CloudFormation::Stack                      sfn-localstack-sam                              -
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

CloudFormation outputs from deployed stack
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Outputs
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Key                 LoopFunction
Description         Loop Lambda Function ARN
Value               arn:aws:lambda:us-east-1:000000000000:function:LoopFunction

Key                 LoopFunctionIamRole
Description         Implicit IAM Role created for Loop function
Value               arn:aws:iam::000000000000:role/sfn-localstack-sam-LoopFunctionRole-9543b55b

Key                 LoopStateMachineArn
Description         Loop State machine ARN
Value               arn:aws:states:us-east-1:000000000000:stateMachine:LoopStateMachine

Key                 LoopStateMachineRoleArn
Description         IAM Role created for Loop State machine based on the specified SAM Policy Templates
Value               arn:aws:iam::000000000000:role/sfn-localstack-sam-LoopStateMachineRole-da8f4160
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------


Successfully created/updated stack - sfn-localstack-sam in us-east-1

ステートマシンを実行。

$ awslocal stepfunctions start-execution --state-machine arn:aws:states:us-east-1:000000000000:stateMachine:LoopStateMachine --name loop
{
    "executionArn": "arn:aws:states:us-east-1:000000000000:execution:LoopStateMachine:loop",
    "startDate": "2024-11-10T00:30:13.247323+09:00"
}

結果の確認。

$ awslocal stepfunctions describe-execution --execution-arn arn:aws:states:us-east-1:000000000000:execution:LoopStateMachine:loop
{
    "executionArn": "arn:aws:states:us-east-1:000000000000:execution:LoopStateMachine:loop",
    "stateMachineArn": "arn:aws:states:us-east-1:000000000000:stateMachine:LoopStateMachine",
    "name": "loop",
    "status": "SUCCEEDED",
    "startDate": "2024-11-10T00:30:13.247323+09:00",
    "stopDate": "2024-11-10T00:30:17.731679+09:00",
    "input": "{}",
    "inputDetails": {
        "included": true
    },
    "output": "{\"iterator\":{\"index\":10,\"step\":1,\"count\":10,\"continue\":false},\"result\":{\"success\":true}}",
    "outputDetails": {
        "included": true
    }
}

ログ。

$ awslocal logs tail /aws/lambda/LoopFunction
2024-11-09T15:30:15.703000+00:00 2024/11/09/[$LATEST]1e73b33beb16d0838ab5a6c31453b241 START RequestId: da88fab1-56ef-4da0-b14b-767435c8229a Version: $LATEST
2024-11-09T15:30:15.741000+00:00 2024/11/09/[$LATEST]1e73b33beb16d0838ab5a6c31453b241 index = 0, step = 1, count = 10, event = {'iterator': {'count': 10, 'index': 0, 'step': 1}}
2024-11-09T15:30:15.780000+00:00 2024/11/09/[$LATEST]1e73b33beb16d0838ab5a6c31453b241 END RequestId: da88fab1-56ef-4da0-b14b-767435c8229a
2024-11-09T15:30:15.818000+00:00 2024/11/09/[$LATEST]1e73b33beb16d0838ab5a6c31453b241 REPORT RequestId: da88fab1-56ef-4da0-b14b-767435c8229a    Duration: 0.88 ms       Billed Duration: 1 ms   Memory Size: 128 MB      Max Memory Used: 128 MB
2024-11-09T15:30:15.929000+00:00 2024/11/09/[$LATEST]1e73b33beb16d0838ab5a6c31453b241 START RequestId: 2378c403-81f9-466a-a3d8-fac5fcedbe3d Version: $LATEST
2024-11-09T15:30:15.977000+00:00 2024/11/09/[$LATEST]1e73b33beb16d0838ab5a6c31453b241 index = 1, step = 1, count = 10, event = {'iterator': {'index': 1, 'step': 1, 'count': 10, 'continue': True}, 'result': {'success': True}}
2024-11-09T15:30:16.025000+00:00 2024/11/09/[$LATEST]1e73b33beb16d0838ab5a6c31453b241 END RequestId: 2378c403-81f9-466a-a3d8-fac5fcedbe3d
2024-11-09T15:30:16.073000+00:00 2024/11/09/[$LATEST]1e73b33beb16d0838ab5a6c31453b241 REPORT RequestId: 2378c403-81f9-466a-a3d8-fac5fcedbe3d    Duration: 0.97 ms       Billed Duration: 1 ms   Memory Size: 128 MB      Max Memory Used: 128 MB
2024-11-09T15:30:16.318000+00:00 2024/11/09/[$LATEST]1e73b33beb16d0838ab5a6c31453b241 START RequestId: d1e3976a-6759-4733-a7f2-1f47ffe4305a Version: $LATEST
2024-11-09T15:30:16.321000+00:00 2024/11/09/[$LATEST]1e73b33beb16d0838ab5a6c31453b241 index = 2, step = 1, count = 10, event = {'iterator': {'index': 2, 'step': 1, 'count': 10, 'continue': True}, 'result': {'success': True}}
2024-11-09T15:30:16.324000+00:00 2024/11/09/[$LATEST]1e73b33beb16d0838ab5a6c31453b241 END RequestId: d1e3976a-6759-4733-a7f2-1f47ffe4305a
2024-11-09T15:30:16.327000+00:00 2024/11/09/[$LATEST]1e73b33beb16d0838ab5a6c31453b241 REPORT RequestId: d1e3976a-6759-4733-a7f2-1f47ffe4305a    Duration: 0.89 ms       Billed Duration: 1 ms   Memory Size: 128 MB      Max Memory Used: 128 MB
2024-11-09T15:30:16.514000+00:00 2024/11/09/[$LATEST]1e73b33beb16d0838ab5a6c31453b241 START RequestId: aa79a59a-8bc0-461e-b39b-b2e65bcadd2c Version: $LATEST
2024-11-09T15:30:16.517000+00:00 2024/11/09/[$LATEST]1e73b33beb16d0838ab5a6c31453b241 index = 3, step = 1, count = 10, event = {'iterator': {'index': 3, 'step': 1, 'count': 10, 'continue': True}, 'result': {'success': True}}
2024-11-09T15:30:16.520000+00:00 2024/11/09/[$LATEST]1e73b33beb16d0838ab5a6c31453b241 END RequestId: aa79a59a-8bc0-461e-b39b-b2e65bcadd2c
2024-11-09T15:30:16.523000+00:00 2024/11/09/[$LATEST]1e73b33beb16d0838ab5a6c31453b241 REPORT RequestId: aa79a59a-8bc0-461e-b39b-b2e65bcadd2c    Duration: 0.73 ms       Billed Duration: 1 ms   Memory Size: 128 MB      Max Memory Used: 128 MB
2024-11-09T15:30:16.688000+00:00 2024/11/09/[$LATEST]1e73b33beb16d0838ab5a6c31453b241 START RequestId: eb5fc091-10d7-4d9e-9004-394aac58e35a Version: $LATEST
2024-11-09T15:30:16.691000+00:00 2024/11/09/[$LATEST]1e73b33beb16d0838ab5a6c31453b241 index = 4, step = 1, count = 10, event = {'iterator': {'index': 4, 'step': 1, 'count': 10, 'continue': True}, 'result': {'success': True}}
2024-11-09T15:30:16.695000+00:00 2024/11/09/[$LATEST]1e73b33beb16d0838ab5a6c31453b241 END RequestId: eb5fc091-10d7-4d9e-9004-394aac58e35a
2024-11-09T15:30:16.699000+00:00 2024/11/09/[$LATEST]1e73b33beb16d0838ab5a6c31453b241 REPORT RequestId: eb5fc091-10d7-4d9e-9004-394aac58e35a    Duration: 0.84 ms       Billed Duration: 1 ms   Memory Size: 128 MB      Max Memory Used: 128 MB
2024-11-09T15:30:16.865000+00:00 2024/11/09/[$LATEST]1e73b33beb16d0838ab5a6c31453b241 START RequestId: f80d6cec-1c36-4198-b339-5f22f2805128 Version: $LATEST
2024-11-09T15:30:16.868000+00:00 2024/11/09/[$LATEST]1e73b33beb16d0838ab5a6c31453b241 index = 5, step = 1, count = 10, event = {'iterator': {'index': 5, 'step': 1, 'count': 10, 'continue': True}, 'result': {'success': True}}
2024-11-09T15:30:16.871000+00:00 2024/11/09/[$LATEST]1e73b33beb16d0838ab5a6c31453b241 END RequestId: f80d6cec-1c36-4198-b339-5f22f2805128
2024-11-09T15:30:16.874000+00:00 2024/11/09/[$LATEST]1e73b33beb16d0838ab5a6c31453b241 REPORT RequestId: f80d6cec-1c36-4198-b339-5f22f2805128    Duration: 0.90 ms       Billed Duration: 1 ms   Memory Size: 128 MB      Max Memory Used: 128 MB
2024-11-09T15:30:17.039000+00:00 2024/11/09/[$LATEST]1e73b33beb16d0838ab5a6c31453b241 START RequestId: e8a7f924-65f2-4a24-80b5-7737935e4bcb Version: $LATEST
2024-11-09T15:30:17.042000+00:00 2024/11/09/[$LATEST]1e73b33beb16d0838ab5a6c31453b241 index = 6, step = 1, count = 10, event = {'iterator': {'index': 6, 'step': 1, 'count': 10, 'continue': True}, 'result': {'success': True}}
2024-11-09T15:30:17.045000+00:00 2024/11/09/[$LATEST]1e73b33beb16d0838ab5a6c31453b241 END RequestId: e8a7f924-65f2-4a24-80b5-7737935e4bcb
2024-11-09T15:30:17.048000+00:00 2024/11/09/[$LATEST]1e73b33beb16d0838ab5a6c31453b241 REPORT RequestId: e8a7f924-65f2-4a24-80b5-7737935e4bcb    Duration: 0.76 ms       Billed Duration: 1 ms   Memory Size: 128 MB      Max Memory Used: 128 MB
2024-11-09T15:30:17.207000+00:00 2024/11/09/[$LATEST]1e73b33beb16d0838ab5a6c31453b241 START RequestId: 50cebd24-5804-46a5-ac29-0f8ab4bc4594 Version: $LATEST
2024-11-09T15:30:17.210000+00:00 2024/11/09/[$LATEST]1e73b33beb16d0838ab5a6c31453b241 index = 7, step = 1, count = 10, event = {'iterator': {'index': 7, 'step': 1, 'count': 10, 'continue': True}, 'result': {'success': True}}
2024-11-09T15:30:17.213000+00:00 2024/11/09/[$LATEST]1e73b33beb16d0838ab5a6c31453b241 END RequestId: 50cebd24-5804-46a5-ac29-0f8ab4bc4594
2024-11-09T15:30:17.217000+00:00 2024/11/09/[$LATEST]1e73b33beb16d0838ab5a6c31453b241 REPORT RequestId: 50cebd24-5804-46a5-ac29-0f8ab4bc4594    Duration: 0.80 ms       Billed Duration: 1 ms   Memory Size: 128 MB      Max Memory Used: 128 MB
2024-11-09T15:30:17.377000+00:00 2024/11/09/[$LATEST]1e73b33beb16d0838ab5a6c31453b241 START RequestId: f52fbc3a-f635-44d0-b224-527c92bd78fe Version: $LATEST
2024-11-09T15:30:17.380000+00:00 2024/11/09/[$LATEST]1e73b33beb16d0838ab5a6c31453b241 index = 8, step = 1, count = 10, event = {'iterator': {'index': 8, 'step': 1, 'count': 10, 'continue': True}, 'result': {'success': True}}
2024-11-09T15:30:17.384000+00:00 2024/11/09/[$LATEST]1e73b33beb16d0838ab5a6c31453b241 END RequestId: f52fbc3a-f635-44d0-b224-527c92bd78fe
2024-11-09T15:30:17.387000+00:00 2024/11/09/[$LATEST]1e73b33beb16d0838ab5a6c31453b241 REPORT RequestId: f52fbc3a-f635-44d0-b224-527c92bd78fe    Duration: 0.77 ms       Billed Duration: 1 ms   Memory Size: 128 MB      Max Memory Used: 128 MB
2024-11-09T15:30:17.573000+00:00 2024/11/09/[$LATEST]1e73b33beb16d0838ab5a6c31453b241 START RequestId: b33f303e-16ea-4acc-a385-adde97e10cbb Version: $LATEST
2024-11-09T15:30:17.576000+00:00 2024/11/09/[$LATEST]1e73b33beb16d0838ab5a6c31453b241 index = 9, step = 1, count = 10, event = {'iterator': {'index': 9, 'step': 1, 'count': 10, 'continue': True}, 'result': {'success': True}}
2024-11-09T15:30:17.579000+00:00 2024/11/09/[$LATEST]1e73b33beb16d0838ab5a6c31453b241 END RequestId: b33f303e-16ea-4acc-a385-adde97e10cbb
2024-11-09T15:30:17.582000+00:00 2024/11/09/[$LATEST]1e73b33beb16d0838ab5a6c31453b241 REPORT RequestId: b33f303e-16ea-4acc-a385-adde97e10cbb    Duration: 1.07 ms       Billed Duration: 2 ms   Memory Size: 128 MB      Max Memory Used: 128 MB

OKですね。

おわりに

LocalStackとAWS SAMで、AWS Step Functionsを構築して動かしてみました。

とりあえずAWS Step Functionsを作って動かしたいなら、AWS SAMだけで十分ですね。

勉強する時は本筋以外のところは少なくするにこしたことはないので、AWS SAMでやるのがよさそうです。