SAM CLIでAPI Gateway + Lambda + DynamoDBを使う
AWSでのサーバレス構築を考えた時に最も無難でポピュラーな構成(悪く言えばあまり面白みのない)として挙げられる、
- API Gateway
- Lambda
- DynamoDB
の構築を、SAM(Serverless Application Model) で行います。
書くこと
- SAM CLIでプロジェクトの作成
- SAMプロジェクトのデプロイ
- SAMプロジェクトを修正してDynamoDBにテーブルを作成
- SAMプロジェクトの更新
SAM CLIでプロジェクトの作成
まずSAM CLIをインストールします。
$ sam --version
SAM CLI, version 0.40.0
インストールができれば早速SAMプロジェクトします。
$ sam init
Which template source would you like to use?
1 - AWS Quick Start Templates
2 - Custom Template Location
Choice: 1
Which runtime would you like to use?
1 - nodejs12.x
2 - python3.8
3 - ruby2.5
4 - go1.x
5 - java11
6 - dotnetcore2.1
7 - nodejs10.x
8 - python3.7
9 - python3.6
10 - python2.7
11 - java8
12 - dotnetcore2.0
13 - dotnetcore1.0
Runtime: 1
Project name [sam-app]: sample
Cloning app templates from https://github.com/awslabs/aws-sam-cli-app-templates.git
-----------------------
Generating application:
-----------------------
Name: sample
Runtime: nodejs12.x
Dependency Manager: npm
Application Template: hello-world
Output Directory: .
Next steps can be found in the README file at ./sample/README.md
sam init
を実行すると、3点の質問を尋ねられます。
ここでは、
- SAMのテンプレートとして
1 - AWS Quick Start Templates
、 - lambdaのランタイムとして
nodejs12.x
を選択し、 - プロジェクト名を入力
しています。
そうすると以下の構造のディレクトリが作成されると思います。
.
├── README.md
├── events
│ └── event.json
├── hello-world
│ ├── app.js
│ ├── package.json
│ └── tests
│ └── unit
│ └── test-handler.js
└── template.yaml
SAMプロジェクトのデプロイ
何もしていませんが、早速このままデプロイしてみましょう。
まず、デプロイの準備をします。
上記のソースのままではデプロイコマンドsam deploy
を使うことができないので、一旦以下のコマンドを実行します。
$ sam build
sam build
を実行すると、SAMプロジェクトのトップレベルに.aws-sam/build
が作成され、
その中にhelloWorldFunction
ディレクトリとtemplate.yaml
が配置されます。
helloWorldFunction
ディレクトリはSAMプロジェクトのトップレベルにあるhello-world
のアーティファクト(成果物、生成物)でこいつをAWS Lambdaへデプロイします。
template.yaml
はSAMプロジェクトのトップレベルにある同名のtemplate.yaml
を整形したものになっています。
このyamlファイルがcloudformationへアップロードされて、サーバレスを構成するリソースたちのスタックが組み立てられます。
ビルドが成功したら以下のコマンドを実行します。
$ sam deploy --guided
すると以下の質問事項に対する応答を求められます。
1. Stack Name // スタック名を入力
2. AWS Region // お好きなAWSリージョン
3. Confirm changes before deploy
// デプロイ実行前にデプロイによって変更されるスタックの状態を確認した上で、
// デプロイを実行できるようにするかどうか(yesにしておいて問題ありません)
4. Allow SAM CLI IAM role creation
// SAM CLIがIAMロールを作っても良いかどうか(yesにしておいて問題ありません)
5. Save arguments to samconfig.toml
// samconfig.tomlを作成し、
// その中にデフォルトのデプロイパラメータを書き込んでおくかどうか(yesにしておいて問題ありません)
そのままデプロイが実行されることになりますが、
先ほどのConfirm changes before deploy
を有効にしていると、
Deploy this changeset? [y/N]:
と聞かれます。
Cloudformationスタックの変更部分の一覧が表示されるので確認の上y
としてあげると、
デプロイが最後まで実行されます。
デプロイ後、AWSコンソールのLambdaには以下のように関数が追加されているはずです。
API Gatewayには以下のように。
API GatewayのDashboardからエンドポイントを確認して、
URLへアクセスすると、
{"message":"hello world"}
と表示されるはずです。
SAMプロジェクトを修正してDynamoDBにテーブルを作成
ここまででhelloWorldFunctionのデプロイとその実行をトリガーするAPI Gatewayのデプロイが成功しました。
ですが、ここではDynamoDBでのデータの読み書きについても触れたいと思います。
まず、hello-worldのSAMテンプレートにあるtemplate.yaml
にはDynamoDBリソースが記載されていないので追記する必要があります。
以下をtemplate.yaml
のResources
に追記してください(丁度既存のHelloWorldFunction
の次あたりに)。
PostFunction:
Type: AWS::Serverless::Function
Properties:
CodeUri: post/
Handler: app.lambdaHandler
Runtime: nodejs12.x
Events:
HelloWorld:
Type: Api
Properties:
Path: /post
Method: post
Role: !GetAtt lambdaFunctionRole.Arn
PostItems:
Type: AWS::DynamoDB::Table
Properties:
AttributeDefinitions:
-
AttributeName: 'partitionKey'
AttributeType: 'S'
TableName: 'postItems'
KeySchema:
-
AttributeName: 'partitionKey'
KeyType: 'HASH'
ProvisionedThroughput:
ReadCapacityUnits: 1
WriteCapacityUnits: 1
lambdaFunctionRole: # lambda関数がDynamoDBとCloudWatchにアクセスするためのロール
Type: AWS::IAM::Role
Properties:
RoleName: 'RoleForLambdaFunction'
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
-
Effect: Allow
Principal:
Service:
- lambda.amazonaws.com
Action:
- sts:AssumeRole
Policies:
-
PolicyName: 'FullAccessToDynamoDB'
PolicyDocument:
Version: '2012-10-17'
Statement:
-
Effect: Allow
Action:
- dynamodb:*
Resource: "*"
-
PolicyName: 'WriteLimitedAccessToCloudWatch'
PolicyDocument:
Version: '2012-10-17'
Statement:
-
Effect: Allow
Action:
- logs:*
Resource: "*"
ここで定義しているリソースは
- ポストを行うLambda関数
- ポストされたデータを保管するDynamoDBテーブル
- Lambda関数にDynamoDBとCloudWatchへのアクセスを許可するロール
の三つです。
リソース定義の詳細についてはここでは触れません(DynamoDBのpartitionKeyとかAWS::Serverless::FuncntionのEvents設定とかIAMロールとか)ので、以下参照のこと。
AWS::Serverless::Functionについて
AWS::DynamoDB::Tableについて
AWS::IAM::Roleについて
上記のPostFunction
リソースでは、CodeUriにpost/
を指定しているので、
SAMプロジェクトにトップディレクトリにpost
ディレクトリを作成し、その中にLambda関数のソースコードを配置します。
中身のapp.js
は例えばこんな感じになります。
post
ディレクトリ内でyarn
を行い、yarn add -D aws-sdk
でDynamoDBを使えるようにしておきます。
const AWS = require('aws-sdk')
const dynamo = new AWS.DynamoDB.DocumentClient({region: 'us-west-2'})
exports.lambdaHandler = async (event, _, _) => {
const requestBody = JSON.parse(event.body)
const table = 'postItems'
const items = {
partitionKey: requestBody.partitionKey,
}
const params = {
TableName: table,
Item: items,
}
try {
await dynamo.put(params, (err, data) => {
if (err) console.error(err)
}).promise()
const response = {
statusCode: 200,
body: JSON.stringify(items)
}
return response
} catch(err) {
console.error(err)
return err
}
}
SAM CLIで作成されるAPI Gatewayでは統合リクエストの設定でプロキシ統合がONになっているので、
ややこしいマッピングテンプレートについて知る必要はなく、渡したリクエストの値はハンドラーの引数event
で参照できます。
上記ではevent.body
をパースして中の値を取り出しています。
SAMプロジェクトの更新
更新したソースコードとtemplate.yaml
を再びビルドしてデプロイします。
まず、SAMプロジェクトのトップレベルで以下を実行します。
$ sam build
すると、.aws-sam/build
配下にPostFunction
ディレクトリが追加されるはずです。
.aws-sam/build/template.yaml
も更新されていると思います。
デプロイは初回で使っていた--guided
を省いて、
$ sam deploy
とだけ実行します。
*この時にCAPABILITY_NAMED_IAMを使えというエラーが出ると思いますので、初回デプロイ時に生成されるsamconfig.toml(SAMプロジェクトのトップディレクトリ)に記載されているパラメータcapabilities
の値をCAPABILITY_NAMED_IAMに書き換えてください。
CloudFormationスタックの更新状態を確認して、
Deploy this changeset? [y/N]: y
と入力します。
デプロイ後にAWSコンソールに入ると、
DynamoDBにテーブルが追加されます。
Lambdaにも追加されています。
試しにPOSTしてみる
API Gatewayでエンドポイントを確認して、
/post
へPOSTを行います。
Advanced Rest ClientとかでBodyにpartitionKeyを指定してPOSTします。
するとPostFunctionのコード内で、リクエストのpartitionKeyをパラメータとして取得してDynamoDBにそのデータを登録します。
こんな感じにデータが入っていると思います。
Lambda関数に紐づけているRoleにはCloudWatchへの書き込み権限もあるので、
CloudWatchのログでlambda関数のログストリームを覗くこともできます(以下画像の右側中央部にある「Views logs in CloudWatch」から別タブで開いてみることができる)。
CloudWatchのログストリーム一覧↓
以上でAWS SAMを使ってAPI Gateway + Lambda + DynamoDBをつかったサーバレス環境を構築できました。
他にもいろいろリソースがあるので自分なりにcloudformationのテンプレートを書き換えてみると面白いと思います。