🐴

【Go言語 / コンテナ】Lambda+DynamoDBをServerless Frameworkで環境構築

2023/02/07に公開

Go言語でスケジュール実行するプログラムを組むことになり、実行環境を検討してみました。

過去にServerless Framework[1]を利用し、AWS Lambda(以下、Lambda)で定期実行する関数を作成したことがありましたので、それを活用することに。

今回、LambdaとDynamoDBでアプリを構成しており、過去に同じ構成での構築経験はありましたが、当時のLambdaはzip形式のみのサポートでした。

Lambdaのコンテナサポートは、リリース当時に試しただけで実務で導入することはなかったので、当時の知見をブラッシュアップする意味でも、Lambdaのコンテナ対応を取り入れることにしました。

この記事の対象者

  • 「Lambda(コンテナ)+DynamoDB」の開発環境、環境構築方法を知りたい方
  • Lambdaをコンテナイメージで開発したい、開発方法を知りたい方
  • DynamoDBを開発環境で利用したい方(dynamodb-localを利用)
  • Go言語でLambda(コンテナ)の開発方法を知りたい方

TL;DR

tree -a .
.
├── .air.toml
├── Dockerfile
├── Dockerfile.dev
├── cmd
│   └── example
│       └── main.go
├── data
│   └── create_table.json
├── docker-compose.yml
├── go.mod
├── go.sum
└── serverless.yml
.air.toml
root = "."
tmp_dir = ".build"

[build]
  args_bin = []
  bin = "/usr/bin/aws-lambda-rie /main"
  cmd = "go build -o /main ./cmd/example"
  delay = 1000
  exclude_dir = ["assets", "tmp", "vendor", "testdata", ".build"]
  exclude_file = []
  exclude_regex = ["_test.go"]
  exclude_unchanged = false
  follow_symlink = false
  full_bin = ""
  include_dir = []
  include_ext = ["go", "tpl", "tmpl", "html"]
  kill_delay = "0s"
  log = "build-errors.log"
  send_interrupt = false
  stop_on_error = true

[color]
  app = ""
  build = "yellow"
  main = "magenta"
  runner = "green"
  watcher = "cyan"

[log]
  time = false

[misc]
  clean_on_exit = false

[screen]
  clear_on_rebuild = false
Dockerfile
FROM golang:1.18-bullseye as builder
WORKDIR /go/src/lambda
COPY go.mod go.sum ./
RUN go mod download
COPY ./ .
RUN GOARCH=amd64 GOOS=linux go build -a -o /main ./cmd/example

FROM debian:bullseye as runner
WORKDIR /app/
COPY --from=builder /main /main
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
ENTRYPOINT [ "/main" ]
Dockerfile.dev
FROM golang:1.18-bullseye
RUN go install github.com/cosmtrek/air@latest
WORKDIR /go/src/lambda
COPY go.mod go.sum ./
RUN go mod download
COPY ./ .
ENV GOOS=linux GOARCH=amd64

ADD https://github.com/aws/aws-lambda-runtime-interface-emulator/releases/latest/download/aws-lambda-rie /usr/bin/aws-lambda-rie
RUN chmod 755 /usr/bin/aws-lambda-rie
CMD ["air", "-c", ".air.toml"]
cmd/example/main.go
package main

import (
	"bytes"
	"context"
	"encoding/json"
	"log"
	"os"
	"time"

	"github.com/aws/aws-lambda-go/events"
	"github.com/aws/aws-lambda-go/lambda"
	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/session"
	"github.com/guregu/dynamo"
)

type item struct {
	Name     string    `dynamo:"Name,hash"`
	Link     string    `dynamo:"Link"`
	Datetime time.Time `dynamo:"Datetime"`
}

type Response events.APIGatewayProxyResponse

