継続は力なり

タイトル通り定期的な更新を心掛けるブログです。

SAM で Step Functions と Lambda のワークフローを管理する

タダです.

Serveless Applicaiton Model(SAM) で Lambda の開発とデプロイを行い,Step Functions が Lambda 関数処理のワークフローを管理しています.ただ,Step Functions のコード管理ができていない課題があって どの Step Functions が どの Lambda を管理しているかとか不明瞭だったのですが,調べていたら SAM が Step Functions をサポートしたというアップデートがありました.この波に乗って SAM で Step Functions と Lambda を管理する場合どう対応が必要なのかをサンプルアプリケーションを動かしてさらってきます.

aws.amazon.com

サンプルアプリケーションの作成

sam init で作っていきます(バージョンは 1.6.2 で確認してます).Lambda は Python で作り,Step Functions Sample App (Stock Trader) のサンプルアプリケーションを選択しました.

$ sam init
Which template source would you like to use?
    1 - AWS Quick Start Templates
    2 - Custom Template Location
Choice: 1

Which runtime would you like to use?
    1 - nodejs12.x
    2 - python3.8
    3 - ruby2.7
    4 - go1.x
    5 - java11
    6 - dotnetcore3.1
    7 - nodejs10.x
    8 - python3.7
    9 - python3.6
    10 - python2.7
    11 - ruby2.5
    12 - java8.al2
    13 - java8
    14 - dotnetcore2.1
Runtime: 9

Project name [sam-app]: sfn-lambda-app

Cloning app templates from https://github.com/awslabs/aws-sam-cli-app-templates.git

AWS quick start application templates:
    1 - Hello World Example
    2 - EventBridge Hello World
    3 - EventBridge App from scratch (100+ Event Schemas)
    4 - Step Functions Sample App (Stock Trader)
Template selection: 4

-----------------------
Generating application:
-----------------------
Name: sfn-lambda-app
Runtime: python3.6
Dependency Manager: pip
Application Template: step-functions-sample-app
Output Directory: .

Next steps can be found in the README file at ./sfn-lambda-app/README.md

出来上がったサンプルアプリケーションのディレクトリ構造とディレクトリの役割は次の通りです.

functions - 株式の値をチェックしたり,購入したり,売却したりするためのアプリケーションの Lambda 関数のコード

statemachines - 株式取引のワークフローをオーケストレーションするステートマシンの定義

tests - Lambda 関数のアプリケーションコードのユニットテスト

template.yaml - アプリケーションの AWS リソースを定義するテンプレート

ディレクトリ構造

$ tree
.
├── README.md
├── functions
│   ├── __init__.py
│   ├── stock_buyer
│   │   ├── __init__.py
│   │   ├── app.py
│   │   └── requirements.txt
│   ├── stock_checker
│   │   ├── __init__.py
│   │   ├── app.py
│   │   └── requirements.txt
│   └── stock_seller
│       ├── __init__.py
│       ├── app.py
│       └── requirements.txt
├── statemachine
│   └── stock_trader.asl.json
├── template.yaml
└── tests
    └── unit
        ├── __init__.py
        ├── test_buyer.py
        ├── test_checker.py
        └── test_seller.py

7 directories, 17 files

Step Functions の ASL の中身は次のようになっています.

