【问题标题】:How refactorable are AWS CDK applications?AWS CDK 应用程序的可重构性如何?
【发布时间】:2020-11-13 17:14:14
【问题描述】:

我正在探索 CDK 应用程序的可重构性。假设我定义了一个自定义构造(堆栈)来创建一个 EKS 集群。我们称之为EksStack。理想情况下,我将创建与集群和 EKS 集群本身关联的角色,如下面的 sn-p 所述(我使用的是 Scala 而不是 Java,因此 sn-ps 将采用 Scala 语法):

class EksStack (scope: Construct, id: String, props: StackProps) extends Stack(scope, id, props) {
    private val role = new Role(this, "eks-role", RoleProps.builder()
        .description(...)
        .managedPolicies(...)
        .assumedBy(...)
        .build()
    )

    private val cluster = new Cluster(this, "eks-cluster", ClusterProps.builder()
        .version(...)
        .role(role)
        .defaultCapacityType(DefaultCapacityType.EC2)
        .build()
    )
}

当我合成应用程序时,我可以看到生成的模板包含 VPC 的定义,以及弹性 IP、NAT、Internet 网关等。

现在假设我想重构 EksStack 并使用不同的堆栈,比如 VpcStack,显式创建 VPC:

class VpcStack (scope: Construct, id: String, props: StackProps) extends Stack(scope, id, props) {
    val vpc = new Vpc(this, VpcId, VpcProps.builder()
      .cidr(...)
      .enableDnsSupport(true)
      .enableDnsHostnames(true)
      .maxAzs(...)
      .build()
    )
}

理想情况下,EksStack 中的集群将只使用对 VpcStack 创建的 VPC 的引用,类似于(注意集群构建器中对 vpc() 的新调用):

class EksStack (scope: Construct, id: String, props: StackProps, vpc: IVpc) extends Stack(scope, id, props) {
    private val role = new Role(this, "eks-role", RoleProps.builder()
        .description(...)
        .managedPolicies(...)
        .assumedBy(...)
        .build()
    )

    private val cluster = new Cluster(this, "eks-cluster", ClusterProps.builder()
        .version(...)
        .role(role)
        .vpc(vpc)
        .defaultCapacityType(DefaultCapacityType.EC2)
        .build()
    )
}

这显然行不通,因为 CloudFormation 会删除 EksStack 创建的 VPC,转而使用 VpcStack 创建的 VPC。我在这里和那里阅读并尝试在EksStack 中添加保留策略,并使用我最初在EksStack 的CloudFormation 模板中看到的ID 覆盖VpcStack 中VPC 的逻辑ID:

val cfnVpc = cluster.getVpc.getNode.getDefaultChild.asInstanceOf[CfnVPC]
cfnVpc.applyRemovalPolicy(RemovalPolicy.RETAIN)

val cfnVpc = vpc.getNode.getDefaultChild.asInstanceOf[CfnVPC]
cfnVpc.overrideLogicalId("LogicalID")

然后重试diff。再次,似乎 VPC 被删除并重新创建。

现在,我看到可以使用“将资源导入堆栈”操作迁移 CloudFormation 资源 (https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/refactor-stacks.html)。我的问题是:我可以在 CDK 中将资源的创建从堆栈移动到另一个堆栈而不重新创建它吗?

编辑: 为了详细说明我的问题,当我在 VpcStack 中定义 VPC 时,我希望 CDK 认为资源是由 VpcStack 创建的,而不是 EksStack。类似于将其定义从一个堆栈移动到另一个堆栈,而无需 CloudFormation 删除原始堆栈以重新创建它。在我的用例中,我将有一个堆栈定义一个最初的创建(显式或隐式,例如我的 VPC),但是在一段时间之后,我可能想要重构我的应用程序,将该资源的创建移动到专用堆栈。我试图了解这种移动是否总是导致资源被重新创建,如果有任何方法可以避免它。