func Handler(ctx context.Context) (Response, error) {
	/**
	 * DynamoDB初期設定
	 */
	dynamoDbEndpoint := os.Getenv("DYNAMODB_ENDPOINT")
	dynamoDbTable := os.Getenv("DYNAMODB_TABLE")

	disableSsl := false
	if len(dynamoDbEndpoint) != 0 {
		disableSsl = true
	}

	dynamoDbRegion := os.Getenv("AWS_REGION")
	if len(dynamoDbRegion) == 0 {
		dynamoDbRegion = "ap-northeast-1"
	}

	sess := session.Must(session.NewSession())
	db := dynamo.New(sess, &aws.Config{Endpoint: aws.String(dynamoDbEndpoint), DisableSSL: aws.Bool(disableSsl), Region: aws.String(dynamoDbRegion)})
	table := db.Table(dynamoDbTable)

	/**
	 * 保存するデータを生成
	 */
	// JSTで現在時刻を取得
	jst, err := time.LoadLocation("Asia/Tokyo")
	if err != nil {
		log.Fatal(err)
		return Response{StatusCode: 500}, err
	}
	now := time.Now().In(jst) // ex. 2023-02-01T12:30:00+09:00

	item := item{
		Name:     "テスト",
		Link:     "https://zenn.dev",
		Datetime: now,
	}

	/**
	 * DynamoDBにPUT
	 */
	if err := table.Put(item).Run(); err != nil {
		log.Fatal(err)
		return Response{StatusCode: 500}, err
	}

	/**
	 * 結果をレスポンス
	 */
	var buf bytes.Buffer
	body, err := json.Marshal(map[string]interface{}{
		"message": "function executed successfully!",
	})
	if err != nil {
		return Response{StatusCode: 404}, err
	}
	json.HTMLEscape(&buf, body)

	resp := Response{
		StatusCode:      200,
		IsBase64Encoded: false,
		Body:            buf.String(),
		Headers: map[string]string{
			"Content-Type": "application/json",
		},
	}

	return resp, nil
}

func main() {
	lambda.Start(Handler)
}

data/create_table.json
{
    "TableName": "example-dev",
    "AttributeDefinitions": [
      {
        "AttributeName": "Name",
        "AttributeType": "S"
      }
    ],
    "KeySchema": [
      {
        "AttributeName": "Name",
        "KeyType": "HASH"
      }
    ],
    "ProvisionedThroughput": {
      "ReadCapacityUnits": 1,
      "WriteCapacityUnits": 1
    }
  }
  
docker-compose.yml
version: '3'
services:
  lambda:
    build:
      context: .
      dockerfile: Dockerfile.dev
    ports:
      - 9000:8080
    environment:
      DYNAMODB_ENDPOINT: dynamodb:8000
      DYNAMODB_TABLE: example-dev
      AWS_ACCESS_KEY_ID: DUMMYIDEXAMPLE
      AWS_SECRET_ACCESS_KEY: DUMMYEXAMPLEKEY
    depends_on:
      - dynamodb
    volumes:
      - .:/go/src/lambda
  dynamodb:
    image: amazon/dynamodb-local
    user: root
    command: -jar DynamoDBLocal.jar -sharedDb -dbPath ./data
    ports:
      - 8000:8000
    volumes:
      - dynamodb-data:/home/dynamodblocal/data

volumes:
  dynamodb-data:
    driver: local
go.mod
module example.com/go-lambda-dynamodb

go 1.18

require github.com/aws/aws-lambda-go v1.37.0

require (
	github.com/aws/aws-sdk-go v1.44.191 // indirect
	github.com/cenkalti/backoff/v4 v4.1.2 // indirect
	github.com/gofrs/uuid v4.2.0+incompatible // indirect
	github.com/guregu/dynamo v1.18.0 // indirect
	github.com/jmespath/go-jmespath v0.4.0 // indirect
	golang.org/x/net v0.1.0 // indirect
)
serverless.yml
service: go-lambda-dynamo

frameworkVersion: '3'

useDotenv: true

