介绍 Amazon CloudFormation 堆栈重构

作者: 凯文·德容 |

导言

随着您的云基础设施的发展和发展,您可能会发现需要重新组织您的 Amazon CloudFormation 堆栈以改善管理、提高模块化程度或适应不断变化的业务需求。CloudFormation 现在提供了一项强大的功能,允许您在堆栈之间移动资源。在这篇文章中,我们将探讨堆栈重构的过程以及它如何帮助您维护组织良好、高效的云基础架构。

了解堆栈重构

堆栈重构是通过将资源从一个堆栈移动到另一个堆栈或在同一堆栈中使用新的逻辑 ID 重命名资源来重组 CloudFormation 堆栈的过程。当你想做以下事情时,此功能特别有用:

  • 将大型单片堆栈拆分成更小、更易于管理的堆栈
  • 重新组织资源以更好地与您的应用程序架构或组织结构保持一致
  • 重命名资源的逻辑 ID 以提高模板的可读性

示例场景

为了演示这种能力,你将创建一个堆栈,然后将它的一些资源移动到一个新的堆栈中。您将评估需要利用的新 CLI 命令来实现这一点。在本示例中,您将拥有一个 SNS 主题,其中包含一个 lambda 函数订阅您的 SNS 主题。随着您对 SNS 主题的使用范围的扩展,您希望将订阅拆分为不同的堆栈。

  1. 使用您的起始模板创建一个before.yaml名为的新模板:
    AWSTemplateFormatVersion: "2010-09-09"
    
    Resources:
      Topic:
        Type: AWS::SNS::Topic
    
      MyFunction:
        Type: AWS::Lambda::Function
        Properties:
          FunctionName: my-function
          Handler: index.handler
          Runtime: python3.12
          Code:
            ZipFile: |
              import json
              def handler(event, context):
                print(json.dumps(event))
                return event
          Role: !GetAtt FunctionRole.Arn
          Timeout: 30
    
      Subscription:
        Type: AWS::SNS::Subscription
        Properties:
          Endpoint: !GetAtt MyFunction.Arn
          Protocol: lambda
          TopicArn: !Ref Topic
    
      FunctionInvokePermission:
        Type: AWS::Lambda::Permission
        Properties:
          Action: lambda:InvokeFunction
          Principal: sns.amazonaws.com
          FunctionName: !GetAtt MyFunction.Arn
          SourceArn: !Ref Topic
    
      FunctionRole:
        Type: AWS::IAM::Role
        Properties:
          AssumeRolePolicyDocument:
            Version: "2012-10-17"
            Statement:
              - Action:
                  - sts:AssumeRole
                Effect: Allow
                Principal:
                  Service:
                    - lambda.amazonaws.com
                Condition:
                  StringEquals:
                    aws:SourceAccount: !Ref AWS::AccountId
                  ArnLike:
                    aws:SourceArn: !Sub "arn:${AWS::Partition}:lambda:${AWS::Region}:${AWS::AccountId}:function:my-function"
          Policies:
            - PolicyName: LambdaPolicy
              PolicyDocument:
                Version: "2012-10-17"
                Statement:
                  - Action:
                      - logs:CreateLogGroup
                      - logs:CreateLogStream
                      - logs:PutLogEvents
                    Resource:
                      - arn:aws:logs:*:*:*
                    Effect: Allow
    YAML
  2. 使用before.yaml模板创建新堆栈。
    aws cloudformation create-stack --stack-name MySns --template-body file://before.yaml --capabilities CAPABILITY_IAM
    
    JSON
  3. 使用以下内容创建一个名afterSns.yaml为的新模板。此模板包含您的 SNS 主题,其中还有一个新的导出内容,用于导出 SNS 主题 ARN。您的其他模板将使用此导出内容来获取所需的 SNS 主题 ARN。
    AWSTemplateFormatVersion: "2010-09-09"
    Resources:
      Topic:
        Type: AWS::SNS::Topic
    Outputs:
      TopicArn:
        Value: !Ref Topic
        Export:
          Name: TopicArn
    YAML
  4. 使用以下内容创建一个名afterLambda.yaml为的新模板。此模板包含创建 SNS 主题的 Lambda 订阅所需的所有资源。此模板将切换!Ref Topic为使用通过使用的导出值!ImportValue TopicArn
    AWSTemplateFormatVersion: "2010-09-09"
    Resources:
      Function:
        Type: AWS::Lambda::Function
        Properties:
          FunctionName: my-function
          Handler: index.handler
          Runtime: python3.12
          Code:
            ZipFile: |
              import json
              def handler(event, context):
                print(json.dumps(event))
                return event
          Role: !GetAtt FunctionRole.Arn
          Timeout: 30
      Subscription:
        Type: AWS::SNS::Subscription
        Properties:
          Endpoint: !GetAtt Function.Arn
          Protocol: lambda
          TopicArn: !ImportValue TopicArn
      FunctionInvokePermission:
        Type: AWS::Lambda::Permission
        Properties:
          Action: lambda:InvokeFunction
          Principal: sns.amazonaws.com
          FunctionName: !GetAtt Function.Arn
          SourceArn: !ImportValue TopicArn
      FunctionRole:
        Type: AWS::IAM::Role
        Properties:
          AssumeRolePolicyDocument:
            Version: "2012-10-17"
            Statement:
              - Action:
                  - sts:AssumeRole
                Effect: Allow
                Principal:
                  Service:
                    - lambda.amazonaws.com
                Condition:
                  StringEquals:
                    aws:SourceAccount: !Ref AWS::AccountId
                  ArnLike:
                    aws:SourceArn: !Sub "arn:${AWS::Partition}:lambda:${AWS::Region}:${AWS::AccountId}:function:my-function"
          Policies:
            - PolicyName: LambdaPolicy
              PolicyDocument:
                Version: "2012-10-17"
                Statement:
                  - Action:
                      - logs:CreateLogGroup
                      - logs:CreateLogStream
                      - logs:PutLogEvents
                    Resource:
                      - arn:aws:logs:*:*:*
                    Effect: Allow
    
    YAML
  5. 创建一个名为的资源映射文件refactor.json来重命名资源的逻辑 ID。此文件定义了重构资源的源和目标堆栈名称以及逻辑 ID。如果逻辑 ID 未更改,则无需指定此文件。
    [
        {
            "Source": {
                "StackName": "MySns",
                "LogicalResourceId": "MyFunction"
            },
            "Destination": {
                "StackName": "MyLambdaSubscription",
                "LogicalResourceId": "Function"
            }
        }
    ]
    
    JSON
  6. 创建堆栈重构任务。您正在使用 enable-stack-creation 来告知重构能力为我们创建目标堆栈。如果目标堆栈已经存在,则无需提供此选项。
    aws cloudformation create-stack-refactor --stack-definitions StackName=MySns,TemplateBody@=file://afterSns.yaml StackName=MyLambdaSubscription,TemplateBody@=file://afterLambda.yaml --enable-stack-creation --resource-mappings file://refactor.json
    
    Bash

    结果:

    {
        "StackRefactorId": "56b06a9a-72ff-4f87-8205-32111bff83f9"
    }
    
    JSON

    为以下步骤捕获堆栈重构 ID。

  7. 评估堆栈重构任务。
    aws cloudformation describe-stack-refactor --stack-refactor-id 56b06a9a-72ff-4f87-8205-32111bff83f9
    
    Bash

    结果:

    {
        "StackRefactorId": "56b06a9a-72ff-4f87-8205-32111bff83f9",
        "StackIds": [
            "arn:aws:cloudformation:<<AWS::Region>>:<<AWS::AccountId>>:stack/MySns/a10bfd30-cc67-11ef-877a-023cc5780193",
            "arn:aws:cloudformation:<<AWS::Region>>:<<AWS::AccountId>>:stack/MyLambdaSubscription/6d117360-cc68-11ef-ba33-06338dcc9d39"
        ],
        "ExecutionStatus": "AVAILABLE",
        "Status": "CREATE_COMPLETE"
    }
    
    JSON

    如果你忘记捕获堆栈重构 ID,你可以运行:

    aws cloudformation list-stack-refactors
    
    Bash

    你可以列出重构通过运行所做的堆栈操作:

    aws cloudformation list-stack-refactor-actions —stack-refactor-id 56b06a9a-72ff-4f87-8205-32111bff83f9
    
    Bash

    您将看到重构将创建一个新的堆栈以及正在移动哪些资源。

    {
        "StackRefactorActions": [
            {
                "Action": "Move",
                "Entity": "Resource",
                "PhysicalResourceId": "MySns-FunctionRole-BMO7ohLu4S6a",
                "Description": "No configuration changes detected.",
                "Detection": "Auto",
                "TagResources": [],
                "UntagResources": [],
                "ResourceMapping": {
                    "Source": {
                        "StackName": "arn:aws:cloudformation:<<AWS::Region>>:<<AWS::AccountId>>:stack/MySns/a10bfd30-cc67-11ef-877a-023cc5780193",
                        "LogicalResourceId": "FunctionRole"
                    },
                    "Destination": {
                        "StackName": "arn:aws:cloudformation:<<AWS::Region>>:<<AWS::AccountId>>:stack/MyLambdaSubscription/6d117360-cc68-11ef-ba33-06338dcc9d39",
                        "LogicalResourceId": "FunctionRole"
                    }
                }
            },
            {
                "Action": "Create",
                "Entity": "Stack",
                "Description": "Stack arn:aws:cloudformation:<<AWS::Region>>:<<AWS::AccountId>>:stack/MyLambdaSubscription/6d117360-cc68-11ef-ba33-06338dcc9d39 created.",
                "Detection": "Manual",
                "TagResources": [],
                "UntagResources": [],
                "ResourceMapping": {
                    "Source": {},
                    "Destination": {}
                }
            },
    ...
    
    JSON
  8. 执行堆栈重构
    aws cloudformation execute-stack-refactor --stack-refactor-id 56b06a9a-72ff-4f87-8205-32111bff83f9
    
    JSON
  9. 等待堆栈重构完成。通过执行以下命令来评估堆栈重构状态:
    aws cloudformation describe-stack-refactor --stack-refactor-id 56b06a9a-72ff-4f87-8205-32111bff83f9
    
    JSON
    {
        "StackRefactorId": "56b06a9a-72ff-4f87-8205-32111bff83f9",
        "StackIds": [
            "arn:aws:cloudformation:<<AWS::Region>>:<<AWS::AccountId>>:stack/MySns/a10bfd30-cc67-11ef-877a-023cc5780193",
            "arn:aws:cloudformation:<<AWS::Region>>:<<AWS::AccountId>>:stack/MyLambdaSubscription/6d117360-cc68-11ef-ba33-06338dcc9d39"
        ],
        "ExecutionStatus": "EXECUTE_COMPLETE",
        "Status": "CREATE_COMPLETE"
    }
    
    JSON

结论

Amazon CloudFormation 中的堆栈重构代表了基础设施管理的重大进步,为无中断地重组云资源提供了一种更安全、更有效的方式。此功能消除了通过保留策略删除资源然后在重组堆栈时导入资源的传统需求,从而帮助您降低配置错误风险并节省时间。通过这篇文章中演示的示例,你已经了解了如何将单一堆栈拆分成更小的、有针对性的堆栈,同时使用导出和导入来维护堆栈之间的依赖关系。您还探索了新的 CloudFormation CLI 命令,这些命令使堆栈重构成为可能,同时在重组期间保持资源稳定性。

随着基础设施的发展,堆栈重构提供了必要的灵活性,使您的 CloudFormation 堆栈组织适应不断变化的需求,同时保持云资源的完整性。对于希望提高基础架构可维护性并使其资源组织与不断变化的架构模式保持一致的团队而言,这种能力尤其有价值。请记住首先在非生产环境中全面测试您的重构计划,并始终确保您的新堆栈结构保持必要的安全性和访问控制。

凯文·德容

Kevin DeJong 是亚马逊云科技的一名软件开发工程师,负责基础设施即代码。他是 cfn-lint 的创建者和维护者。凯文使用 CloudFormation 服务已有 10 多年。


*前述特定亚马逊云科技生成式人工智能相关的服务仅在亚马逊云科技海外区域可用,亚马逊云科技中国仅为帮助您发展海外业务和/或了解行业前沿技术选择推荐该服务。