AWS CDK のカスタムリソースの3種類の AwsSdkCall それぞれのトリガータイミングを確認してみた

AWS CDK のカスタムリソースの3種類の AwsSdkCall それぞれのトリガータイミングを確認してみた

Clock Icon2025.01.11

こんにちは、製造ビジネステクノロジー部の若槻です。

AWS CDK ではカスタムリソースAwsCustomResource コンストラクト)を使うことにより、CDK の各種ライフサイクル時に任意の AWS API を呼び出したり Lambda 関数を実行したりできます。

https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.custom_resources.AwsCustomResource.html

カスタムリソースでは処理を呼び出したいタイミングによって下記の 3 種類のトリガー(AwsSdkCall)が使用できます。

onCreate?
Type: AwsSdkCall (optional, default: the call when the resource is updated)

The AWS SDK call to make when the resource is created.

onDelete?
Type: AwsSdkCall (optional, default: no call)

The AWS SDK call to make when the resource is deleted.

onUpdate?
Type: AwsSdkCall (optional, default: no call)

The AWS SDK call to make when the resource is updated.

今回はこの AWS CDK のカスタムリソースの3種類の AwsSdkCall それぞれのトリガータイミングを実際に検証して確認してみました。

最初に結論

検証結果です。ライフサイクルごとに呼び出される AwsSdkCall は次の通りとなりました。

ライフサイクル AwsSdkCall
スタック作成時 onCreate
スタック更新時 onUpdate
スタック削除時 onDelete
カスタムリソース追加時 onCreate
カスタムリソース更新時 onUpdate
カスタムリソース置き換え時 onUpdate
カスタムリソース削除時 onDelete
カスタムリソースに変更がない時 onUpdate

確認してみた

CDK コード

検証に使用したベースとなる CDK コードは下記の通りです。コンテキストの値と AwsSdkCall のトリガータイミングによって SSM パラメーターに値を保存するカスタムリソースを作成しています。それにより、各ライフサイクル時に保存される値を確認します。

lib/main-stack.ts
import * as cdk 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';

interface MainStackProps {
  operation:
    | 'stack-created' // スタック作成時
    | 'stack-updated' // スタック更新時
    | 'stack-deleted' // スタック削除時
    | 'stack-rolled-back' // スタックのロールバック発生時
    | 'cr-added' // カスタムリソース追加時
    | 'cr-updated' // カスタムリソース更新時
    | 'cr-replaced' // カスタムリソース置き換え時
    | 'cr-deleted' // カスタムリソース削除時
    | 'cr-no-change'; // カスタムリソースに変更がない時
}

export class MainStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props: MainStackProps) {
    super(scope, id);

    const { operation } = props;
    const parameterName = 'MyParameter';

    // SSM パラメーター "MyParameter" に "${operation}_${lifecycleMethod}" という値を保存するカスタムリソース
    new cr.AwsCustomResource(this, 'MyCustomResource', {
      onCreate: {
        service: 'SSM',
        action: 'putParameter',
        parameters: {
          Name: parameterName,
          Value: `${operation}_onCreate`,
          Type: 'String',
          Overwrite: true,
        },
        physicalResourceId: cr.PhysicalResourceId.of('my-unique-id'),
      },
      onUpdate: {
        service: 'SSM',
        action: 'putParameter',
        parameters: {
          Name: parameterName,
          Value: `${operation}_onUpdate`,
          Type: 'String',
          Overwrite: true,
        },
        physicalResourceId: cr.PhysicalResourceId.of('my-unique-id'),
      },
      onDelete: {
        service: 'SSM',
        action: 'putParameter',
        parameters: {
          Name: parameterName,
          Value: `${operation}_onDelete`,
          Type: 'String',
          Overwrite: true,
        },
        physicalResourceId: cr.PhysicalResourceId.of('my-unique-id'),
      },
      policy: cr.AwsCustomResourcePolicy.fromStatements([
        new iam.PolicyStatement({
          actions: ['ssm:PutParameter'],
          resources: ['*'],
        }),
      ]),
    });
  }
}

SSM パラメーターは下記の通り別途作成済みです。

$ aws ssm get-parameter --name MyParameter --query 'Parameter.Value' --output text
First Parameter

以下、各ライフサイクルごとの検証内容を記載していますが、検証しやすさのため最初の検証結果のテーブルの順とはなっていない点にはご留意ください。

スタック作成時

初回の CDK スタック作成のデプロイの場合。

cdk deploy -c operation=stack-created

onCreate が呼び出されました。

$ aws ssm get-parameter --name MyParameter --query 'Parameter.Value' --output text
stack-created_onCreate

カスタムリソースに変更がない時

カスタムリソース含めスタック全体のコードを変更せずにデプロイした場合。

