CloudFormation栈5分钟TTL自动删除失败,求解决方案
解决CloudFormation栈TTL自动删除失败的方案
你遇到的问题核心是主栈与嵌套的TTL删除栈之间形成了循环依赖:主栈需要先删除嵌套栈才能完成自身删除,但嵌套栈的Lambda函数又依赖主栈的存在来执行删除操作,最终导致两者都无法正常删除。下面提供两种可行的解决方案:
方案一:使用自定义资源实现TTL自动删除(推荐)
这种方式避免了嵌套栈的依赖问题,通过自定义资源+Lambda直接管理栈的删除计划,逻辑更清晰。修改后的模板如下:
AWSTemplateFormatVersion: '2010-09-09' Description: Demo stack, creates one SSM parameter and gets deleted after 5 minutes. Resources: DemoParameter: Type: "AWS::SSM::Parameter" Properties: Type: "String" Value: "date" Description: "SSM Parameter for running date command." AllowedPattern: "^[a-zA-Z]{1,10}$" # 负责TTL删除逻辑的Lambda函数 StackTTLFunction: Type: AWS::Lambda::Function Properties: Handler: index.lambda_handler Runtime: python3.9 Code: ZipFile: | import boto3 import time import cfnresponse client = boto3.client('cloudformation') def lambda_handler(event, context): try: if event['RequestType'] == 'Create': # 计算TTL触发时间,创建CloudWatch定时规则 ttl_minutes = int(event['ResourceProperties']['TTL']) trigger_time = time.strftime('%Y-%m-%dT%H:%M:%S', time.gmtime(time.time() + ttl_minutes*60)) stack_id_suffix = event['StackId'].split('/')[-1] rule_name = f"DeleteStack-{stack_id_suffix}" target_id = f"DeleteStackTarget-{stack_id_suffix}" # 创建定时规则 events_client = boto3.client('events') events_client.put_rule( Name=rule_name, ScheduleExpression=f"at({trigger_time})", State='ENABLED' ) # 将Lambda设为规则目标 events_client.put_targets( Rule=rule_name, Targets=[{'Id': target_id, 'Arn': context.invoked_function_arn, 'Input': f'{{"StackId": "{event["StackId"]}", "Action": "Delete"}}'}] ) # 给CloudWatch Events添加调用Lambda的权限 lambda_client = boto3.client('lambda') lambda_client.add_permission( FunctionName=context.function_name, StatementId=f"AllowCloudWatch-{rule_name}", Action='lambda:InvokeFunction', Principal='events.amazonaws.com', SourceArn=f"arn:aws:events:{context.region}:{context.account_id}:rule/{rule_name}" ) cfnresponse.send(event, context, cfnresponse.SUCCESS, {}, rule_name) elif event['RequestType'] == 'Delete': # 栈删除时清理CloudWatch规则和Lambda权限 rule_name = event['PhysicalResourceId'] stack_id_suffix = event['StackId'].split('/')[-1] try: events_client = boto3.client('events') events_client.remove_targets(Rule=rule_name, Ids=[f"DeleteStackTarget-{stack_id_suffix}"]) events_client.delete_rule(Name=rule_name) lambda_client = boto3.client('lambda') lambda_client.remove_permission(FunctionName=context.function_name, StatementId=f"AllowCloudWatch-{rule_name}") except Exception as e: print(f"Cleanup warning: {str(e)}") cfnresponse.send(event, context, cfnresponse.SUCCESS, {}) elif event.get('Action') == 'Delete': # 触发栈删除操作 client.delete_stack(StackName=event['StackId'].split('/')[-1]) except Exception as e: print(f"Error: {str(e)}") cfnresponse.send(event, context, cfnresponse.FAILED, {'Error': str(e)}) Role: !GetAtt StackTTLRole.Arn # Lambda所需的IAM角色 StackTTLRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Principal: {Service: lambda.amazonaws.com} Action: sts:AssumeRole Policies: - PolicyName: StackTTLPolicy PolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Action: - cloudformation:DeleteStack - events:PutRule - events:PutTargets - events:RemoveTargets - events:DeleteRule - lambda:AddPermission - lambda:RemovePermission - logs:CreateLogGroup - logs:CreateLogStream - logs:PutLogEvents Resource: "*" # 自定义资源,触发TTL逻辑 StackTTL: Type: Custom::StackTTL Properties: ServiceToken: !GetAtt StackTTLFunction.Arn TTL: 5
方案优势
- 完全避免嵌套栈的循环依赖问题
- 自动清理所有临时资源(CloudWatch规则、Lambda权限),无残留
- 逻辑独立,与主栈资源无直接引用冲突
方案二:调整嵌套栈的删除策略
如果坚持使用原嵌套栈的方式,需要修改主栈中嵌套栈的删除策略,同时调整嵌套栈的Lambda逻辑:
- 修改主栈中嵌套栈的定义,添加
DeletionPolicy: Retain:
DeleteAfterTTLStack: Type: "AWS::CloudFormation::Stack" Properties: TemplateURL: 'https://datameetgeobk.s3.amazonaws.com/cftemplates/cfn-stack-ttl_updated.yaml.txt' Parameters: StackName: !Ref 'AWS::StackName' TTL: '5' NestedStackName: !Ref 'DeleteAfterTTLStack' DeletionPolicy: Retain
- 修改嵌套栈的Lambda函数,在删除主栈后,额外删除嵌套栈本身(需要将嵌套栈名作为参数传入)
这种方式需要额外修改嵌套栈的代码,相比方案一复杂度更高,仅适合必须保留嵌套栈架构的场景。
内容的提问来源于stack exchange,提问作者shantanuo
相关产品推荐
相关产品推荐

