12
1

AWS CDKで使える便利なAwsCustomResourceの紹介

Last updated at Posted at 2023-12-20

この記事は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のputObjectdeleteObjectをカスタムリソースから実行する例を想定します。
コードの全体は以下です。

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を利用して実行されています。

またonCreateonDeleteでリソースの作成・削除のタイミングで実行するAPIとパラメータの指定を行えます。
ここでは作成時にputObject、削除時にdeleteObjectをそれぞれ実行するように設定を行っています。

面白いのはAPIを実行するLambda関数の権限設定です。
policyにはLambdaの実行ロールに指定するIAM Policyの設定をしますが、今回はpolicyの設定にAwsCustomResourcePolicy.fromSdkCallsメソッドを利用しました。
このメソッドはserviceactionから必要な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などに指定するserviceactionはいくつかの指定方法が可能です。
こちらのドキュメントによくまとまっているのでぜひご覧ください。
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に追加されます。

またAwsCustomResourceiam.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ライフを!

12
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
12
1