どこにでもいる30代SEの学習ブログ

主にプログラミング関連の学習内容。読んだ本の感想や株式投資についても書いてます。

【AWS】Lambdaをローカル環境でテストするのにSAMを使う

ローカル環境でLambdaをテストするにはAWS SAM」を使います。Lambdaのテスト方法は主には以下の2択です。

  1. 本物のLambda関数でテスト
  2. ローカル環境でSAMでテスト

本物のLambda関数をテストしたくない場合、ローカル環境でSAMの方を選択することになります。本記事ではローカル環境でSAMを使用し、Lambda関数をテストする方法を紹介します。

[1] AWS SAMのインストール

SAMのインストール方法とチュートリアルの実行例は下記記事で紹介しています。

https://predora005.hatenablog.com/entry/2021/06/01/190000predora005.hatenablog.com

[2] プロジェクトの作成

sam initでプロジェクトを作成します。ここでは、Python3.8のHello, Worldプロジェクトを例に説明していきます。詳細は別記事で紹介しています。

$ sam init
...(途中省略)...
    -----------------------
    Generating application:
    -----------------------
    Name: sam-app
    Runtime: python3.8
    Dependency Manager: pip
    Application Template: hello-world
    Output Directory: .

sam initは対話形式ですが、以下のようにパラメータを指定することも可能です。

# パラメータを指定
$ sam init --name sam-app --runtime nodejs10.x --dependency-manager npm --app-template hello-world
$ sam init --name sam-app --package-type image --base-image nodejs10.x-bas

# GitHubに保存したテンプレートから作成
$ sam init --location gh:aws-samples/cookiecutter-aws-sam-python
$ sam init --location git+ssh://git@github.com/aws-samples/cookiecutter-aws-sam-python.git
$ sam init --location hg+ssh://hg@bitbucket.org/repo/template-name

# zip化したテンプレートから作成
$ sam init --location /path/to/template.zip
$ sam init --location https://example.com/path/to/template.zip

# ローカルに保存されたテンプレートから作成
$ sam init --location /path/to/template/folder

[3] ビルド

sam buildでビルドします。

$ cd sam-app/
$ sam build
Building codeuri: /home/ec2-user/sam-app/hello_world runtime: python3.8 metadata: {} functions: ['HelloWorldFunction']
Running PythonPipBuilder:ResolveDependencies
Running PythonPipBuilder:CopySource

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

[4] テスト

[4-1] 関数を直接呼び出し

sam local invokeで関数をローカルで呼び出せます。

$ sam local invoke
Invoking app.lambda_handler (python3.8)
Image was not found.
Building image........................
Skip pulling image and use local one: amazon/aws-sam-cli-emulation-image-python3.8:rapid-1.23.0.

Mounting /home/ec2-user/sam-app/.aws-sam/build/HelloWorldFunction as /var/task:ro,delegated inside runtime container
END RequestId: e3e79ef0-1db0-4713-a3bb-434dd4022e4f
REPORT RequestId: e3e79ef0-1db0-4713-a3bb-434dd4022e4f  Init Duration: 0.34 ms    Duration: 307.81 mBilled Duration: 400 ms    Memory Size: 128 MB    Max Memory Used: 128 MB    
{"statusCode": 200, "body": "{\"message\": \"hello world\"}"}

-eオプションで、イベントファイルを読み込ませることも可能です。

$ sam local invoke "HelloWorldFunction" -e events/event.json

[4-2] API Gatewayをローカルで起動

sam local start-apiで、API Gatewayのローカルインスタンスを起動できます。

$ sam local start-api
Mounting HelloWorldFunction at http://127.0.0.1:3000/hello [GET]
You can now browse to the above endpoints to invoke your functions. You do not need to restart/reload SAM CLI while working on your functions, changes will be reflected instantly/automatically. You only need to restart SAM CLI if you update your AWS SAM template
2021-05-01 14:38:53  * Running on http://127.0.0.1:3000/ (Press CTRL+C to quit)

表示されたURL(エンドポイント)にアクセスすると、応答が返ってきます。

$ curl http://127.0.0.1:3000/hello
{"message": "hello world"}

[4-3] Lambda関数のエンドポイントをローカルに作成

[4-3-1] 実行方法

sam local start-lambdaでLambda関数のエンドポイントを作成できます。単発でテストする分にはsam local invokeでも問題ありませんが、AWS SDKを用いてプログラムから起動する場合などで有効活用できます。

$ sam local start-lambda
Starting the Local Lambda Service. You can now invoke your Lambda Functions defined in your template through the endpoint.
2021-05-01 14:39:41  * Running on http://127.0.0.1:3001/ (Press CTRL+C to quit)

aws lambda invokeを使い、エンドポイント経由で起動できます。

$ aws lambda invoke --function-name "HelloWorldFunction" --endpoint-url "http://127.0.0.1:3001" --payload file://events/event.json response.json
{
    "StatusCode": 200
}

[4-3-2] コマンドのオプション