cdk deploy -c operation=cr-no-change

onUpdate が呼び出されました。

$ aws ssm get-parameter --name MyParameter --query 'Parameter.Value' --output text
cr-no-change_onUpdate

カスタムリソース更新時

カスタムリソースのパラメーターを更新してデプロイした場合。

$ git diff lib/main-stack.ts
diff --git a/lib/main-stack.ts b/lib/main-stack.ts
index 3c411b1..01d2b37 100644
--- a/lib/main-stack.ts
+++ b/lib/main-stack.ts
@@ -44,6 +44,7 @@ export class MainStack extends cdk.Stack {
           Value: `${operation}_onUpdate`,
           Type: 'String',
           Overwrite: true,
+          hoge: 'fuga',
         },
         physicalResourceId: cr.PhysicalResourceId.of('my-unique-id'),
       },
cdk deploy -c operation=cr-updated

カスタムリソースの更新が走るのでデプロイ完了までに作成時と同じくらい時間がかかります。

onUpdate が呼び出されました。

$ aws ssm get-parameter --name MyParameter --query 'Parameter.Value' --output text
cr-updated_onUpdate

スタック更新時

同じスタック内に適当なリソースの実装を追加した場合。

$ git diff
diff --git a/lib/main-stack.ts b/lib/main-stack.ts
index 01d2b37..f03a663 100644
--- a/lib/main-stack.ts
+++ b/lib/main-stack.ts
@@ -2,6 +2,7 @@ import * as cdk 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 s3 from 'aws-cdk-lib/aws-s3';

 interface MainStackProps {
   operation:
@@ -78,5 +79,8 @@ export class MainStack extends cdk.Stack {
         })
       );
     }
+
+    // 適当なリソースの実装を追加
+    new s3.Bucket(this, 'MyBucket');
   }
 }
cdk deploy -c operation=stack-updated

onUpdate が呼び出されました。

$ aws ssm get-parameter --name MyParameter --query 'Parameter.Value' --output text
stack-updated_onUpdate

カスタムリソース置き換え時

カスタムリソースの物理 ID を変更してデプロイした場合。

$ git diff
diff --git a/lib/main-stack.ts b/lib/main-stack.ts
index d30a31e..67b6c8d 100644
--- a/lib/main-stack.ts
+++ b/lib/main-stack.ts
@@ -34,7 +34,7 @@ export class MainStack extends cdk.Stack {
           Type: 'String',
           Overwrite: true,
         },
-        physicalResourceId: cr.PhysicalResourceId.of('my-unique-id'),
+        physicalResourceId: cr.PhysicalResourceId.of('my-unique-id-2'),
       },
       onUpdate: {
         service: 'SSM',
cdk deploy -c operation=cr-replaced

onUpdate が呼び出されました。

$ aws ssm get-parameter --name MyParameter --query 'Parameter.Value' --output text
cr-replaced_onUpdate

カスタムリソース削除時

カスタムリソースを削除してデプロイした場合。

$ git diff
diff --git a/lib/main-stack.ts b/lib/main-stack.ts
index 2515848..869aefe 100644
--- a/lib/main-stack.ts
+++ b/lib/main-stack.ts
@@ -22,48 +22,5 @@ export class MainStack extends cdk.Stack {

     const { operation } = props;
     const parameterName = 'MyParameter';
-
-    // SSM パラメーター "MyParameter" に "${operation}_${lifecycleMethod}" という値を保存するカスタムリソース
-    new cr.AwsCustomResource(this, 'MyCustomResource', {
-      onCreate: {
-        service: 'SSM',
-        action: 'putParameter',
-        parameters: {
-          Name: parameterName,
-          Value: `${operation}_onCreate`,
-          Type: 'String',
-          Overwrite: true,
-        },
-        physicalResourceId: cr.PhysicalResourceId.of('my-unique-id'),
-      },
-      onUpdate: {
-        service: 'SSM',
-        action: 'putParameter',
-        parameters: {
-          Name: parameterName,
-          Value: `${operation}_onUpdate`,
-          Type: 'String',
-          Overwrite: true,
-        },
-        physicalResourceId: cr.PhysicalResourceId.of('my-unique-id'),
-      },
-      onDelete: {
-        service: 'SSM',
-        action: 'putParameter',
-        parameters: {
-          Name: parameterName,
-          Value: `${operation}_onDelete`,
-          Type: 'String',
-          Overwrite: true,
-        },
-        physicalResourceId: cr.PhysicalResourceId.of('my-unique-id'),
-      },
-      policy: cr.AwsCustomResourcePolicy.fromStatements([
-        new iam.PolicyStatement({
-          actions: ['ssm:PutParameter'],
-          resources: ['*'],
-        }),
-      ]),
-    });
   }
 }
