Migrate resources across CDK stacks

When you start building infrastructure as code. You will run into the situation that, you want to split a stack into separate ones. Or, you deployed a resource in the wrong stack. This is all part of the development lifecycle. You try things, some succeed, some will fail. But deployed production resources are hard to rebuild. They either have a complex data migration path or you can’t afford the downtime.

If you accept that some resources or located in unexpected stacks. Your complexity of your infrastructure will increase. And complexity will lead to incidents in the future.

In this blog post I will explain how you can move a deployed resource from one CDK stack to another.

Introduction

First of all, it is important to realize that CDK generates CloudFormation. CloudFormation offers functionality to bring existing resources under it’s control. This means you can import an already deployed resource into a CloudFormation stack.
Now how does that relate to CDK?

Lets assume you created a CDK stack that created a DynamoDB table. This table contains information or is in use. In these scenarios you cannot delete the resource and recreate it. That would lead to data loss or downtime. So in this scenario we could migrate the resource from one stack to another.

Prerequisites

You need to have the following command line tools in place.

Make sure you have valid credentials in the correct account.

Step 1 – Remove the resource from the existing stack

Download the existing CloudFormation template:

aws cloudformation get-template \
    --output json \
    --stack-name <my-cloudformation-stack-name> | jq -r '.TemplateBody' > original-template.yml
cp original-template.yml modified-template.yml

In the template modified-template.yml you need to locate the resource you want to move to your “new” stack. Once you found it you need to add the following DeletionPolicy: Retain to the resource.

Resources:
  MyTable794EDED1:
    Type: AWS::DynamoDB::Table
    Properties:
      KeySchema:
        - AttributeName: id
          KeyType: HASH
      AttributeDefinitions:
        - AttributeName: id
          AttributeType: S
      ProvisionedThroughput:
        ReadCapacityUnits: 5
        WriteCapacityUnits: 5
      TableName: Games
    UpdateReplacePolicy: Retain
    DeletionPolicy: Retain

This will preserve the resource when you delete it from the stack. Now we will update the CloudFormation stack:

aws cloudformation update-stack \
    --stack-name <my-cloudformation-stack-name> \
    --template-body file://modified-template.yml
cp modified-template.yml final-template.yml

The next step depends a bit on your situation. In some cases you can remove the complete stack because, there are no other resources in the stack. Or because they are not needed anymore. But in other cases you need to move out a resource. (A DynamoDB Table in this example)

When you don’t need the other resources anymore you can delete the complete stack. If you want to preserve the rest of the resources. You will need to remove the resource from the template that you want to move. So we remove MyTable794EDED1 from the final-template.yml file.

By re-running the update-stack command again. We will instruct CloudFormation to remove the resource. But, because we have the Retain policy set. The resource will only be removed from the stack.

aws cloudformation update-stack \
    --stack-name <my-cloudformation-stack-name> \
    --template-body file://final-template.yml

Step 2 – Import the resource

When you move resources around you either move them to a new or an existing stack. When you deal with an existing stack you need the existing template first.
If you are updating an existing stack, continue with “Download the existing stack”. Otherwise you can skip that section.

Download the existing stack

Download the existing CloudFormation template:

aws cloudformation get-template \
    --output json \
    --stack-name <my-other-cloudformation-stack-name> | jq -r '.TemplateBody' > template.yml

Import the existing resource

When you want to move the resource to a new stack. You need to create a new template.yml file. If you want to move the resource in an existing stack you already have the existing template ready.

To import the resource we need the resource definition. You can copy that from the modified-template.yml file from step 1. I could recommend this because the CloudFormation definition should match the deployed resource. The second thing is it needs to have the DeletionPolicy: Retain definition. So our example will look like this:

Resources:
  MyTable794EDED1:
    Type: AWS::DynamoDB::Table
    Properties:
      KeySchema:
        - AttributeName: id
          KeyType: HASH
      AttributeDefinitions:
        - AttributeName: id
          AttributeType: S
      ProvisionedThroughput:
        ReadCapacityUnits: 5
        WriteCapacityUnits: 5
      TableName: Games
    UpdateReplacePolicy: Retain
    DeletionPolicy: Retain

Next we need to create a file, lets call it import.json. In this file we will define the following for each resource what we import:

  • AWS Resource type, the type of the resource itself.
  • LogicalResourceId, the identifier in the template. In this example that would be Games.
  • ResourceIdentifier, the identifier of the existing resource.

As you can see this file contains the relation between the stack and what is already deployed. The file looks as followed:

[
    {
        "ResourceType":"AWS::DynamoDB::Table",
        "LogicalResourceId":"MyTable794EDED1",
        "ResourceIdentifier": {
            "TableName":"Games"
        }
    }
]

Next, you will need deploy the stack. You can do this with the following commands:

aws cloudformation create-change-set \
    --stack-name <my-other-cloudformation-stack-name> \
    --change-set-name ImportChangeSet \
    --change-set-type IMPORT \
    --template-body file://template.yml \
    --resources-to-import file://import.json
aws cloudformation wait change-set-create-complete --change-set-name ImportChangeSet --stack-name <my-other-cloudformation-stack-name>
aws cloudformation execute-change-set --change-set-name ImportChangeSet --stack-name <my-other-cloudformation-stack-name>

Step 3 – Redeploy using CDK

At this point the DynamoDB table is part of the new stack. But when you redeploy the CDK stack it will not render the DynamoDB resource definition. So for this we will need to add the definition to the CDK stack.
For this example I used the following CDK snippet. But when you move from one CDK stack to another you can just move the relevant code.

dynamodb.Table(self, "MyTable",
    table_name="GamesTable",
    partition_key=dynamodb.Attribute(name="id", type=dynamodb.AttributeType.STRING),
    billing_mode=dynamodb.BillingMode.PROVISIONED
)

Now in theory this should render the same template you used during the import. But if there are changes CloudFormation will thread them as updates. It tries to change the resource to its new state.
After you deployed using CDK its business as usual.

Conclusion

By reducing complexity in your infrastructure. You reduce the chance on failures and incidents. This reduction can be achieved by moving resources to more logical stacks. The process itself does contain manual steps. But when your resources contain data or cannot afford downtime. This might be your only solution.

Joris has been working with the AWS cloud since 2009 and focussing on building event driven architectures. While working with the cloud from (almost) the start he has seen most of the services being launched. Joris strongly believes in automation and infrastructure as code and is open to learn new things and experiment with them, because that is the way to learn and grow. In his spare time he enjoys running and runs a small micro brewery from his home.
Share this article: Tweet this post / Post on LinkedIn