開発覚書はてな版

個人的な開発関連の備忘録

【Docker】Step Functions localとSAM LocalをDocker内で使用してみる

概要

注意点

github.com

上記の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 : コンテナのホストを指定
  • 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\\\"}\"}"}}

サンプルソース一式

github.com

おわりに

  • Docker環境内でStep Functions+Lambdaの動作確認が出来るためかなり便利です。
  • 自動テストなどの環境構築も楽になるので、テスト戦略が広がります。