AWS SAM CLI と localstack を利用して Lambda をローカル実行してみよう

はじめに

Lambda 開発する中で、動作確認をローカルで実行できないかと思ったことはありませんか?

そんな疑問も、AWSが提供している SAM CLI を活用することで、ローカル実行することができます。さらに、dockerを使ったlocalstack も使用することで、S3 や SecretsManager などのAWSリソースを、ローカル環境でエミュレートすることも可能です。

それでは、ローカル環境でLambdaの実行ができるように環境を作ってみましょう。

実施環境

・mac OS(10.14.5)

・SAM CLI(1.6.2)

・AWS CLI(2.0.59)

・python (3.7)

・docker-compose(1.25.4)

事前準備

dockerインストール
homebrewインストール
は割愛します。こちらは、AWSのSAM CLIドキュメントに記載されています。

  • SAM CLIインストール
brew tap aws/tap
brew install aws-sam-cli

sam --version
SAM CLI, version 1.6.2
  • AWS CLIインストール

localstackコンテナでのAWSリソースデータを作る際使うのでインストールしておきます。

brew install awscli
  • AWS認証情報設定

localstack用プロファイルとしてダミー値で登録します。

aws configure --profile=localstack
AWS Access Key ID [None]: dummy
AWS Secret Access Key [None]: dummy
Default region name [None]: ap-northeast-1
Default output format [None]: json

設定入ったか確認します。

$ cat ~/.aws/credentials
[localstack]
aws_access_key_id = dummy
aws_secret_access_key = dummy
[default]
aws_access_key_id = dummy
aws_secret_access_key = dummy

$ cat ~/.aws/config
[profile localstack]
region = ap-northeast-1
output = json
[default]
region = ap-northeast-1
output = json

localstack profileとして登録されていますね。

Lambda モジュール作成

  • Lambda雛形作成

SAM CLIコマンドで、Lambda用のファイルの雛形が楽に作成できます。
今回はPythonで作ってみます。

今回はハローワールドとして作成してみます。(2つとも「1」を選択)
色々テンプレートが用意されているようですね。

$ sam init --runtime python3.7 --name lambda_test
Which template source would you like to use?
        1 - AWS Quick Start Templates
        2 - Custom Template Location
Choice: 1 

Cloning app templates from https://github.com/awslabs/aws-sam-cli-app-templates.git

AWS quick start application templates:
        1 - Hello World Example
        2 - EventBridge Hello World
        3 - EventBridge App from scratch (100+ Event Schemas)
        4 - Step Functions Sample App (Stock Trader)
Template selection: 1

-----------------------
Generating application:
-----------------------
Name: lambda_test
Runtime: python3.7
Dependency Manager: pip
Application Template: hello-world
Output Directory: .

Next steps can be found in the README file at ./lambda_test/README.md
$

そうすると下記のように雛形が作成されています。

$ ls -l
drwxr-xr-x  9 hoge  hoge  288  5 15 20:44 lambda_test

$ ls -l lambda_test
-rw-r--r--  1 hoge  hoge  8180  5 15 20:44 README.md
-rw-r--r--  1 hoge  hoge     0  5 15 20:44 __init__.py
drwxr-xr-x  3 hoge  hoge    96  5 15 20:44 events
drwxr-xr-x  5 hoge  hoge   160  5 15 20:44 hello_world
-rw-r--r--  1 hoge  hoge  1631  5 15 20:44 template.yaml
drwxr-xr-x  4 hoge  hoge   128  5 15 20:44 tests

このように雛形ファイルができてました。
Lambda 実行ファイルは、hello_world/app.py になります。

それでは各設定ファイルを設定していきましょう。

  • template.yaml

Lambdaの環境変数をここに定義します。Environmentのところを追加します。
コンソールにある環境変数の設定がこれに該当します。

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.7
      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
      Environment:
        Variables:
          S3_BUCKET: 'test-bucket'
          S3_PATH: '/sample_data/test.csv'
          SECRET_KEY: 'test_key'
          TZ: 'Asia/Tokyo'
  • requirements.txt

hello_worldフォルダ配下にファイルがあります。
こちらのファイルに、処理で使うpythonのモジュールを記載します。
AWSリソース使うため boto3 を入れます。

requests
boto3
  • docker-compose.yml

こちらにlocalstackを設定します。雛形では作られないのでファイル作成して設定しましょう。

version: '3'

services:
  localstack:
    image: localstack/localstack
    ports:
      - "4566:4566"
      - "4571:4571"
      - "8080:8080"
    environment:
      - SERVICES=s3,lambda,secretsmanager,logs
      - DATA_DIR=/tmp/localstack/data
      - DEFAULT_REGION=ap-northeast-1
    volumes:
      - "${TMPDIR:-/tmp/localstack}:/tmp/localstack"

environment の SERVICES のところに起動したいサービス(上記は、s3/lambda/secretsmanager/logsを指定)を設定します。

それでは、docker-compose を起動してみましょう。

  • docker-compose 起動
