この記事はJapan AWS Jr. Champions Advent Calendar 2023の20日目の記事です。
AWS CDKの便利なconstructであるAwsCustomResource
について色々と紹介していきます。
AwsCustomResource
とは?
AwsCustomResource
とはAWSのAPIを実行するCloudFormationのカスタムリソースを作成してくれるconstructです。
CloudFormationではカスタムリソースでAPIを実行しようとするとLambda関数を構築する必要がありますが、このconstructを利用すると裏側で指定したAPIを実行するLambda関数を自動作成してくれます。
https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.custom_resources.AwsCustomResource.html
主な用途としてはLambda backedなカスタムリソースと全く同様で、CloudFormationで対応していないリソースの作成・更新・削除になります。
他にも同様にAPIを実行した返り値をCloudFormationテンプレート上で利用するなど様々な場面で利用出来ます。
どのように利用するか
前提
利用するCDKのバージョンは以下です。
$ cdk --version
2.115.0 (build 58027ee)
構築
今回は分かりやすくAWSのAPIの中でも利用頻度の高そうなS3のputObject
とdeleteObject
をカスタムリソースから実行する例を想定します。
コードの全体は以下です。
import * as cdk from 'aws-cdk-lib';
import { Construct } from 'constructs';
import { Bucket } from 'aws-cdk-lib/aws-s3';
import { AwsCustomResource, AwsCustomResourcePolicy, PhysicalResourceId } from 'aws-cdk-lib/custom-resources';
export class TestCustomResourceStack extends cdk.Stack {
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
const bucket = new Bucket(this, 'MyBucket', {});
new AwsCustomResource(this, 'MyCustomResource', {
onCreate: {
service: 'S3',
action: 'putObject',
parameters: {
Bucket: bucket.bucketName,
Key: 'my-file.txt',
Body: 'Hello, world!',
},
physicalResourceId: PhysicalResourceId.of('my-file'),
},
onDelete: {
service: 'S3',
action: 'deleteObject',
parameters: {
Bucket: bucket.bucketName,
Key: 'my-file.txt',
},
},
policy: AwsCustomResourcePolicy.fromSdkCalls({
resources: [`${bucket.bucketArn}/*`],
}),
});
}
}
現在LambdaはデフォルトでNode.js v18のランタイムかつAWS SDK for JavaScript v3を利用して実行されています。
またonCreate
やonDelete
でリソースの作成・削除のタイミングで実行するAPIとパラメータの指定を行えます。
ここでは作成時にputObject
、削除時にdeleteObject
をそれぞれ実行するように設定を行っています。
面白いのはAPIを実行するLambda関数の権限設定です。
policy
にはLambdaの実行ロールに指定するIAM Policyの設定をしますが、今回はpolicy
の設定にAwsCustomResourcePolicy.fromSdkCalls
メソッドを利用しました。
このメソッドはservice
とaction
から必要なPolicyを自動で設定します。
このケースだとpubObject
APIの実行に必要なのはs3:PutObject
という対応したactionになるので自動で設定が可能です。
ただ、API名と必要なPolicyのactionが対応していないケース(listObjectV2
など)もあるのでその場合は、fromStatements
メソッドで必要なStatementを渡してあげることが出来ます。
デプロイをして動かしてみる
実際にデプロイをしたのちに、Bucket内のオブジェクトを取得してみます。
$ cdk deploy --all
$ aws s3api list-objects-v2 --bucket {bucketName}
{
"Contents": [
{
"Key": "my-file.txt",
"LastModified": "2023-12-16T11:10:22+00:00",
"ETag": "\"6cd3556deb0da54bca060b4c39479839\"",
"Size": 13,
"StorageClass": "STANDARD"
}
]
}
my-file.txt
が取得出来ているのでputObjectが正しく実行出来ていることが分かります。
次にStackの削除を行い、オブジェクトの削除が出来ているか確認します。
$ cdk destroy --all
$ aws s3api list-objects-v2 --bucket {bucketName}
CLIでオブジェクトのリストを取得しようとしても何も出力されないので無事削除されたことが確認出来ました。
このようにAwsCustomResource
を利用すると自分でLambda関数を記述することなくAWS APIの実行が可能です!
その他の注意事項や利用方法
onCreateとonUpdate
onCreate
を省略し、かつonUpdate
を指定した場合、onCreate
にはonUpdate
で指定したプロパティが設定されます。
UpdateするにはCreateが必要なので自然な挙動ではあるのですが、少し分かりづらいので注意が必要です。
serviceとactionの指定方法
onCreate
などに指定するservice
とaction
はいくつかの指定方法が可能です。
こちらのドキュメントによくまとまっているのでぜひご覧ください。
https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.custom_resources.AwsSdkCall.html#service
https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.custom_resources.AwsSdkCall.html#action
LambdaはデフォルトでSDK v3を利用しますが、ドキュメントに記載のv2の方法でも指定が可能です。
内部でservice名とaction名の正規化を行なっているためです。
SingletonFunction
AwsCustomResource
は内部的にLambdaの作成時にSingletonFunction
を利用しるので、Stack内で複数のAwsCustomResource
を作成してもLambda関数は1つのみ作成されます。
後述するrole
設定などはこの単一のLambdaにアタッチされる形です。
権限設定
role
プロパティにIAM Roleを指定することでLambdaの実行ロールを設定することが可能です。
role
を指定した場合、前述したAwsCustomResourcePolicy.fromSdkCalls
などで指定したポリシーはこちらのRoleに追加されます。
またAwsCustomResource
はiam.IGrantable
interfaceを実装しているため、grantXXX
メソッドを利用してLambdaの実行ロールに追加ポリシーの追加が出来ます。
前述したlistObjectV2
などの利用時でAwsCustomResourcePolicy.fromSdkCalls
が使えないケースではこのような権限設定がよりCDKらしい書き方かもしれません。
export class TestCustomResourceStack extends cdk.Stack {
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
const bucket = new Bucket(this, 'MyBucket', {});
const customResource = new AwsCustomResource(this, 'MyCustomResource', {
onCreate: {
service: 'S3',
action: 'listObjectsV2',
parameters: {
Bucket: bucket.bucketName,
},
physicalResourceId: PhysicalResourceId.of('my-file'),
},
policy: AwsCustomResourcePolicy.fromSdkCalls({
resources: [`${bucket.bucketArn}/*`],
}),
});
bucket.grantRead(customResource);
}
}
カスタムリソースから返却された値の利用
カスタムリソースが返却された値をCDK内で利用することが出来ます。
例えば既存パラメータストアから値を取得したいとします。
GetParameter
APIは以下のような構造でレスポンスを返します。
なのでParameter.Value
と辿っていけば目的の値が取得できそうです。
{
Parameter: {
Name: "STRING_VALUE",
Type: "String" || "StringList" || "SecureString",
Value: "STRING_VALUE",
Version: Number("long"),
Selector: "STRING_VALUE",
SourceResult: "STRING_VALUE",
LastModifiedDate: new Date("TIMESTAMP"),
ARN: "STRING_VALUE",
DataType: "STRING_VALUE",
},
};
このCDKコードではgetResponseField
メソッドを利用して、パラメーターストアから取得した値を参照しています。
引数には欲しいデータへのパスを渡します(今回はParameter.Value
)。
import * as cdk from 'aws-cdk-lib';
import { Construct } from 'constructs';
import { AwsCustomResource, AwsCustomResourcePolicy, PhysicalResourceId } from 'aws-cdk-lib/custom-resources';
export class TestCustomResourceStack extends cdk.Stack {
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
// https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.custom_resources-readme.html#custom-resource-examples
const getParameter = new AwsCustomResource(this, 'GetParameter', {
onUpdate: {
service: 'SSM',
action: 'GetParameter',
parameters: {
Name: '/test/my-param',
},
physicalResourceId: PhysicalResourceId.of(Date.now().toString()),
},
policy: AwsCustomResourcePolicy.fromSdkCalls({
resources: AwsCustomResourcePolicy.ANY_RESOURCE,
}),
});
const someValue = getParameter.getResponseField('Parameter.Value'), // 値の取得
}
}
終わりに
この記事では簡単にですがAwsCustomResource
の紹介をさせていただきました。
まだまだ紹介しきれていない機能がたくさんあるのでぜひドキュメントをご覧ください!
https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.custom_resources-readme.html#custom-resources-for-aws-apis
AwsCustomResource
を活用してよりよいCustomResourceライフを!