{
    "Comment": "A state machine that does mock stock trading.",
    "StartAt": "Check Stock Value",
    "States": {
        "Check Stock Value": {
            "Type": "Task",
            "Resource": "${StockCheckerFunctionArn}",
            "Retry": [
                {
                    "ErrorEquals": [
                        "States.TaskFailed"
                    ],
                    "IntervalSeconds": 15,
                    "MaxAttempts": 5,
                    "BackoffRate": 1.5
                }
            ],
            "Next": "Buy or Sell?"
        },
        "Buy or Sell?": {
            "Type": "Choice",
            "Choices": [
                {
                    "Variable": "$.stock_price",
                    "NumericLessThanEquals": 50,
                    "Next": "Buy Stock"
                }
            ],
            "Default": "Sell Stock"
        },
        "Sell Stock": {
            "Type": "Task",
            "Resource": "${StockSellerFunctionArn}",
            "Retry": [
                {
                    "ErrorEquals": [
                        "States.TaskFailed"
                    ],
                    "IntervalSeconds": 2,
                    "MaxAttempts": 3,
                    "BackoffRate": 1
                }
            ],
            "Next": "Record Transaction"
        },
        "Buy Stock": {
            "Type": "Task",
            "Resource": "${StockBuyerFunctionArn}",
            "Retry": [
                {
                    "ErrorEquals": [
                        "States.TaskFailed"
                    ],
                    "IntervalSeconds": 2,
                    "MaxAttempts": 3,
                    "BackoffRate": 1
                }
            ],
            "Next": "Record Transaction"
        },
        "Record Transaction": {
            "Type": "Task",
            "Resource": "${DDBPutItem}",
            "Parameters": {
                "TableName": "${DDBTable}",
                "Item": {
                    "Id": {
                        "S.$": "$.id"
                    },
                    "Type": {
                        "S.$": "$.type"
                    },
                    "Price": {
                        "N.$": "$.price"
                    },
                    "Quantity": {
                        "N.$": "$.qty"
                    },
                    "Timestamp": {
                        "S.$": "$.timestamp"
                    }
                }
            },
            "Retry": [
                {
                    "ErrorEquals": [
                        "States.TaskFailed"
                    ],
                    "IntervalSeconds": 20,
                    "MaxAttempts": 5,
                    "BackoffRate": 10
                }
            ],
            "End": true
        }
    }
}

templates.yaml の中身で Lambda と Step Functions の管理は次のコードで定義していました.DefinitionUri が ASL の定義場所,DefinitionSubstitutionsがステートマシンの定義に変数のマッピングする場所,Events' がステートマシンのトリガーになるイベント,Policies` がステートマシンの IAM ロールを指定する場所です.

docs.aws.amazon.com

~中略~
Resources:
  StockTradingStateMachine:
    Type: AWS::Serverless::StateMachine # More info about State Machine Resource: https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-resource-statemachine.html
    Properties:
      DefinitionUri: statemachine/stock_trader.asl.json
      DefinitionSubstitutions:
        StockCheckerFunctionArn: !GetAtt StockCheckerFunction.Arn
        StockSellerFunctionArn: !GetAtt StockSellerFunction.Arn
        StockBuyerFunctionArn: !GetAtt StockBuyerFunction.Arn
        DDBPutItem: !Sub arn:${AWS::Partition}:states:::dynamodb:putItem
        DDBTable: !Ref TransactionTable
      Events:
        HourlyTradingSchedule:
          Type: Schedule # More info about Schedule Event Source: https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-property-statemachine-schedule.html
          Properties:
            Description: Schedule to run the stock trading state machine every hour
            Enabled: False # This schedule is disabled by default to avoid incurring charges.
            Schedule: "rate(1 hour)"
      Policies: # Find out more about SAM policy templates: https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-policy-templates.html
        - LambdaInvokePolicy:
            FunctionName: !Ref StockCheckerFunction
        - LambdaInvokePolicy:
            FunctionName: !Ref StockSellerFunction
        - LambdaInvokePolicy:
            FunctionName: !Ref StockBuyerFunction
        - DynamoDBWritePolicy:
            TableName: !Ref TransactionTable
~中略~

サンプルアプリケーションのビルドとデプロイ

サンプルアプリケーションのビルドとデプロイをしてみます.

$ sam build --use-container
~中略~
Build Succeeded

Built Artifacts  : .aws-sam/build
Built Template   : .aws-sam/build/template.yaml

Commands you can use next
=========================
[*] Invoke Function: sam local invoke
[*] Deploy: sam deploy --guided
    
Running PythonPipBuilder:ResolveDependencies
Running PythonPipBuilder:CopySource
$ sam deploy --guided
~中略~
Successfully created/updated stack - sfn-lambda-app in ap-northeast-1

Step Functions の定義もデプロイできていたので,ステートマシンの手動実行もできました.

f:id:sadayoshi_tada:20201108234053p:plain f:id:sadayoshi_tada:20201108234216p:plain

SAM のデプロイとステートマシンの実行確認できたので,クリーンアップします.

aws cloudformation delete-stack --stack-name sfn-lambda-app

まとめ

SAM で Lambda と Step Functions を管理するコードのプロジェクト作成とデプロイの確認ができました.SAM の Step Functions の扱いをさらえたので実務でも適用を検討していけたらと思います!