AWS CDKで定義したリソースをローカル環境で実行してみた
こんにちは。プロダクトグループのさかいゅです。
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のプロジェクトを作成し、必要なパッケージをインストールします。
1 2 3 4 5 6 | # プロジェクトを作成 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関数を実装します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | 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 ) ; } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | const AWS = require( "aws-sdk" ); exports.handler = async event => { var dynamodbOption = {}; if (process.env.ENV === "local" ) { dynamodbOption = { 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
以外の値を指定します。
1 2 | npm run build ENV=dev cdk deploy |
ローカル実行
ここまでで、リソースをAWSにデプロイすることができました。ここから、ローカル環境で実行できるように設定していきます。
Dockerのネットワーク設定
SAM CLI、DynamoDB Localのいずれも、Dockerコンテナで実行されます。Dockerコンテナで実行しているLambdaからDockerコンテナで実行しているDynamoDB Localに接続するために、同じネットワーク設定を利用する必要があります。今回はsam-cli
という名前で作成しました。(任意の名前でOKです)
1 | docker network create sam-cli |
DynamoDB Local
DynamoDB Localは、公式のDockerイメージがありますので、Dockerを使って構築します。デフォルトで起動するとInMemoryで起動されるためコンテナを停止すると、テーブル定義やデータが消えてしまいます。毎回テーブルの作成やデータを入れ直すのは効率が悪いので、今回はデータを永続化するように設定しました。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | 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 |
1 | docker-compose up -d |
テーブルの作成とデータの作成
サンプルテーブルと適当なデータをDynamoDB Localに作成します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | { "AttributeDefinitions" : [ { "AttributeName" : "Id" , "AttributeType" : "S" } ], "TableName" : "samples" , "KeySchema" : [ { "AttributeName" : "Id" , "KeyType" : "HASH" } ], "ProvisionedThroughput" : { "ReadCapacityUnits" : 1, "WriteCapacityUnits" : 1 } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | { "samples" : [ { "PutRequest" : { "Item" : { "Id" : { "S" : "sample1" }, "Name" : { "S" : "HogeHoge" } } } }, { "PutRequest" : { "Item" : { "Id" : { "S" : "sample2" }, "Name" : { "S" : "FooFoo" } } } } ] } |
DynamoDB Localは、ACCESS_KEY_IDやリージョン名を利用して、ファイルを保存するようなので、テーブルを作成する前に、環境変数を設定しておきます。(リージョン名はプログラムの指定と合っていれば何でもOKです)
1 2 3 4 5 6 | 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のテンプレートファイルを作成することができます。ここは、公式ドキュメントに記載されている通りに進めます。
1 | cdk synth --no-staging > template.yaml |
API Gatewayを実行
生成したCloudFormationのテンプレートファイルを利用して、APIを実行します。DynamoDB Localに接続する処理を実行する場合は、--docker-network
オプションを指定してください。
1 2 3 4 | $ 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
を参照して設定してください。
1 2 3 4 5 6 7 | … sampleLambdaXXXXX: Type: AWS : : Lambda : : Function Properties: Code: S3Bucket: … |
1 2 3 4 5 6 7 8 9 10 11 | $ 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関数を実行します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | { "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 } ] } |
1 | 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つとして参考になれば幸いです。