AWS SAMで作ったLamdaコードがローカルのDynamoDBに接続できないときの対処
メモ AWSDockerサーバーレス

AWS SAMで作ったLamdaコードがローカルのDynamoDBに接続できないときの対処

2022.01.23

こんにちはjunです。最近はこの静的ブログに動的な機能を追加するために、サーバーレス環境でのサービス開発を行っています。API gateway をトリガーとしてAWS Lambdaでコードを実行してDynamoDBで内容を保存するといったよくある構成で開発をしていました。

AWS Lambdaはweb上でもコードを書いて実行できますが、できたらローカル上でコードを書いてテストしてからデプロイしたいです。特に更新や改修の時はローカルで検証できることが好ましいです。

AWSはその辺も考えており、AWS SAM(serverless application model) を用意してくれています。私も開発ではこのSAMを使用してローカルで構築後、テストして本番に適用しています。

ひとまずチュートリアルのデプロイができたので、DynamoDBを用いた環境をローカルに作ってコードをテストすることにしました。しかし、DynamoDBをscanする処理でいつもストップし、

Inaccessible host: `127.0.0.1' at port `8000'. This service may not be available in the `ap-north-east1' region.

というメッセージでDynamoDBに接続できませんでした。ちなみにDynamoDBの環境はDockerを用いてローカルで構築しており、localost:8000に待機していました。DynamoDBのコンテナは起動しているのに、どうして接続できないのか悩んでいました。

結論を言うとDockerのネットワークの設定が間違っていたからです。詳細を解説します。

LambdaのコードとDynamoDBのコンテナ設定

DynamoDBはこちらのDocker imageを使用して、以下の設定を用いていました。(参考サイトからのコピペです...)

docker-compose.yml
version: "3"

services:
  dynamodb-local:
    container_name: dynamodb-local
    image: amazon/dynamodb-local
    ports:
      - 8000:8000
    command: -jar DynamoDBLocal.jar -dbPath /data -sharedDb
    volumes:
      - ./data:/data
    networks:
      - lambda-local
networks:
  lambda-local:
    external: true

上記の- 8000:8000 の通りlocalhostの8000にDynamoDBのエンドポイントが待機しています。 docker-compose upしてこの記述の通り、Lambdaのコードで以下のように記述しました。

app.js
const AWS = require('aws-sdk')
let response;

var dynamoOpt = {
    endpoint: "http://localhost:8000",
};

exports.lambdaHandler = async (event, context) => {
    let params = {
            TableName:'table',
        };

        try{
            const result = await dynamodb.scan(params).promise()
            return {
                statusCode: 200,
                body: JSON.stringify(result.Items),
            };
        }catch (e){
            return {
                statusCode: 500,
                body: e.message,
            };
        }
}

そしてビルドしてテストをしても

sam build
sam local invoke -e events/event.json

Inaccessible host: `127.0.0.1' at port `8000'. This service may not be available in the `ap-north-east1' region.

とDynamoDBのホストに接続できていないというエラーが発生しました。色々検索した結果、以下の記事がキーとなり解決しました。

Serverless連載1: SAMを使ったローカルテスト(Go編)

解決法

上記記事の図をみて一気に理解できました。原因はSAMのコンテナがDynamoDBのコンテナに接続できず、またホスト(localhost)の指定が間違っていたからです。

SAMもdockerで実行されている

SAMは自動デプロイだけでなく、ローカルでのテストもサポートしています。テストではDockerを用いることがドキュメントにも書かれています。

LambdaのコードはDockerのあるコンテナにて実行されることになります。そのため、localhost:8000というのは私のホストOSの8000ポートでなく、SAMが実行されている環境の8000ポートに向いていたのです。SAMコンテナ内のlocahost:8000にはDynamoDBなんてありませんから、Inaccessible host になるのは当たり前です。

つまりやることはSAMコンテナネットワークと、DynamoDBコンテナネットワークを接続してあげる必要があります。

DynamoDBの設定とテスト実行時の引数を与える

先ほどのDynamoDBのdocker-compose.yamlを見てみると、ネットワークの設定があります。

docker-compose.yml
version: "3"

services:
  dynamodb-local:
    container_name: dynamodb-local
    image: amazon/dynamodb-local
    ports:
      - 8000:8000
    command: -jar DynamoDBLocal.jar -dbPath /data -sharedDb
    volumes:
      - ./data:/data
    networks:
      - lambda-local
networks:
  lambda-local:
    external: true #これ!

そしてSAMコンテナの立ち上げの際、このネットワークに接続できる引数があります。

sam local invoke -e events/event.json --docker-network lambda-local

--docker-network lambda-local を指定することで、DynamoDBコンテナのネットワークにアクセスできるようになります。そしてホストも以下のように書き換えます。

app.js
var dynamoOpt = {
    endpoint: "http://dynamodb-local:8000",
};

実際のコードではホスト部分は環境変数に置き換えます。とりあえず上記ホストに変更して、もう一度先ほどのコマンドを実行しました。結果、なんとかDynamoDBに接続してCRUD操作ができるようになりました。

複数のコンテナを立てていたりすると、どこがどう繋がっているか分からなくなるので結構はまりました。汗)

Copyright © 2021 jun. All rights reserved.