简体   繁体   中英

How refactorable are AWS CDK applications?

I'm exploring how refactorable CDK applications are. Suppose I defined a custom construct (a stack) to create an EKS cluster. Let's call it EksStack . Ideally, I'd create the role to be associated with the cluster and the EKS cluster itself, as described by the following snippet (I'm using Scala instead of Java, so the snippets are going to be in Scala syntax):

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()
    )
}

When I synthetize the application, I can see that the generated template contains the definition of the VPC, together with the Elastic IPs, NATs, Inte.net Gateways, and so on.

Now suppose that I want to refactor EksStack and have a different stack, say VpcStack , explicitly create the 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()
    )
}

Ideally, the cluster in EksStack would just be using the reference to the VPC created by VpcStack , something like (note the new call to vpc() in the builder of cluster):

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()
    )
}

This obviously doesn't work, as CloudFormation would delete the VPC created by EksStack in favor of the one created by VpcStack . I read here and there and tried to add a retain policy in EksStack and to override the logical ID of the VPC in VpcStack , using the ID I originally saw in the CloudFormation template for EksStack :

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

and

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

and then retried the diff . Again, it seems that the VPC is deleted and re-created.

Now, I saw that it is possible to migrate CloudFormation resources ( https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/refactor-stacks.html ) using the "Import resources into stack" action. My question is: can I move the creation of a resource from a stack to another in CDK without re-creating it?

EDIT: To elaborate a bit on my problem, when I define the VPC in VpcStack , I'd like CDK to think that the resource was created by VpcStack instead ok EksStack . Something like moving the definition of it from one stack to another without having CloudFormation delete the original one to re-create it. In my use case, I'd have a stack define a create initially (either explicitly or implicitly, such as my VPC), but then, after I while, I might want to refactor my application, moving the creation of that resource in a dedicated stack. I'm trying to understand if this moving always leads to the resource being re-created of if there's any way to avoid it.

I think when it comes to refactorability when CDK and CloudFormation in general, especially multi stack configurations, there are a few principles to keep in mind.

  1. The entire app should be able to be completely deleted and recreated. All data management is handled in the app, there is no manual processes that need to occur.

  2. Don't always rely on auto interstack dependency management using Stack exports. I like to classify CloudFormation dependencies into two categories: Hard and soft. Hard dependencies means that you cannot delete the resource because the things using it will prevent it from happening. Soft dependencies are the opposite, the resource could be deleted and recreated without issue even though something else is using it. Hard dependency examples: VPC, Su.nets. Soft dependency examples: Topic/Queue/Role.

  3. You'll have a better time passing stack soft dependencies as Stack parameters of SSM Parameter type because you'll be able to update the stack providing the dependencies independent of those using it. Whereas you get into a deadlock when using default stack export method. You can't delete the resources because something else is importing it. So you end up having to do annoying things to make it work like deploying once with it duplicated then deploying again deleting the old stuff. It requires a little extra work to use SSM Parameters without causing stack exports but it is worth it long term for soft dependencies.

  4. For hard dependencies, I disagree with using a lookup because you really do want to prevent deletion if something is using it bc you'll end up with a DELETE_FAILED stack and that is a terrible place to end up. So for things like VPC/Su.nets, I think it's really important to actually use stack export/import technique and if you do need to recreate your VPC because of a change, if you followed principle 1, you just need to do a CDK destroy then deploy and all will be good because you built your CDK app to be fully recreatable.

  5. When it comes to recreatability with data, CustomResources are your friend.

I'm not sure if I understand the problem, but if you are trying to reference an existing resource you can use a context query. (eg Vpc.fromLookup ).

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

Additionally, if you would like to use the Vpc created from VpcStack inside of EksStack you can output the vpc id from the VpcStack and use the context query in the eks stack that way.

this is C# code but the principal is the same.

var myVpc = new Vpc(...);


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

and then when you create the EksStack you can import the vpc id that you previously exported.

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

where EksStackProps is

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM