CLOVER🍀

That was when it all began.

AWS Step Functionsをローカルで動かす

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

AWS Step Functionsをちょっと試してみたいのですが、どうやらローカルで動かせるみたいなので、ちょっと試してみようと。

AWS Step Functions とは - AWS Step Functions

Step Functions Local (ダウンロード可能バージョン) のセットアップ - AWS Step Functions

AWS Step Functionsというのは、(複数の)AWS Lambdaを組み合わせてアプリケーションを作成できるサービスのようです。

今回は、ドキュメントに沿って、シンプルにひとつのAWS LambdaをAWS Step Functionsでローカル環境で動かして
みることを目標にします。

環境

今回は、Pythonを使ってAWS Lambdaに登録する関数を作成し、AWS Step Functionsを動かしていきたいと思います。

ローカルのPythonのバージョンと、仮想環境を作成しておきます。。

$ python3 -V
Python 3.6.7

$ python3 -m venv venv
$ . venv/bin/activate

AWS Step Functionsのローカル版のインストール

こちらに沿って、進めていきます。

Step Functions Local (ダウンロード可能バージョン) のセットアップ - AWS Step Functions

まずは、圧縮ファイルをダウンロード。そのまま展開すると、中身が全部バラまかれるので、ディレクトリを作成してその中に
まとめるようにしておきました。

$ wget https://s3.amazonaws.com/stepfunctionslocal/StepFunctionsLocal.tar.gz
$ mkdir StepFunctionsLocal
$ tar xf StepFunctionsLocal.tar.gz -C StepFunctionsLocal

ちなみに、Docker版もあるようですが、今回はこちらのJARファイルで提供されるものを使用します。

バージョン確認。

$ java -jar StepFunctionsLocal/StepFunctionsLocal.jar -v
Step Functions Local
Version: 1.0.1
Build: 2019-02-22
Step Functions Local
Version: 1.0.1
Build: 2019-02-22

AWS CLIAWS SAM CLIも使うので、インストールしておきましょう。Access Keyなどの設定は、適当です。

$ pip3 install awscli
$ aws configure
AWS Access Key ID [None]: my-access-key
AWS Secret Access Key [None]: my-secret-key
Default region name [None]: us-east-1
Default output format [None]: 


$ pip3 install aws-sam-cli
$ sam --version
SAM CLI, version 0.15.0

AWS Step Functionsのローカル版を起動して、

$ java -jar StepFunctionsLocal/StepFunctionsLocal.jar

AWS CLIからの接続確認。

$ aws stepfunctions --endpoint https://localhost:8083 list-state-machines
{
    "stateMachines": []
}

1度、StepFunctionsLocal.jarを終了しておきます。

AWS Lambda関数を作る

続いて、AWS Step Functionsから呼び出す、AWS Lambda関数を作成していきます。

こちらを参考に作成すればOKです。

Quick Start - AWS Serverless Application Model

ランタイムはPython 3.6を指定して、「sam init」。

$ sam init --runtime python3.6

「sam-app」というディレクトリが作成され、その中に各種ファイルが配置されています。そのまま、ローカルでAPI Gateway越しに
AWS Lambdaを起動させることができます。

$ cd sam-app
$ sam local start-api
2019-04-22 23:01:18 Found credentials in shared credentials file: ~/.aws/credentials
2019-04-22 23:01:18 Mounting HelloWorldFunction at https://127.0.0.1:3000/hello [GET]
2019-04-22 23:01:18 You can now browse to the above endpoints to invoke your functions. You do not need to restart/reload SAM CLI while working on your functions, changes will be reflected instantly/automatically. You only need to restart SAM CLI if you update your AWS SAM template
2019-04-22 23:01:18  * Running on https://127.0.0.1:3000/ (Press CTRL+C to quit)

curlで確認。初回は、AWS Lambdaのランタイムに合わせたDockerイメージのダウンロードが入るので、とても時間がかかりますが、
ちゃんと動きました。

$ curl localhost:3000/hello
{"message": "hello world"}

中身を少し見ておきましょうか。出力されたPythonファイル。
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("https://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", "")
        }),
    }

テンプレートファイル。
template.yaml

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

  Sample SAM Template for sam-app

# More info about Globals: https://github.com/awslabs/serverless-application-model/blob/master/docs/globals.rst
Globals:
  Function:
    Timeout: 3

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.6
      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

ここで、API Gatewayは止めて、ローカルでAWS Lambdaを起動させます。

$ sam loc start-lambda
2019-04-22 23:18:15 Found credentials in shared credentials file: ~/.aws/credentials
2019-04-22 23:18:15 Starting the Local Lambda Service. You can now invoke your Lambda Functions defined in your template through the endpoint.
2019-04-22 23:18:15  * Running on https://127.0.0.1:3001/ (Press CTRL+C to quit)

AWS Step Functionsに、作成したAWS Lambda関数を呼び出すように設定する

では、作成したAWS Lambda関数を、AWS Step Functionsから呼び出すように設定しましょう。

AWS Lambdaのエンドポイントを指定して、ローカル用のStep Functionsを起動します。ローカルのAWS Lambdaのエンドポイントは、
「sam loc start-lambda」実行時にコンソールに出力されています。

$ java -jar StepFunctionocal/StepFunctionsLocal.jar --lambda-endpoint https://localhost:3001

ステートマシンの定義を作成します。
hello-state-machine.json

{
    "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
        }
    }
}

「Resource」に、先ほど指定したAWS Lambdaの関数名を指定します。