$ TMPDIR=/private$TMPDIR docker-compose up -d
Creating network "lambda_test_default" with the default driver
Creating lambda_test_localstack_1 ... done

下記URLでサービスが立ち上がったか見れます。(runningであれば起動済み)

http://localhost:4566/health

それでは、実際にデータ設定してみましょう。

テスト用データ作成

AWS CLIで、profileに「localstack」を指定、endpoint-urlに「http://localhost:4566」を指定して実行することで、localstackにデータ設定することができます。

  • S3バケット作成
$ aws s3 mb s3://test-bucket --endpoint-url=http://localhost:4566 --profile=localstack
make_bucket: test-bucket
  • S3ファイルアップロード

サンプルとしてtest.csvを用意し、アップロードします。

$ aws s3 --endpoint-url=http://localhost:4566 cp tests/test.csv s3://test-bucket/sample_data/ --profile=localstack
upload: tests/test.csv to s3://test-bucket/sample_data/test.csv
  • S3アップロード確認
$ aws s3 ls --endpoint-url=http://localhost:4566 s3://test-bucket/sample_data/  --profile=localstack
2021-05-15 21:56:47         17 test.csv

S3にアップロードしたということをエミュレートできていいますね。

  • SecretsManagerキー作成

キーとしてサンプルファイルを用意して登録します。

$ aws --endpoint-url=http://localhost:4566 secretsmanager create-secret --name test_key  --description "secrets key" --secret-string file://tests/samplekey --profile localstack
{
    "ARN": "arn:aws:secretsmanager:ap-northeast-1:1234567890:secret:test_key-dVhXa",
    "Name": "test_key",
    "VersionId": "44647d9c-f589-4aa7-bfed-5e4d77f2a355"
}
  • SecretsManagerキー取得して確認
$ aws --endpoint-url=http://localhost:4566 secretsmanager  get-secret-value --secret-id test_key --profile localstack
{
    "ARN": "arn:aws:secretsmanager:ap-northeast-1:1234567890:secret:test_key-dVhXa",
    "Name": "test_key",
    "VersionId": "44647d9c-f589-4aa7-bfed-5e4d77f2a355",
    "SecretString": "# sample\ntest key\n",
    "VersionStages": [
        "AWSCURRENT"
    ],
    "CreatedDate": "2021-05-15T22:03:05+09:00"
}

※キーファイルはサンプルなので下記のようにしています。
# sample
test key

コマンド結果のSecretStringの箇所に、設定したキーが確認できますね。

これでモックデータも作成できましたので、実際にLambdaのMain処理を用意します。

Lambda処理作成

雛形作成時に hello_world/app.py が作成されているので、こちらにコーディングします。

S3、SecretsManagerを使用する際、endpoint-urlに「`http://localstack:4566`」を指定することで localstackのデータを参照することができます。

import os
import io
import json
import boto3
import logging

# logger
logger = logging.getLogger()
logger.setLevel(logging.INFO)

# S3ファイル取得
def getS3File(bucketName, s3FilePath, dlName):

    try:
        # s3 client
        s3Cli = boto3.client('s3', endpoint_url='http://localstack:4566')
        tmpFile = os.path.join('/tmp/', dlName)
        logger.info(tmpFile)
        s3Cli.download_file(bucketName, s3FilePath, tmpFile)
        # ファイルダウンロードできていること確認
        logger.info(os.listdir(path='/tmp/'))
    except (Exception) as e:
        logger.error(str(e))
        raise e

# シークレットキー取得
def getSecret(secretsName):
    session = boto3.session.Session()
    client = session.client(
        service_name='secretsmanager',
        region_name='ap-northeast-1',
        endpoint_url='http://localstack:4566'
    )
    try:
        response = client.get_secret_value(SecretId=secretsName)
    except Exception as e:
        logger.error(str(e))
        raise e
    else:
        if 'SecretString' in response:
            secret = response['SecretString']
        else:
            secret = base64.b64decode(response['SecretBinary'])

    return secret

def lambda_handler(event, context):

    # 環境変数取得
    s3Bucket = os.environ['S3_BUCKET']
    s3Path = os.environ['S3_PATH']
    secretKey = os.environ['SECRET_KEY']

    # 環境変数設定できているか確認
    logger.info(s3Bucket)
    logger.info(s3Path)
    logger.info(secretKey)

    # S3ファイル取得
    getS3File(s3Bucket, s3Path, 'lambda-test.csv')

    # SecretsManager値取得
    keyData = getSecret(secretKey)
    logger.info(keyData)

    return {
        "statusCode": 200,
        "body": json.dumps({
            "message": "hello world",
        }),
    }

これでソースも用意できたので実際に Lambda 実行してみましょう。

Lambda実行

  • ビルド
$ sam build --use-container
Starting Build inside a container
Building codeuri: hello_world/ runtime: python3.7 metadata: {} functions: ['HelloWorldFunction']

Fetching amazon/aws-sam-cli-build-image-python3.7 Docker container image.......
Mounting /Users/hogehoge/hello_world as /tmp/samcli/source:ro,delegated inside runtime container

Build Succeeded

