【Serverless Framework】S3イベントでLambda起動をローカルで実行したい


前記事でserverless frameworkでのローカル環境構築方法を勉強してみたのですが、その続きみたいな記事になります。
今回はS3イベントをトリガーにしてLambdaを起動し、LambdaがS3やDynamoDBにアクセスするような構成を想定してローカルに動作確認用の環境を構築していきます。

ローカルに構築するインフラ構成

必要な物

  • Nodejs
  • npm
  • Java
  • awscli

手順

やっていきます。

プロジェクト作成

$ npx serverless create -t aws-nodejs -p local-s3-lambda-app
$ cd local-s3-lambda-app
$ npm init -y

必要なモジュール類をインストール

$ npm i serverless serverless-dynamodb-local serverless-s3-local aws-sdk

Lambdaとして動かす関数を用意

2つLambdaを作成します。

  1. S3にオブジェクトをPutするLambda(trigger)
  2. S3イベントでトリガーされるLambda(handler)
s3_trigger.js
"use strict";
const AWS = require("aws-sdk");
const fs = require("fs");
const s3Client = new AWS.S3({
  s3ForcePathStyle: true,
  accessKeyId: "S3RVER",
  secretAccessKey: "S3RVER",
  endpoint: new AWS.Endpoint("http://localhost:4569"),
});

module.exports.trigger = async (event, context, callback) => {
  const uploadFile = fs.readFileSync("./upload_data/upload_data.json");
  const params = {
    Bucket: "local-bucket",
    Key: "upload_data.json",
    Body: uploadFile,
  };
  const response = await s3Client
    .putObject(params)
    .promise()
    .catch((err) => {
      console.log(err, err.stack);
    });
  console.log("OK");
  callback();
};

handler.js

"use strict";
const AWS = require("aws-sdk");
const dynamodbClient = new AWS.DynamoDB({
  endpoint: "http://localhost:8000",
});

module.exports.handler = async (event) => {
  const params = {
    TableName: "SomeTable",
  };
  const response = await dynamodbClient
    .scan(params)
    .promise()
    .catch((err) => console.log(err));
  console.log(JSON.stringify(response, null, 1));
};

serverless.ymlを修正

serverless.ymlにローカルで動かすS3とDynamoDBとLambdaを記述します。

serverless.yml
service: local-s3-lambda-app
configValidationMode: error
frameworkVersion: "2"

plugins:
  - serverless-s3-local
  - serverless-dynamodb-local

provider:
  name: aws
  runtime: nodejs12.x
  stage: dev
  region: ap-northeast-1
  lambdaHashingVersion: 20201221

custom:
  s3:
    host: localhost
    directory: ./local_s3
  dynamodb:
    stages:
      - dev
    start:
      port: 8000
      inMemory: true
      migrate: true
      seed: true
    seed:
      development:
        sources:
          - table: SomeTable
            sources: [./seed/records.json]

resources:
  Resources:
    NewResource:
      Type: AWS::S3::Bucket
      Properties:
        BucketName: local-bucket
      Resources:
    SomeTable:
      Type: AWS::DynamoDB::Table
      Properties:
        TableName: SomeTable
        AttributeDefinitions:
          - AttributeName: attr1
            AttributeType: S
        KeySchema:
          - AttributeName: attr1
            KeyType: HASH
        ProvisionedThroughput:
          ReadCapacityUnits: 1
          WriteCapacityUnits: 1

functions:
  trigger:
    handler: s3_trigger.trigger
  handler:
    handler: handler.handler
    events:
      - s3:
          bucket: local-bucket
          event: s3:Put

必要なファイルを追加

S3にPutするファイルとDynamoDBに初期投入するデータを用意します。

S3用

$ mkdir upload_data
$ touch upload_data/upload_data.json
$ echo '{}' > upload_data/upload_data.json

DynamoDB用

$ mkdir seed
$ touch seed/records.json
records.json
[
  {
    "attr1": "value1-1",
    "attr2": "value2-1",
    "attr3": "value3-1"
  },
  {
    "attr1": "value1-2",
    "attr2": "value2-2",
    "attr3": "value3-2"
  }
]

localDynamoDBをインストール

$ $(npm bin)/sls dynamodb install

ローカルで起動してみる

順番はこんな感じです

  1. S3を起動する
  2. DynamoDBを起動する
  3. Lambda(s3_trigger)をinvoke localする

S3イベントがhandlerをinvokeしてDynamoDBのレコードがコンソールに出力されていればOKです。

$ $(npm bin)/sls s3 start
$ $(npm bin)/sls dynamodb start
$ $(npm bin)/sls invoke local -f trigger

{
 "Items": [
  {
   "attr2": {
    "S": "value2-2"
   },
   "attr1": {
    "S": "value1-2"
   },
   "attr3": {
    "S": "value3-2"
   }
  },
  {
   "attr2": {
    "S": "value2-1"
   },
   "attr1": {
    "S": "value1-1"
   },
   "attr3": {
    "S": "value3-1"
   }
  }
 ],
 "Count": 2,
 "ScannedCount": 2
}

無事出力されました!

終わりに

ローカルでS3イベントからLambda起動してDynamoDBの読み取りまでできちゃうのはすごいですね。
他にもapi-gatewayも動かせたりするので、まだまだローカルでの実行環境構築はいろんなことができそうです。
以上です。