Lambda関数のローカルでの開発〜デプロイ – SAMを利用した場合

はじめに

  • API Gateway、EventBridege、Webhookから呼び出すLambda関数を、ローカルでデバッグしやすい環境をつくります。

手順

必要なツールのインストール

  • AWS CLI
  • AWS SAM CLI (Serverless Application Model CLI)
  • Docker

プロジェクトの初期化

AWS SAM CLIを使ってプロジェクトを作成します。

% sam init

	SAM CLI now collects telemetry to better understand customer needs.

	You can OPT OUT and disable telemetry collection by setting the
	environment variable SAM_CLI_TELEMETRY=0 in your shell.
	Thanks for your help!

	Learn More: https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-telemetry.html


You can preselect a particular runtime or package type when using the `sam init` experience.
Call `sam init --help` to learn more.

Which template source would you like to use?
	1 - AWS Quick Start Templates
	2 - Custom Template Location
Choice: 1

Choose an AWS Quick Start application template
	1 - Hello World Example
	2 - Data processing
	3 - Hello World Example with Powertools for AWS Lambda
	4 - Multi-step workflow
	5 - Scheduled task
	6 - Standalone function
	7 - Serverless API
	8 - Infrastructure event management
	9 - Lambda Response Streaming
	10 - GraphQLApi Hello World Example
	11 - Full Stack
	12 - Lambda EFS example
	13 - Serverless Connector Hello World Example
	14 - Multi-step workflow with Connectors
	15 - DynamoDB Example
	16 - Machine Learning
Template: 1

Use the most popular runtime and package type? (python3.13 and zip) [y/N]: N

Which runtime would you like to use?
	1 - dotnet8
	2 - dotnet6
	3 - go (provided.al2)
	4 - go (provided.al2023)
	5 - graalvm.java11 (provided.al2)
	6 - graalvm.java17 (provided.al2)
	7 - java21
	8 - java17
	9 - java11
	10 - java8.al2
	11 - nodejs22.x
	12 - nodejs20.x
	13 - nodejs18.x
	14 - python3.9
	15 - python3.8
	16 - python3.13
	17 - python3.12
	18 - python3.11
	19 - python3.10
	20 - ruby3.3
	21 - ruby3.2
	22 - rust (provided.al2)
	23 - rust (provided.al2023)
Runtime: 12

What package type would you like to use?
	1 - Zip
	2 - Image
Package type: 1

Based on your selections, the only dependency manager available is npm.
We will proceed copying the template using npm.

Select your starter template
	1 - Hello World Example
	2 - Hello World Example TypeScript
Template: 1

Would you like to enable X-Ray tracing on the function(s) in your application?  [y/N]: N

Would you like to enable monitoring using CloudWatch Application Insights?
For more info, please view https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/cloudwatch-application-insights.html [y/N]: 

Would you like to set Structured Logging in JSON format on your Lambda functions?  [y/N]: 

Project name [sam-app]: shopify-lambda-api
                                                                                                                                                                                                 
Cloning from https://github.com/aws/aws-sam-cli-app-templates (process may take a moment)                                                                                                        

    -----------------------
    Generating application:
    -----------------------
    Name: shopify-lambda-api
    Runtime: nodejs20.x
    Architectures: x86_64
    Dependency Manager: npm
    Application Template: hello-world
    Output Directory: .
    Configuration file: shopify-lambda-api/samconfig.toml
    
    Next steps can be found in the README file at shopify-lambda-api/README.md
        

Commands you can use next
=========================
[*] Create pipeline: cd shopify-lambda-api && sam pipeline init --bootstrap
[*] Validate SAM template: cd shopify-lambda-api && sam validate
[*] Test Function in the Cloud: cd shopify-lambda-api && sam sync --stack-name {stack-name} --watch

作成後のディレクトリ構造

shopify-lambda-api/
./template.yaml
./hello-world/.npmignore
./hello-world/tests/unit/test-handler.mjs
./hello-world/package.json
./hello-world/app.mjs
./README.md
./.gitignore
./samconfig.toml
./events/event.json

./template.yml

AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: >
  shopify-lambda-api

  Sample SAM Template for shopify-lambda-api
  
