The AWS Cloud Development Kit and You

The AWS CDK Makes Cloud Formation Templates Better

Introduction

I remember when I first started with AWS, many years ago. This was before Lambdas and much of their cloud offerings when we basically had S3 and EC2 instances. I manually configured all the things in the AWS console. I learned later, that this was bad, and started to describe my infrastructure with Cloud Formation Templates.

Cloud Formation Templates were a game-changer. I could now set up multiple environments exactly the same. This allowed me to find errors in my infrastructure configuration before we deployed to production, and became indispensable once I moved away from apps hosted on EC2 instances, and towards building apps with Lambdas and DynamoDB.

Managing the templates was a tedious process. My first templates were a couple of EC2 instances, perhaps a Cloud Front Distribution, and some Route53 bits. Now I was trying to handle dozens of Lambdas, and their associated IAM Roles, DynamoDB tables, SNS Topics, etc. A lot of the time, most of the configuration for any given resource was very similar, and after copying and pasting a few times, the cognitive dissonance was too much to bear, and I needed a programmatic solution.

I would write code that generated Cloud Formation Templates in JSON. Sometimes these took the form of a parser that would parse an existing template, add to it, and recompile it. Other times I found that building an object graph was a better approach. The problem was, with each new resource type I would need to poor over the documentation and design a class for it. This took the time I never seemed to have, and building a reusable library was a pipe dream.

Then one day, not too long ago, I discovered the AWS Cloud Development Kit. This was the library I always wanted but never had time to write.

What is The Cloud Development Kit?

The AWS Cloud Development Kit (CDK) allows you to model your cloud infrastructure in a number of programming languages, including Typescript, .Net, Java, and Python. Instead of creating templates with resources, you create classes that represent both templates and resources. You can then run a command-line utility that will compile your code, and use it to generate Cloud Formation templates. You can even deploy those templates with the same command-line utility.

Even if this was all you could do with it, that would be enough. Strongly typed Cloud Formation templates will save you from figuring out what typo you made in a YAML file, but wait, there's more...

The CDK Handles a Lot of Security-Related Tasks For You

Most of the time, creating AWS resources also includes creating IAM roles and policies to access those resources. For me, this tends to be the most tedious part of authoring Cloud Formation templates. The CDK isn't just a mapping of class properties to YAML properties, they've also created an abstraction on top of that mapping, that applies sane default values, and automatically generates IAM roles and policies so your resources can interact.

This doesn't alleviate your responsibility of writing IAM policies for Lambdas or Containers, but much of the work of connecting Code Pipelines to S3 buckets, etc, is handled for you. You don't even need to create an instance of an IAM role class.

Sharing Things Between Cloud Formation Stacks is Now Easy

I've certainly been there. I'm building this Cloud Formation template, and I keep adding things to it. It's a little heavy, but it's just one more resource... Boom. I hit the resource limit for a Cloud Formation stack. Now I've got to figure out how to break up the stack into two (or more), and I've got to get various ARNs and such from one template to another. When I deploy these stacks, I will also need to make sure I do it in the right order, so that outputs I need to reference in one stack exist on another stack.

CDK handles this for you in two ways. First, you use strings when you instantiate a resource class, like the name of an S3 bucket, but then pass the class instance around, and the CDK uses that to get the information it needs. Second, it builds a dependency graph of your stacks, so when you deploy one stack, it will traverse the graph, and deploy any stacks the deployed stack depends, on first.

export class StackA extends Stack {
    // I'm exposing the S3 bucket as a property, 
    // so I can use it in other stacks.
    myBucket: Bucket,
    // "scope" is used by the CDK to manage
    // the dependency graph.
    constructor(scope: App) {
        super(scope, "StackA");
        // Create the S3 bucket.
        // The first argument is the bucket's scope. In this case StackA.
        this.myBucket = new Bucket(this, "MyBucket", {...});
    }
}

...

export class StackB extends Stack {
    constructor(scope: App, stackA: StackA) {
        super(scope, "StackB");
        // create a cloud front distribution
        const dist = new CloudFrontWebDistribution(this, "MyDistribution", {
            ...,
            originConfigs: [
                {
                    s3OriginSource: {
                         // CDK uses the instance of the bucket class,
                         // I don't need to export the ARN from StackA.
                         // BONUS: Because myBucket's "scope" is StackA,
                         // using "cdk deploy StackB" will update/deploy StackA first
                         s3BucketSource: stackA.myBucket,
                         ...
                    },
                    ...
                }
            ]
        });
    }
}

Using Language Paradigms You Are Familiar With

On my most recent project, I had the need to create 5 different micro-sites that only varied in a few ways, mainly domain and S3 bucket names. With plain Cloud Formation Templates, I would need to do a lot of copying/pasting. Moreover, if I wanted to make a unilateral change to how all sites work, perhaps a Cloud Front Distribution behavior, I would need to do it 5 times. This is where the programable nature of CDK really shines. I was able to create a subclass of Stack, that allowed me to only change what would be different for each site. This is basic OOP inheritance, but when using it to build Cloud Formation Templates, it becomes extremely powerful.

export type SiteStackProps = {
    stackName: string,
    subDomain: string,
    s3BucketName: string
}

export abstract class SiteStack extends Stack {
    bucket: Bucket,
    distribution: CloudFrontWebDistribution,
    constructor(scope: App, props: SiteStackProps) {
        // The child class determines the stack name,
        // since these are unique within an account.
        super(scope, props.stackName);
        // I create a bucket, but use the passed in bucket name.
        this.bucket = new Bucket(this, "Bucket", {...})
        // I create a cloud front distribution, but use the passed in domain name.
        this.distribution = new CloudFrontWebDistribution(this, "Distribution", {...})
        // More was created in the actual implementation, but this should give you the gist.
        ...
    }
}

export class Site1Stack extends SiteStack {
     constructor(scope: App) {
         super(scope, {
             stackName: "Site1Stack",
             subDomain: "site-1",
             s3BucketName: "site-1-bucket"
         });
     }
}

export class Site2Stack extends SiteStack {
     constructor(scope: App) {
         super(scope, {
             stackName: "Site2Stack",
             subDomain: "site-2",
             s3BucketName: "site-2-bucket"
         });

         // For this stack, I may need to give 
         // bucket read access to some lambda.
         // I can have access to all resources exposed
         // as properties on the parent class.
         this.bucket.grantRead(...)
     }
}

Immensely Powerful for Large Organizations

Most large organizations that have done any type of cloud development have specific requirements around how AWS resources are configured. It could be specific tags that denote what team owns the resource, or security rules around S3 bucket access, or encryption. Imagine if you could create a library of customized resource primitives with this configuration already done. This would prevent hours of searching through documentation as developers from different teams build out their own cloud infrastructure. You don't need to configure all the things for an S3 bucket, you simply have a CompanyS3Bucket class used by everyone, that adds tags or configures encryption the same way, for every S3 bucket across the organization.

Conclusion

I hope this little post has served to highlight some of the features and benefits of using the CDK. I was able to convert a handful of Cloud Formation Templates to a full CDK implementation in a sprint, including building a Code Pipeline that would update itself and deploy all the things. I think this should be in the arsenal of anyone who builds AWS cloud infrastructure.

Introducing the JBS Quick Launch Lab!

FREE 1/2 Day Assessment

Quantify what it will take to implement your next big idea! Our intensive 1/2 day session will deliver tangible timelines, costs, high-level requirements, and recommend architectures that will work best, and all for FREE. Let JBS show you why over 20 years of experience matters.
Yes, I'd Like A FREE Assessment