CDK と LocalStack でローカルに Lambda と DynamoDB の実行環境を構築
AWS CDK (Cloud Development Kit) を使って、ローカル環境の LocalStack に Lambda 関数と DynamoDB のテーブルを構築してみました。
下記のようなツールを使用し、CDK によるスタックと Lambda 関数ハンドラーは TypeScript で実装しました。
- AWS CDK v1.105.0
- LocalStack
- Node.js v14.17.0
- AWS CLI 2.2.5
今回のソースは http://github.com/fits/try_samples/tree/master/blog/20210524/
1. はじめに
今回は cdk init
を使わずに CDK のコードを自前で構築する事にします。
まず、TypeScript で実装するために下記モジュールを
- typescript
- ts-node
- @types/node
CDK を使って Lambda と DynamoDB を定義するために下記モジュールを
TypeScript のビルド用に下記モジュールを
- esbuild
Lambda から DynamoDB へ接続するために下記モジュールを
- @aws-sdk/client-dynamodb (AWS SDK for JavaScript v3)
そして、aws-cdk を使って LocalStack に対してデプロイ等を実施するために下記モジュールを
- aws-cdk-local
これらをインストールして package.json は以下のようになりました。
package.json
{ "name": "cdk_localstack_sample", "version": "1.0.0", "description": "", "devDependencies": { "@types/node": "^14.17.0", "aws-cdk": "^1.105.0", "aws-cdk-local": "^1.65.4", "esbuild": "^0.12.1", "ts-node": "^9.1.1", "typescript": "^4.2.4" }, "dependencies": { "@aws-cdk/aws-dynamodb": "^1.105.0", "@aws-cdk/aws-lambda": "^1.105.0", "@aws-cdk/aws-lambda-nodejs": "^1.105.0", "@aws-sdk/client-dynamodb": "^3.16.0" } }
CDK 用の設定ファイル cdk.json
はシンプルに以下のようにしました。
cdk.json
{ "app": "npx ts-node --prefer-ts-exts app.ts" }
2. 実装
DynamoDB のテーブルと Lambda 関数を構築するだけの簡単なスタックを定義しました。
app.ts (スタック定義)
import { App, Construct, Stack, StackProps, CfnOutput } from '@aws-cdk/core' import * as dynamodb from '@aws-cdk/aws-dynamodb' import * as lambda from '@aws-cdk/aws-lambda' import { NodejsFunction } from '@aws-cdk/aws-lambda-nodejs' class SampleStack extends Stack { constructor(scope: Construct, id: string, props?: StackProps) { super(scope, id, props) const table = new dynamodb.Table(this, 'Items', { partitionKey: { name: 'id', type: dynamodb.AttributeType.STRING} }) const func = new NodejsFunction(this, 'SampleFunc', { runtime: lambda.Runtime.NODEJS_14_X, entry: './src/handler.ts', environment: { 'TABLE_NAME': table.tableName } }) // テーブルへの書き込み権限を Lambda に付与 table.grantWriteData(func) // Lambda 関数名の出力 new CfnOutput(this, 'functionName', { value: func.functionName }) } } const app = new App() new SampleStack(app, 'SampleStack')
続いて Lambda 関数ハンドラーです。
LocalStack で実行している場合に、接続先の DynamoDB を LocalStack のものに切り替える必要があります。
また、LocalStack では Node.js ランタイムの Lambda 関数ハンドラーを docker で実行するようになっており、その際に LocalStack のホスト名は LOCALSTACK_HOSTNAME
環境変数で、ポート番号は EDGE_PORT
環境変数でそれぞれ渡されるようになっていました。
そこで、今回はこれらの環境変数の値を利用して DynamoDB の接続先を変えるようにしてみました。
src/handler.ts(Lambda 関数ハンドラー)
import { DynamoDBClient, PutItemCommand } from '@aws-sdk/client-dynamodb' const tableName = process.env.TABLE_NAME const config = {} if (process.env.LOCALSTACK_HOSTNAME) { // LocalStack の DynamoDB へ接続するための設定 config['endpoint'] = `http://${process.env.LOCALSTACK_HOSTNAME}:${process.env.EDGE_PORT}` } const client = new DynamoDBClient(config) export interface Input { id: string } export const handler = async (event: Input) => { const res = await client.send(new PutItemCommand({ TableName: tableName, Item: { id: { S: event.id } } })) console.log(`dynamodb put-item: ${JSON.stringify(res)}`) return { statusCode: 201, body: { id: event.id } } }
3. デプロイと動作確認
それでは、LocalStack を実行してデプロイを実施してみます。
LocalStack 実行
docker コマンドを使って LocalStack を実行します。
Node.js ランタイムの Lambda 関数を実行するために LAMBDA_EXECUTOR
環境変数の値を docker
にして、/var/run/docker.sock
をマッピングしています。
docker で LocalStack 実行
$ docker run --rm -it -p 4566:4566 -e LAMBDA_EXECUTOR=docker -v /var/run/docker.sock:/var/run/docker.sock localstack/localstack
デプロイ
aws-cdk-local モジュールには、cdklocal
コマンドという LocalStack に対してデプロイ等を実施する cdk コマンドのラッパーが用意されているので、このコマンドを使います。
cdk コマンドと同様に初回時は bootstrap
を実行します。
ブートストラップ処理
$ npx cdklocal bootstrap ⏳ Bootstrapping environment aws://000000000000/ap-northeast-1... CDKToolkit: creating CloudFormation changeset... 7:38:43 AM | UPDATE_IN_PROGRESS | AWS::CloudFormation::Stack | UsePublicAccessBlockConfiguration ✅ Environment aws://000000000000/ap-northeast-1 bootstrapped.
続いてデプロイを実施します。
デプロイ処理
$ npx cdklocal deploy --require-approval never Bundling asset SampleStack/SampleFunc/Code/Stage... ・・・ SampleStack: deploying... ・・・ SampleStack: creating CloudFormation changeset... ・・・ ✅ SampleStack Outputs: SampleStack.functionName = SampleStack-lambda-c75c6ee1 Stack ARN: arn:aws:cloudformation:us-east-1:000000000000:stack/SampleStack/c83384b9
これで DynamoDB のテーブルと Lambda 関数が作られ、Lambda の関数名は SampleStack-lambda-c75c6ee1
となりました。
動作確認
ここからは AWS CLI v2 を使って動作確認してみます。
LocalStack へ接続するには --endpoint-url
に http://localhost:4566
を設定して aws コマンドを使います。
それでは、lambda invoke
を使って SampleStack-lambda-c75c6ee1
を実行してみます。
Lambda の実行
$ aws --endpoint-url=http://localhost:4566 lambda invoke --function-name SampleStack-lambda-c75c6ee1 --payload '{"id":"id1"}' --cli-binary-format raw-in-base64-out output.json { "StatusCode": 200, "LogResult": "", "ExecutedVersion": "$LATEST" }
output.json の内容は以下のようになり、正常に実行できているようです。
output.json
{"body":{"id":"id1"},"statusCode":201}
次に、この Lambda 関数が出力した CloudWatch のログを確認してみます。 こちらも特に問題は無さそうです。
CloudWatch のログ確認
$ aws --endpoint-url=http://localhost:4566 logs tail "/aws/lambda/SampleStack-lambda-c75c6ee1" 2021-05-23T22:42:30.401000+00:00 2021/05/23/[LATEST]24101d6b START RequestId: ced21627-9622-15e9-45cd-e1c1c93c39a9 Version: $LATEST 2021-05-23T22:42:30.419000+00:00 2021/05/23/[LATEST]24101d6b 2021-05-23T22:42:30.437000+00:00 2021/05/23/[LATEST]24101d6b 2021-05-23T22:42:28.141Z ced21627-9622-15e9-45cd-e1c1c93c39a9 INFO dynamodb put-item: {"$metadata":{"httpStatusCode":200,"requestId":"0f5854a3-1110-42e7-bd95-3ff8bd8bb003","attempts":1,"totalRetryDelay":0},"ConsumedCapacity":{"CapacityUnits":1,"TableName":"SampleStack-Items5C12978B-b5cec818"}} 2021-05-23T22:42:30.473000+00:00 2021/05/23/[LATEST]24101d6b END RequestId: ced21627-9622-15e9-45cd-e1c1c93c39a9 ・・・
最後に、SampleStack-Items5C12978B-b5cec818
テーブルの内容も出力してみました。
DynamoDB テーブルの検索結果
$ aws --endpoint-url=http://localhost:4566 dynamodb scan --table-name SampleStack-Items5C12978B-b5cec818 { "Items": [ { "id": { "S": "id1" } } ], "Count": 1, "ScannedCount": 1, "ConsumedCapacity": null }