CloudFormation栈删除时Route53托管区因证书验证记录无法删除的解决方法
解决CloudFormation删除栈时Route53托管区无法删除的问题
问题场景
我使用以下CloudFormation模板创建example.com的Route53托管区和ACM SSL/TLS证书:
Resources: HostedZone: Type: AWS::Route53::HostedZone Properties: Name: example.com Certificate: Type: AWS::CertificateManager::Certificate Properties: DomainName: example.com ValidationMethod: DNS DomainValidationOptions: - DomainName: example.com HostedZoneId: !Ref HostedZone
删除栈时收到报错:
The specified hosted zone contains non-required resource record sets and so cannot be deleted. (Service: Route53, Status Code: 400, Request ID: …)
原因是ACM证书DNS验证过程中自动添加的记录留在了托管区,CloudFormation未按正确顺序先清理这些记录再删除托管区,导致无法删除托管区。
解决方案
方案1:手动定义DNS验证记录(推荐)
将ACM自动创建的验证记录转为栈内显式管理的Route53资源,确保CloudFormation能按顺序删除记录、证书、托管区:
Resources: HostedZone: Type: AWS::Route53::HostedZone Properties: Name: example.com Certificate: Type: AWS::CertificateManager::Certificate Properties: DomainName: example.com ValidationMethod: DNS DomainValidationOptions: - DomainName: example.com HostedZoneId: !Ref HostedZone # 手动创建ACM验证所需的DNS记录 CertificateValidationRecord: Type: AWS::Route53::RecordSet Properties: HostedZoneId: !Ref HostedZone Name: !GetAtt Certificate.DomainValidationOptions.0.ResourceRecord.Name Type: !GetAtt Certificate.DomainValidationOptions.0.ResourceRecord.Type TTL: 300 ResourceRecords: - !GetAtt Certificate.DomainValidationOptions.0.ResourceRecord.Value DependsOn: HostedZone
删除栈时,CloudFormation会先删除CertificateValidationRecord,再删除Certificate,最后删除HostedZone,无残留记录干扰。
方案2:使用自定义Lambda清理托管区记录
添加自定义Lambda资源,在栈删除时自动清理托管区中非必需的记录(除NS、SOA外):
Resources: HostedZone: Type: AWS::Route53::HostedZone Properties: Name: example.com Certificate: Type: AWS::CertificateManager::Certificate Properties: DomainName: example.com ValidationMethod: DNS DomainValidationOptions: - DomainName: example.com HostedZoneId: !Ref HostedZone # 清理托管区记录的Lambda函数 CleanHostedZoneFunction: Type: AWS::Lambda::Function Properties: Runtime: python3.11 Handler: index.lambda_handler Role: !GetAtt CleanHostedZoneRole.Arn Code: ZipFile: | import boto3 import cfnresponse def lambda_handler(event, context): route53 = boto3.client('route53') hosted_zone_id = event['ResourceProperties']['HostedZoneId'] try: if event['RequestType'] == 'Delete': # 获取托管区所有记录 record_sets = route53.list_resource_record_sets(HostedZoneId=hosted_zone_id)['ResourceRecordSets'] # 收集要删除的记录(跳过NS和SOA) changes = [] for record in record_sets: if record['Type'] not in ['NS', 'SOA']: changes.append({ 'Action': 'DELETE', 'ResourceRecordSet': record }) # 批量删除 if changes: route53.change_resource_record_sets( HostedZoneId=hosted_zone_id, ChangeBatch={'Changes': changes} ) cfnresponse.send(event, context, cfnresponse.SUCCESS, {}) except Exception as e: cfnresponse.send(event, context, cfnresponse.FAILED, {'Error': str(e)}) # Lambda所需的IAM角色 CleanHostedZoneRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Principal: Service: lambda.amazonaws.com Action: sts:AssumeRole Policies: - PolicyName: CleanHostedZonePermissions PolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Action: - route53:ListResourceRecordSets - route53:ChangeResourceRecordSets Resource: !Sub 'arn:aws:route53:::hostedzone/${HostedZone}' - Effect: Allow Action: - logs:CreateLogGroup - logs:CreateLogStream - logs:PutLogEvents Resource: '*' # 触发自定义资源的栈资源 CleanHostedZoneResource: Type: Custom::CleanHostedZone Properties: ServiceToken: !GetAtt CleanHostedZoneFunction.Arn HostedZoneId: !Ref HostedZone DependsOn: Certificate
栈删除时,自定义资源会先清理托管区的验证记录,再允许托管区被正常删除。
内容的提问来源于stack exchange,提问作者Garret Wilson
相关产品推荐
相关产品推荐

