[アップデート] CloudFormationのカスタムリソースのタイムアウト値を設定できるようになりました

カスタムリソースを使用する時の必須設定レベル
2024.06.19

カスタムリソース作成処理が失敗した時の虚無な時間を無くしたい

こんにちは、のんピ(@non____97)です。

皆さんはCloudFormationのカスタムリソースの作成が失敗した時の虚無な時間を無くしたいしたいなと思ったことはありますか? 私はあります。

CloudFormationカスタムリソースのタイムアウトのデフォルト値は1時間です。もし、カスタムリソースを作成するLambda関数が正常に処理できなかった場合は1時間待つことになります。加えて、スタックを正常に作成できなかった場合に発生するロールバックでも1時間待つことになるため、合計2時間虚無な時間を過ごすことになります。これは悲しいですね。

今回、アップデートによりCloudFormationのカスタムリソースのタイムアウト値を設定できるようになりました。

AWS公式ドキュメントを確認すると、タイムアウト値は1秒単位で1秒から3,600秒までで選択できるようです。

Properties

ServiceTimeout

The maximum time, in seconds, that can elapse before a custom resource operation times out.

The value must be an integer from 1 to 3600. The default value is 3600 seconds (1 hour).

Required: No

Type: String

Update requires: No interruption

AWS::CloudFormation::CustomResource - AWS CloudFormation

これはありがたいですね。リソース作成に時間がかからないものであれば1分程度で設定する形でも良さそうです。また、同期実行するLambda関数であればLambda関数の実行タイムアウトの上限である15分で設定するのも良さそうです。

実際に試してみました。

やってみた

AWS CDKのコードの説明

今回はAWS CDKを使ってカスタムリソースを作成します。

作成するカスタムリソースはIMDSのデフォルト設定を変更するものです。具体的にはカスタムリソースの作成や更新時にはIMDSでセッショントークンを要求する = IMDSv2のみ許可し、削除時には未設定の状態にします。

AwsCustomResourceを使用した場合の同様の処理は以下記事でも紹介しています。

今回はあえてカスタムリソースのレスポンスを返さなかった場合、設定したタイムアウト値でタイムアウトをされることを確認したかったので、Lambda関数をプロバイダーとしてカスタムリソースを作成します。

AWS CDKにおけるカスタムリソースの作成方法は以下があります。

Provider Compute Type Error Handling Submit to CloudFormation Max Timeout Language Footprint
sns.Topic Self-managed Manual Manual Unlimited Any Depends
lambda.Function AWS Lambda Manual Manual 15min Any Small
core.CustomResourceProvider AWS Lambda Auto Auto 15min Node.js Small
custom-resources.Provider AWS Lambda Auto Auto Unlimited Async Any Large

抜粋 : Custom Resource Providers

なお、AWS CDKにおけるカスタムリソース作成時の推奨はCustom Resource Provider Framework(custom-resources.Provider)です。Custom Resource Provider Frameworkの使用方法は以下記事をご覧ください。

Lambda関数で実行するコードは以下のとおりです。

./lib/src/lambda/imdsv2/index.py

import boto3
from botocore.exceptions import ClientError
import cfnresponse

ec2_client = boto3.client("ec2")


def lambda_handler(event, context):
    print("Received event:", event)
    print(boto3.__version__)

    try:
        request_type = event["RequestType"]
        if request_type == "Create":
            on_create()
        elif request_type == "Update":
            on_update()
        elif request_type == "Delete":
            on_delete()
        else:
            raise ValueError("Invalid request type")

        cfnresponse.send(event, context, cfnresponse.SUCCESS, {"Response": "Success"})
    except Exception as e:
        print("Error:", e)
        cfnresponse.send(event, context, cfnresponse.FAILED, {})


def on_create() -> None:
    modify_instance_metadata_defaults(HttpTokens="required")


def on_update() -> None:
    modify_instance_metadata_defaults(HttpTokens="required")


