内容
CDK で Custom Resource を利用する場合、
Provider + CustomResource を使用する方法と、
AwsCustomResourceを使用する方法がある。
それぞれの使用方法を記載する。
今回は、適当な Lambda Function (sample1Function, sample2Function) をそれぞれ実行する Custom Resource を作成する。
AwsCustomResource
概要
単一のAPIを呼び出すだけの目的で Custom Resource を利用する場合に使用できる。
今回は、Custom Resource で Invoke APIを実行する。
複数の Custom Resource を作成する場合に多少クセがあり、注意点を後述する。
実装
import * as path from 'node:path';
import { Stack, StackProps } from 'aws-cdk-lib';
import { Construct } from 'constructs';
import * as cr from 'aws-cdk-lib/custom-resources';
import * as iam from 'aws-cdk-lib/aws-iam';
import * as lambda from 'aws-cdk-lib/aws-lambda';
export class AwsCustomResourceSampleStack extends Stack {
constructor(scope: Construct, id: string, props?: StackProps) {
super(scope, id, props);
const stackName = this.stackName;
// 適当なLambda Function 1
const sample1FunctionName = `${stackName}-sample1Function`;
const sample1Function = new lambda.Function(this, sample1FunctionName, {
code: lambda.Code.fromAsset(path.join(__dirname, 'handlers')),
handler: 'sample1.handler',
runtime: lambda.Runtime.PYTHON_3_11,
functionName: sample1FunctionName,
});
// 適当なLambda Function 2
const sample2FunctionName = `${stackName}-sample2Function`;
const sample2Function = new lambda.Function(this, sample2FunctionName, {
code: lambda.Code.fromAsset(path.join(__dirname, 'handlers')),
handler: 'sample2.handler',
runtime: lambda.Runtime.PYTHON_3_11,
functionName: sample2FunctionName,
});
// Lambda Functionを実行するCustom ResourceにアタッチするRole
// ※ 全Custom Resourceを実行できるように権限を付与する
const customResourceFunctionName = `${stackName}-CustomResourceFunction`;
const customResourceFunctionRole = new iam.Role(this, `${customResourceFunctionName}Role`, {
assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'),
inlinePolicies: {
lambdaPolicy: new iam.PolicyDocument({
statements: [
new iam.PolicyStatement({
actions: ['lambda:InvokeFunction'],
resources: [sample1Function.functionArn, sample2Function.functionArn],
}),
],
}),
},
roleName: `${customResourceFunctionName}Role`,
});
// Custom Resourceで実行する内容 (sample1Functionを実行する)
const sample1FunctionInvokeContent = {
action: 'Invoke',
service: 'Lambda',
parameters: {
/* eslint-disable @typescript-eslint/naming-convention */
FunctionName: sample1Function.functionName,
InvocationType: 'Event',
Payload: JSON.stringify({ datetime: Date.now() }), // 毎回Custom Resourceを実行する工夫
/* eslint-enable @typescript-eslint/naming-convention */
},
physicalResourceId: cr.PhysicalResourceId.of(`${stackName}-sample1FunctionInvokeCustomResource`),
};
// sample1Functionを実行するCustom Resource
new cr.AwsCustomResource(this, `${stackName}-sample1FunctionInvokeCustomResource`, {
installLatestAwsSdk: true,
functionName: customResourceFunctionName,
onCreate: sample1FunctionInvokeContent,
onUpdate: sample1FunctionInvokeContent,
role: customResourceFunctionRole,
});
// Custom Resourceで実行する内容 (sample2Functionを実行する)
const sample2FunctionInvokeContent = {
action: 'Invoke',
service: 'Lambda',
parameters: {
/* eslint-disable @typescript-eslint/naming-convention */
FunctionName: sample2Function.functionName,
InvocationType: 'Event',
/* eslint-enable @typescript-eslint/naming-convention */
},
physicalResourceId: cr.PhysicalResourceId.of(Date.now().toString()), // 毎回Custom Resourceを実行する工夫
};
// sample2Functionを実行するCustom Resource
new cr.AwsCustomResource(this, `${stackName}-sample2FunctionInvokeCustomResource`, {
installLatestAwsSdk: true,
functionName: customResourceFunctionName,
onCreate: sample2FunctionInvokeContent,
onUpdate: sample2FunctionInvokeContent,
role: customResourceFunctionRole,
});
}
}
cr.AwsCustomResource
の onCreate
、onUpdate
で指定する parameters
は、Invokeを参照。
Custom Resource のUpdateイベントは、Custom Resourceに変更がない限り発生しない。
(Custom Resource から実行するLambda Functionに変更があっても発生しない)
そのため、Custom Resource を毎回実行したい場合は、日時Date.now()
等を使って工夫する。
複数の Custom Resource を作成する場合の注意点
These calls are created using a singleton Lambda function.
--- (翻訳) ---
これらの呼び出しは、シングルトン Lambda 関数を使用して作成されます。
AwsCustomResource に上記が記載されている。
これはCDK実装から生成されるCFNテンプレートを見るとわかりやすい。
# Lambda Functionを実行するCustom ResourceにアタッチするRole
sampleAwsCustomResourceSampleStackCustomResourceFunctionRole2E76:
Type: AWS::IAM::Role
Properties:
Policies:
- PolicyDocument:
Statement:
- Action: lambda:InvokeFunction
Effect: Allow
Resource:
- Fn::GetAtt:
- sampleAwsCustomResourceSampleStacksample1Function7826
- Arn
- Fn::GetAtt:
- sampleAwsCustomResourceSampleStacksample2Function301A
- Arn
Version: "2012-10-17"
PolicyName: lambdaPolicy
RoleName: sample-AwsCustomResourceSampleStack-CustomResourceFunctionRole
# sample1Functionを実行するCustom Resource
sampleAwsCustomResourceSampleStacksample1FunctionInvokeCustomResource361C:
Type: Custom::AWS
Properties:
ServiceToken:
Fn::GetAtt:
- AWS679f53fac002430cb0da5b7982bd22872D16
- Arn
# Custom Resourceとして実行されるLambda Function
AWS679f53fac002430cb0da5b7982bd22872D16:
Type: AWS::Lambda::Function
Properties:
FunctionName: sample-AwsCustomResourceSampleStack-CustomResourceFunction
Handler: index.handler
Role:
Fn::GetAtt:
- sampleAwsCustomResourceSampleStackCustomResourceFunctionRole2E76
- Arn
Runtime: nodejs18.x
# sample2Functionを実行するCustom Resource
sampleAwsCustomResourceSampleStacksample2FunctionInvokeCustomResource3FA7:
Type: Custom::AWS
Properties:
ServiceToken:
Fn::GetAtt:
- AWS679f53fac002430cb0da5b7982bd22872D16
- Arn
CDK上でAwsCustomResource
で2つのCustom Resourceを作成しているが、
Custom Resourceとして実行されるLambda Functionは1つで、
共通パーツとして2つのCustom Resourceから使用される。
そして、そのLambda FunctionにアタッチされるRoleも1つなので、
Roleには全Custom Resourceを実行できるように権限を付与する必要がある。
Provider
概要
CFN や SAM で実装する Custom Resource に似ている。
複数のAPIを呼び出したり、ロジックを組み込んだ Custom Resource を利用する場合に使用。
実装
import * as path from 'node:path';
import { CustomResource, Stack, StackProps } from 'aws-cdk-lib';
import { Construct } from 'constructs';
import * as cr from 'aws-cdk-lib/custom-resources';
import * as iam from 'aws-cdk-lib/aws-iam';
import * as lambda from 'aws-cdk-lib/aws-lambda';
export class CustomResourceSampleStack extends Stack {
constructor(scope: Construct, id: string, props?: StackProps) {
super(scope, id, props);
const stackName = this.stackName;
// 適当なLambda Function 1
const sample1FunctionName = `${stackName}-sample1Function`;
const sample1Function = new lambda.Function(this, sample1FunctionName, {
code: lambda.Code.fromAsset(path.join(__dirname, 'handlers')),
handler: 'sample1.handler',
runtime: lambda.Runtime.PYTHON_3_11,
functionName: sample1FunctionName,
});
// 適当なLambda Function 2
const sample2FunctionName = `${stackName}-sample2Function`;
const sample2Function = new lambda.Function(this, sample2FunctionName, {
code: lambda.Code.fromAsset(path.join(__dirname, 'handlers')),
handler: 'sample2.handler',
runtime: lambda.Runtime.PYTHON_3_11,
functionName: sample2FunctionName,
});
// Lambda Functionを実行するCustom ResourceにアタッチするRole
const customResourceFunctionRole = new iam.Role(this, `${stackName}-CustomResourceFunctionRole`, {
assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'),
inlinePolicies: {
lambdaPolicy: new iam.PolicyDocument({
statements: [
new iam.PolicyStatement({
actions: ['lambda:InvokeFunction'],
resources: [sample1Function.functionArn, sample2Function.functionArn],
}),
],
}),
},
roleName: `${stackName}-CustomResourceFunctionRole`,
});
// Custom Resourceとして実行するLambda Function
const invokeLambdaFunction = new lambda.Function(this, `${stackName}-invokeLambdaFunction`, {
code: lambda.Code.fromAsset(path.join(__dirname, 'handlers')),
handler: 'invoke_lambda.handler',
runtime: lambda.Runtime.PYTHON_3_11,
functionName: `${stackName}-invokeLambdaFunction`,
role: customResourceFunctionRole,
});
// Custom Resourceとして実行するLambda FunctionをProviderに指定する
const invokeLambdaProvider = new cr.Provider(this, `${stackName}-invokeLambdaProvider`, {
onEventHandler: invokeLambdaFunction,
});
// sample1Functionを実行するCustom Resource
new CustomResource(this, `${stackName}-invokeSample1CustomResource`, {
serviceToken: invokeLambdaProvider.serviceToken,
properties: {
FUNCTION_NAME: sample1Function.functionName
}
});
// sample2Functionを実行するCustom Resource
new CustomResource(this, `${stackName}-invokeSample2CustomResource`, {
serviceToken: invokeLambdaProvider.serviceToken,
properties: {
FUNCTION_NAME: sample2Function.functionName
}
});
}
}
CustomResource の serviceToken には、
provider.serviceToken
以外にも、
Lambda FunctionのARNや、SNSのトピックARNを直に指定することも可能だが非推奨となっている。
import boto3 # type: ignore
def handler(event, context):
request_type = event["RequestType"]
properties = event["ResourceProperties"]
lambda_client = boto3.client("lambda")
if request_type == "Create" or request_type == "Update":
return sync_invoke_lambda(lambda_client, properties["FUNCTION_NAME"])
if request_type == "Delete":
return
raise Exception("Invalid request type: %s" % request_type)
def sync_invoke_lambda(client, function_name):
client.invoke(
FunctionName=function_name,
InvocationType="RequestResponse",
)
CFN、SAM での Custom Resource 実装とは異なり、cfn-response を呼び出す必要はない。
(Custom Resource Lambda Function の成功/失敗に応じてProviderがCFNに応答してくれる)
備考
Provider を使用した場合、CFNテンプレートは以下のようになっている。
Provider によって Custom Resource として実行する Lambda Function を呼び出す Lambda Function が作成される。
# Lambda Functionを実行するCustom ResourceにアタッチするRole
sampleCustomResourceSampleStackCustomResourceFunctionRole5814:
Type: AWS::IAM::Role
Properties:
Policies:
- PolicyDocument:
Statement:
- Action: lambda:InvokeFunction
Effect: Allow
Resource:
- Fn::GetAtt:
- sampleCustomResourceSampleStacksample1FunctionB498
- Arn
- Fn::GetAtt:
- sampleCustomResourceSampleStacksample2Function98EB
- Arn
Version: "2012-10-17"
PolicyName: lambdaPolicy
RoleName: sample-CustomResourceSampleStack-CustomResourceFunctionRole
# Custom Resourceとして実行するLambda Function
sampleCustomResourceSampleStackinvokeLambdaFunction10F04414:
Type: AWS::Lambda::Function
Properties:
FunctionName: sample-CustomResourceSampleStack-invokeLambdaFunction
Handler: invoke_lambda.handler
Role:
Fn::GetAtt:
- sampleCustomResourceSampleStackCustomResourceFunctionRole5814
- Arn
Runtime: python3.11
# providerのLambda Function
sampleCustomResourceSampleStackinvokeLambdaProviderframeworkonEvent93E9:
Type: AWS::Lambda::Function
Properties:
Description: AWS CDK resource provider framework - onEvent (sample-CustomResourceSampleStack/sample-CustomResourceSampleStack-invokeLambdaProvider)
Environment:
Variables:
USER_ON_EVENT_FUNCTION_ARN:
Fn::GetAtt:
- sampleCustomResourceSampleStackinvokeLambdaFunction10F04414
- Arn
Handler: framework.onEvent
Runtime: nodejs18.x
# sample1Functionを実行するCustom Resource
sampleCustomResourceSampleStackinvokeSample1CustomResource:
Type: AWS::CloudFormation::CustomResource
Properties:
ServiceToken:
Fn::GetAtt:
- sampleCustomResourceSampleStackinvokeLambdaProviderframeworkonEvent93E9
- Arn
FUNCTION_NAME:
Ref: sampleCustomResourceSampleStacksample1FunctionB498ADB6
# sample2Functionを実行するCustom Resource
sampleCustomResourceSampleStackinvokeSample2CustomResource:
Type: AWS::CloudFormation::CustomResource
Properties:
ServiceToken:
Fn::GetAtt:
- sampleCustomResourceSampleStackinvokeLambdaProviderframeworkonEvent93E9
- Arn
FUNCTION_NAME:
Ref: sampleCustomResourceSampleStacksample2Function98EB7E85