こんにちは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のネットワークの設定が間違っていたからです。詳細を解説します。
DynamoDBはこちらのDocker imageを使用して、以下の設定を用いていました。(参考サイトからのコピペです...)
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のコードで以下のように記述しました。
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を用いることがドキュメントにも書かれています。
LambdaのコードはDockerのあるコンテナにて実行されることになります。そのため、localhost:8000
というのは私のホストOSの8000ポートでなく、SAMが実行されている環境の8000ポートに向いていたのです。SAMコンテナ内のlocahost:8000
にはDynamoDBなんてありませんから、Inaccessible host
になるのは当たり前です。
つまりやることはSAMコンテナネットワークと、DynamoDBコンテナネットワークを接続してあげる必要があります。
先ほどのDynamoDBのdocker-compose.yamlを見てみると、ネットワークの設定があります。
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コンテナのネットワークにアクセスできるようになります。そしてホストも以下のように書き換えます。
var dynamoOpt = {
endpoint: "http://dynamodb-local:8000",
};
実際のコードではホスト部分は環境変数に置き換えます。とりあえず上記ホストに変更して、もう一度先ほどのコマンドを実行しました。結果、なんとかDynamoDBに接続してCRUD操作ができるようになりました。
複数のコンテナを立てていたりすると、どこがどう繋がっているか分からなくなるので結構はまりました。汗)