def on_delete() -> None:
    modify_instance_metadata_defaults(HttpTokens="no-preference")


def modify_instance_metadata_defaults(HttpTokens: str) -> None:
    try:
        response = ec2_client.modify_instance_metadata_defaults(HttpTokens=HttpTokens)
        print(response)
    except ClientError as e:
        print(e)

使用しているmodify_instance_metadata_defaultsですが、LambdaのPython 3.12ランタイムにインストールされているboto3のバージョンは1.34.42に含まれていないようでした。

そのため、最新のboto3をバンドルしたLambda Layerを作成し、Lambda関数にアタッチしています。boto3はデプロイ前に以下のように指定したディレクトリにインストールしています。

$ mkdir -p lib/src/lambda/python
$ pip3 install boto3 -t ./lib/src/lambda/layer/python
Collecting boto3
  Downloading boto3-1.34.129-py3-none-any.whl.metadata (6.6 kB)
Collecting botocore<1.35.0,>=1.34.129 (from boto3)
  Downloading botocore-1.34.129-py3-none-any.whl.metadata (5.7 kB)
Collecting jmespath<2.0.0,>=0.7.1 (from boto3)
  Downloading jmespath-1.0.1-py3-none-any.whl.metadata (7.6 kB)
Collecting s3transfer<0.11.0,>=0.10.0 (from boto3)
  Downloading s3transfer-0.10.1-py3-none-any.whl.metadata (1.7 kB)
Collecting python-dateutil<3.0.0,>=2.1 (from botocore<1.35.0,>=1.34.129->boto3)
  Downloading python_dateutil-2.9.0.post0-py2.py3-none-any.whl.metadata (8.4 kB)
Collecting urllib3!=2.2.0,<3,>=1.25.4 (from botocore<1.35.0,>=1.34.129->boto3)
  Downloading urllib3-2.2.2-py3-none-any.whl.metadata (6.4 kB)
Collecting six>=1.5 (from python-dateutil<3.0.0,>=2.1->botocore<1.35.0,>=1.34.129->boto3)
  Downloading six-1.16.0-py2.py3-none-any.whl.metadata (1.8 kB)
Downloading boto3-1.34.129-py3-none-any.whl (139 kB)
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 139.2/139.2 kB 2.0 MB/s eta 0:00:00
Downloading botocore-1.34.129-py3-none-any.whl (12.3 MB)
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 12.3/12.3 MB 13.1 MB/s eta 0:00:00
Downloading jmespath-1.0.1-py3-none-any.whl (20 kB)
Downloading s3transfer-0.10.1-py3-none-any.whl (82 kB)
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 82.2/82.2 kB 5.9 MB/s eta 0:00:00
Downloading python_dateutil-2.9.0.post0-py2.py3-none-any.whl (229 kB)
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 229.9/229.9 kB 7.2 MB/s eta 0:00:00
Downloading urllib3-2.2.2-py3-none-any.whl (121 kB)
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 121.4/121.4 kB 15.7 MB/s eta 0:00:00
Downloading six-1.16.0-py2.py3-none-any.whl (11 kB)
Installing collected packages: urllib3, six, jmespath, python-dateutil, botocore, s3transfer, boto3
Successfully installed boto3-1.34.129 botocore-1.34.129 jmespath-1.0.1 python-dateutil-2.9.0.post0 s3transfer-0.10.1 six-1.16.0 urllib3-2.2.2

また、本題のカスタムリソースのタイムアウト値は10秒に設定しました。

スタックを作成するコードは以下のとおりです。

import * as cdk from "aws-cdk-lib";
import { Construct } from "constructs";
import * as path from "path";
import * as fs from "fs";

export class CustomResourceStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    // Lambda Function
    const imdsv2Lambda = new cdk.aws_lambda.SingletonFunction(
      this,
      "Imdsv2Lambda",
      {
        code: new cdk.aws_lambda.InlineCode(
          fs.readFileSync(
            path.join(__dirname, "./src/lambda/imdsv2/index.py"),
            { encoding: "utf-8" }
          )
        ),
        uuid: "975fff39-68dd-41c1-af55-3e7af5ec9ee6",
        handler: "index.lambda_handler",
        runtime: cdk.aws_lambda.Runtime.PYTHON_3_12,
        architecture: cdk.aws_lambda.Architecture.ARM_64,
        loggingFormat: cdk.aws_lambda.LoggingFormat.JSON,
      }
    );

    imdsv2Lambda.role?.addManagedPolicy(
      new cdk.aws_iam.ManagedPolicy(
        this,
        "ModifyInstanceMetadataDefaultsPolicy",
        {
          statements: [
            new cdk.aws_iam.PolicyStatement({
              actions: ["ec2:ModifyInstanceMetadataDefaults"],
              resources: ["*"],
            }),
          ],
        }
      )
    );

    // Lambda Layer
    const boto3Layer = new cdk.aws_lambda.LayerVersion(this, "Boto3Layer", {
      code: cdk.aws_lambda.AssetCode.fromAsset(
        path.join(__dirname, "./src/lambda/layer")
      ),
      compatibleRuntimes: [cdk.aws_lambda.Runtime.PYTHON_3_12],
      compatibleArchitectures: [cdk.aws_lambda.Architecture.ARM_64],
    });
    imdsv2Lambda.addLayers(boto3Layer);

    // Custom Resource
    const customResource = new cdk.CustomResource(this, "CustomResource", {
      serviceToken: imdsv2Lambda.functionArn,
      properties: { ServiceTimeout: 10 },
    });
  }
}

スタックのデプロイ

スタックをデプロイする前にIMDSデフォルトの設定値を確認しておきます。全てNo preferenceです。

1.デプロイ前のIMDSデフォルト

それでは、npx cdk deployでスタックをデプロイします。

デプロイ後、IMDSデフォルトの設定値を確認するとMetadata versionがV2 only (token required)に変わりました。カスタムリソースで意図したとおりに設定されていますね。

2.V2 only (token required)になったことを確認

スタックのリソース一覧は以下のようになっています。2024/06/19/[$LATEST]feeda2f59f4540c5aed490b0c8c69bafという物理名のカスタムリソースが作成されていますね。

3_リソース一覧

カスタムリソースを作成するLambda関数が出力したログは以下のとおりです。受け取ったイベントに'ServiceTimeout': '10'が含まれていますね。

{
    "time": "2024-06-19T08:45:24.255Z",
    "type": "platform.initStart",
    "record": {
        "initializationType": "on-demand",
        "phase": "init",
        "runtimeVersion": "python:3.12.v28",
        "runtimeVersionArn": "arn:aws:lambda:us-east-1::runtime:68a46227c0fb5718a1bbf0378fdf0e66719fd3fac7a8aea038370a0343198447",
        "functionName": "CustomResourceStack-SingletonLambda975fff3968dd41c-L5DiFbl4LYio",
        "functionVersion": "$LATEST"
    }
}

{
    "timestamp": "2024-06-19T08:45:24Z",
    "level": "INFO",
    "message": "Found credentials in environment variables.",
    "logger": "botocore.credentials",
    "requestId": ""
}

{
    "time": "2024-06-19T08:45:25.096Z",
    "type": "platform.start",
    "record": {
        "requestId": "7e17d3e5-f1aa-4983-a734-379b5da774ad",
        "version": "$LATEST"
    }
}

Received event: {'RequestType': 'Create', 'ServiceToken': 'arn:aws:lambda:us-east-1:<AWSアカウントID>:function:CustomResourceStack-SingletonLambda975fff3968dd41c-L5DiFbl4LYio', 'ServiceTimeout': '10', 'ResponseURL': 'https://cloudformation-custom-resource-response-useast1.s3.amazonaws.com/arn%3Aaws%3Acloudformation%3Aus-east-1%3A<AWSアカウントID>%3Astack/CustomResourceStack/220b3f70-2e18-11ef-bdf1-0e5a89a0c50b%7CCustomResource%7C97a94ddd-e81a-4ee2-ac54-1c3da3408e09 'StackId': 'arn:aws:cloudformation:us-east-1:<AWSアカウントID>:stack/CustomResourceStack/220b3f70-2e18-11ef-bdf1-0e5a89a0c50b', 'RequestId': '97a94ddd-e81a-4ee2-ac54-1c3da3408e09', 'LogicalResourceId': 'CustomResource', 'ResourceType': 'AWS::CloudFormation::CustomResource', 'ResourceProperties': {'ServiceToken': 'arn:aws:lambda:us-east-1:<AWSアカウントID>:function:CustomResourceStack-SingletonLambda975fff3968dd41c-L5DiFbl4LYio', 'ServiceTimeout': '10'}}

1.34.129

{'Return': True, 'ResponseMetadata': {'RequestId': '123b29ab-8fc4-4b57-91ed-2b59e19bcbce', 'HTTPStatusCode': 200, 'HTTPHeaders': {'x-amzn-requestid': '123b29ab-8fc4-4b57-91ed-2b59e19bcbce', 'cache-control': 'no-cache, no-store', 'strict-transport-security': 'max-age=31536000; includeSubDomains', 'vary': 'accept-encoding', 'content-type': 'text/xml;charset=UTF-8', 'transfer-encoding': 'chunked', 'date': 'Wed, 19 Jun 2024 08:45:25 GMT', 'server': 'AmazonEC2'}, 'RetryAttempts': 0}}

https://cloudformation-custom-resource-response-useast1.s3.amazonaws.com/arn%3Aaws%3Acloudformation%3Aus-east-1%3A<AWSアカウントID>%3Astack/CustomResourceStack/220b3f70-2e18-11ef-bdf1-0e5a89a0c50b%7CCustomResource%7C97a94ddd-e81a-4ee2-ac54-1c3da3408e09

Response body:

{
    "Status": "SUCCESS",
    "Reason": "See the details in CloudWatch Log Stream: 2024/06/19/[$LATEST]feeda2f59f4540c5aed490b0c8c69baf",
    "PhysicalResourceId": "2024/06/19/[$LATEST]feeda2f59f4540c5aed490b0c8c69baf",
    "StackId": "arn:aws:cloudformation:us-east-1:<AWSアカウントID>:stack/CustomResourceStack/220b3f70-2e18-11ef-bdf1-0e5a89a0c50b",
    "RequestId": "97a94ddd-e81a-4ee2-ac54-1c3da3408e09",
    "LogicalResourceId": "CustomResource",
    "NoEcho": false,
    "Data": {
        "Response": "Success"
    }
}

Status code: 200

{
    "time": "2024-06-19T08:45:26.740Z",
    "type": "platform.report",
    "record": {
        "requestId": "7e17d3e5-f1aa-4983-a734-379b5da774ad",
        "metrics": {
            "durationMs": 1643.427,
            "billedDurationMs": 1644,
            "memorySizeMB": 128,
            "maxMemoryUsedMB": 88,
            "initDurationMs": 839.541
        },
        "status": "success"
    }
}

スタックの削除

npx cdk destroyでスタックを削除した時の挙動も確認します。

スタックを削除すると、以下のようにNo preferenceに戻っていました。

4.スタック削除をするとNo preferenceに戻っていた

意図したとおりですね。

カスタムリソースを作成するLambda関数が出力したログは以下のとおりです。Deleteリクエストを受け取って動作したことが分かります。

{
    "time": "2024-06-19T08:48:04.383Z",
    "type": "platform.start",
    "record": {
        "requestId": "7250ee94-c331-4906-abaf-ec8dd0f44cfa",
        "version": "$LATEST"
    }
}

Received event: {'RequestType': 'Delete', 'ServiceToken': 'arn:aws:lambda:us-east-1:<AWSアカウントID>:function:CustomResourceStack-SingletonLambda975fff3968dd41c-L5DiFbl4LYio', 'ServiceTimeout': '10', 'ResponseURL': 'https://cloudformation-custom-resource-response-useast1.s3.amazonaws.com/arn%3Aaws%3Acloudformation%3Aus-east-1%3A<AWSアカウントID>%3Astack/CustomResourceStack/220b3f70-2e18-11ef-bdf1-0e5a89a0c50b%7CCustomResource%7C1ae0ca16-f35c-4a34-9ce7-a641ad960524 'StackId': 'arn:aws:cloudformation:us-east-1:<AWSアカウントID>:stack/CustomResourceStack/220b3f70-2e18-11ef-bdf1-0e5a89a0c50b', 'RequestId': '1ae0ca16-f35c-4a34-9ce7-a641ad960524', 'LogicalResourceId': 'CustomResource', 'PhysicalResourceId': '2024/06/19/[$LATEST]feeda2f59f4540c5aed490b0c8c69baf', 'ResourceType': 'AWS::CloudFormation::CustomResource', 'ResourceProperties': {'ServiceToken': 'arn:aws:lambda:us-east-1:<AWSアカウントID>:function:CustomResourceStack-SingletonLambda975fff3968dd41c-L5DiFbl4LYio', 'ServiceTimeout': '10'}}

1.34.129

{'Return': True, 'ResponseMetadata': {'RequestId': 'e3527b88-08a1-4ef7-9605-c5db275ce1ee', 'HTTPStatusCode': 200, 'HTTPHeaders': {'x-amzn-requestid': 'e3527b88-08a1-4ef7-9605-c5db275ce1ee', 'cache-control': 'no-cache, no-store', 'strict-transport-security': 'max-age=31536000; includeSubDomains', 'vary': 'accept-encoding', 'content-type': 'text/xml;charset=UTF-8', 'transfer-encoding': 'chunked', 'date': 'Wed, 19 Jun 2024 08:48:04 GMT', 'server': 'AmazonEC2'}, 'RetryAttempts': 0}}
https://cloudformation-custom-resource-response-useast1.s3.amazonaws.com/arn%3Aaws%3Acloudformation%3Aus-east-1%3A<AWSアカウントID>%3Astack/CustomResourceStack/220b3f70-2e18-11ef-bdf1-0e5a89a0c50b%7CCustomResource%7C1ae0ca16-f35c-4a34-9ce7-a641ad960524

Response body:

{
    "Status": "SUCCESS",
    "Reason": "See the details in CloudWatch Log Stream: 2024/06/19/[$LATEST]feeda2f59f4540c5aed490b0c8c69baf",
    "PhysicalResourceId": "2024/06/19/[$LATEST]feeda2f59f4540c5aed490b0c8c69baf",
    "StackId": "arn:aws:cloudformation:us-east-1:<AWSアカウントID>:stack/CustomResourceStack/220b3f70-2e18-11ef-bdf1-0e5a89a0c50b",
    "RequestId": "1ae0ca16-f35c-4a34-9ce7-a641ad960524",
    "LogicalResourceId": "CustomResource",
    "NoEcho": false,
    "Data": {
        "Response": "Success"
    }
}

Status code: 200

{
    "time": "2024-06-19T08:48:05.956Z",
    "type": "platform.report",
    "record": {
        "requestId": "7250ee94-c331-4906-abaf-ec8dd0f44cfa",
        "metrics": {
            "durationMs": 1572.29,
            "billedDurationMs": 1573,
            "memorySizeMB": 128,
            "maxMemoryUsedMB": 88
        },
        "status": "success"
    }
}

cfn-responseの記述を削除してスタックをデプロイ

Lamnda関数内のcfn-responseの記述を削除してスタックをデプロイします。

以下のようにcfnresponse.send()をコメントアウトしました。

./lib/src/lambda/imdsv2/index.py

import boto3
from botocore.exceptions import ClientError
import cfnresponse

ec2_client = boto3.client("ec2")


def lambda_handler(event, context):
    print("Received event:", event)
    print(boto3.__version__)

    try:
        request_type = event["RequestType"]
        if request_type == "Create":
            on_create()
        elif request_type == "Update":
            on_update()
        elif request_type == "Delete":
            on_delete()
        else:
            raise ValueError("Invalid request type")

        # cfnresponse.send(event, context, cfnresponse.SUCCESS, {"Response": "Success"})
    except Exception as e:
        print("Error:", e)
        # cfnresponse.send(event, context, cfnresponse.FAILED, {"Response": "Success"})


def on_create() -> None:
    modify_instance_metadata_defaults(HttpTokens="required")


def on_update() -> None:
    modify_instance_metadata_defaults(HttpTokens="required")


def on_delete() -> None:
    modify_instance_metadata_defaults(HttpTokens="no-preference")


def modify_instance_metadata_defaults(HttpTokens: str) -> None:
    try:
        response = ec2_client.modify_instance_metadata_defaults(HttpTokens=HttpTokens)
        print(response)
    except ClientError as e:
        print(e)

デプロイしようとしたところ、作成およびロールバックのための削除に失敗しました。

$ npx cdk deploy

✨  Synthesis time: 9.45s

CustomResourceStack:  start: Building e8ed7ffb26ddc803a6c269dd9b83c0099a140224fafabe325bf3902cf15023d6:current_account-current_region
CustomResourceStack:  success: Built e8ed7ffb26ddc803a6c269dd9b83c0099a140224fafabe325bf3902cf15023d6:current_account-current_region
CustomResourceStack:  start: Publishing e8ed7ffb26ddc803a6c269dd9b83c0099a140224fafabe325bf3902cf15023d6:current_account-current_region
CustomResourceStack:  success: Published e8ed7ffb26ddc803a6c269dd9b83c0099a140224fafabe325bf3902cf15023d6:current_account-current_region
This deployment will make potentially sensitive changes according to your current security approval level (--require-approval broadening).
Please confirm you intend to make the following modifications:

IAM Statement Changes
┌───┬────────────────────────────────────────────────────────────────────┬────────┬────────────────┬──────────────────────────────┬───────────┐
│   │ Resource                                                           │ Effect │ Action         │ Principal                    │ Condition │
├───┼────────────────────────────────────────────────────────────────────┼────────┼────────────────┼──────────────────────────────┼───────────┤
│ + │ ${SingletonLambda975fff3968dd41c1af553e7af5ec9ee6/ServiceRole.Arn} │ Allow  │ sts:AssumeRole │ Service:lambda.amazonaws.com │           │
└───┴────────────────────────────────────────────────────────────────────┴────────┴────────────────┴──────────────────────────────┴───────────┘
IAM Policy Changes
┌───┬────────────────────────────────────────────────────────────────┬────────────────────────────────────────────────────────────────────────────────┐
│   │ Resource                                                       │ Managed Policy ARN                                                             │
├───┼────────────────────────────────────────────────────────────────┼────────────────────────────────────────────────────────────────────────────────┤
│ + │ ${SingletonLambda975fff3968dd41c1af553e7af5ec9ee6/ServiceRole} │ arn:${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole │
│ + │ ${SingletonLambda975fff3968dd41c1af553e7af5ec9ee6/ServiceRole} │ ${ModifyInstanceMetadataDefaultsPolicy}                                        │
└───┴────────────────────────────────────────────────────────────────┴────────────────────────────────────────────────────────────────────────────────┘
(NOTE: There may be security-related changes not in this list. See https://github.com/aws/aws-cdk/issues/1299)

Do you wish to deploy these changes (y/n)? y
CustomResourceStack: deploying... [1/1]
CustomResourceStack: creating CloudFormation changeset...
15:02:37 | CREATE_FAILED        | AWS::CloudFormation::CustomResource | CustomResource
CloudFormation did not receive a response from your Custom Resource. Please check your logs for requestId [5fcf076e-ed00-4b8b-957a-c88df3d5a7a7]. If you are using the Python cfn-response module, you may ne
ed to update your Lambda function code so that CloudFormation can attach the updated version.

15:03:01 | DELETE_FAILED        | AWS::CloudFormation::CustomResource | CustomResource
CloudFormation did not receive a response from your Custom Resource. Please check your logs for requestId [63fea4e8-c41c-4403-a182-b1dc03fa381f]. If you are using the Python cfn-response module, you may ne
ed to update your Lambda function code so that CloudFormation can attach the updated version.


 ❌  CustomResourceStack failed: Error: The stack named CustomResourceStack failed creation, it may need to be manually deleted from the AWS console: ROLLBACK_FAILED (The following resource(s) failed to delete: [CustomResource]. ): CloudFormation did not receive a response from your Custom Resource. Please check your logs for requestId [5fcf076e-ed00-4b8b-957a-c88df3d5a7a7]. If you are using the Python cfn-response module, you may need to update your Lambda function code so that CloudFormation can attach the updated version.
    at FullCloudFormationDeployment.monitorDeployment (/<ディレクトリパス>/custom-resource/node_modules/aws-cdk/lib/index.js:451:10568)
    at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
    at async Object.deployStack2 [as deployStack] (/<ディレクトリパス>/custom-resource/node_modules/aws-cdk/lib/index.js:454:199515)
    at async /<ディレクトリパス>/custom-resource/node_modules/aws-cdk/lib/index.js:454:181237

 ❌ Deployment failed: Error: The stack named CustomResourceStack failed creation, it may need to be manually deleted from the AWS console: ROLLBACK_FAILED (The following resource(s) failed to delete: [CustomResource]. ): CloudFormation did not receive a response from your Custom Resource. Please check your logs for requestId [5fcf076e-ed00-4b8b-957a-c88df3d5a7a7]. If you are using the Python cfn-response module, you may need to update your Lambda function code so that CloudFormation can attach the updated version.
    at FullCloudFormationDeployment.monitorDeployment (/<ディレクトリパス>/custom-resource/node_modules/aws-cdk/lib/index.js:451:10568)
    at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
    at async Object.deployStack2 [as deployStack] (/<ディレクトリパス>/custom-resource/node_modules/aws-cdk/lib/index.js:454:199515)
    at async /<ディレクトリパス>/custom-resource/node_modules/aws-cdk/lib/index.js:454:181237

The stack named CustomResourceStack failed creation, it may need to be manually deleted from the AWS console: ROLLBACK_FAILED (The following resource(s) failed to delete: [CustomResource]. ): CloudFormation did not receive a response from your Custom Resource. Please check your logs for requestId [5fcf076e-ed00-4b8b-957a-c88df3d5a7a7]. If you are using the Python cfn-response module, you may need to update your Lambda function code so that CloudFormation can attach the updated version.

マネジメントコンソールでスタックのイベントを確認すると、カスタムリソースを作成および削除しようとして10秒を超過するとCloudFormation did not receive a response from your Custom Resourceとエラーになっていることが分かります。

5.CloudFormation did not receive a response from your Custom Resource

Lambda関数のコード側で不具合があったとしても、適切なタイムアウト値を設定していれば虚無な時間を過ごすことはなくなりそうです。

カスタムリソースを使用する時の必須設定レベル

CloudFormationのカスタムリソースのタイムアウト値を設定できるようになったアップデートを紹介しました。

カスタムリソースの作成に1時間処理に時間がかかるケースも稀だと思うので、カスタムリソースを使用する時の必須設定レベルだなと感じています。上手に使えばカスタムリソースを使ったデプロイが加速させることができそうです。

この記事が誰かの助けになれば幸いです。

以上、AWS事業本部 コンサルティング部の のんピ(@non____97)でした!