Built Artifacts  : .aws-sam/build
Built Template   : .aws-sam/build/template.yaml

Commands you can use next
=========================
[*] Invoke Function: sam local invoke
[*] Deploy: sam deploy --guided
    
Running PythonPipBuilder:ResolveDependencies
Running PythonPipBuilder:CopySource
  • localstackネットワークID確認
$ docker network ls
NETWORK ID          NAME                  DRIVER              SCOPE
1234abcd        lambda_test_default   bridge              local

NETWORK IDをメモしておきましょう。Lambda実行時に指定します。

  • Lambdaローカル実行

docker-network に上記でメモしました NETWORK ID を指定します。

log-file を指定すると実行ログがファイル出力できます。

$sam local invoke HelloWorldFunction --docker-network <上記で確認したNETWORK ID> --log-file check.log --profile=localstack

これで、Lambdaをローカルで実行できました。

実行ログを見てみると正常に実行できていることが確認できます。

START RequestId: 54f546e0-af12-1b3f-0a4f-255106ac6bf9 Version: $LATEST
[INFO]  2021-05-15T15:56:37.581Z        54f546e0-af12-1b3f-0a4f-255106ac6bf9    test-bucket
[INFO]  2021-05-15T15:56:37.581Z        54f546e0-af12-1b3f-0a4f-255106ac6bf9    sample_data/test.csv
[INFO]  2021-05-15T15:56:37.581Z        54f546e0-af12-1b3f-0a4f-255106ac6bf9    test_key
[INFO]  2021-05-15T15:56:37.605Z        54f546e0-af12-1b3f-0a4f-255106ac6bf9    Found credentials in environment variables.
[INFO]  2021-05-15T15:56:38.425Z        54f546e0-af12-1b3f-0a4f-255106ac6bf9    /tmp/lambda-test.csv
[INFO]  2021-05-15T15:56:38.491Z        54f546e0-af12-1b3f-0a4f-255106ac6bf9    ['lambda-test.csv']
[INFO]  2021-05-15T15:56:38.518Z        54f546e0-af12-1b3f-0a4f-255106ac6bf9    Found credentials in environment variables.
[INFO]  2021-05-15T15:56:39.24Z 54f546e0-af12-1b3f-0a4f-255106ac6bf9    # sample
test key

END RequestId: 54f546e0-af12-1b3f-0a4f-255106ac6bf9
REPORT RequestId: 54f546e0-af12-1b3f-0a4f-255106ac6bf9  Init Duration: 1069.92 ms       Duration: 1447.93 ms    Billed Duration: 1500 ms        Memory Size: 128 MB       Max Memory Used: 43 MB

{"statusCode":200,"body":"{\"message\": \"hello world\"}"}

 

他にもS3 PUTトリガーなどのイベント情報設定も下記コマンドで作れます。

$sam local generate-event s3 put --bucket test-bucket --key sample_data/test.csv > events/event.json

下記のようにeven.jsonができます。

{
  "Records": [
    {
      "eventVersion": "2.0",
      "eventSource": "aws:s3",
      "awsRegion": "ap-northeast-1",
      "eventTime": "1970-01-01T00:00:00.000Z",
      "eventName": "ObjectCreated:Put",
      "userIdentity": {
        "principalId": "EXAMPLE"
      },
      "requestParameters": {
        "sourceIPAddress": "127.0.0.1"
      },
      "responseElements": {
        "x-amz-request-id": "EXAMPLE123456789",
        "x-amz-id-2": "EXAMPLE123/5678abcdefghijklambdaisawesome/mnopqrstuvwxyzABCDEFGH"
      },
      "s3": {
        "s3SchemaVersion": "1.0",
        "configurationId": "testConfigRule",
        "bucket": {
          "name": "test-bucket",
          "ownerIdentity": {
            "principalId": "EXAMPLE"
          },
          "arn": "arn:aws:s3:::test-bucket"
        },
        "object": {
          "key": "sample_data/test.csv",
          "size": 1024,
          "eTag": "0123456789abcdef0123456789abcdef",
          "sequencer": "0A1B2C3D4E5F678901"
        }
      }
    }
  ]
}

いかがでしたでしょうか。Lambda処理をローカル環境にて実行できました。

SAM CLI とlocalstackを使用することで、AWSコンソールにデプロイせずとも、AWSリソースをエミュレートし、Lambda処理をローカル実行できるのは開発捗りますよね。

  • (補足)docker-compose 停止

終わったら起動したdocker-compose停止しておきましょう。

docker-compose down

参考

SAM CLIインストールのドキュメント

https://docs.aws.amazon.com/ja_jp/serverless-application-model/latest/developerguide/serverless-sam-cli-install.html

SAM CLIコマンドコマンドリファレンス

https://docs.aws.amazon.com/ja_jp/serverless-application-model/latest/developerguide/serverless-sam-cli-command-reference.html

localstackのgithub

https://github.com/localstack/localstack


--------------------------
システム開発のご要望・ご相談はこちらから

コメントを残す

メールアドレスが公開されることはありません。