provider:
  name: aws
  region: ap-northeast-1
  stage: ${opt:stage, 'dev'}
  environment:
    DYNAMODB_TABLE: example-${opt:stage, self:provider.stage}
  ecr:
    images:
      go-lambda-dynamo_example:
        path: ./
        file: Dockerfile
  iamRoleStatements:
    - Effect: Allow
      Action:
        - dynamodb:Query
        - dynamodb:Scan
        - dynamodb:GetItem
        - dynamodb:PutItem
      Resource:
        - "arn:aws:dynamodb:${opt:region, self:provider.region}:*:table/${self:provider.environment.DYNAMODB_TABLE}"

functions:
  example:
    image:
      name: go-lambda-dynamo_example
    environment:
      DYNAMODB_TABLE: ${self:provider.environment.DYNAMODB_TABLE}

resources:
  Resources:
    DynamoDbTable:
      Type: 'AWS::DynamoDB::Table'
      Properties:
        TableName: ${self:provider.environment.DYNAMODB_TABLE}
        AttributeDefinitions:
          - AttributeName: Name
            AttributeType: S
        KeySchema:
          - AttributeName: Name
            KeyType: HASH
        ProvisionedThroughput:
          ReadCapacityUnits: 1
          WriteCapacityUnits: 1          

開発環境構築

はじめに開発環境を構築するまでの流れについて記載します。

まず、LambdaのコンテナサポートについてはAWS公式ドキュメントに記載があるので、こちらを参考に実行環境を整えていきます。

https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/go-image.html

DynamoDBをローカル環境で再現する方法を調べると、dynamodb-localというDockerイメージがAWSから提供されていますのでコレを利用します。

https://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/DynamoDBLocal.html

Lambda用とDynamoDB用のコンテナを作成し、データのやり取りができるよう開発環境を整えていきます。

Serverless Framework

「Serverless Framework golang」等で検索すると、Serverlesss Frameworkの初期設定から始まる記事がほとんどです。

しかし、今回の開発環境ではServerless Frameworkを一切使わないです。

zip形式のLambda関数を実装する際は、ローカル環境でsls invoke localとコマンドを実行するとServerless Frameworkで定義された関数をローカル環境でテストすることができます。

これはバックグラウンドでLambdaをエミュレータするDockerイメージを実行することで実現しているようです。

しかし、コンテナでLambda関数を実装する場合、AWSから提供されているRIE(Runtime Interface Emulator) というエミュレータをDockerイメージに追加し、テストを行うことになります。

そのため、Serverless Frameworkのエミュレータが不要になり、開発時は全く利用することがないです。

AWS環境にdeployするまでは利用することがないので、本番環境構築時に改めてセッティングを行います。

Lambdaの開発用Dockerイメージ

LambdaのDockerイメージを下記のように定義します。

Dockerfile.dev
FROM golang:1.18-bullseye
RUN go install github.com/cosmtrek/air@latest
WORKDIR /go/src/lambda
COPY go.mod go.sum ./
RUN go mod download
COPY ./ .
ENV GOOS=linux GOARCH=amd64

ADD https://github.com/aws/aws-lambda-runtime-interface-emulator/releases/latest/download/aws-lambda-rie /usr/bin/aws-lambda-rie
RUN chmod 755 /usr/bin/aws-lambda-rie
CMD ["air", "-c", ".air.toml"]

なるべく本番と開発環境で実行環境を揃えたかったですが、開発に使いたいツールやライブラリの都合上、完全に分けてイメージを作るようにしました。

そのため、ファイル名をDockerfile.devと開発用Dockerfileとしています。

前述のRIEを追加している点は公式ドキュメント[2]にならっており、1点大きく異なる点が air というパッケージを利用している点です。

https://github.com/cosmtrek/air

通常、Go言語でLambdaを開発していると、ソースコードを修正するたびにコンテナを再起動しないと修正内容が反映されないです。

airを利用することでホットリロードできるようになり、いちいち再起動する必要がなくなります。

最終行のCMD ["air", "-c", ".air.toml"]airを起動しており、.air.tolmが設定ファイルとなります。

.air.toml
root = "."
tmp_dir = ".build"

[build]
  args_bin = []
  bin = "/usr/bin/aws-lambda-rie /main"
  cmd = "go build -o /main ./cmd/example"
  delay = 1000
  exclude_dir = ["assets", "tmp", "vendor", "testdata", ".build"]
  exclude_file = []
  exclude_regex = ["_test.go"]
  exclude_unchanged = false
  follow_symlink = false
  full_bin = ""
  include_dir = []
  include_ext = ["go", "tpl", "tmpl", "html"]
  kill_delay = "0s"
  log = "build-errors.log"
  send_interrupt = false
  stop_on_error = true

[color]
  app = ""
  build = "yellow"
  main = "magenta"
  runner = "green"
  watcher = "cyan"

[log]
  time = false

[misc]
  clean_on_exit = false

[screen]
  clear_on_rebuild = false

基本はairのサンプルファイル[3]と同じですが、binの指定に通常バイナリファイルだけを書くところをRIEを間にかませるようにしている点が大きく異なります。

bin = "/usr/bin/aws-lambda-rie /main"

開発環境のDynamoDB

開発環境ではAWSから提供されているdynamodb-localというイメージを使い開発を行います。

docker composeで立ち上げますが、それも公式ドキュメントにならって定義するだけです。

https://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/DynamoDBLocal.DownloadingAndRunning.html

AWS様にお膳立てしてもらっていますので、次の工程に移ります。

docker compose

前述のLambda、DynamoDB用のコンテナを実行するため、docker-compose.ymlを下記のように定義します。

docker-compose.yml
version: '3'
services:
  lambda:
    build:
      context: .
      dockerfile: Dockerfile.dev
    ports:
      - 9000:8080
    environment:
      DYNAMODB_ENDPOINT: dynamodb:8000
      DYNAMODB_TABLE: example-dev
      AWS_ACCESS_KEY_ID: DUMMYIDEXAMPLE
      AWS_SECRET_ACCESS_KEY: DUMMYEXAMPLEKEY
    depends_on:
      - dynamodb
    volumes:
      - .:/go/src/lambda
  dynamodb:
    image: amazon/dynamodb-local
    user: root
    command: -jar DynamoDBLocal.jar -sharedDb -dbPath ./data
    ports:
      - 8000:8000
    volumes:
      - dynamodb-data:/home/dynamodblocal/data

volumes:
  dynamodb-data:
    driver: local

DynamoDBのデータは永続化するためにvolumeをマウントさせています。
※参考:https://developers.freee.co.jp/entry/dynamodb-local

環境変数

DynamoDBの接続設定は環境に応じて変わるため、設定値を環境変数として渡しています。

dynamodb-localの仕様として、AWS_ACCESS_KEY_IDAWS_SECRET_ACCESS_KEYは何らかの値を設定しておく必要があります。

また、接続先は dynamodb:8000 となります。

記事によってはlocalhost:8000と指定している場合もありますが、Lambdaをzip形式で実行するかコンテナ形式で実行するかで変わるので要注意です。

ソースコード(Go言語)

サンプルとして「DynamoDBにデータを追加するLambda関数」をGo言語で実装してみます。

※実装するファイルは/cmd/example/main.goの1ファイル

はじめに下記コマンドで初期設定&必要なライブラリを追加します。

go mod init example.com/go-lambda-dynamodb
go get github.com/aws/aws-lambda-go/lambda github.com/aws/aws-sdk-go/aws github.com/guregu/dynamo

DynamoDBを操作するパッケージは色々選択肢があるが、下記の記事を参考にguregu/dynamoを利用することにします。

https://future-architect.github.io/articles/20220601b/

処理の大まかなフローは下記のようにし、実装をしてみます。

  1. DynamoDBの初期設定
  2. 保存するデータを生成
  3. DynamoDBにデータを保存
  4. 結果をレスポンス

※DynamoDBのスキーマ定義は動作確認の説明で記載

/cmd/example/main.go
package main

import (
	"bytes"
	"context"
	"encoding/json"
	"log"
	"os"
	"time"

	"github.com/aws/aws-lambda-go/events"
	"github.com/aws/aws-lambda-go/lambda"
	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/session"
	"github.com/guregu/dynamo"
)

type item struct {
	Name     string    `dynamo:"Name,hash"`
	Link     string    `dynamo:"Link"`
	Datetime time.Time `dynamo:"Datetime"`
}

type Response events.APIGatewayProxyResponse

func Handler(ctx context.Context) (Response, error) {
	/**
	 * DynamoDB初期設定
	 */
	dynamoDbEndpoint := os.Getenv("DYNAMODB_ENDPOINT")
	dynamoDbTable := os.Getenv("DYNAMODB_TABLE")

	disableSsl := false
	if len(dynamoDbEndpoint) != 0 {
		disableSsl = true
	}

	dynamoDbRegion := os.Getenv("AWS_REGION")
	if len(dynamoDbRegion) == 0 {
		dynamoDbRegion = "ap-northeast-1"
	}

	sess := session.Must(session.NewSession())
	db := dynamo.New(sess, &aws.Config{Endpoint: aws.String(dynamoDbEndpoint), DisableSSL: aws.Bool(disableSsl), Region: aws.String(dynamoDbRegion)})
	table := db.Table(dynamoDbTable)

	/**
	 * 保存するデータを生成
	 */
	// JSTで現在時刻を取得
	jst, err := time.LoadLocation("Asia/Tokyo")
	if err != nil {
		log.Fatal(err)
		return Response{StatusCode: 500}, err
	}
	now := time.Now().In(jst) // ex. 2023-02-01T12:30:00+09:00

	item := item{
		Name:     "テスト",
		Link:     "https://zenn.dev",
		Datetime: now,
	}

	/**
	 * DynamoDBにPUT
	 */
	if err := table.Put(item).Run(); err != nil {
		log.Fatal(err)
		return Response{StatusCode: 500}, err
	}

	/**
	 * 結果をレスポンス
	 */
	var buf bytes.Buffer
	body, err := json.Marshal(map[string]interface{}{
		"message": "function executed successfully!",
	})
	if err != nil {
		return Response{StatusCode: 404}, err
	}
	json.HTMLEscape(&buf, body)

	resp := Response{
		StatusCode:      200,
		IsBase64Encoded: false,
		Body:            buf.String(),
		Headers: map[string]string{
			"Content-Type": "application/json",
		},
	}

	return resp, nil
}

func main() {
	lambda.Start(Handler)
}

動作確認

開発環境に必要な下記ファイルが揃ったら、動作確認をしてみます。

tree -a .
.
├── .air.toml
├── Dockerfile.dev
├── cmd
│   └── example
│       └── main.go
├── docker-compose.yml
├── go.mod
└── go.sum

はじめにdocker compose up -dを実行しコンテナを起動します。

docker compose up -d

初期設定

次にDynamoDBのテーブルを作成します。

AWSで実行する環境はServerless Frameworkがよしなに管理してくれるが、開発環境の場合は手動で作成する必要があります。

テーブル作成用のjsonファイル(/data/create_table.json)を予め作成しておき、AWS CLIでテーブルを作成します。

/data/create_table.json
{
    "TableName": "example-dev",
    "AttributeDefinitions": [
      {
        "AttributeName": "Name",
        "AttributeType": "S"
      }
    ],
    "KeySchema": [
      {
        "AttributeName": "Name",
        "KeyType": "HASH"
      }
    ],
    "ProvisionedThroughput": {
      "ReadCapacityUnits": 1,
      "WriteCapacityUnits": 1
    }
  }
aws dynamodb create-table --cli-input-json file://data/create_table.json --endpoint-url http://localhost:8000 

実行

準備が整ったのでLambda関数をテストします。

テスト方法はcurl -XPOST "http://localhost:9000/2015-03-31/functions/function/invocations" -d '{}'と実行するだけです。

curl -XPOST "http://localhost:9000/2015-03-31/functions/function/invocations" -d '{}'
{"statusCode":200,"headers":{"Content-Type":"application/json"},"multiValueHeaders":null,"body":"{\"message\":\"function executed successfully!\"}"}%

正常に処理されたら上記のようにレスポンスが返ってきます。

DynamoDBにデータが保存されているか確認するにはscanコマンドを実行すると、保存されている全データを閲覧できます。

aws dynamodb scan --table-name example-dev --endpoint-url http://localhost:8000
{
    "Items": [
        {
            "Link": {
                "S": "https://zenn.dev"
            },
            "Datetime": {
                "S": "2023-02-01T11:32:43.146708001+09:00"
            },
            "Name": {
                "S": "テスト"
            }
        }
    ],
    "Count": 1,
    "ScannedCount": 1,
    "ConsumedCapacity": null
}

トラブルシューティング

context deadline exceeded

Lambda関数を実行した際にcontext deadline exceededとエラーなる場合、DynamoDBへの接続先が誤っている可能性が高いです。

2023/02/01 13:37:34 RequestCanceled: request context canceled
caused by: context deadline exceeded

実行直後に接続エラーとはならず、しばらく待たされてレスポンスされるので気づきにくいです。。。

僕はdocker-compose.ymlで設定する環境変数DYNAMODB_ENDPOINTdynamodb:8000と指定すべきところ、dynamodbとだけ指定してたためこのエラーに遭遇しました。

原因に関連しないレスポンスが返ってくるので、かなりハマってしまいました。

dlv

Go言語でデバッグする際、dlvというデバッグツールを利用するのが一般的ですが、Lambda(コンテナ)の開発環境で利用する方法が調べきれておらず、導入に至っていないです。

APIサーバを構築した時はairdlvを導入し、.air.tomlfull_bindlvのコマンドを設定していました。

.air.toml
# full_bin = ""
full_bin = "dlv --listen=:2345 --headless=true --api-version=2 --accept-multiclient exec /main"

ただ、上記のように設定した場合、RIEを間にかますことができないです。

RIEをかましてバイナリを実行するか、dlvをかましてバイナリを実行するかになると思いますが、RIEがないとそもそもローカル環境で実行できないので選択の余地がないような。。。

違う方法でdlvを実行することでいけるのかも知れないが。。。(わかる方いればコメント頂けると助かります)

本番環境構築

開発環境で動作確認できたら、AWSに実行環境を作成します。

Lambdaの本番用Dockerイメージ

はじめに本番用のDockerイメージを作成します。

開発用のDockerイメージはテスト用のエミュレータ(RIE)や開発に便利なパッケージを入れていましたが、それらが一切不要となるので別途軽量なイメージを作成します。

Dockerfile
FROM golang:1.18-bullseye as builder
WORKDIR /go/src/lambda
COPY go.mod go.sum ./
RUN go mod download
COPY ./ .
RUN GOARCH=amd64 GOOS=linux go build -a -o /main ./cmd/example

FROM debian:bullseye as runner
WORKDIR /app/
COPY --from=builder /main /main
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
ENTRYPOINT [ "/main" ]

Serverless Framework

最後にServerless Frameworkで実行環境を下記のように定義します。

serverless.yml
service: go-lambda-dynamo

frameworkVersion: '3'

useDotenv: true

provider:
  name: aws
  region: ap-northeast-1
  stage: ${opt:stage, 'dev'}
  environment:
    DYNAMODB_TABLE: example-${opt:stage, self:provider.stage}
  ecr:
    images:
      go-lambda-dynamo_example:
        path: ./
        file: Dockerfile
  iamRoleStatements:
    - Effect: Allow
      Action:
        - dynamodb:Query
        - dynamodb:Scan
        - dynamodb:GetItem
        - dynamodb:PutItem
      Resource:
        - "arn:aws:dynamodb:${opt:region, self:provider.region}:*:table/${self:provider.environment.DYNAMODB_TABLE}"

