この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
こんにちは。プロダクトグループのさかいゅです。
CDK(Cloud Development Kit)で実装したリソースをローカル環境で実行できるように、ローカル開発環境について調査しました。
公式ドキュメントにSAM CLIを利用してローカル環境で実行する例を参考にローカル開発環境を構築してみましたので、紹介させていただきます。
バージョン情報
- AWS CLI 1.16.220
- CDK 1.13.1
- SAM CLI 0.22.0
- Docker 19.03.2
- Visual Studio Code 1.39.2
概要
CDKで実装した、API Gateway + Lambda + DynamoDBでサーバーレスな構成のWebAPIをSAM CLIとDynamoDB Localを利用してローカル開発環境を作成します。
プロジェクトの作成
CDKのプロジェクトを作成し、必要なパッケージをインストールします。
# プロジェクトを作成
cdk init --language typescript
# 必要なパッケージをインストール
npm install --save @aws-cdk/aws-lambda @aws-cdk/aws-apigateway @aws-cdk/aws-dynamodb
npm install --save-dev @types/node
リソースの定義
今回利用するリソースとDynamoDBにアクセスする単純なLambda関数を実装します。
lib/sample_project-stack.ts
import cdk = require("@aws-cdk/core");
import lambda = require("@aws-cdk/aws-lambda");
import apigateway = require("@aws-cdk/aws-apigateway");
import dynamodb = require("@aws-cdk/aws-dynamodb");
export class SampleProjectStack extends cdk.Stack {
constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
const env = process.env.ENV || "local";
// DynamoDBテーブル
const sampleTable = new dynamodb.Table(this, "sampleTable", {
tableName: "samples",
billingMode: dynamodb.BillingMode.PAY_PER_REQUEST,
partitionKey: { name: "id", type: dynamodb.AttributeType.STRING }
});
// Lambda関数
const sampleLambda = new lambda.Function(this, "sampleLambda", {
runtime: lambda.Runtime.NODEJS_8_10,
handler: "index.handler",
code: lambda.Code.asset("src/lambda/sample"),
timeout: cdk.Duration.seconds(60),
environment: {
ENV: env
}
});
sampleTable.grantReadWriteData(sampleLambda);
// API Gateway
const sampleApi = new apigateway.RestApi(this, "sampleApi", {
restApiName: "sampleApi"
});
const samplesResource = sampleApi.root.addResource("samples");
const getSamplesIntegration = new apigateway.LambdaIntegration(sampleLambda);
samplesResource.addMethod("GET", getSamplesIntegration);
}
}
src/lambda/sample/index.js
const AWS = require("aws-sdk");
exports.handler = async event => {
var dynamodbOption = {};
if (process.env.ENV === "local") {
dynamodbOption = {
endpoint: "http://dynamodb:8000",
region: "local",
accessKeyId: "local",
secretAccessKey: "local"
};
}
var docClient = new AWS.DynamoDB.DocumentClient(dynamodbOption);
var params = {
TableName: "samples"
};
var scanResult = await docClient.scan(params).promise();
var responseBody = {
samples: scanResult.Items
};
const response = {
statusCode: 200,
body: JSON.stringify(responseBody)
};
return response;
};
デプロイ
TypeScriptをコンパイルして、デプロイコマンドを実行することで、AWSにデプロイすることができます。Lambda関数内で、ENV
を参照しlocal
の場合、DynamoDB Localのエンドポイントを指定するように実装していますので、AWSにデプロイする時は、local
以外の値を指定します。
npm run build
ENV=dev cdk deploy
ローカル実行
ここまでで、リソースをAWSにデプロイすることができました。ここから、ローカル環境で実行できるように設定していきます。
Dockerのネットワーク設定
SAM CLI、DynamoDB Localのいずれも、Dockerコンテナで実行されます。Dockerコンテナで実行しているLambdaからDockerコンテナで実行しているDynamoDB Localに接続するために、同じネットワーク設定を利用する必要があります。今回はsam-cli
という名前で作成しました。(任意の名前でOKです)
docker network create sam-cli
DynamoDB Local
DynamoDB Localは、公式のDockerイメージがありますので、Dockerを使って構築します。デフォルトで起動するとInMemoryで起動されるためコンテナを停止すると、テーブル定義やデータが消えてしまいます。毎回テーブルの作成やデータを入れ直すのは効率が悪いので、今回はデータを永続化するように設定しました。
docker-compose.yml
version: "3.7"
services:
dynamodb:
image: amazon/dynamodb-local
container_name: dynamodb
ports:
- 8000:8000
command: -jar DynamoDBLocal.jar -dbPath /data
volumes:
- $PWD/dynamodb/data:/data
networks:
- sam-cli
networks:
sam-cli:
external: true
docker-compose up -d
テーブルの作成とデータの作成
サンプルテーブルと適当なデータをDynamoDB Localに作成します。
samples.json
{
"AttributeDefinitions": [
{
"AttributeName": "Id",
"AttributeType": "S"
}
],
"TableName": "samples",
"KeySchema": [
{
"AttributeName": "Id",
"KeyType": "HASH"
}
],
"ProvisionedThroughput": {
"ReadCapacityUnits": 1,
"WriteCapacityUnits": 1
}
}
samples_data.json
{
"samples": [
{
"PutRequest": {
"Item": {
"Id": { "S": "sample1" },
"Name": { "S": "HogeHoge" }
}
}
},
{
"PutRequest": {
"Item": {
"Id": { "S": "sample2" },
"Name": { "S": "FooFoo" }
}
}
}
]
}
DynamoDB Localは、ACCESS_KEY_IDやリージョン名を利用して、ファイルを保存するようなので、テーブルを作成する前に、環境変数を設定しておきます。(リージョン名はプログラムの指定と合っていれば何でもOKです)
export AWS_SECRET_ACCESS_KEY=local
export AWS_ACCESS_KEY_ID=local
export AWS_DEFAULT_REGION=local
aws dynamodb create-table --endpoint-url http://localhost:8000 --cli-input-json file://dynamodb/samples.json
aws dynamodb batch-write-item --endpoint-url http://localhost:8000 --request-items file://dynamodb/samples_data.json
SAM CLI
cdk synth
コマンドでCloudFormationのテンプレートファイルを作成することができます。ここは、公式ドキュメントに記載されている通りに進めます。
cdk synth --no-staging > template.yaml
API Gatewayを実行
生成したCloudFormationのテンプレートファイルを利用して、APIを実行します。DynamoDB Localに接続する処理を実行する場合は、--docker-network
オプションを指定してください。
$ sam local start-api -t template.yaml --docker-network sam-cli
Mounting sampleLambdaXXXXX at http://127.0.0.1:3000/samples [GET]
You can now browse to the above endpoints to invoke your functions. You do not need to restart/reload SAM CLI while working on your functions, changes will be reflected instantly/automatically. You only need to restart SAM CLI if you update your AWS SAM template
2019-10-25 00:31:58 * Running on http://127.0.0.1:3000/ (Press CTRL+C to quit)
もちろんブラウザでアクセスすることができます。
Lambda関数を実行
Lambda関数を直接実行することもできます。API Gateway実行時と同様に、必要に応じて--docker-network
オプションを指定してください。また、Lambdaの関数名はtemplate.yaml
を参照して設定してください。
template.yaml
…
sampleLambdaXXXXX:
Type: AWS::Lambda::Function
Properties:
Code:
S3Bucket:
…
$ sam local invoke sampleLambdaXXXXX --no-event --docker-network sam-cli
Invoking index.handler (nodejs8.10)
2019-10-25 00:42:50 Found credentials in environment variables.
Fetching lambci/lambda:nodejs8.10 Docker container image......
Mounting /XXXX/XXXX/SampleProject/src/lambda/sample as /var/task:ro,delegated inside runtime container
START RequestId: XXXX-YYYY Version: $LATEST
END RequestId: XXXX-YYYY
REPORT RequestId: XXXX-YYYY Duration: 297.76 ms Billed Duration: 300 ms Memory Size: 128 MB Max Memory Used: 44 MB
{"statusCode":200,"body":"{\"samples\":[{\"Id\":\"sample1\",\"Name\":\"HogeHoge\"},{\"Id\":\"sample2\",\"Name\":\"FooFoo\"}]}"}
デバッグ
SAM CLIを利用してLambda関数をデバッグすることができます。今回はVisual Studio Codeを利用して、デバッグしてみます。
デバッグ設定を作成して、--debug-port
を指定してLambda関数を実行します。
launch.json
{
"version": "0.2.0",
"configurations": [
{
"name": "CDK + SAM CLIでデバッグ",
"type": "node",
"request": "attach",
"address": "localhost",
"port": 5858,
"localRoot": "${workspaceRoot}/src/lambda/sample",
"remoteRoot": "/var/task",
"protocol": "inspector",
"stopOnEntry": false
}
]
}
sam local invoke sampleLambdaXXXXX --no-event --docker-network sam-cli --debug-port 5858
ブレークポイントで止まりました。成功ですね。通常のデバッグと同様に変数の中身を確認したり、ステップ実行をすることができます。
注意事項
デバッグを実行する際に、ランタイムの設定がNode.jsの10系だとaws-sdkモジュールがImportModuleErrorとなり実行できませんでした。なんらかの回避策があるかもしれませんが、今回はNode.jsの8系で実行しています。
さいごに
テストはAWSにデプロイして実施するべきですが、デプロイする前の動作確認には十分使える環境だと思いました。 AWSリソースをローカルで実行する方法の1つとして参考になれば幸いです。