template.yamlの、Resourcesの配下に指定する関数名です。

AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31

...

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:
...

このファイルを使って、名前を「HelloStateMachine」としてステートマシンを作成します。「create-state-machine」という
サブコマンドを使用します。

$ aws stepfunctions --endpoint https://localhost:8083 create-state-machine --name 'HelloStateMachine' --role-arn 'arn:aws:iam::012345678901:role/DummyRole' --definition file:https://hello-state-machine.json
{
    "stateMachineArn": "arn:aws:states:us-east-1:123456789012:stateMachine:HelloStateMachine",
    "creationDate": 1555942950.429
}

ステートマシンの設定をファイルで定義した時に「HelloWorld」とか書いていましたが、これはステートの名前、
ステートマシンの名前としては「--name」オプションで指定した「HelloStateMachine」となるようです。

「--role-arn」の値は、書式に合ってさえいれば適当でよいみたいです。

この結果、得られた「stateMachineArn」を使ってステートマシンを呼び出します。「start-execution」サブコマンドを使います。

$ aws stepfunctions --endpoint https://localhost:8083 start-execution --state-machine arn:aws:states:us-east-1:123456789012:stateMachine:HelloStateMachine --name test
{
    "executionArn": "arn:aws:states:us-east-1:123456789012:execution:HelloStateMachine:test",
    "startDate": 1555942979.028
}

実行名は、ドキュメント通り「test」としました。これは、実行ごとにユニークな名前である必要があるらしいです。

得られた「executionArn」を使って、「describe-execution」サブコマンドで呼び出し結果を確認することができます。

$ aws stepfunctions --endpoint https://localhost:8083 describe-execution --execution-arn arn:aws:states:us-east-1:123456789012:execution:HelloStateMachine:test
{
    "executionArn": "arn:aws:states:us-east-1:123456789012:execution:HelloStateMachine:test",
    "stateMachineArn": "arn:aws:states:us-east-1:123456789012:stateMachine:HelloStateMachine",
    "name": "test",
    "status": "SUCCEEDED",
    "startDate": 1555942979.028,
    "stopDate": 1555942982.967,
    "input": "{}",
    "output": "{\"statusCode\": 200, \"body\": \"{\\\"message\\\": \\\"hello world\\\"}\"}"
}

とりあえず、動かすことができましたね…。

ステートマシンの削除は、作成時に得られた「stateMachineArn」を使って行います。

$ aws stepfunctions --endpoint https://localhost:8083 delete-state-machine --state-machine-arn arn:aws:states:us-east-1:123456789012:stateMachine:HelloStateMachine

入力値を利用するAWS Lambda関数を呼び出してみる

作成したAWS Lambda関数を、入力値を受け取るように修正してみましょう。

以下のように、「name」という名前の入力値を利用するようにしてみます。

    return {
        "statusCode": 200,
        "body": json.dumps({
            "message": "hello {}".format(event['name']),
            # "location": ip.text.replace("\n", "")
        }),
    }

ローカルのAWS Lambdaを再起動。

$ sam loc start-lambda

ステートマシンの登録。

$ aws stepfunctions --endpoint https://localhost:8083 create-state-machine --name 'HelloStateMachine' --role-arn 'arn:aws:iam::012345678901:role/DummyRole' --definition file:https://hello-state-machine.json
{
    "stateMachineArn": "arn:aws:states:us-east-1:123456789012:stateMachine:HelloStateMachine",
    "creationDate": 1555944685.994
}

実行時に入力値を与えるわけですが、「start-execution」の「--input」引数で入力値を指定することができます。

$ aws stepfunctions --endpoint https://localhost:8083 start-execution --state-machine arn:aws:states:us-east-1:123456789012:stateMachine:HelloStateMachine --name test --input '{"name": "カツオ"}'
{
    "executionArn": "arn:aws:states:us-east-1:123456789012:execution:HelloStateMachine:test",
    "startDate": 1555944793.934
}

また、入力値をファイルにして
input.json

{"name": "カツオ"}

このファイルを入力値として利用することもできます。

$ aws stepfunctions --endpoint https://localhost:8083 start-execution --state-machine arn:aws:states:us-east-1:123456789012:stateMachine:HelloStateMachine --name test --input file:https://input.json

結果の確認。ちょっと日本語の部分が微妙なことになっていますが…。

$ aws stepfunctions --endpoint https://localhost:8083 describe-execution --execution-arn arn:aws:states:us-east-1:123456789012:execution:HelloStateMachine:test
{
    "executionArn": "arn:aws:states:us-east-1:123456789012:execution:HelloStateMachine:test",
    "stateMachineArn": "arn:aws:states:us-east-1:123456789012:stateMachine:HelloStateMachine",
    "name": "test",
    "status": "SUCCEEDED",
    "startDate": 1555944793.934,
    "stopDate": 1555944797.618,
    "input": "{\"name\": \"???\"}",
    "output": "{\"statusCode\": 200, \"body\": \"{\\\"message\\\": \\\"hello \\\\u30ab\\\\u30c4\\\\u30aa\\\"}\"}"
}

一応、内容も確認しておきますか。

$ groovy -e 'println("\u30ab" + "\u30c4" + "\u30aa")'
カツオ

OKそうですね。

今回はあくまで単一のAWS LambdaをAWS Step Functionsから呼び出しただけですが、そのうち複数のAWS Lambdaを
つなげて呼び出すようなステートマシンを作成してみたいと思います。