# More info about Globals: https://github.com/awslabs/serverless-application-model/blob/master/docs/globals.rst
Globals:
  Function:
    Timeout: 3

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.lambdaHandler
      Runtime: nodejs20.x
      Architectures:
        - x86_64
      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

Outputs:
  # ServerlessRestApi is an implicit API created out of Events key under Serverless::Function
  # Find out more about other implicit resources you can reference within SAM
  # https://github.com/awslabs/serverless-application-model/blob/master/docs/internals/generated_resources.rst#api
  HelloWorldApi:
    Description: "API Gateway endpoint URL for Prod stage for Hello World function"
    Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/hello/"
  HelloWorldFunction:
    Description: "Hello World Lambda Function ARN"
    Value: !GetAtt HelloWorldFunction.Arn
  HelloWorldFunctionIamRole:
    Description: "Implicit IAM Role created for Hello World function"
    Value: !GetAtt HelloWorldFunctionRole.Arn

./hello-world/tests/unit/test-handler.mjs

'use strict';

import { lambdaHandler } from '../../app.mjs';
import { expect } from 'chai';
var event, context;

describe('Tests index', function () {
    it('verifies successful response', async () => {
        const result = await lambdaHandler(event, context)

        expect(result).to.be.an('object');
        expect(result.statusCode).to.equal(200);
        expect(result.body).to.be.an('string');

        let response = JSON.parse(result.body);

        expect(response).to.be.an('object');
        expect(response.message).to.be.equal("hello world");
    });
});

./hello-world/app.mjs

/**
 *
 * Event doc: https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html#api-gateway-simple-proxy-for-lambda-input-format
 * @param {Object} event - API Gateway Lambda Proxy Input Format
 *
 * Context doc: https://docs.aws.amazon.com/lambda/latest/dg/nodejs-prog-model-context.html 
 * @param {Object} context
 *
 * Return doc: https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html
 * @returns {Object} object - API Gateway Lambda Proxy Output Format
 * 
 */

export const lambdaHandler = async (event, context) => {
    const response = {
      statusCode: 200,
      body: JSON.stringify({
        message: 'hello world',
      })
    };

    return response;
  };

./samconfig.toml

# More information about the configuration file can be found here:
# https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-config.html
version = 0.1

[default.global.parameters]
stack_name = "shopify-lambda-api"

[default.build.parameters]
cached = true
parallel = true

[default.validate.parameters]
lint = true

[default.deploy.parameters]
capabilities = "CAPABILITY_IAM"
confirm_changeset = true
resolve_s3 = true

[default.package.parameters]
resolve_s3 = true

[default.sync.parameters]
watch = true

[default.local_start_api.parameters]
warm_containers = "EAGER"

[default.local_start_lambda.parameters]
warm_containers = "EAGER"

./events/event.json

{
  "body": "{\"message\": \"hello world\"}",
  "resource": "/{proxy+}",
  "path": "/path/to/resource",
  "httpMethod": "POST",
  "isBase64Encoded": false,
  "queryStringParameters": {
    "foo": "bar"
  },
  "pathParameters": {
    "proxy": "/path/to/resource"
  },
  "stageVariables": {
    "baz": "qux"
  },
  "headers": {
    "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
    "Accept-Encoding": "gzip, deflate, sdch",
    "Accept-Language": "en-US,en;q=0.8",
    "Cache-Control": "max-age=0",
    "CloudFront-Forwarded-Proto": "https",
    "CloudFront-Is-Desktop-Viewer": "true",
    "CloudFront-Is-Mobile-Viewer": "false",
    "CloudFront-Is-SmartTV-Viewer": "false",
    "CloudFront-Is-Tablet-Viewer": "false",
    "CloudFront-Viewer-Country": "US",
    "Host": "1234567890.execute-api.us-east-1.amazonaws.com",
    "Upgrade-Insecure-Requests": "1",
    "User-Agent": "Custom User Agent String",
    "Via": "1.1 08f323deadbeefa7af34d5feb414ce27.cloudfront.net (CloudFront)",
    "X-Amz-Cf-Id": "cDehVQoZnx43VYQb9j2-nvCh-9z396Uhbp027Y2JvkCPNLmGJHqlaA==",
    "X-Forwarded-For": "127.0.0.1, 127.0.0.2",
    "X-Forwarded-Port": "443",
    "X-Forwarded-Proto": "https"
  },
  "requestContext": {
    "accountId": "123456789012",
    "resourceId": "123456",
    "stage": "prod",
    "requestId": "c6af9ac6-7b61-11e6-9a41-93e8deadbeef",
    "requestTime": "09/Apr/2015:12:34:56 +0000",
    "requestTimeEpoch": 1428582896000,
    "identity": {
      "cognitoIdentityPoolId": null,
      "accountId": null,
      "cognitoIdentityId": null,
      "caller": null,
      "accessKey": null,
      "sourceIp": "127.0.0.1",
      "cognitoAuthenticationType": null,
      "cognitoAuthenticationProvider": null,
      "userArn": null,
      "userAgent": "Custom User Agent String",
      "user": null
    },
    "path": "/prod/path/to/resource",
    "resourcePath": "/{proxy+}",
    "httpMethod": "POST",
    "apiId": "1234567890",
    "protocol": "HTTP/1.1"
  }
}

