Edited at

Serverless Application Model に入門してみた

More than 1 year has passed since last update.


はじめに

クラウドを低コストで運用できるAWS Serverless(API Gateway + Lambda + DynamoDB)に入門してみました。

Serverless.png

基本的には、クラスメソッドさんの記事が分かりやすく参考になりました。


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環境というのが参考にした記事との差分になります。


ディレクトリ構成

ディレクトリ構成はこのようにしました。

dir.png

コマンドは、C:\Git\AWS\Serverless\api_backend で実行しました。


Lambda

get, put, delete を1つのJavaScriptファイルにまとめて記載します。

テーブル名は環境変数から取得するようにしています。


index.js

'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));
});
};



テンプレート


template.yaml

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)を環境に合わせて実体化する作業を言います。



  • Lambdaのアップロード

    package.png

     


  • Templateの実体化


    template.png

     


  • コマンド


    事前に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が生成されます。


serverless-output.yam

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のマネージメントコンソールでスタックが生成されていることが確認できます。

CloudFormation.png

ここで重要なのが --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&param2=value2

ヘッダー
Accept: application/json
Content-type: application/json

リクエスト本文
{"message1":"Hello","message2":"Lambda"}

最後に「テスト」ボタンを押下すると、DynamoDBのテーブルにitemが追加され、テストが成功します。



  • DynamoDB
    DynamoDB.png