logo
Menu
Multi-region Deployments with CDK

Multi-region Deployments with CDK

Code Included!

Published Mar 28, 2024
From the second you deploy your resources into the AWS Cloud, you have access to a global computing network. Something unimaginable in the past and grossly underutilized even by today’s standards. In this article, I’ll show you two simple ways you can begin leveraging the power of global deployments with CDK.

Prerequisites

If you plan on following along, make sure you have completed these tasks.
  1. Installed the AWS CLI
  2. Installed the CDK CLI
  3. Configured IAM credentials
  4. Bootstrapped at least two regions in an AWS account using the cdk bootstrap command

Root Stack with Substacks

This design implements a “Root” stack that then deploys the subsequent region-specific stacks. The root stack will be deployed into the default region associated with the profile (us-east-1 unless specified). There are several things achieved with this.
  1. We develop a clear hierarchy
    1. The main benefit of this is dependency management since we create a clear route by which we can pass information both up and back down the tree
  2. We create a point of centralization that we can use to build dependencies that are used by many dependents
    1. For example, if we have an SQS queue that is used globally within the application, we can build it in the root stack and pass it through to the props of the application stacks
  3. We have a “non-regional” stack to house global resources
    1. Building on the last point, we have a stack that handles deploying for things that aren’t regional in AWS (IAM, S3, etc.).
    2. The stack itself is tied to a region, but there’s no reason that the resources the stack deploys also have to be tied to that region.
Here is an example of the root stack taken from here in the repository.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import * as cdk from 'aws-cdk-lib';
import { Construct } from 'constructs';
import { AppStack } from './app-stack/app-stack';

export class MultiRegionExampleStack extends cdk.Stack {
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);

const regions = ['us-east-1', 'eu-west-1'];

// store the regional stacks in an object for debugging
const regionalStacks = regions.reduce((accu, region) => {
const regionalStack = new AppStack(this, region, {
env: { region },
});
return { ...accu, [region]: regionalStack };
}, {});
}
}
Some caveats:
  1. If you’re familiar with the NestedStack construct, you are probably thinking “What a perfect use case!”. Sadly at the time of writing this, it seems that NestedStacks have to be deployed in the same region that the source stack is deployed in. I don’t believe this is a CDK limitation, but instead a CloudFormation one. There is an open issue with some discussion around implementing this in CDK, however it does seem to be rather complicated. But hey if this is a blocker for you, get active and let them know!
  2. Another Cloudformation feature that is commonly used to solve this problem is Stack Sets. At the time of writing this, there is no support out of the box in CDK. But there is a CDK Labs construct in the experimental phase you can try out. But per their readme, I would avoid getting overly tied to its implementation until it is generally released.
  3. In theory, you shouldn’t need to reference values from an application stack deployed in Region A in an application stack deployed in Region B. As they are siblings, so dependencies should be managed via prop drilling. However reality is often finicky, so if you do end up in this situation, I would recommend storing the dependency value in ParameterStore and dynamically referencing it in the dependent stack. If you’d like to learn more, I wrote a deep-dive here that you may find useful.

Deploy the Stacks Directly

For a more traditional IaC experience, we can remove the root stack. Opting to instead deploy the stacks directly. We do lose some of the “superpowers” the first option provides, with the benefit of having something simpler to reason about.
There are two main options I’ve observed
  1. Instantiate your region-specific stack however many times you need in the /bin/app.ts file. You can find a full example here in the CDK docs. As well as this branch in the repository.
  2. Instantiate your region-specific stack once in the /bin/app.ts file, and use the cdk deploy command to deploy it to the regions you desire. This branch has one approach you might take.
While this does supply you with a more traditional experience there are still benefits to using CDK, that you wouldn’t have access to otherwise.
  1. Higher level constructs
    1. If like many others you are on a Serverless journey, you may be looking to migrate to Fargate. One of my personal favorites in the CDK library is the ApplicationLoadBalancedFargateService. It’s a mouthful because it does a lot for very little. In ~30 lines (or less) of code, it will deploy an ECS cluster, service, and task definition. As well as provide one-line APIs for wiring in the cluster, load balancer, and any VPC configuration you may need to do. All of this would be hundreds of lines of Cloudformation templating.
  2. Component libraries
    1. If the standard library can’t meet your needs, constructs.dev is an open-source marketplace that houses thousands of constructs. Ranging from monitoring tools, linters, and everything else you can imagine codifying in the cloud.
    2. Not only are there public libraries, but you can also create centralization within your organization through private ones. Making it possible to standardize higher-level patterns in the cloud.

Conclusion

With CDK, it’s never been easier to duplicate resources into multiple AWS regions. You can bring your code closer to your customers and increase your fault tolerance with just a few clicks.

Find me on LinkedIn | Github | Mastodon