この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
AWS SAMでサーバーレスなアプリケーションを作っていると、1つのテンプレートファイル内にLambdaやDynamoDBの定義を書くと楽です。 しかし、あとから「DynamoDBの定義を分離したいな……」となることがたまにあります。例えば、CloudFormationのリソース上限200個が見えてきたり、そもそもデータストア層とアプリ層を同じテンプレートに書いて頻繁にデプロイ対象にしたくないなどです。
そんなとき、CloudFormationのインポート機能を使えばテンプレートファイルを分離できるのでは?と思ったので試してみました。
適当なAPIを作成する
アプリを初期化
sam init \
--runtime python3.7 \
--name CfnImportSampleApp \
--app-template hello-world
AWS SAMテンプレート
最初は、AWS SAMテンプレート内にDynamoDBテーブルの定義を書いています。
template.yaml
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: CfnImportSampleApp
Resources:
HelloWorldFunction:
Type: AWS::Serverless::Function
Properties:
CodeUri: hello_world/
Handler: app.lambda_handler
Runtime: python3.7
Timeout: 5
Environment:
Variables:
TABLE_NAME: !Ref ParameterTable
Policies:
- arn:aws:iam::aws:policy/AmazonDynamoDBReadOnlyAccess
Events:
HelloWorld:
Type: Api
Properties:
Path: /hello
Method: get
ParameterTable:
Type: AWS::DynamoDB::Table
Properties:
TableName: CfnImportSampleTable
AttributeDefinitions:
- AttributeName: userId
AttributeType: S
KeySchema:
- AttributeName: userId
KeyType: HASH
BillingMode: PAY_PER_REQUEST
Outputs:
HelloWorldApi:
Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/hello/"
Lambdaコード
app.py
import boto3
import json
import os
dynamodb = boto3.resource('dynamodb')
table_name = os.environ['TABLE_NAME']
def lambda_handler(event, context):
table = dynamodb.Table(table_name)
res = table.get_item(Key={
'userId': '1234'
})
return {
'statusCode': 200,
'body': json.dumps({
'message': 'hello world',
'data': res['Item']
}),
}
デプロイ
sam build
sam package \
--output-template-file packaged.yaml \
--s3-bucket cm-fujii.genki-deploy
sam deploy \
--template-file packaged.yaml \
--stack-name Cfn-Import-Sample-Stack \
--capabilities CAPABILITY_NAMED_IAM \
--no-fail-on-empty-changeset
DynamoDBへデータ追加
適当に追加しました。
{
"todo": "xxx",
"userId": "1234"
}
APIを叩いて確認
実際にAPIを叩いてみると、下記の応答が返ってきます。バッチリですね。
$ curl https://xxx.execute-api.ap-northeast-1.amazonaws.com/Prod/hello/
{"message": "hello world", "data": {"userId": "1234", "todo": "xxx"}}
AWS SAMのDynamoDBを削除する
DynamoDBのDeletionPolicyをRetainにする
このままDynamoDBテーブルの定義を削除した場合、テーブル自体が削除されてしまいします。そのため、DeletionPolicy属性を設定することで、リソース(DynamoDBテーブル)を残しつつCloudFormationスタックからDynamoDBの定義を削除させます。
template.yaml
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: CfnImportSampleApp
Resources:
HelloWorldFunction:
Type: AWS::Serverless::Function
Properties:
CodeUri: hello_world/
Handler: app.lambda_handler
Runtime: python3.7
Timeout: 5
Environment:
Variables:
TABLE_NAME: !Ref ParameterTable
Policies:
- arn:aws:iam::aws:policy/AmazonDynamoDBReadOnlyAccess
Events:
HelloWorld:
Type: Api
Properties:
Path: /hello
Method: get
ParameterTable:
Type: AWS::DynamoDB::Table
DeletionPolicy: Retain
Properties:
TableName: CfnImportSampleTable
AttributeDefinitions:
- AttributeName: userId
AttributeType: S
KeySchema:
- AttributeName: userId
KeyType: HASH
BillingMode: PAY_PER_REQUEST
Outputs:
HelloWorldApi:
Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/hello/"
DeletionPolicy: Retain
を付与したあと、再度デプロイを行います。これでAWS SAMテンプレートから削除する準備が整いました。
DynamoDBテーブルを削除する
AWS SAMテンプレートからDynamoDBテーブルの定義をまるっと削除します。Lambdaの環境変数部分も一度削除しておきます。 この作業中でもDynamoDBテーブルを参照したい場合は、環境変数部分にDynamoDBテーブル名を直接記載すると良いですね。
template.yaml
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: CfnImportSampleApp
Resources:
HelloWorldFunction:
Type: AWS::Serverless::Function
Properties:
CodeUri: hello_world/
Handler: app.lambda_handler
Runtime: python3.7
Timeout: 5
Policies:
- arn:aws:iam::aws:policy/AmazonDynamoDBReadOnlyAccess
Events:
HelloWorld:
Type: Api
Properties:
Path: /hello
Method: get
Outputs:
HelloWorldApi:
Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/hello/"
この状態でデプロイすると、CloudFormation管理リソースからDynamoDBが削除されました。
しかし、実際のDynamoDBテーブルは残り続けています。
CloudFormationで既存のDynamoDBテーブルをインポートする
CloudFormatioin用のテンプレートを作成する
datastore.yaml
というファイルを新規作成し、先ほどのDynamoDBテーブルをインポートさせます。なお、CloudFormationで既存リソースをインポートする場合はDeletionPolicy: Retain
が必須なので付与させています。
datastore.yaml
AWSTemplateFormatVersion: '2010-09-09'
Resources:
ParameterTable:
Type: AWS::DynamoDB::Table
DeletionPolicy: Retain
Properties:
TableName: CfnImportSampleTable
AttributeDefinitions:
- AttributeName: userId
AttributeType: S
KeySchema:
- AttributeName: userId
KeyType: HASH
BillingMode: PAY_PER_REQUEST
CloudFormationで既存リソースをインポートする
CloudFormation画面で「既存のリソースを使用」を選択します。
先ほど作成したテンプレートファイル(datastore.yaml
)をアップロードして次に進みます。
識別子にインポートしたいDynamoDBテーブル名を入力して次に進みます。
- 識別子の値: CfnImportSampleTable
スタック名を入力して次に進みます。
- スタック名: Cfn-Import-Datastore-Sample-Stack
最終チェックを行い、既存リソースをインポートしましょう!
無事に完了しました!!
ここまでの作業で、DynamoDBテーブルの定義を分離できました。
AWS SAMで異なるテンプレートで定義したDynamoDBを参照する
異なるテンプレート(CloudFormationスタック)で定義したDynamoDBテーブルを参照する方法はいくつかあります。
- AWS SAMテンプレート内でDynamoDBテーブル名を固定文字列として持つ
- AWS SAMテンプレート内でクロススタック参照をする
今回はクロススタック参照を利用してみます。
Outputsを定義する
クロススタック参照を行うためには、まずOutputでExportする必要があります。
そのため、datastore.yaml
にOutputs
を追加します。
(最初から追加しようとしましたが、OutputsがあるテンプレートでCloudFormationの既存リソースはインポートできませんでした)
datastore.yaml
AWSTemplateFormatVersion: '2010-09-09'
Resources:
ParameterTable:
Type: AWS::DynamoDB::Table
DeletionPolicy: Retain
Properties:
TableName: CfnImportSampleTable
AttributeDefinitions:
- AttributeName: userId
AttributeType: S
KeySchema:
- AttributeName: userId
KeyType: HASH
BillingMode: PAY_PER_REQUEST
Outputs:
ParameterTableName:
Value: !Ref ParameterTable
Export:
Name: !Sub ${AWS::StackName}-ParameterTableName
デプロイします。
aws cloudformation deploy \
--template-file datastore.yaml \
--stack-name Cfn-Import-Datastore-Sample-Stack \
--capabilities CAPABILITY_NAMED_IAM \
--no-fail-on-empty-changeset
無事にOutputsできました。
AWS SAMで異なるスタックのDynamoDBテーブルを参照する(クロススタック参照)
Lambdaの環境変数でクロススタック参照を行うようにtemplate.yaml
を修正します。
template.yaml
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: CfnImportSampleApp
Resources:
HelloWorldFunction:
Type: AWS::Serverless::Function
Properties:
CodeUri: hello_world/
Handler: app.lambda_handler
Runtime: python3.7
Timeout: 5
Environment:
Variables:
TABLE_NAME:
Fn::ImportValue: Cfn-Import-Datastore-Sample-Stack-ParameterTableName
Policies:
- arn:aws:iam::aws:policy/AmazonDynamoDBReadOnlyAccess
Events:
HelloWorld:
Type: Api
Properties:
Path: /hello
Method: get
Outputs:
HelloWorldApi:
Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/hello/"
再デプロイを行いましょう。
動作確認
せっかくなのでDynamoDBの内容を書き換えました。
{
"todo": "finish!!!",
"userId": "1234"
}
この状態でAPIを叩くと、バッチリと返ってきました!!!
$ curl https://xxx.execute-api.ap-northeast-1.amazonaws.com/Prod/hello/
{"message": "hello world", "data": {"userId": "1234", "todo": "finish!!!"}}
さいごに
CloudFormationの既存リソースインポート機能を使って、DynamoDBテーブルの定義を別テンプレート(スタック)に分離してみました。 何らかの参考になれば幸いです。
参考
- 【アップデート】ついに来た!CloudFormationで手動で作成したリソースをStackにインポート可能になりました | Developers.IO
- CloudFormationがリソースのインポートに対応しました! | Developers.IO
- 既存リソースの CloudFormation 管理への取り込み - AWS CloudFormation
- スタックへの既存リソースのインポート - AWS CloudFormation
- インポートオペレーションをサポートするリソース - AWS CloudFormation
- CloudFormationのスタック間でリソースを参照する | Developers.IO