You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

更新CloudFormation自定义资源为何会导致其被删除?

解决CloudFormation自定义资源更新时误触发删除逻辑的问题

这个坑我也踩过!本质原因是CloudFormation处理自定义资源更新的机制:当你更新自定义资源的属性且返回了新的PhysicalResourceId时,CloudFormation会先创建一个新的资源实例,然后在UPDATE_COMPLETE_CLEANUP_IN_PROGRESS阶段删除旧的实例——这时候发送的Delete事件和栈删除时的Delete事件几乎一模一样,唯一的区别可以通过事件里的字段和你自己的状态跟踪来区分。

下面是几种可靠的区分方法,按推荐程度排序:

方法1:尽量返回相同的PhysicalResourceId(优先推荐)

如果你的自定义资源支持原地更新(比如修改S3桶的标签、Route53记录的属性,不需要重建资源),在处理Update事件时直接返回原来的PhysicalResourceId即可。这样CloudFormation会认为是原地修改资源,不会触发后续的Delete清理事件,从根源上避免问题。

示例代码片段(Python):

def handler(event, context):
    if event['RequestType'] == 'Update':
        # 执行原地更新逻辑(比如修改资源属性)
        return {
            'Status': 'SUCCESS',
            'PhysicalResourceId': event['PhysicalResourceId'], # 返回原ID
            'Data': {}
        }

方法2:通过OldResourceProperties字段判断

当Delete事件是更新阶段的旧资源清理时,CloudFormation会在事件中携带OldResourceProperties字段(存储旧的资源属性);而栈删除时的Delete事件没有这个字段。这是最直接的区分方式。

示例判断逻辑:

def handler(event, context):
    if event['RequestType'] == 'Delete':
        # 检查是否存在旧属性,判断是更新清理还是栈删除
        if 'OldResourceProperties' in event:
            # 这是更新过程中删除旧资源,只清理旧资源的残留(比如临时文件、旧记录)
            print(f"Cleaning up old resource {event['PhysicalResourceId']} during update")
            # 这里写旧资源的清理逻辑,不要删除新资源
        else:
            # 真实的栈删除,执行完整的资源销毁逻辑
            print(f"Performing full delete for resource {event['PhysicalResourceId']}")
            # 这里写删除核心资源的逻辑
        return {'Status': 'SUCCESS', 'PhysicalResourceId': event['PhysicalResourceId']}

方法3:用状态存储跟踪活跃资源ID

如果你的自定义资源必须生成新的PhysicalResourceId(比如创建新的AMI、新的S3桶),可以用DynamoDB存储每个栈+逻辑资源对应的活跃PhysicalResourceId

  1. 处理Create/Update事件时,将当前有效的PhysicalResourceId存入DynamoDB(键用StackId-LogicalResourceId
  2. 收到Delete事件时,检查当前的PhysicalResourceId是否是活跃ID:
    • 如果是,说明是栈删除,执行完整删除逻辑
    • 如果不是,说明是更新时的旧资源清理,只做残留清理

示例代码片段:

import boto3
dynamodb = boto3.resource('dynamodb')
tracker_table = dynamodb.Table('CustomResourceTracker')

def handler(event, context):
    stack_logical_key = f"{event['StackId']}-{event['LogicalResourceId']}"
    
    if event['RequestType'] == 'Create':
        new_physical_id = "your-generated-resource-id"
        # 记录活跃资源
        tracker_table.put_item(Item={
            'StackLogicalKey': stack_logical_key,
            'ActivePhysicalId': new_physical_id
        })
        return {'Status': 'SUCCESS', 'PhysicalResourceId': new_physical_id}
    
    elif event['RequestType'] == 'Update':
        new_physical_id = "your-updated-resource-id"
        # 更新活跃资源记录
        tracker_table.put_item(Item={
            'StackLogicalKey': stack_logical_key,
            'ActivePhysicalId': new_physical_id
        })
        return {'Status': 'SUCCESS', 'PhysicalResourceId': new_physical_id}
    
    elif event['RequestType'] == 'Delete':
        # 查询当前活跃资源ID
        response = tracker_table.get_item(Key={'StackLogicalKey': stack_logical_key})
        active_id = response.get('Item', {}).get('ActivePhysicalId')
        
        if event['PhysicalResourceId'] == active_id:
            # 真实栈删除,执行完整清理
            print(f"Deleting active resource {active_id}")
            # 清理核心资源
            tracker_table.delete_item(Key={'StackLogicalKey': stack_logical_key})
        else:
            # 更新时的旧资源清理,只做残留处理
            print(f"Cleaning up outdated resource {event['PhysicalResourceId']}")
        return {'Status': 'SUCCESS', 'PhysicalResourceId': event['PhysicalResourceId']}

注意事项

  • 给Lambda角色添加DynamoDB的读写权限,确保能正常访问跟踪表
  • 处理Delete事件时,无论哪种情况都要返回SUCCESS,避免CloudFormation栈卡住
  • 如果用状态存储,要考虑栈删除时的异常情况(比如Lambda没机会删除DynamoDB记录),可以给表加TTL自动清理过期记录

内容的提问来源于stack exchange,提问作者Guss

火山引擎 最新活动