Step Functions LocalとLocalStackを利用してローカル開発環境を構築してみた

こんにちは、坂巻です。

Step Functionsがローカル環境で実行できるようになりました!!

Step Functionsファンとして早速試してみたいと思います!

Step Functions Localは、JARパッケージと、Dockerイメージが用意されていますが、今回はDockerイメージを利用したいと思います。また、併せてローカル環境にLocalStackを起動させ、その上のLambda、DynamoDBに接続するワークフローを試してみたいと思います。

前提

  • Dockerが利用可能なこと
  • AWS CLIが利用可能なこと

本エントリの検証環境のOSは、Mac(macOS Mojave)を使用しています。

準備

Docker HubよりLocalStackと、Step Functions Localのイメージを取得します。

$ docker pull localstack/localstack
$ docker pull amazon/aws-stepfunctions-local

取得したイメージを確認します。

$ docker images
REPOSITORY                       TAG                 IMAGE ID            CREATED             SIZE
amazon/aws-stepfunctions-local   latest              2ad816a79480        8 days ago          351MB
localstack/localstack            latest              ab199231fda1        2 weeks ago         1.11GB

LocalStack設定

LocalStackのコンテナをバックグラウンドで起動します。

$ docker run -d -p 4567-4578:4567-4578 -p 8080:8080 localstack/localstack

コンテナが起動したことを確認します。

$ docker ps
CONTAINER ID        IMAGE                   COMMAND                  CREATED             STATUS              PORTS                                                                     NAMES
31a3aaa8f3c8        localstack/localstack   "/usr/bin/supervisor…"   5 seconds ago       Up 2 seconds        0.0.0.0:4567-4578->4567-4578/tcp, 0.0.0.0:8080->8080/tcp, 4579-4584/tcp   amazing_montalcini

DynamoDBテーブル作成

以下のコマンドを実行してDynamoDBに「TestTable」テーブルを作成します。

$ aws dynamodb create-table --table-name TestTable \
    --attribute-definitions AttributeName=Id,AttributeType=N \
    --key-schema AttributeName=Id,KeyType=HASH \
    --endpoint-url http://localhost:4569 \
    --provisioned-throughput ReadCapacityUnits=1,WriteCapacityUnits=1

LocalStack上に作成しますので、endpoint-urlオプションでLocalStackのDynamoDBエンドポイントを指定しています。LocalStackの各サービスに対応するエンドポイントについては以下をご確認ください。

作成したテーブルをスキャンしてみます。

$ aws dynamodb scan --table-name TestTable --endpoint-url http://localhost:4569

当然、データは何もありません。

{
    "Count": 0,
    "Items": [],
    "ScannedCount": 0,
    "ConsumedCapacity": null
}

