概要
- この記事では、Step Functions localとSAM LocalをDocker内で実行するサンプルを作成します。
注意点
上記のissueに記載がありますが、以下の不具合があります。
- SAM CLIのバージョンが1.27.2時点で、Docker内でSAM CLI使用時に
--docker-volume-basedir
のパスが正しく反映されません。 - Lambdaを
sam local invoke
で実行するとRuntime.HandlerNotFound: index.handler is undefined or not exported
エラーが発生します。 - 上記のエラーが発生しないように今回は対応します。
動作環境
実装方法
- SAM CLIがインストールされているDockerfileを用意する。
- docker-compose.ymlに上記のDockerfileとStep Functions localを起動する内容を記載する。
- SAM CLIがインストールされているDockerfileを起動時に
sam local start-lambda
を実行する。
サンプルソース
./docker/sam-local/Dockerfile
FROM amazon/aws-cli:2.2.29 RUN yum -y update && yum install -y python3 RUN python3 -m pip install aws-sam-cli==1.27.2 ENTRYPOINT [""]
docker-compose.yml
version: "3.8" services: sam-local: build: context: . dockerfile: ./docker/sam-local/Dockerfile ports: - 3001:3001 working_dir: $PWD volumes: - /var/run/docker.sock:/var/run/docker.sock - $PWD:$PWD command: > sam local start-lambda --host 0.0.0.0 --container-host host.docker.internal sfn-local: image: amazon/aws-stepfunctions-local:1.7.9 ports: - 8083:8083 environment: AWS_DEFAULT_REGION: ap-northeast-1 AWS_ACCESS_KEY_ID: dummy AWS_SECRET_ACCESS_KEY: dummy LAMBDA_ENDPOINT: http://sam-local:3001
- sam-local
- 注意点に記載がある通りDocker内では
--docker-volume-basedir
が正しく動作しないので、ホストの$PWDをworking_dirに設定して、コンテナ内のパスとホストのパスを同じにする volumes
に/var/run/docker.sock:/var/run/docker.sock
を指定することでDocker内でDocker操作を出来るようにするsam local start-lambda
--host 0.0.0.0
: ホストを修正--container-host host.docker.internal
: コンテナのホストを指定
- 注意点に記載がある通りDocker内では
- sfn-local
LAMBDA_ENDPOINT
に sam-localを指定する
template.yaml
AWSTemplateFormatVersion: '2010-09-09' Transform: AWS::Serverless-2016-10-31 Description: Sample SAM Template for sam-app Globals: Function: Timeout: 3 Resources: HelloWorldFunction: Type: AWS::Serverless::Function Properties: CodeUri: hello-world/ Handler: app.lambdaHandler Runtime: nodejs14.x
sam init
で作成したのをそのまま使用
hello-world/app.js
let response; exports.lambdaHandler = async (event, context) => { try { response = { 'statusCode': 200, 'body': JSON.stringify({ message: 'hello world', // location: ret.data.trim() }) } } catch (err) { console.log(err); return err; } return response };
sam init
で作成したのをそのまま使用
statemachine/sfn.asl.json
{ "StartAt": "HelloWorld", "States": { "HelloWorld": { "Type": "Task", "Resource": "arn:aws:lambda:ap-northeast-1:123456789012:function:HelloWorldFunction", "End": true } } }
- HelloWorldFunctionを実行するステートマシンの定義を作成
動作確認
Docker起動
# cmd docker-compose up # log sfn-local_1 | Step Functions Local sfn-local_1 | Version: 1.7.9 sfn-local_1 | Build: 2021-06-24 sfn-local_1 | 2021-08-14 09:27:54.668: Configure [AWS_DEFAULT_REGION] to [ap-northeast-1] sfn-local_1 | 2021-08-14 09:27:54.670: Configure [LAMBDA_ENDPOINT] to [http://sam-local:3001] sfn-local_1 | 2021-08-14 09:27:54.686: Loaded credentials from environment sfn-local_1 | 2021-08-14 09:27:54.686: Starting server on port 8083 with account 123456789012, region ap-northeast-1 sam-local_1 | Starting the Local Lambda Service. You can now invoke your Lambda Functions defined in your template through the endpoint. sam-local_1 | 2021-08-14 09:27:55 * Running on http://0.0.0.0:3001/ (Press CTRL+C to quit) sfn-local_1 | Aug 14, 2021 9:27:55 AM com.amazonaws.internal.DefaultServiceEndpointBuilder getServiceEndpoint sfn-local_1 | INFO: {databrew, ap-northeast-1} was not found in region metadata, trying to construct an endpoint using the standard pattern for this region: 'databrew.ap-northeast-1.amazonaws.com'.
Step Functions登録
# cmd aws stepfunctions --endpoint http://localhost:8083 create-state-machine --name "HelloWorld" --role-arn "arn:aws:iam::012345678901:role/DummyRole" --definition file://./statemachine/sfn.asl.json # log sfn-local_1 | 2021-08-14 09:34:00.940: CreateStateMachine => {"requestClientOptions":{"readLimit":131073,"skipAppendUriPath":false},"requestMetricCollector":null,"customRequestHeaders":null,"customQueryParameters":null,"cloneSource":null,"sdkRequestTimeout":null,"sdkClientExecutionTimeout":null,"name":"HelloWorld","definition":"{\n \"StartAt\": \"HelloWorld\",\n \"States\": {\n \"HelloWorld\": {\n \"Type\": \"Task\",\n \"Resource\": \"arn:aws:lambda:ap-northeast-1:123456789012:function:HelloWorldFunction\",\n \"End\": true\n }\n }\n}\n","roleArn":"arn:aws:iam::012345678901:role/DummyRole","type":null,"loggingConfiguration":null,"tags":null,"tracingConfiguration":null,"requestCredentials":null,"requestCredentialsProvider":null,"generalProgressListener":{"syncCallSafe":true},"readLimit":131073,"cloneRoot":null} sfn-local_1 | 2021-08-14 09:34:00.964: [200] CreateStateMachine <= {"sdkResponseMetadata":null,"sdkHttpMetadata":null,"stateMachineArn":"arn:aws:states:ap-northeast-1:123456789012:stateMachine:HelloWorld","creationDate":1628933640955}
Step Functions実行
# cmd aws stepfunctions --endpoint http://localhost:8083 start-execution --state-machine arn:aws:states:ap-northeast-1:123456789012:stateMachine:HelloWorld --name test # log sfn-local_1 | 2021-08-14 09:35:28.206: [200] StartExecution <= {"sdkResponseMetadata":null,"sdkHttpMetadata":null,"executionArn":"arn:aws:states:ap-northeast-1:123456789012:execution:HelloWorld:test","startDate":1628933728202} sfn-local_1 | 2021-08-14 09:35:28.221: arn:aws:states:ap-northeast-1:123456789012:execution:HelloWorld:test : {"Type":"ExecutionStarted","PreviousEventId":0,"ExecutionStartedEventDetails":{"Input":"{}","RoleArn":"arn:aws:iam::012345678901:role/DummyRole"}} sfn-local_1 | 2021-08-14 09:35:28.223: arn:aws:states:ap-northeast-1:123456789012:execution:HelloWorld:test : {"Type":"TaskStateEntered","PreviousEventId":0,"StateEnteredEventDetails":{"Name":"HelloWorld","Input":"{}"}} sfn-local_1 | 2021-08-14 09:35:28.233: arn:aws:states:ap-northeast-1:123456789012:execution:HelloWorld:test : {"Type":"LambdaFunctionScheduled","PreviousEventId":2,"LambdaFunctionScheduledEventDetails":{"Resource":"arn:aws:lambda:ap-northeast-1:123456789012:function:HelloWorldFunction","Input":"{}"}} sfn-local_1 | 2021-08-14 09:35:28.233: arn:aws:states:ap-northeast-1:123456789012:execution:HelloWorld:test : {"Type":"LambdaFunctionStarted","PreviousEventId":3} sfn-local_1 | 2021-08-14 09:35:32.097: arn:aws:states:ap-northeast-1:123456789012:execution:HelloWorld:test : {"Type":"LambdaFunctionSucceeded","PreviousEventId":4,"LambdaFunctionSucceededEventDetails":{"Output":"{\"statusCode\":200,\"body\":\"{\\\"message\\\":\\\"hello world\\\"}\"}"}} sfn-local_1 | 2021-08-14 09:35:32.099: arn:aws:states:ap-northeast-1:123456789012:execution:HelloWorld:test : {"Type":"TaskStateExited","PreviousEventId":5,"StateExitedEventDetails":{"Name":"HelloWorld","Output":"{\"statusCode\":200,\"body\":\"{\\\"message\\\":\\\"hello world\\\"}\"}"}} sfn-local_1 | 2021-08-14 09:35:32.103: arn:aws:states:ap-northeast-1:123456789012:execution:HelloWorld:test : {"Type":"ExecutionSucceeded","PreviousEventId":6,"ExecutionSucceededEventDetails":{"Output":"{\"statusCode\":200,\"body\":\"{\\\"message\\\":\\\"hello world\\\"}\"}"}}
サンプルソース一式
おわりに
- Docker環境内でStep Functions+Lambdaの動作確認が出来るためかなり便利です。
- 自動テストなどの環境構築も楽になるので、テスト戦略が広がります。