You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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逻辑:

  1. 修改主栈中嵌套栈的定义,添加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
  1. 修改嵌套栈的Lambda函数,在删除主栈后,额外删除嵌套栈本身(需要将嵌套栈名作为参数传入)

这种方式需要额外修改嵌套栈的代码,相比方案一复杂度更高,仅适合必须保留嵌套栈架构的场景。

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.14 09:16:48