ちなみに、この状態でLocalStackのWebインタフェース(http://localhost:8080/)にアクセスすると、LocalStack内のリソースが確認できます。

Lambda関数作成

DynamoDBにデータを挿入するLambda関数を作成していきます。以下コードのスクリプト「lambda.py」を作成します。

import boto3
def lambda_handler(event, context):
    dynamodb = boto3.resource('dynamodb', region_name='us-east-1',endpoint_url='http://localhost:4569/')
    table = dynamodb.Table('TestTable')
    response = table.put_item(
        Item={
            'Id': 1,
            'Name': 'Hachikuji'
        }
    )
    return response

作成したスクリプトをアーカイブします。

$ zip lambda.zip lambda.py

LocalStack上にLambda関数「TestFunction」を作成します。zip-fileには先程アーカイブしたファイルを指定します。roleはダミーなので任意の設定です。

$ aws lambda create-function \
  --function-name=TestFunction \
  --runtime=python3.6 \
  --role=DummyRole \
  --handler=lambda.lambda_handler \
  --zip-file fileb://lambda.zip \
  --endpoint-url=http://localhost:4574

Step Functions Local設定

StepFunctions Localにステートマシンを作成していきます。

設定オプションファイル作成

StepFunctions Localコンテナを起動した際に参照される、設定オプションファイル「sf-credentials.txt」を作成します。LAMBDA_ENDPOINTには、LocalStack上のLambdaエンドポイントを指定しています。

LAMBDA_ENDPOINT=http://host.docker.internal:4574

設定オプションのエンドポインドの指定には注意が必要です。コンテナ(ここではStepFunctions Local)からホスト上のサービスに接続する際はhost.docker.internalと指定します。上記エンドポイントをhttp://localhost:4574にすると、ホスト上のサービスに接続できず、ステートマシン実行時にエラーとなります。 他の設定オプションについては以下をご確認ください。

StepFunctions Localコンテナ起動

StepFunctions Localのコンテナを起動します。

$ docker run -d -p 8083:8083 --env-file sf-credentials.txt amazon/aws-stepfunctions-local

コンテナが起動したことを確認します。

$ docker ps
CONTAINER ID        IMAGE                            COMMAND                  CREATED             STATUS              PORTS                                                                     NAMES
1544eb909804        amazon/aws-stepfunctions-local   "java -jar StepFunct…"   9 seconds ago       Up 6 seconds        0.0.0.0:8083->8083/tcp                                                    clever_mcclintock
31a3aaa8f3c8        localstack/localstack            "/usr/bin/supervisor…"   7 minutes ago       Up 7 minutes        0.0.0.0:4567-4578->4567-4578/tcp, 0.0.0.0:8080->8080/tcp, 4579-4584/tcp   amazing_montalcini

設定オプションファイルが正しく読み込まれているか、コンテナに設定された環境変数を確認します。

$ PID=`docker ps -l -q`
$ docker inspect --format='{{range .Config.Env}}{{println .}}{{end}}' ${PID}

設定オプションにて指定した環境変数が確認できました。

LAMBDA_ENDPOINT=http://host.docker.internal:4574
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin

ステートマシン作成

ステートマシン定義ファイル「defin_statemachine.json」を作成します。先程作成したLambda関数を実行するタスクを設定しています。

{
  "StartAt": "Shinobu",
  "States": {
    "Shinobu": {
      "Type": "Task",
      "Resource": "arn:aws:lambda:::function:TestFunction",
      "End": true
    }
  }
}

StepFunctions Localにステートマシン「TestState」を作成します。role-arnは必須のパラメータなので指定が必要ですが、ダミーですので任意の値で問題ないです。

$ aws stepfunctions create-state-machine \
  --name TestState \
  --definition file://defin_statemachine.json \
  --role-arn "arn:aws:iam::000000000000:role/DummyRole" \
  --endpoint http://localhost:8083

ステートマシンが正常に作成されるとARNが返されます。出力されたARNはステートマシンの実行時に利用します。

{
    "creationDate": 1550051950.565,
    "stateMachineArn": "arn:aws:states:us-east-1:123456789012:stateMachine:TestState"
}

やってみた

ステートマシン実行

作成したステートマシンを実行します。state-machine arnには、ステートマシン作成時に出力されたARNを指定します。

$ aws stepfunctions start-execution \
  --state-machine arn:aws:states:us-east-1:123456789012:stateMachine:TestState \
  --endpoint http://localhost:8083

ステートマシンが正常に作成されるとARNが出力されます。

{
    "startDate": 1550052026.215,
    "executionArn": "arn:aws:states:us-east-1:123456789012:execution:TestState:e029e17e-ec1c-4f0b-815a-50e31dcc4090"
}

ステートマシン結果確認

ステートマシンの実行結果を確認します。

$ aws stepfunctions describe-execution \
  --execution-arn arn:aws:states:us-east-1:123456789012:execution:TestState:e029e17e-ec1c-4f0b-815a-50e31dcc4090 \
  --endpoint=http://localhost:8083

statusSUCCEEDEDでステートマシンの正常終了が確認できました。

{
    "status": "SUCCEEDED",
    "startDate": 1550052026.215,
    "name": "e029e17e-ec1c-4f0b-815a-50e31dcc4090",
    "executionArn": "arn:aws:states:us-east-1:123456789012:execution:TestState:e029e17e-ec1c-4f0b-815a-50e31dcc4090",
    "stateMachineArn": "arn:aws:states:us-east-1:123456789012:stateMachine:TestState",
    "stopDate": 1550052029.798,
    "output": "{\n  \"ResponseMetadata\": {\n    \"HTTPHeaders\": {\n      \"access-control-allow-headers\": \"authorization,content-type,content-md5,cache-control,x-amz-content-sha256,x-amz-date,x-amz-security-token,x-amz-user-agent\", \n      \"access-control-allow-methods\": \"HEAD,GET,PUT,POST,DELETE,OPTIONS,PATCH\", \n      \"access-control-allow-origin\": \"*\", \n      \"content-length\": \"2\", \n      \"content-type\": \"application/x-amz-json-1.0\", \n      \"date\": \"Wed, 13 Feb 2019 10:00:29 GMT\", \n      \"server\": \"BaseHTTP/0.3 Python/2.7.15\", \n      \"x-amz-crc32\": \"2745614147\", \n      \"x-amzn-requestid\": \"051b120c-55ec-40d0-980b-cfcda1ba0faa\"\n    }, \n    \"HTTPStatusCode\": 200, \n    \"RequestId\": \"051b120c-55ec-40d0-980b-cfcda1ba0faa\", \n    \"RetryAttempts\": 0\n  }\n}\n",
    "input": "{}"
}

テーブルスキャン

DynamoDBのテーブルをスキャンします。

$ aws dynamodb scan --table-name TestTable --endpoint-url http://localhost:4569

ステートマシンから、Lambda関数が実行され、DynamoDBにデータが挿入されていることが確認できました。

{
    "Count": 1,
    "Items": [
        {
            "Id": {
                "N": "1"
            },
            "Name": {
                "S": "Hachikuji"
            }
        }
    ],
    "ScannedCount": 1,
    "ConsumedCapacity": null
}

コンテナ停止/削除

動作の確認がとれましたので、起動したコンテナを停止し削除します。

$ docker stop 1544eb909804 && docker rm 1544eb909804
$ docker stop 31a3aaa8f3c8 && docker rm 31a3aaa8f3c8

コンテナイメージを削除します。

$ docker rmi 2ad816a79480 
$ docker rmi ab199231fda1 

さいごに

Step Functions Localも、LocalStackもDockerイメージが用意されおり、コンテナを起動するだけで、容易にローカル開発環境の構築が行えました。また、Step Functionsはステートマシンの状態遷移における料金体系で、比較的安価な料金ではありましたが、こちらを利用することで、料金を気にすることなく、開発に集中できるのではないでしょうか。

参考