はじめに
クラウドを低コストで運用できるAWS Serverless(API Gateway + Lambda + DynamoDB)に入門してみました。
基本的には、クラスメソッドさんの記事が分かりやすく参考になりました。
WS Serverless Application Model (AWS SAM) を使ってサーバーレスアプリケーションを構築する
ただし、そのままでは動作しない部分があったので、今回は、その変更部分を中心に記載します。
環境
私の環境は以下です。
- OS: Windows 10
- AWS CLI: aws-cli/1.11.82 Python/2.7.9 Windows/8 botocore/1.5.45
Windows環境というのが参考にした記事との差分になります。
ディレクトリ構成
コマンドは、C:\Git\AWS\Serverless\api_backend
で実行しました。
Lambda
get, put, delete を1つのJavaScriptファイルにまとめて記載します。
テーブル名は環境変数から取得するようにしています。
'use strict';
const AWS = require('aws-sdk');
const dynamo = new AWS.DynamoDB.DocumentClient();
const tableName = process.env.TABLE_NAME;
const createResponse = (statusCode, body) => {
return {
statusCode: statusCode,
body: body
};
};
exports.get = (event, context, callback) => {
let params = {
TableName: tableName,
Key: {
id: event.pathParameters.resourceId
}
};
let dbGet = (params) => { return dynamo.get(params).promise() };
dbGet(params).then( (data) => {
if (!data.Item) {
callback(null, createResponse(404, "ITEM NOT FOUND"));
return;
}
console.log(`RETRIEVED ITEM SUCCESSFULLY WITH doc = ${data.Item.doc}`);
callback(null, createResponse(200, data.Item.doc));
}).catch( (err) => {
console.log(`GET ITEM FAILED FOR doc = ${params.Key.id}, WITH ERROR: ${err}`);
callback(null, createResponse(500, err));
});
};
exports.put = (event, context, callback) => {
let item = {
id: event.pathParameters.resourceId,
doc: event.body
};
let params = {
TableName: tableName,
Item: item
};
let dbPut = (params) => { return dynamo.put(params).promise() };
dbPut(params).then( (data) => {
console.log(`PUT ITEM SUCCEEDED WITH doc = ${item.doc}`);
callback(null, createResponse(200, null));
}).catch( (err) => {
console.log(`PUT ITEM FAILED FOR doc = ${item.doc}, WITH ERROR: ${err}`);
callback(null, createResponse(500, err));
});
};
exports.delete = (event, context, callback) => {
let params = {
TableName: tableName,
Key: {
id: event.pathParameters.resourceId
},
ReturnValues: 'ALL_OLD'
};
let dbDelete = (params) => { return dynamo.delete(params).promise() };
dbDelete(params).then( (data) => {
if (!data.Attributes) {
callback(null, createResponse(404, "ITEM NOT FOUND FOR DELETION"));
return;
}
console.log(`DELETED ITEM SUCCESSFULLY WITH id = ${event.pathParameters.resourceId}`);
callback(null, createResponse(200, null));
}).catch( (err) => {
console.log(`DELETE ITEM FAILED FOR id = ${event.pathParameters.resourceId}, WITH ERROR: ${err}`);
callback(null, createResponse(500, err));
});
};
テンプレート
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: Simple CRUD webservice. State is stored in a SimpleTable (DynamoDB) resource.
Resources:
GetFunction:
Type: AWS::Serverless::Function
Properties:
Handler: index.get
Runtime: nodejs6.10
Policies: AmazonDynamoDBReadOnlyAccess
CodeUri: /Git/AWS/Serverless/api_backend/src
Environment:
Variables:
TABLE_NAME: !Ref Table
Events:
GetResource:
Type: Api
Properties:
Path: /resource/{resourceId}
Method: get
PutFunction:
Type: AWS::Serverless::Function
Properties:
Handler: index.put
Runtime: nodejs6.10
Policies: AmazonDynamoDBFullAccess
CodeUri: /Git/AWS/Serverless/api_backend/src
Environment:
Variables:
TABLE_NAME: !Ref Table
Events:
PutResource:
Type: Api
Properties:
Path: /resource/{resourceId}
Method: put
DeleteFunction:
Type: AWS::Serverless::Function
Properties:
Handler: index.delete
Runtime: nodejs6.10
Policies: AmazonDynamoDBFullAccess
CodeUri: /Git/AWS/Serverless/api_backend/src
Environment:
Variables:
TABLE_NAME: !Ref Table
Events:
DeleteResource:
Type: Api
Properties:
Path: /resource/{resourceId}
Method: delete
Table:
Type: AWS::Serverless::SimpleTable
LambdaのJavaScriptファイルを配置したディレクトリをCodeUri
として指定しました。
CodeUriはフルパスで指定する必要がありました。
CodeUriを相対パスで CodeUri:src
のように指定するとLambdaの参照を解決できず、以下のエラーがでていました。
Unable to upload artifact src referenced by CodeUri parameter of GetFunction resource.
Parameter CodeUri of resource GetFunction refers to a file or folder that does not exist C:\Git\AWS\Serverless\api_backend\config\src
package
packageとは、LambdaをS3にアップロードし、CloudFormationのテンプレート(template.yaml)を環境に合わせて実体化する作業を言います。
-
コマンド
事前にS3のバケット()を作成しておきます。
以下のコマンドの<bucket-name>
部分を作成したS3のバケット名に置き換えてpackageコマンドを実行します。
aws cloudformation package --template-file config/template.yaml --output-template-file config/serverless-output.yaml --s3-bucket <bucket-name>
-
実体化されたTemplate
packageコマンドを実行すると、以下のような実体化されたTemplateが生成されます。
AWSTemplateFormatVersion: '2010-09-09'
Description: Simple CRUD webservice. State is stored in a SimpleTable (DynamoDB) resource.
Resources:
DeleteFunction:
Properties:
CodeUri: s3://<bucket-name>/a14396c7934ad2583710bf066a1340aa
Environment:
Variables:
TABLE_NAME:
Ref: Table
Events:
DeleteResource:
Properties:
Method: delete
Path: /resource/{resourceId}
Type: Api
Handler: index.delete
Policies: AmazonDynamoDBFullAccess
Runtime: nodejs6.10
Type: AWS::Serverless::Function
GetFunction:
Properties:
CodeUri: s3://<bucket-name>/a14396c7934ad2583710bf066a1340aa
Environment:
Variables:
TABLE_NAME:
Ref: Table
Events:
GetResource:
Properties:
Method: get
Path: /resource/{resourceId}
Type: Api
Handler: index.get
Policies: AmazonDynamoDBReadOnlyAccess
Runtime: nodejs6.10
Type: AWS::Serverless::Function
PutFunction:
Properties:
CodeUri: s3://<bucket-name>/a14396c7934ad2583710bf066a1340aa
Environment:
Variables:
TABLE_NAME:
Ref: Table
Events:
PutResource:
Properties:
Method: put
Path: /resource/{resourceId}
Type: Api
Handler: index.put
Policies: AmazonDynamoDBFullAccess
Runtime: nodejs6.10
Type: AWS::Serverless::Function
Table:
Type: AWS::Serverless::SimpleTable
Transform: AWS::Serverless-2016-10-31
deploy
deployコマンドで、API Gateway, Lambda, DynamoDBのテーブルを実際に作成します。
stack-nameは、今回作成する一式を管理するための名前です。
aws cloudformation deploy --template-file config/serverless-output.yaml --stack-name serverless-api-backend-20170503 --capabilities CAPABILITY_IAM
CloudFormationのマネージメントコンソールでスタックが生成されていることが確認できます。
ここで重要なのが --capabilities CAPABILITY_IAM
オプションです。これは、LambdaからDynamoDBを呼び出したりするためのポリシー設定をCloudFrontに許可するかどうかの指定になります。これを忘れると以下のエラーメッセージになります。
An error occurred (InsufficientCapabilitiesException) when calling the ExecuteChangeSet operation: Requires capabilities : [CAPABILITY_IAM]
Test
最後に作成したServerless Application ModelのテストとしてAPI GatewayからLambdaを呼び出し、DynamoDBを操作してみます。
API Gatewayのマネージメントコンソールで、serverless-api-backend-20170503
を開きます。
リソース → /resource/{resourceId} → PUT → テスト
の順にクリックしてメソッドテストの画面を開き、メソッドテストの各項目には、次のように入力します。
項目 | 値 |
---|---|
パス | res001 |
クエリ文字列 | param1=value1¶m2=value2 |
ヘッダー | Accept: application/json Content-type: application/json |
リクエスト本文 | {"message1":"Hello","message2":"Lambda"} |
最後に「テスト」ボタンを押下すると、DynamoDBのテーブルにitemが追加され、テストが成功します。