cdk deploy -c operation=cr-deleted

onDelete が呼び出されました。

$ aws ssm get-parameter --name MyParameter --query 'Parameter.Value' --output text
cr-replaced_onDelete

カスタムリソース追加時

既存のスタックにカスタムリソースを追加してデプロイした場合。

$ git diff
diff --git a/lib/main-stack.ts b/lib/main-stack.ts
index 869aefe..2515848 100644
--- a/lib/main-stack.ts
+++ b/lib/main-stack.ts
@@ -22,5 +22,48 @@ export class MainStack extends cdk.Stack {

     const { operation } = props;
     const parameterName = 'MyParameter';
+
+    // SSM パラメーター "MyParameter" に "${operation}_${lifecycleMethod}" という値を保存するカスタムリソース
+    new cr.AwsCustomResource(this, 'MyCustomResource', {
+      onCreate: {
+        service: 'SSM',
+        action: 'putParameter',
+        parameters: {
+          Name: parameterName,
+          Value: `${operation}_onCreate`,
+          Type: 'String',
+          Overwrite: true,
+        },
+        physicalResourceId: cr.PhysicalResourceId.of('my-unique-id'),
+      },
+      onUpdate: {
+        service: 'SSM',
+        action: 'putParameter',
+        parameters: {
+          Name: parameterName,
+          Value: `${operation}_onUpdate`,
+          Type: 'String',
+          Overwrite: true,
+        },
+        physicalResourceId: cr.PhysicalResourceId.of('my-unique-id'),
+      },
+      onDelete: {
+        service: 'SSM',
+        action: 'putParameter',
+        parameters: {
+          Name: parameterName,
+          Value: `${operation}_onDelete`,
+          Type: 'String',
+          Overwrite: true,
+        },
+        physicalResourceId: cr.PhysicalResourceId.of('my-unique-id'),
+      },
+      policy: cr.AwsCustomResourcePolicy.fromStatements([
+        new iam.PolicyStatement({
+          actions: ['ssm:PutParameter'],
+          resources: ['*'],
+        }),
+      ]),
+    });
   }
 }
cdk deploy -c operation=cr-added

onCreate が呼び出されました。

$ aws ssm get-parameter --name MyParameter --query 'Parameter.Value' --output text
cr-added_onCreate

スタックの削除

スタックを削除した場合。

cdk destroy -c operation=stack-deleted

onDelete が呼び出されました。

$ aws ssm get-parameter --name MyParameter --query 'Parameter.Value' --output text
stack-created_onDelete

ちなみに operation が stack-created となっているのは、スタック作成時に使用されたコンテキスト値がそのまま削除時にも使用されるという挙動となるためのようです。この仕様で困る場面は限られそうですが、削除時にコンテキストに依存する処理がある場合は注意が必要そうです。

デプロイロールバック時は?

最後に番外編として次のようにデプロイが失敗してロールバックとなる実装を追加してデプロイしてみます。

$ git diff
diff --git a/lib/main-stack.ts b/lib/main-stack.ts
index 3c411b1..a569b55 100644
--- a/lib/main-stack.ts
+++ b/lib/main-stack.ts
@@ -77,5 +77,15 @@ export class MainStack extends cdk.Stack {
         })
       );
     }
+
+    // ロールバックを検証するために存在しないマネージドポリシーを指定したIAMロールを作成する
+    new iam.Role(this, 'MyRole', {
+      assumedBy: new iam.AccountRootPrincipal(),
+      managedPolicies: [
+        {
+          managedPolicyArn: 'arn:aws:iam::aws:policy/NotExistManagedPolicy',
+        },
+      ],
+    });
   }
 }

スタックの更新を行い、デプロイを失敗させてロールバックを発生させます。

cdk deploy -c operation=stack-rolled-back

