この記事に書かれていること
- SAM CLIの環境構築方法
- SAM CLIを使ってLocalでLambdaを起動する方法
- SAM CLIを使ってLocalで起動しているLambdaから、Localで用意したDynamoDB containerにアクセスする方法
- これらの処理を僕が趣味で作っているAWS Lambdaを例に説明します。
この記事に書かれていないこと
- SAM CLIとは何か?
- ( そのあたりの説明は公式ドキュメントに譲らせていただきます! )
- Lambdaを利用する際のwebpackの設定
利用環境
- nodejs8.10
- TypeScript 3.4.5
- SAM CLI 0.15.0
- python 3.7.2
事前準備
aws-sam-cliのinstall
Installing the AWS SAM CLI on macOS というAWS公式の手順に則ってinstallします。
aws-sam-cliは、pythonのバージョン 2.7、3.6、3.7 に対応しています。もし手元の環境がそれらのバージョンに一致していないのであれば、対応しているバージョンのpythonをinstallしましょう。なお2.7は2020年の1月にはメンテナンスが終了されますので、今から入れるのであれば 3以上にすると良いでしょう。
$ brew install pyenv
$ brew install pyenv-virtualenv
$ pyenv install 3.7.2
$ pyenv local 3.7.2
$ brew tap aws/tap
$ brew install aws-sam-cli
$ sam --version
SAM CLI, version 0.15.0
dynamodb-localのdocker imageをpull
こちらもamazon公式のdocker imageを利用します。下記のコマンドを実行してdocker imageをpullしましょう
docker pull amazon/dynamodb-local
SAM Localテスト用データ作成
aws-sam-cliを使ってLocalからLambdaを起動するためのデータを作成します。今回は、シンプルにAPI Gatewayから起動することにします。
sam local generate-event \
apigateway aws-proxy \
--path datadog_report \
--method GET > events/event_apigateway.json
このコマンドによって作成されたjsonはこちらになります。
実装
docker-composeの設定
version: "3"
services:
dynamodb-local:
container_name: dynamodb
image: amazon/dynamodb-local
build: ./
ports:
- 8000:8000
command: -jar DynamoDBLocal.jar -dbPath /data -sharedDb
volumes:
- ./data:/data
networks:
- lambda-local
networks:
lambda-local:
external: true
この設定において重要な点は3点あります。
1点目は、DynamoDB localのコマンドオプションに -dbPath /data
を指定している点です。-dbPath
でdockerがマウントしているvolumeに書き出すことによって、指定したディレクトリにデータを吐き出させるようにしています。こうすることで、データを永続化しています。-inMemory
オプションを使ってしまうと、毎回データが削除されてしまうので、開発時にそのオプションを利用するのは少し手間が掛かってしまうでしょう。(テストのときはあると良さそうです)
2点目は、DynamoDB localのコマンドオプションに、 -sharedDb
オプションを指定しているところです。-sharedDb
オプションを指定しない場合、データはmyaccesskeyid_region.db というフォーマットで格納されます。これはこれで、毎回起動するときにそのあたりのパラメータをちゃんと設定できていればよいのですが、今回は簡単のため-sharedDb
オプションを指定しています。
3点目は、networksを指定しているところです。aws-sam-localによってlocalで実行されるLambdaは、起動時にdockerのnetworkを指定することができます。ここで指定したnetworksを aws-sam-localの起動時にも利用することによって、localで起動しているLambdaから、このdocker containerにアクセスすることができるようになります。
これらDynamoDB localのオプション内容については、公式ドキュメントに記載があるので参照してください。ということで設定ができたので、下記コマンドを実行してDynamoDB Localの環境を構築しましょう。
docker network create lambda-local
docker-compose up
typescript
ぼくが趣味で作っている、AWS Lambdaのコードから取ってきたやつです。
https://github.com/selmertsx/datadog_slack_report
今思えばちょっと設計に改善の余地ありですな...。この後新しい機能を追加予定なので、そのときにでもリファクタリングしようと思います。一旦必要そうなもののみ引っ張ってきました。
// https://github.com/selmertsx/datadog_slack_report/blob/c4e59fdb60b2e190bd58f7e823268d8b697e3dfb/src/index.ts
import { APIGatewayEvent, Callback, Context } from "aws-lambda";
import moment from "moment-timezone";
import "source-map-support/register";
import { Billing } from "./Billing";
import { SlackClient } from "./SlackClient";
export async function datadog_handler(event: APIGatewayEvent, context: Context, callback: Callback) {
const fromTime = moment({ hour: 0, minute: 0, second: 0 })
.tz("Asia/Tokyo")
.subtract(1, "days")
.format("X");
const toTime = moment({ hour: 23, minute: 59, second: 59 })
.tz("Asia/Tokyo")
.subtract(1, "days")
.format("X");
try {
const billing = new Billing();
const report = await billing.calculate(fromTime, toTime);
const slackClient = new SlackClient();
await slackClient.post(report.slackMessageDetail());
callback(null, {
statusCode: 200,
headers: {
"Content-Type": "application/json;charset=UTF-8",
},
body: JSON.stringify({ status: 200, message: "OK" }),
});
} catch (err) {
throw new Error(err);
}
}
// https://github.com/selmertsx/datadog_slack_report/blob/e078d2427806f3f9b402a3af1fbe79c98b0e2a5a/src/DynamoDBClient.ts
import { DynamoDB } from "aws-sdk";
import { ReservedPlan } from "./typings/datadog";
export class DynamoDBClient {
private client = new DynamoDB.DocumentClient({
endpoint: "http://dynamodb:8000", // ここが重要!!!!!
region: "ap-north-east1",
});
public getReservedPlans(): Promise<ReservedPlan[]> {
return new Promise<any>((resolve: any, rejects: any) => {
this.client.scan({ TableName: "DatadogPlan" }, (error, data) => {
if (error) {
rejects(error);
} else if (data.Items == undefined) {
resolve([]);
} else {
const results: ReservedPlan[] = [];
data.Items.forEach(item => {
results.push({ productName: item.Product, plannedHostCount: item.PlannedHostCount });
});
resolve(results);
}
});
});
}
}
さて、長々とコードが書いてあるのであれなのですが、重要なのは1点だけです。DynamoDBのendpointについて http://${dynamodb-localのcontainer名}:8000
としていることです。これによってSAM Localで起動したAWS Lambdaから、LocalのDynamoDBにアクセスすることができます。
private client = new DynamoDB.DocumentClient({
endpoint: "http://dynamodb:8000", // ここが重要!!!!!
region: "ap-north-east1",
});
起動方法
ということで、ここまでやったら後は起動するだけ。起動する際は、 sam local invoke
コマンドの --docker-network
オプションに、先程 docker-compose.yml
で指定した network名を設定してみましょう。具体的には下記のコマンドになります。
$ npx webpack --config webpack.prod.js
$ sam local invoke --docker-network lambda-local -e events/event_apigateway.json --env-vars .env.json DatadogReport
2019-04-25 10:30:30 Found credentials in environment variables.
2019-04-25 10:30:30 Invoking index.datadog_handler (nodejs8.10)
Fetching lambci/lambda:nodejs8.10 Docker container image......
2019-04-25 10:30:33 Mounting /Users/shuhei.morioka/project/speee/datadog_slack_report as /var/task:ro,delegated inside runtime container
START RequestId: dbefc77e-42dc-1d21-a444-0abc44875df5 Version: $LATEST
END RequestId: dbefc77e-42dc-1d21-a444-0abc44875df5
REPORT RequestId: dbefc77e-42dc-1d21-a444-0abc44875df5 Duration: 4299.35 ms Billed Duration: 4300 ms Memory Size: 256 MB Max Memory Used: 121 MB
ということで、Localで動いているAWS LambdaからDynamoDB Localにアクセスすることができました〜。