Docker で Step Functions Local の実行環境を構築する

最近、車を購入したので、色々なところに遠出をしています。エンジニアの内山です。

今回は、Step Functions をローカルPC内で実行する環境の構築について、ご紹介します。

概要

Step Functions は、複数の Lambda 関数を組み合わせて機能を実装できるようにするサービスです。
ループや分岐などのロジックを組むことができます。
ロジックが複雑になってくると、ローカルPCで動作確認したいと思うようになってきます。

ローカルPCで動作させるために Step Functions Local というツールが公式で用意されています。
今回は、Docker と Step Functions Local を用いて、動作チェック環境をローカルPC(Mac)上に構築する方法をご紹介します。

さっさと試したい方は以下のリポジトリをご参照ください。

https://github.com/memememomo/step-functions-local-test

構成

上記のような環境を構築します。設定するコンテナとしては以下の二種類があります。

  • SAM Local用のコンテナ
  • Step Functions用のコンテナ

SAM Local は、ローカルPC上で Lambda 関数を実行するツールです。Lambda 関数の実行時には、実行用のコンテナを作成します。注意点として、Docker Compose で作成したネットワーク(net)を設定する必要がある ということです。このことについては後述します。

また、図には含めていませんが、Goプログラムをビルドする用のコンテナも作成しています。

各 Dockerfile の設定

各コンテナ用の Dockerfile を設定していきます。

SAM Local 用の Dockerfile

SAM Local 用のコンテナでは、Python がインストールされているイメージを使用します。
Dockerfile では、SAM Local 用のコマンド(aws-sam-cli)をインストールするように設定しています。

1
2
3
4
5
6
7
8
9
10
11
12
13
# Dockerfile_sam
FROM python:3.5-alpine

ENV PYTHONUSERBASE=/usr/local

RUN apk add --no-cache py-pip git bash gcc libc-dev && \
pip install --upgrade pip && \
pip install --user awscli==1.16.76 aws-sam-cli==0.9.0

WORKDIR /var/opt
COPY . /var/opt/

EXPOSE 3001

Step Functions Local 用の Dockerfile

Step Functions Local 用のコンテナでは、公式で用意されているイメージを使用します。

1
2
3
4
# Dockerfile_stepfunctions
FROM amazon/aws-stepfunctions-local

EXPOSE 8083

Goプログラムビルド用の Dockerfile

Goプログラムビルド用のコンテナでは、Goがインストールされているイメージを使用します。
Dockerfile では、必要なツールをインストールするように設定しています。

1
2
3
4
5
6
7
8
9
10
11
# Dockerfile_go
FROM golang:1.11.5-alpine

RUN apk add --no-cache git bash make curl gcc libc-dev openssl && \
go get -u github.com/golang/dep/cmd/dep

WORKDIR /go/src/step-functions-local-test
COPY . /go/src/step-functions-local-test

RUN dep ensure
RUN cd ./handlers/helloworld/ && CGO_ENABLED=0 GOOS=linux go build -v -installsuffix cgo -o main .

各種スクリプト

Docker コンテナ内で実行するスクリプトを作成していきます。

Goプログラムビルドスクリプト

go-build.sh という名前で、Goプログラムビルドスクリプトを作成します。
内容としては、依存モジュールのインストールとコンパイルコマンドの実行を行っています。

1
2
3
4
#!/usr/bin/env bash

dep ensure
cd ./handlers/helloworld/ && CGO_ENABLED=0 GOOS=linux go build -v -installsuffix cgo -o main .

SAM Local 起動スクリプト

start-lambda.sh という名前で、SAM Local を起動するスクリプトを作成します。
先述したとおり、Lambda関数実行用コンテナにDockerのネットワークを設定する ようにします。これが行われていないと、SAM LocalのLambdaから他のコンテナ(DynamoDB Localなど)にアクセスすることができなくなります。
--docker-networkオプションで、Dockerのネットワークを設定しています。

1
2
3
4
5
6
7
#!/bin/bash

sam local start-lambda \
--docker-volume-basedir "${VOLUME}" \
--docker-network step-functions-local-test_net \
--host 0.0.0.0 \
--template template.yml

Lambda関数用のプログラム