aws lambda invokeの主なオプションは次の通りです。

オプション 内容
function-name 関数名
endpoint-url エンドポイント
invocation-type RequestResponse,Event, DryRunのいずれか。
RequestResponseは同期的呼出、Eventは非同期呼出。
payload Lambda関数へ渡す入力
log-type TailLogResultを出力、Noneで出力無し。
ローカルではTailは使えないよう。
no-verify-ssl SSL 証明書認証をスキップ

詳細は公式のリファレンスに記載があります。

invoke — AWS CLI 1.19.62 Command Reference

sam local start-lambdaのオプションについても公式に一覧が載っています。

sam local start-lambda - AWS Serverless Application Model

[4-4] サンプルイベントを使う

sam local generate-eventを使うと、サンプルイベントを作成してくれます。helpオプションでイベントの種類が確認できます。

$ sam local generate-event --help
...(途中省略)...
Commands:
  alexa-skills-kit
  alexa-smart-home
  apigateway
  appsync
  batch
  cloudformation
  cloudfront
  cloudwatch
  codecommit
  codepipeline
  cognito
  config
  connect
  dynamodb
  kinesis
  lex
  rekognition
  s3
  sagemaker
  ses
  sns
  sqs
  stepfunctions

以下はAPI Gateway(apigateway)の例です。まずはヘルプで使い方を確認します。

$ sam local generate-event apigateway -h
Usage: sam local generate-event apigateway [OPTIONS] COMMAND [ARGS]...

Options:
  -h, --help  Show this message and exit.

Commands:
  authorizer  Generates an Amazon API Gateway Authorizer Event
  aws-proxy   Generates an Amazon API Gateway AWS Proxy Event

イベントを出力し、出力したイベントをLambda関数に渡すことができます。

$ sam local generate-event apigateway aws-proxy > events/apigateway-event.json
$ sam local invoke "HelloWorldFunction" -e events/apigateway-event.json 

[4-5] より詳細を知りたい場合は

公式(下記)を参照してください。

docs.aws.amazon.com

[5] ユニットテスト(pytest)

AWS SAMの機能ではありませんが、Hello, Worldテンプレートにユニットテスト用のソースが格納されています。pytestで実行すると以下のようになります。

$ pytest tests/unit/
======================================= test session starts =======================================
platform linux -- Python 3.8.5, pytest-6.2.3, py-1.10.0, pluggy-0.13.1
rootdir: /home/ec2-user/sam-app
plugins: mock-3.6.0
collected 1 item                                                                                  

tests/unit/test_handler.py .                                                                [100%]

======================================== 1 passed in 0.03s ========================================

pytestのインストールはpipで可能です。

$ pip3 install -U pytest pytest-mock --user

Installation and Getting Started — pytest documentation

終わりに

「SAM」は「Cloud Formation」と似た機能(公式は拡張機能と言っている)であり、学習コストがかかるのも「CloudFormation」と同じです。しかし、「CloudFormation」は現在フル活用している程に便利な機能なので、SAMも少しずつ使いこなせるようにしたいと思います。

参考文献

出典

補足

[補足1] ローカルエンドポイントに対してAWS SDKから呼出

AWS SDK for Python (Boto3) のインストールはpipコマンドで行います。

$ pip3 install boto3 --user

下記公式に掲載されているソースコードをそのまま実行すると、次の結果が得られます。

$ python3 lambda-endpoint.py 
Traceback (most recent call last):
  File "lambda-endpoint.py", line 30, in <module>
    assert response == "Hello World"
AssertionError

responseは"Hello World"ではないので、この動作は正常です。assertで例外を出さないためには、数行の追加とassertの行を変更が必要です。

import json

...(途中省略)...

payload = json.load(response['Payload'])
body = json.loads(payload['body'])
# (変更前) assert response == "Hello World"
assert body['message'] == "hello world"

Integrating with automated tests - AWS Serverless Application Model

[補足2] デプロイ時に生成されるsamconfig.toml

version = 0.1
[default]
[default.deploy]
[default.deploy.parameters]
stack_name = "sam-app"
s3_bucket = "aws-sam-cli-managed-default-samclisourcebucket-xgx2dia4ru2p"
s3_prefix = "sam-app"
region = "ap-northeast-1"
confirm_changeset = true
capabilities = "CAPABILITY_IAM"

[補足3] パッケージ

作成したアプリをパッケージ化してS3バケットにアップロードします。

$ sam package --template-file template.yaml --s3-bucket {バケット名} \
--s3-prefix hello_world --output-template-file template_packaged.yaml

--s3-prefixはzipを直接バケット直下に置いてもいい場合は指定不要です。上の例ではhello_worldフォルダ下にzipが格納されます。

--output-template-fileには変更後のテンプレート出力先をセットします。テンプレート内のCodeUriがzipが格納されたS3のURLに変更されています。

このパッケージを使って、デプロイが可能になります。

$ sam deploy --template-file /home/ec2-user/sam-app/package_template.yaml --stack-name <YOUR STACK NAME>