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 »