CloudFormationのカスタムリソースにLambda関数を関連付けると、スタックを作る時とか更新する時に、Lambda関数を動かせる。
https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/template-custom-resources.html
https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/template-custom-resources-lambda.html
テンプレートの書き方をいつもド忘れするから、ひな形をメモっておく。
まえがき: カスタムリソースの使い途とか書き方とか
カスタムリソースを使わず済むなら使わない方がテンプレートはシンプルになると思う。
Lambda関数呼べるとなると、もはやだいたいなんでもできるんだけども、例えばLambda関数でCreate*系のAPIを呼んでリソースを作ったりすると、テンプレートのどの部分でどうリソースが作られているのか把握するのが難しくなる気がしてる。
カスタムリソースで呼ばれるLambda関数は、テンプレート内で記述するリソースのプロパティの値として使う情報を取得するとか、CloudFormationがまだ対応していないプロパティを設定するとか、そういう用途で活躍する。
カスタムリソースに関連付けられるLambda関数のコードは、S3バケットに置いておいてテンプレート内で参照することもできるし、テンプレート内にベタ書きすることもできる。
個人的には、ベタ書きするほうが、一目でテンプレート全体を確認できるから便利と思う。
カスタムリソースのLambda関数はたいてい短いからベタ書きしてもテンプレートがゴチャつかないというのも、ベタ書きで良いと思う理由。
ベタ書きするとなると、YAMLで書くのが良い。
JSONだとコードをダブルクオーテーションで囲みまくる必要があってつらいけど、YAMLならコードをそのまま書ける。
ところで、カスタムリソースが呼び出すLambda関数に誤りがあってエラーになると、スタックの作成がCREATE_IN_PROGRESS
で止まる。
CREATE_IN_PROGRESS
が長時間続くなら、Lambda関数のログとかメトリクスを見てエラーが出ていないか確認するようにしてる。
ひな形
ひな形は下記。
<>で囲んでる部分は、置き換えるべき部分。
カスタムリソースの論理IDとCustom::Stringで指定するリソースタイプの名前(<カスタムリソースの論理ID>の部分)は同じにしなくてもいいハズだけど、揃えた方がわかりやすい気がするので揃えてる。
AWSTemplateFormatVersion: '2010-09-09'
Resources:
<カスタムリソースLambda関数の出力を利用するリソースの論理ID>:
Type: <XX::XX::XX>
Properties:
<XX>: !GetAtt <カスタムリソースの論理ID>.<Lambda関数が返すresponseDataオブジェクト配下のプロパティ名>
<カスタムリソースの論理ID>:
Type: Custom::<カスタムリソースの論理ID>
Properties:
ServiceToken: !GetAtt <カスタムリソースLambda関数の論理ID>.Arn
<カスタムリソースLambda関数のevent.ResourcePropertiesオブジェクト配下のプロパティ名>: <プロパティの値>
<カスタムリソースLambda関数の論理ID>:
Type: AWS::Lambda::Function
Properties:
Handler: index.handler
Role: !GetAtt <カスタムリソースLambda関数の実行ロールの論理ID>.Arn
Code:
ZipFile: !Sub |
var cfnresponse = require('cfn-response');
var AWS = require('aws-sdk');
exports.handler = function(event, context) {
<処理を書く>
var responseData = {};
responseData.<Lambda関数が返すresponseDataオブジェクト配下のプロパティ名> = <何らかの値>;
cfnresponse.send(event, context, cfnresponse.SUCCESS, responseData, '', true);
};
Runtime: nodejs6.10
<カスタムリソースLambda関数の実行ロールの論理ID>:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service:
- lambda.amazonaws.com
Action:
- sts:AssumeRole
Path: "/"
Policies: # 実行ロールの権限は、Lambda関数で実行するAPIに応じて変更する
- PolicyName: root
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action:
- logs:CreateLogGroup
- logs:CreateLogStream
- logs:PutLogEvents
Resource: "arn:aws:logs:*:*:*"
実際の例
実際に動くテンプレートは下記。
カスタムリソースで呼ばれるLambda関数が出力した値を、パラメータストアに格納している。
AWSTemplateFormatVersion: '2010-09-09'
Resources:
Parameter:
Type: AWS::SSM::Parameter
Properties:
Name: /tmp/CustomResourceTest
Type: String
Value: !GetAtt CustomResource.data
CustomResource:
Type: Custom::CustomResource
Properties:
ServiceToken: !GetAtt CustomResourceFunction.Arn
CustomResourceArgument: hoge
CustomResourceFunction:
Type: AWS::Lambda::Function
Properties:
Handler: index.handler
Role: !GetAtt CustomResourceFunctionExecutionRole.Arn
Code:
ZipFile: !Sub |
var cfnresponse = require('cfn-response');
exports.handler = function(event, context) {
console.log(JSON.stringify(event));
console.log(JSON.stringify(context));
var responseData = {};
responseData.data = 'Hello from lambda invoked by custom resource! Argument is: ' + event.ResourceProperties.CustomResourceArgument;
cfnresponse.send(event, context, cfnresponse.SUCCESS, responseData, '', true);
};
Runtime: nodejs6.10
CustomResourceFunctionExecutionRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service:
- lambda.amazonaws.com
Action:
- sts:AssumeRole
Path: "/"
Policies:
- PolicyName: root
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action:
- logs:CreateLogGroup
- logs:CreateLogStream
- logs:PutLogEvents
Resource: "arn:aws:logs:*:*:*"
CLIでパラメータを取得すると、確かに格納されている。
$ aws ssm get-parameter --name /tmp/CustomResourceTest
{
"Parameter": {
"Version": 1,
"Type": "String",
"Name": "/tmp/CustomResourceTest",
"Value": "Hello from lambda invoked by custom resource! Argument is: hoge"
}
}
ドキュメント
カスタムリソースで呼ばれるLambdaの書き方については、下記のドキュメントに詳しく書かれてる。
cfn-responseモジュールの詳細とかも書かれてる。
https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-properties-lambda-function-code.html