SAM で DynamoDB のテーブルを作る
前回はだいぶ駆け足で AWS SAM を使いました。
ただし、DynamoDB とか S3 とか、他のリソースと組み合わせるところまでは到達できませんでした。
なので、今日は DynamoDB のテーブルを作って、Lambda から書き込んでみようと思う。
前回のお話
tercel-tech.hatenablog.com
今回のポイントは、SAM を使って DynamoDB のテーブルを自動作成する点と、それを Lambda と連携させる点である。
リソースの自動作成に関しては CloudFormation と呼ばれるサービスが有名だが、SAM を使うとより簡易にリソース定義を記述できることを示す。
そんなわけで、本日のお品書きがコチラ。 3つの演習を通じて、インフラ構成を含めてコード化する技術 (IaC (Infrastructure as Code)) に触れる。
【練習①】2行で作れる DynamoDB
前回の続きからスタートする。 前回の記事をいちいち読むのがだるい場合は、sam init
で Python 3.8 の AWS Quick Start Templates から、Hello World Example のボイラープレートを選んでプロジェクトを新規作成した状態をスタート地点にしても全く問題ない。
ここでは最も簡単な例として、 String
型の id
というプライマリキーを持つ DynamoDB をひとつ作ってみよう。
SAM のプロジェクトでは、Lambda や API Gateway を含め、あらゆるリソースを template.yaml
というファイルに定義している。 そのため DynamoDB を足したくなったらこのファイルを弄る必要があるのだ。
template.yaml の編集(2行追加)
template.yaml
を開くと、まず夥しい既存コードに泡を吹くかも知れないが、とりあえず落ち着いてほしい。
DynamoDB を作るだけであれば、たった2行のリソース定義を追加するだけでよいのだ(ちなみに、HelloWorldDB
の代わりに好きな名前をつけることができる)。
template.yaml
(diff)
AWSTemplateFormatVersion: '2010-09-09' Transform: AWS::Serverless-2016-10-31 Description: > sam-app Sample SAM Template for sam-app Globals: Function: Timeout: 3 Resources: + HelloWorldDB: + Type: AWS::Serverless::SimpleTable HelloWorldFunction: Type: AWS::Serverless::Function Properties: CodeUri: hello_world/ Handler: app.lambda_handler Runtime: python3.8 Events: HelloWorld: Type: Api Properties: Path: /hello Method: get Outputs: HelloWorldApi: Description: "API Gateway endpoint URL for Prod stage for Hello World function" Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/hello/" HelloWorldFunction: Description: "Hello World Lambda Function ARN" Value: !GetAtt HelloWorldFunction.Arn HelloWorldFunctionIamRole: Description: "Implicit IAM Role created for Hello World function" Value: !GetAtt HelloWorldFunctionRole.Arn
たったこれだけで、DynamoDB が出来上がる。
DynamoDB のデプロイ
物は試しにデプロイしてみよう。
$ sam deploy --guided
少し待つと、ターミナルに DynamoDB が作られる様子が出力される。
デプロイが正常終了すると、Successfully が表示される。
結果の確認
AWS マネジメントコンソールにログインして、DynamoDB → 「テーブル」を選択しよう。 新しいテーブルが追加されていたら成功だ。
template.yaml
にたった2行書き足してデプロイするだけで DynamoDB のリソースが作られてしまった。
これを知っているだけでだいぶ友達に差がつけられるのではと思う。 たった2行で DynamoDB を作り終えている傍らで、ライバルはマネジメントコンソールでぽちぽちやっているかも知れない。
おまけ: CloudFormation だったら……?
SAM は、従来の CloudFormation よりも簡略化された表記でリソースを定義できる。
もしも CloudFormation で似たような DynamoDB を作ろうとすると、けっこう大変なのだ。
SAM版
Resources: HelloWorldDB: Type: AWS::Serverless::SimpleTable
CloudFormation版
Resources: HelloWorldDB: Type: AWS::DynamoDB::Table Properties: AttributeDefinitions: - AttributeName: id AttributeType: S KeySchema: - AttributeName: id KeyType: HASH ProvisionedThroughput: ReadCapacityUnits: 5 WriteCapacityUnits: 5 StreamSpecification: StreamViewType: NEW_IMAGE
とはいえすべてのリソースにこのような簡便記法が用意されているわけではないし、やはり細かくチューニングしたくなった場合はそれだけパラメータを細かく設定する必要がある。
なお、リソースの一覧は以下のページに詳しい。
【練習②】テーブル名を Lambda から参照する
作成した DynamoDB のテーブル名には、sam-app-HelloWorldDB-██████
のような適当な乱数が与えられている。
コイツをどうにかして Lambda 関数から参照できるようにしてみよう。
template.yaml の編集(3行追加)
template.yaml
(diff)
(前略) Resources: HelloWorldDB: Type: AWS::Serverless::SimpleTable HelloWorldFunction: Type: AWS::Serverless::Function Properties: CodeUri: hello_world/ Handler: app.lambda_handler Runtime: python3.8 Events: HelloWorld: Type: Api Properties: Path: /hello Method: get + Environment: + Variables: + TableName: !Ref HelloWorldDB (後略)
yaml を編集する際にはインデントに十分注意しよう。Environment
と Events
は同じ位置にすること。
また、!Ref
の後ろに続く HelloWorldDB
は、前節で作った DynamoDB のリソース名に合わせる必要がある(下図の緑枠)。
これで、TableName
という環境変数に、テーブル名が自動的に格納されるようになる。
環境変数の参照
Python で環境変数 TableName
を参照するには、os.environ['TableName']
と書けばよい*1。
app.py
import os import json def lambda_handler(event, context): table_name = os.environ['TableName'] return { "statusCode": 200, "body": json.dumps({ "result": table_name }), }
template.yaml
と app.py
の対応関係は下図のとおりである。
うっかりしていると見落としがちだが、template.yaml
と app.py
はあくまで TableName
という環境変数を媒介してテーブル名を参照している点に注目してみよう。
では、その TableName
の値はどこから来ているかというと、template.yaml
内部で HelloWorldDB
というリソース名を !Ref
している。
参照元のリソースが、色々乗り移って最終的に Lambda に繋がっているわけだが、初見だと些かややこしく認知負荷が高いので注意したい。
ちなみに、一見尤もらしいTableName
という環境変数名は僕が勝手に付けたものなので、たとえば UNKO
とか命名を変えても参照関係がしっかり繋がってさえいれば動くので好きにしてほしい。
では、実際にデプロイして curl
コマンドを叩いてみよう、ターミナルにテーブル名が出力されるはずだ。
Lambda 側から、きちんと環境変数経由でテーブル名を参照できている証左である。
【練習③】 DynamoDB にデータを書き込んでみよう
権限の追加(お手軽編)
第1話でやったときと同じく、Lambda に対してガバガバ権限の AmazonDynamoDBFullAccess
を与えるだけなら、template.yaml
に1行追加するだけでよい。
template.yaml
(diff)
(前略)
Resources:
HelloWorldDB:
Type: AWS::Serverless::SimpleTable
HelloWorldFunction:
Type: AWS::Serverless::Function
Properties:
CodeUri: hello_world/
Handler: app.lambda_handler
Runtime: python3.8
+ Policies: AmazonDynamoDBFullAccess
Events:
HelloWorld:
Type: Api
Properties:
Path: /hello
Method: get
Environment:
Variables:
TableName: !Ref HelloWorldDB
(後略)
ただ、AmazonDynamoDBFullAccess
は、DynamoDB のあらゆるテーブルに対してあらゆる操作を許す権限なので、もうちょっと厳しくしたい。
権限の追加(改良編)
HelloWorldDB
に対する読み書きができれば十分なので、AmazonDynamoDBFullAccess
の代わりにDynamoDBCrudPolicy
を使おう。
template.yaml
(diff)
(前略) Resources: HelloWorldDB: Type: AWS::Serverless::SimpleTable HelloWorldFunction: Type: AWS::Serverless::Function Properties: CodeUri: hello_world/ Handler: app.lambda_handler Runtime: python3.8 + Policies: + - DynamoDBCrudPolicy: + TableName: !Ref HelloWorldDB Events: HelloWorld: Type: Api Properties: Path: /hello Method: get Environment: Variables: TableName: !Ref HelloWorldDB (後略)
DynamoDB に UUID を登録する Lambda を作る
1話で作ったモノを再利用して以下の Lambda を書いてデプロイしよう。
app.py
import os import json import uuid import boto3 dynamodb = boto3.resource('dynamodb') # DynamoDB オブジェクト def lambda_handler(event, context): table_name = os.environ['TableName'] # 環境変数からテーブル名を取得 table = dynamodb.Table(table_name) item = { 'id': str(uuid.uuid4()) } # UUID を発行 table.put_item(Item = item) # DynamoDB に書き込む return { 'statusCode': 200, 'body': json.dumps(item) }
curl
を叩くと、Lambda の中で発行した UUID が返却される。
まとめとか
今日は SAM の template.yaml
を修正して、DynamoDB のテーブルを作り、環境変数を経由して Lambda にテーブル名を引き渡す方法を学んだ。
また、Lambda から DynamoDB にアクセスするための権限設定を AWS SAM で行う方法を学んだ。
初日の Lambda 関数を利用して、DynamoDB にデータを登録することを確認した。
そういえば、宅配ボックスにこんな本が入っていたのだけど……?
あー、これ注文しておいた本だ!
もう届いたのか。
IoT も勉強しないといけないし、学ぶことが急に増えたねー。
とはいえ、いろいろなところで学んだ知識は最終的にひとつに繋がると思う。
結局、センサから吸い上げたデータを溜めるには Dynamo か S3 と連携する Lambda が書けないと話にならないしね。
ふむ。
なんか、まぁ GW が充実してそうで何より。