handlers/helloworld/main.go という名前のファイルで、Lambda関数用のGoプログラムを作成します。
今回は、”Hello World!”という文字列を返すだけのLambda関数を実装しています。

1
2
3
4
5
6
7
8
9
10
11
package main

import "github.com/aws/aws-lambda-go/lambda"

func handler() (string, error) {
return "Hello World!", nil
}

func main() {
lambda.Start(handler)
}

Gopkg.toml という名前のファイルで、依存モジュールを記述します。

1
2
3
4
5
6
7
[prune]
go-tests = true
unused-packages = true

[[constraint]]
name = "github.com/aws/aws-lambda-go"
version = "1.2.0"

SAM テンプレート(template.yml)の設定

SAM Local で読み込ませるテンプレート(template.yml)を作成します。
“HelloWorld”という名前のLambda関数を作成し、先程作成したプログラムを実行するように設定しています。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31

Globals:
Function:
Runtime: go1.x
Timeout: 900

Resources:
HelloWorld:
Properties:
CodeUri: ./handlers/helloworld
FunctionName: HelloWorld
Handler: main
Type: AWS::Serverless::Function

Step Functions Local の設定

Step Functions Local に読み込ませるための設定ファイルを作成します。
aws-stepfunctions-local-credentials.txtというファイル名にします。
今回は Lambda のエンドポイントを指定しています(http://sam-local:3001/)。

1
2
3
4
5
6
7
8
9
10
11
12
13
AWS_DEFAULT_REGION=ap-northeast-1
AWS_ACCESS_KEY_ID=dummy
AWS_SECRET_ACCESS_KEY=dummy
WAIT_TIME_SCALE=10
LAMBDA_ENDPOINT=http://sam-local:3001
BATCH_ENDPOINT=
DYNAMODB_ENDPOINT=
ECS_ENDPOINT=
GLUE_ENDPOINT=
SAGE_MAKER_ENDPOINT=
SQS_ENDPOINT=
SNS_ENDPOINT=
# https://docs.aws.amazon.com/ja_jp/step-functions/latest/dg/sfn-local-config-options.html#docker-credentials

Docker Composeの設定

Docker Composeで複数のDockerコンテナを連携させる設定を記述します。
docker-compose.yml というファイル名にします。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
version: '3'
services:
sam-local:
build:
context: ./
dockerfile: ./Dockerfile_sam
command: ./start-lambda.sh
volumes:
- .:/var/opt/
- /var/run/docker.sock:/var/run/docker.sock
environment:
- VOLUME=$PWD
env_file:
- .env
networks:
- net

step-functions-local:
build:
context: ./
dockerfile: ./Dockerfile_stepfunctions
ports:
- '8083:8083'
env_file:
- aws-stepfunctions-local-credentials.txt
depends_on:
- sam-local
networks:
- net

# Goビルド用
go-build:
build:
context: ./
dockerfile: ./Dockerfile_go
command: ./go-build.sh
volumes:
- .:/go/src/step-functions-local-test/:cached


networks:
net:
driver: bridge

以上で、設定が一通りできました。

コンテナの起動

以下のコマンドでコンテナを起動します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
# ビルド
$ COMPOSE_PROJECT_NAME=step-functions-local-test docker-compose build

# Lambdaハンドラーのビルド
$ COMPOSE_PROJECT_NAME=step-functions-local-test docker-compose run go-build ./go-build.sh

# サーバ立ち上げ
$ COMPOSE_PROJECT_NAME=step-functions-local-test docker-compose up
```

`docker ps` で確認すると、コンテナが起動していることを確認できると思います。


## Step Functions の実行

起動している Step Functions コンテナ内に StateMachine を作成して、実行します。
コンテナを立ち上げたコンソールとは別のコンソールで、以下のコマンドを実行します。

```bash
# StateMachineを作成
$ aws stepfunctions --endpoint http://localhost:8083 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:HelloWorld\",\
\"End\": true\
}\
}\
}\
}}" --name "HelloWorld" --role-arn "arn:aws:iam::012345678901:role/DummyRole"


# StateMachineを実行
$ aws stepfunctions --endpoint http://localhost:8083 start-execution --state-machine arn:aws:states:us-east-1:123456789012:stateMachine:HelloWorld

コンテナを立ち上げたコンソールで、以下のような出力が表示されたら成功です。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
step-functions-local_1  | 2019-07-31 00:49:58.444: [200] StartExecution <= {"sdkResponseMetadata":null,"sdkHttpMetadata":null,"executionArn":"arn:aws:states:ap-northeast-1:123456789012:execution:HelloWorld:9f49a299-36f7-4b22-95b1-fb7ae816b113","startDate":1564534198424}
step-functions-local_1 | 2019-07-31 00:49:58.483: arn:aws:states:ap-northeast-1:123456789012:execution:HelloWorld:9f49a299-36f7-4b22-95b1-fb7ae816b113 : {"Type":"ExecutionStarted","PreviousEventId":0,"ExecutionStartedEventDetails":{"Input":"{}","RoleArn":"arn:aws:iam::012345678901:role/DummyRole"}}
step-functions-local_1 | 2019-07-31 00:49:58.487: arn:aws:states:ap-northeast-1:123456789012:execution:HelloWorld:9f49a299-36f7-4b22-95b1-fb7ae816b113 : {"Type":"TaskStateEntered","PreviousEventId":0,"StateEnteredEventDetails":{"Name":"HelloWorld","Input":"{}"}}
step-functions-local_1 | 2019-07-31 00:49:58.507: arn:aws:states:ap-northeast-1:123456789012:execution:HelloWorld:9f49a299-36f7-4b22-95b1-fb7ae816b113 : {"Type":"LambdaFunctionScheduled","PreviousEventId":2,"LambdaFunctionScheduledEventDetails":{"Resource":"arn:aws:lambda:us-east-1:123456789012:function:HelloWorld","Input":"{}"}}
step-functions-local_1 | 2019-07-31 00:49:58.508: arn:aws:states:ap-northeast-1:123456789012:execution:HelloWorld:9f49a299-36f7-4b22-95b1-fb7ae816b113 : {"Type":"LambdaFunctionStarted","PreviousEventId":3}
sam-local_1 | 2019-07-31 00:49:58 Invoking main (go1.x)
sam-local_1 |
sam-local_1 | Fetching lambci/lambda:go1.x Docker container image......
sam-local_1 | 2019-07-31 00:50:01 Mounting /Users/uchiko/project/step-functions-local-test/handlers/helloworld as /var/task:ro inside runtime container
sam-local_1 | START RequestId: bf7b22bb-7cc7-1703-a07b-f94434457538 Version: $LATEST
sam-local_1 | END RequestId: bf7b22bb-7cc7-1703-a07b-f94434457538
sam-local_1 | REPORT RequestId: bf7b22bb-7cc7-1703-a07b-f94434457538 Duration: 1.21 ms Billed Duration: 100 ms Memory Size: 128 MB Max Memory Used: 5 MB
sam-local_1 | 2019-07-31 00:50:03 172.19.0.3 - - [31/Jul/2019 00:50:03] "POST /2015-03-31/functions/HelloWorld/invocations HTTP/1.1" 200 -
step-functions-local_1 | 2019-07-31 00:50:03.689: arn:aws:states:ap-northeast-1:123456789012:execution:HelloWorld:9f49a299-36f7-4b22-95b1-fb7ae816b113 : {"Type":"LambdaFunctionSucceeded","PreviousEventId":4,"LambdaFunctionSucceededEventDetails":{"Output":"\"Hello World!\""}}
step-functions-local_1 | 2019-07-31 00:50:03.691: arn:aws:states:ap-northeast-1:123456789012:execution:HelloWorld:9f49a299-36f7-4b22-95b1-fb7ae816b113 : {"Type":"TaskStateExited","PreviousEventId":5,"StateExitedEventDetails":{"Name":"HelloWorld","Output":"\"Hello World!\""}}
step-functions-local_1 | 2019-07-31 00:50:03.695: arn:aws:states:ap-northeast-1:123456789012:execution:HelloWorld:9f49a299-36f7-4b22-95b1-fb7ae816b113 : {"Type":"ExecutionSucceeded","PreviousEventId":0,"ExecutionSucceededEventDetails":{"Output":"\"Hello World!\""}}

まとめ

ローカルPC上で Step Functions の動作チェックを行える環境の構築方法をご紹介しました。
Step Functions のロジックは複雑になりがちなので、ぜひ取り入れてみてください。

このエントリーをはてなブックマークに追加