概要
AWS CloudFormation Macros について
ということで、先日発表されました AWS CloudFormation のマクロを早速試してみました。
マクロとは?
AWS CloudFormation Macros は、CloudFormation テンプレートで、検索や置換などの単純な操作からテンプレート全体の変換までカスタム処理を実行できます。
以前は、AWS::Include 変換および AWS::Serverless 変換を使用して、CloudFormation によってホストされたテンプレートを処理できました。今は、CloudFormation Macros を使用して独自のカスタム変換を作成できるようになりました。
つまり、CloudFormation のテンプレートの実行時に、独自の処理を行うことができるもののようです。
実際にどのようなことができるのかというと、下記のような例がありました。
- テンプレート内に python コードを記述
- リソース定義表現を短くする
- カスタム CloudWatch metrics の出力
- 文字列を関数としてテンプレート内で使う
なにやらいろいろできそうな気がします。
自分は、1回の記述で同じリソースを複数個作るマクロを作ってみました。
マクロを作る
マクロを知る
まずはどのようにマクロが動くかを調べます。
AWS CloudFormation を AWS Lambda によるマクロで拡張する や Using AWS CloudFormation Macros to Perform Custom Processing on Templates によると、
- マクロの実態は Lambda 関数
- その関数の ARN を
AWS::CloudFormation::Macro
リソースに指定することでスタックが作られる際に関数が実行される
とのことです。
この時、Lambda 関数が受け取るペイロードは以下のようです。
{
"region" : "us-east-1",
"accountId" : "$ACCOUNT_ID",
"fragment" : { ... },
"transformId" : "$TRANSFORM_ID",
"params" : { ... },
"requestId" : "$REQUEST_ID",
"templateParameterValues" : { ... }
}
この中で一番大事なパラメータが fragment
で、この中に処理するテンプレートの内容が含まれます。
Lambda 関数のレスポンスはというと、
{
"requestId" : "$REQUEST_ID",
"status" : "$STATUS",
"fragment" : { ... }
}
こちらの fragment
が JSON形式の CloudFormation テンプレートとして適用されるようです。
マクロとなる Lambda 関数の作成
まずはマクロの本体となる Lambda 関数を作ります。
今回は、
AWSTemplateFormatVersion: "2010-09-09"
Resources:
Resource:
Type: AWS::SNS::Topic
Properties:
DisplayName: test topic
TopicName: TestTopic
↑こんな感じのテンプレートの1回の実行で、指定した回数分の SNS トピックが作成できる
という感じのマクロを作ります。
テンプレート&使いかたをはじめに出しておくと、
AWSTemplateFormatVersion: "2010-09-09"
Transform: [MultiplexResourceMacro]
Parameters:
NumberOfRepeats:
Type: Number
Default: 1
Resources:
Resource:
Type: AWS::SNS::Topic
Properties:
DisplayName: test topic
TopicName: TestTopic
このようなテンプレートを実行すると、NumberOfRepeats に指定した個数の SNS トピックが作成できる、といったものです。
Lambda 関数でやることは、Resources 以下をとってきて指定された回数ループし、新しく Resouses
として返すといった内容になります。
実際の処理は以下のような感じです。(Lambda のランタイムは node.js 8.10)
'use strict'
const getPropertyGenerator = (type) => {
switch (type) {
case 'AWS::SNS::Topic':
return createSNSTopic
}
}
const createSNSTopic = (properties, count) => {
return {
DisplayName: properties.DisplayName,
TopicName: `${properties.TopicName}-${count}`
}
}
exports.handler = async (event, context, callback) => {
const numberOfRepeats = event.templateParameterValues.NumberOfRepeats
const resources = event.fragment.Resources
const keys = Object.keys(resources)
if (keys.length > 1) {
throw Error('Number of resources shouud be 1')
}
const resource = resources[keys[0]]
const resourceType = resource.Type
const properties = resource.Properties
for (let i = 0; i < numberOfRepeats; i++) {
const ret = {
Type: resourceType,
Properties: getPropertyGenerator(resourceType)(properties, i)
}
resources[`${keys[0]}${i}`] = ret
}
delete resources[keys[0]]
return {
'requestId': event['requestId'],
'status': 'success',
'fragment': event['fragment']
}
}
これをデプロイします。
デプロイに以下の Template を使います。
ここでは、AWS::Serverless::Function
タイプのリソースを MultiplexResourceMacro
という名前で作り、Function の ARN を登録しています。
Transform: AWS::Serverless-2016-10-31
Description: Macro for cloudformation
Resources:
Function:
Type: AWS::Serverless::Function
Properties:
CodeUri: ./index.js
Handler: index.handler
Runtime: nodejs8.10
Macro:
Type: AWS::CloudFormation::Macro
Properties:
Name: MultiplexResourceMacro
FunctionName: !GetAtt Function.Arn
これを、↓こんな感じでデプロイします。
aws cloudformation package --template-file macro.yaml --output-template-file cfn-transformed-template.yaml --s3-bucket <S3_BUCKET_NAME>
aws cloudformation deploy --template-file ./cfn-transformed-template.yaml --capabilities CAPABILITY_IAM --stack-name <STACK_NAME>
マクロを使ってみる
といった感じで作ったマクロを使ってみます。
上に書いている SNS 作成テンプレートを指定し以下のコマンドで実行します。
aws cloudformation deploy --template-file template.yml --stack-name TestSNS --parameter-overrides NumberOfRepeats=5
すると、
↑こんな感じで5個のトピックが無事作成できました。
感想
今回作ったマクロはコード見ても分かる通り全く汎用性がないので、改善の余地大ありです。
ただマクロ機能自体は便利なので、うまく使えればかなり色んなことができそうだなと思いました。
パッと思いついたものだと、テンプレート構文的には正しくても運用ポリシー上イケてないテンプレートを弾く、みたいな使い方だと簡単にできそうかなーと感じました。
ちなみに今回作ったマクロ/使ったテンプレートは以下のリポジトリにありますので、よかったら使ってみてください。