functions:
  example:
    image:
      name: go-lambda-dynamo_example
    environment:
      DYNAMODB_TABLE: ${self:provider.environment.DYNAMODB_TABLE}

resources:
  Resources:
    DynamoDbTable:
      Type: 'AWS::DynamoDB::Table'
      Properties:
        TableName: ${self:provider.environment.DYNAMODB_TABLE}
        AttributeDefinitions:
          - AttributeName: Name
            AttributeType: S
        KeySchema:
          - AttributeName: Name
            KeyType: HASH
        ProvisionedThroughput:
          ReadCapacityUnits: 1
          WriteCapacityUnits: 1

Lambdaの設定

Lambdaに関する設定は下記の部分です。

  ecr:
    images:
      go-lambda-dynamo_example:
        path: ./
        file: Dockerfile
	
  :
  :
 
functions:
  example:
    image:
      name: go-lambda-dynamo_example
    environment:
      DYNAMODB_TABLE: ${self:provider.environment.DYNAMODB_TABLE}

Dockerfileのディレクトリとファイル名を指定するだけで、buildしたイメージをECRへpushまでしてくれます。

go-lambda-dynamo_exampleがイメージタグになり、functions部分でそのイメージタグを指定するだけになります。

必要であればDYNAMODB_TABLEのように環境変数を設定することが可能です。

ちなみにLambdaがコンテナサポートされた際にServerless Frameworkもそれに追従する形で対応されましたが、その当時は事前に手動でECRへpushしておき、イメージのdigestをserverless.ymlに記載する形でした。

過去の設定方法
functions:
  app:
    image: 123456789012.dkr.ecr.ap-northeast-1.amazonaws.com/example@sha256:1dfac5075ffc3b8beec1b509797ba8028a3b29e7847413e000dfas42345

その後、現在の設定方法にバージョンアップされています。

https://zenn.dev/qazx7412/articles/75862c72d6effa

DynamoDBの設定

DynamoDBに関する設定は下記の部分です。

  iamRoleStatements:
    - Effect: Allow
      Action:
        - dynamodb:Query
        - dynamodb:Scan
        - dynamodb:GetItem
        - dynamodb:PutItem
      Resource:
        - "arn:aws:dynamodb:${opt:region, self:provider.region}:*:table/${self:provider.environment.DYNAMODB_TABLE}"
  
  :
  :
  
resources:
  Resources:
    DynamoDbTable:
      Type: 'AWS::DynamoDB::Table'
      Properties:
        TableName: ${self:provider.environment.DYNAMODB_TABLE}
        AttributeDefinitions:
          - AttributeName: Name
            AttributeType: S
        KeySchema:
          - AttributeName: Name
            KeyType: HASH
        ProvisionedThroughput:
          ReadCapacityUnits: 1
          WriteCapacityUnits: 1

resources部分で作成するテーブルの定義を記載します。

テーブル定義は開発環境で作成したcreate_table.jsonと同じ構成となります。

iamRoleStatements部分ではLambdaからDynamoDBへのアクセス許可を設定します。

deploy

本番環境の構成を定義できたので、AWSにdeployしてみます。

sls deploy

deployできたら定義したresourceがAWSに作成されていることを確認してみます。

試しにECRを確認すると指定したタグでイメージがpushされていることを確認できます。

動作確認

最後に本番環境で動作確認を行います。

AWSのコンソール上からLambda関数をテストすると、成功していることが確認できます。

また、slsコマンドでもテストすることができ、その場合下記のように関数を指定して実行します。

serverless invoke -f example
{
    "statusCode": 200,
    "headers": {
        "Content-Type": "application/json"
    },
    "multiValueHeaders": null,
    "body": "{\"message\":\"function executed successfully!\"}"
}

DynamoDBに登録されているかコンソール上で確認するとソースコード内で生成した情報が登録されていることが確認できます。

脚注
  1. https://www.serverless.com/ ↩︎

  2. https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/go-image.html ↩︎

  3. https://github.com/cosmtrek/air/blob/master/air_example.toml ↩︎

Discussion