Main: start: Building 7d699f15f2b169e8182fcb91df5a0c3a8443246e469795599df4071cb5e03766:current_account-current_region
Main: success: Built 7d699f15f2b169e8182fcb91df5a0c3a8443246e469795599df4071cb5e03766:current_account-current_region
Main: start: Publishing 7d699f15f2b169e8182fcb91df5a0c3a8443246e469795599df4071cb5e03766:current_account-current_region
Main: success: Published 7d699f15f2b169e8182fcb91df5a0c3a8443246e469795599df4071cb5e03766:current_account-current_region
Main: deploying... [1/1]
Main: updating stack...
Main |   0 | 4:08:01 PM | UPDATE_IN_PROGRESS   | AWS::CloudFormation::Stack | Main User Initiated
Main |   0 | 4:08:04 PM | CREATE_IN_PROGRESS   | AWS::IAM::Role        | MyRole (MyRoleF48FFE04)
Main |   0 | 4:08:04 PM | UPDATE_IN_PROGRESS   | Custom::AWS           | MyCustomResource/Resource/Default (MyCustomResource776A0129)
Main |   0 | 4:08:06 PM | CREATE_IN_PROGRESS   | AWS::IAM::Role        | MyRole (MyRoleF48FFE04) Policy arn:aws:iam::aws:policy/NotExistManagedPolicy does not exist or is not attachable. (Service: Iam, Status Code: 404, Request ID: 7354189e-1e48-4312-a74e-81c9f680404d)
Main |   0 | 4:08:06 PM | CREATE_IN_PROGRESS   | AWS::IAM::Role        | MyRole (MyRoleF48FFE04) Resource creation Initiated
Main |   0 | 4:08:06 PM | CREATE_FAILED        | AWS::IAM::Role        | MyRole (MyRoleF48FFE04) Resource handler returned message: "Policy arn:aws:iam::aws:policy/NotExistManagedPolicy does not exist or is not attachable. (Service: Iam, Status Code: 404, Request ID: 7354189e-1e48-4312-a74e-81c9f680404d)" (RequestToken: 1a5401de-918c-d411-3954-47abe67e21b7, HandlerErrorCode: NotFound)
Main |   0 | 4:08:06 PM | UPDATE_FAILED        | Custom::AWS           | MyCustomResource/Resource/Default (MyCustomResource776A0129) Resource update cancelled
Main |   0 | 4:08:07 PM | UPDATE_ROLLBACK_IN_P | AWS::CloudFormation::Stack | Main The following resource(s) failed to create: [MyRoleF48FFE04]. The following resource(s) failed to update: [MyCustomResource776A0129].
Main |   0 | 4:08:09 PM | UPDATE_IN_PROGRESS   | Custom::AWS           | MyCustomResource/Resource/Default (MyCustomResource776A0129)
Main |   1 | 4:08:11 PM | UPDATE_COMPLETE      | Custom::AWS           | MyCustomResource/Resource/Default (MyCustomResource776A0129)
Main |   2 | 4:08:11 PM | UPDATE_ROLLBACK_COMP | AWS::CloudFormation::Stack | Main
Main |   2 | 4:08:13 PM | DELETE_IN_PROGRESS   | AWS::IAM::Role        | MyRole (MyRoleF48FFE04)
Main |   3 | 4:08:22 PM | DELETE_COMPLETE      | AWS::IAM::Role        | MyRole (MyRoleF48FFE04)
Main |   4 | 4:08:23 PM | UPDATE_ROLLBACK_COMP | AWS::CloudFormation::Stack | Main

Failed resources:
Main | 4:08:06 PM | CREATE_FAILED        | AWS::IAM::Role        | MyRole (MyRoleF48FFE04) Resource handler returned message: "Policy arn:aws:iam::aws:policy/NotExistManagedPolicy does not exist or is not attachable. (Service: Iam, Status Code: 404, Request ID: 7354189e-1e48-4312-a74e-81c9f680404d)" (RequestToken: 1a5401de-918c-d411-3954-47abe67e21b7, HandlerErrorCode: NotFound)
❌  Main failed: Error: The stack named Main failed to deploy: UPDATE_ROLLBACK_COMPLETE: Resource handler returned message: "Policy arn:aws:iam::aws:policy/NotExistManagedPolicy does not exist or is not attachable. (Service: Iam, Status Code: 404, Request ID: 7354189e-1e48-4312-a74e-81c9f680404d)" (RequestToken: 1a5401de-918c-d411-3954-47abe67e21b7, HandlerErrorCode: NotFound)

するとパラメーターの値は更新されておらず、カスタムリソースは呼び出されていないようでした。

$ aws ssm get-parameter --name MyParameter --query 'Parameter.Value' --output text
# 変更なし

本部分の挙動確認はロールバックとなったリソースがカスタムリソースに依存している場合など、いくつかのパターンの検証が必要となりそうなのでまたの機会に行いたいと思います。

おわりに

AWS CDK のカスタムリソースの3種類の AwsSdkCall それぞれのトリガータイミングを実際に検証して確認してみました。

結論として「スタック作成時」と「カスタムリソース作成時」は onCreate、「スタック削除時」と「カスタムリソース削除時」は onDelete、それ以外は onUpdate が呼び出されるというそれなりに直感的な挙動となること確認できました。

参考

https://dev.classmethod.jp/articles/create-custom-resources-with-aws-cdk-without-using-lambda-functions/

https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.custom_resources-readme.html#custom-resource-config

以上

Share this article

facebook logohatena logotwitter logo

© Classmethod, Inc. All rights reserved.