【问题讨论】:

    标签: amazon-web-services amazon-cloudformation aws-cdk infrastructure-as-code


    【解决方案1】:

    我认为,当谈到 CDK 和 CloudFormation 时的可重构性时,尤其是多堆栈配置,需要牢记一些原则。

    1. 整个应用程序应该能够被完全删除和重新创建。所有数据管理都在应用程序中处理,无需手动处理。

    2. 不要总是依赖使用堆栈导出的自动堆栈间依赖管理。我喜欢将 CloudFormation 依赖项分为两类:硬的和软的。硬依赖意味着你不能删除资源,因为使用它的东西会阻止它发生。软依赖是相反的,即使其他东西正在使用它,也可以毫无问题地删除和重新创建资源。硬依赖示例:VPC、子网。软依赖示例:Topic/Queue/Role。

    3. 您将有更好的时间将堆栈软依赖项作为 SSM 参数类型的堆栈参数传递,因为您将能够更新堆栈以提供独立于使用它的依赖项的依赖项。而使用默认堆栈导出方法时会陷入死锁。您无法删除资源,因为其他东西正在导入它。所以你最终不得不做一些烦人的事情来让它工作,比如部署一次并复制它,然后再次部署删除旧的东西。在不导致堆栈导出的情况下使用 SSM 参数需要做一些额外的工作,但对于软依赖项而言,长期来看是值得的。

    4. 对于硬依赖,我不同意使用查找,因为如果有东西正在使用它,你确实希望防止删除,因为你最终会得到一个 DELETE_FAILED 堆栈,这是一个糟糕的结局。因此,对于 VPC/子网之类的东西,我认为实际使用堆栈导出/导入技术非常重要,如果您确实需要因为更改而重新创建 VPC,如果您遵循原则 1,那么您只需要进行 CDK 销毁部署,一切都会好起来的,因为您将 CDK 应用程序构建为完全可重新创建的。

    5. 当谈到数据的可重复性时,CustomResources 是您的朋友。

    【讨论】:

    • 这些原则真正阐明了 AWS CF 的工作原理,谢谢!基于此,我认为在我的用例中要走的路是将一个堆栈创建的 CDK 对象的引用简单地传递给其他堆栈。只要堆栈位于同一区域,这应该是在生产堆栈中合成 AWS CloudFormation exports 和在消费堆栈中的 Fn::ImportValue
    【解决方案2】:

    我不确定我是否理解问题,但如果您尝试引用现有资源,您可以使用上下文查询。 (例如Vpc.fromLookup)。

    https://docs.aws.amazon.com/cdk/latest/guide/context.html

    此外,如果您想使用从 EksStack 内部的 VpcStack 创建的 Vpc,您可以从 VpcStack 输出 vpc id 并以这种方式在 eks 堆栈中使用上下文查询。

    这是 C# 代码,但主体是相同的。

    var myVpc = new Vpc(...);
    
    
    new CfnOutput(this, "MyVpcIdOutput", new CfnOutputProps()
    {
        ExportName = "VpcIdOutput",
        Value = myVpc.VpcId
    }
               
    

    然后当您创建 EksStack 时,您可以导入您之前导出的 vpc id。

    new EksStack(this, "MyCoolStack", new EksStackProps()
    {
        MyVpcId = Fn.ImportValue("VpcIdOutput")
    }
    

    EksStackProps 在哪里

    public class EksStackProps 
    {
        public string MyVpcId { get; set; }
    }
    

    【讨论】:

    • 感谢您的回复!我在问题中记下了一些额外的解释。如果我理解得很好,我唯一能做的就是从另一个堆栈中引用现有的 VPC。因此,VPC 仍将“属于”EksStack(例如,我将在 CloudFormation 的“输出”选项卡中看到它)。无论如何,我想得越多,它就越有意义。请问如果 VPC 不存在,查找的行为是什么?我对角色进行了调查,如果资源不存在,查找似乎永远不会返回 null:github.com/aws/aws-cdk/issues/9941
    • 哦,好吧,我想我明白了。您不能将“创作”本身移动到另一个堆栈。但无论如何,您都不需要删除现有的 vpc。您可以将 EksStack 更改为仅使用 Vpc.fromLookup。您的 EksStack 只能做两件事之一。创建一个新的 VPC 或使用现有的 VPC。如果您希望 EksStack 对此负责,或者您想以其他方式执行此操作,这将成为您作为开发人员的决定。
    • 回答您的其他问题,如果在目标区域/帐户中找不到vpc,它将在合成过程中抛出异常。
    猜你喜欢
    • 2020-06-28
    • 2020-08-10
    • 2021-11-29
    • 2020-06-18
    • 2021-09-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多