Lambda関数の作成

プロジェクト内にあるコードファイル(app.pyapp.js)を編集します。

Node.jsの例 (app.js)

exports.handler = async (event) => {
    console.log("Event Received:", event);
    return {
        statusCode: 200,
        body: "Hello from Lambda!",
    };
};

テストイベントの設定

events/event.jsonにテストデータを追加します。

例 (API Gateway用)

{
    "httpMethod": "GET",
    "path": "/hello",
    "queryStringParameters": {
        "name": "World"
    }
}

例 (EventBridge用)

{
    "source": "com.example.myapp",
    "detail-type": "MyDetailType",
    "detail": {
        "key1": "value1"
    }
}

ローカルでテスト実行

a) Lambda関数のローカル実行

テストイベントを使って関数を実行します。

sam local invoke "HelloWorldFunction" -e events/event.json
  • "HelloWorldFunction"template.yaml に定義されたLambda関数の名前です。
  • 出力例: { "statusCode": 200, "body": "Hello from Lambda!" }

b) API Gatewayのローカルエミュレーション

API Gatewayをエミュレートし、HTTPリクエストでLambdaをテストします。

sam local start-api
  • デフォルトで http://localhost:3000 にエンドポイントが作成されます。
  • ブラウザやPostmanでリクエストを送信: GET http://localhost:3000/hello?name=World

c) EventBridgeイベントのエミュレート

EventBridgeのイベントを生成してLambdaをトリガーします。

sam local generate-event eventbridge put-event \
  --source "com.example.myapp" \
  --detail-type "MyDetailType" \
  --detail '{"key1":"value1"}' > events/eventbridge.json

sam local invoke "HelloWorldFunction" -e events/eventbridge.json

デバッグ設定

ローカルでデバッグを行うには、SAM CLIにデバッグポートを指定します。

例 (Node.jsのデバッグ)

sam local invoke -d 9229 "HelloWorldFunction"

デバッガ(VS CodeやPyCharm)を設定して、指定ポートに接続します。

本番デプロイ

開発が完了したら、SAM CLIを使ってデプロイします。

  1. デプロイ実行 sam deploy --guided
  2. プロンプトで設定
    • デプロイ先リージョン(例: us-east-1
    • スタック名(例: my-lambda-stack
  3. デプロイ結果の確認 Outputs: HelloWorldApi: Description: API Gateway endpoint URL Value: https://abc123.execute-api.us-east-1.amazonaws.com/Prod/hello/

デプロイ後、生成されたAPI GatewayエンドポイントでLambdaを呼び出せます。

まとめ

  1. 必要なツール(AWS CLI, SAM CLI, Docker)をインストール。
  2. sam init でプロジェクトを作成。
  3. Lambda関数やテストイベントを設定。
  4. sam local invokesam local start-api を使ってローカルでテスト。
  5. sam deploy で本番環境にデプロイ。

これで、ローカル環境でAWS Lambdaを効率的に開発・テストするフローが構築できます!

関連記事

カテゴリー

アーカイブ

Lang »