A No-Nonsense Guide To AWS Cloud Development Kit (CDK)

A No-Nonsense Guide To AWS Cloud Development Kit (CDK)

In my previous post, I gave you five reasons why you should use the AWS Cloud Development Kit (CDK)! Today, I will give you a high-level overview of what the CDK is and how to use it. This guide assumes you already have an AWS account, have a basic understanding of some AWS resources, and have npm installed.

Table of Contents

🐦 Follow me on Twitter if you would like to see more content like this! 🐦

Key CDK Terms

  • App - You can think of a CDK app as the composition of all the resources needed for your application. This can deploy your entire AWS infrastructure.
  • Stacks - A stack in AWS CDK is the same as in AWS CloudFormation. You can think of stacks as groups of resources needed for your application. This is defined as a single unit of deployment. An app can be composed of one or more stacks.
  • Constructs - You can think of constructs as an individual object in a stack. These objects can be composed of one or many AWS resources. Constructs are the basic building blocks of an app or stack. Each stack is composed of constructs.
  • Etc - I have listed the most important concepts. You can read more on the AWS documentation!

Here is a useful diagram explaining these relationships. AppStacks.png

AWS Construct Library

AWS provides an entire library full of constructs that are ready to use. They are very well documented and provide no-nonsense example. Let's look at the different types of constructs.

L1

Some constructs provide a single resource such as an Amazon S3 bucket or a Lambda function. These are known as level-one or L1 for short. These constructs are very basic and must be manually configured. They will have a Cfn prefix and correspond directly to AWS CloudFormation specifications. New AWS services are supported in the AWS CDK as soon as AWS CloudFormation does. CfnBucket is a good example. It represents an Amazon S3 bucket where you MUST explicitly configure ALL the properties.

const bucket = new s3.CfnBucket(this, "MyBucket", {
  bucketName: "MyBucket"
});

L2

The next level, curated or L2, provides constructs with common boilerplates and glue logic. These will come with convenient defaults and reduces the amount of knowledge you need to know about them. They will typically encapsulate their corresponding L1 modules. A good example is s3.Bucket. This class will create an Amazon S3 bucket with default properties and methods such as bucket.addLifeCycleRule(), which adds a lifecycle rule to the bucket.

new s3.Bucket(this, 'MyFirstBucket', {
  versioned: true
});

L3

While others, called high-level or patterns, allow you to provision resources that are commonly used together. Construct patterns are designed to help you provision multiple resources based on common patterns with a limited amount of knowledge in a concise manner. s3deploy.BucketDeployment is a great example of a high-level construct. The L3 construct takes a bucket as an argument and uploads your code.

const websiteBucket = new s3.Bucket(this, 'WebsiteBucket', {
  websiteIndexDocument: 'index.html',
  publicReadAccess: true
});

new s3deploy.BucketDeployment(this, 'DeployWebsite', {
  sources: [s3deploy.Source.asset('./website-dist')],
  destinationBucket: websiteBucket,
  destinationKeyPrefix: 'web/static' // optional prefix in destination bucket
});

Stacks vs Constructs

Stacks and constructs can both be used to provision groups of AWS resources. Your cloud application can have many stacks leveraging different levels of constructs. The key difference is that stacks are deployed independently. Therefore, you should decouple constructs into stacks if you would like to isolate different layers of your cloud infrastructure. For example, you could compose multiple constructs into aDevStack. This stack can be configured for a development environment while having a different composition for production.

How Many Stacks Should My Application Have?

There is no hard and fast rule to determine this. I asked an AWS employee this question. He gave me a great response!

A stack is just a group of resources. One giant group of resources will inhibit your ability to tear down resources independently of your other resources. Dividing your resources into smaller groups can make it easier to manage and roll back/forward changes.

He recommended always putting your networking and database layer (stateful resources) into a base stack. Next, you should put your application (stateless) resources into one or more stacks. This way you can tear down the app and recreate it while leaving up your network and database layer intact.

A second recommendation came from another AWS employee. He noted the parallels between Conway's law and deploying stacks.

"Any organization that designs a system (defined broadly) will produce a design whose structure is a copy of the organization's communication structure." — Melvin E. Conway

Essentially, you may want to break down your stacks to mirror how your teams operates. At the end of the day, build in a way that makes the most sense for you and your team.

Getting Started - CDK Example

TLDR - Here is a link to my Github Repo with the fulling working example.

Install AWS CLI

First, you need to have the AWS CLI installed and configured. The CDK uses the same credentials as AWS CLI.

Install AWS CDK

Next, install the AWS CDK Toolkit using npm

npm install -g aws-cdk
# verify installation
cdk --version

Create The App

Create a directory and initialize a project with your language of choice. I love TypeScript!

mkdir hello-cdk
cd hello-cdk
# init project
cdk init app --language typescript

App Structure

This is what the app file structure should looked like. A few notes. bin folder is the entry point. This is where the app is initialized. cdk.out directory is where the CloudFormation template gets generated. Lastly, lib is where all of your stacks and constructs will live. image.png

Build The App

Next, you will need to build your project whenever you make coding changes.

npm run build
# or you can watch
npm run watch

List Stacks in Your App

Verify everything is working and you see the stacks you are expecting. You should only have one stack at this point.

cdk ls

Add an Amazon S3 bucket

Next, I will show you how to add a construct. We will add a simply S3 bucket. You need to add dependencies for each package you wish to use. Please make sure all of your cdk package versions match or you can run into trouble!

npm install @aws-cdk/aws-s3

Update the stack inside the lib folder (~hello-cdk-stack.ts)

import * as cdk from '@aws-cdk/core';
import * as s3 from '@aws-cdk/aws-s3';

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

    // new code
    new s3.Bucket(this, 'MyFirstBucket', {
      versioned: true
    });
  }
}

A couple of notes here

  • The scope, id, and props variables are used as parameters for constructing both stacks and constructs.
  • scope - This is the parent of the stack. In this case, the parent is our app. The scope for the s3 bucket is the HelloCdkStack stack.
  • id - This is a way to uniquely identify resources across deployments. MyFirstBucket is the id of the s3 bucket construct.
  • props - These are values that will be used to define our construct. You can see in the Bucket constructor that we are passing an option of versioned: true. These are typically always going to be optional.

Synthesize App

As I stated in my previous post, CDK uses AWS CloudFormation under the hood. We need to convert our app code into a CloudFormation template. You can take a look in the cdk.out directory to see what the template looks like!

cdk synth

Deploy App

The moment of truth! Let's deploy our bucket! If you have more than one stack defined you will need to choose one or use the --all option. You will see a nice progress bar.

cdk deploy

This is a high-level diagram of the workflow of building and deploying a CDK app.

image.png

Modify App

At some point, you will need to update your infrastructure. Let's update our S3 construct.

new s3.Bucket(this, 'MyFirstBucket', {
  versioned: true,
  removalPolicy: cdk.RemovalPolicy.DESTROY,
  autoDeleteObjects: true
});

CDK offers a very nice tool to see what changes will be deployed. This is useful for see what resources will be removed, added, or modified. You can see what IAM policies will be adjusted.

cdk diff

If you are happy with your changes you can deploy!

cdk deploy

Destroy App Resources

Finally, the day will come when you no longer need your resources! You can either just remove the resources you no longer need from your CDK code or destroy all the resources using a very useful command.

cdk destroy

Writing Your Own Constructs

In addition to using the AWS Construct library, you can also create your own! Writing your own is as simple as composing a number of constructs already available via the Construct library. This allows you to encapsulate a number of constructs and easily reuse them. Let's look at a simple example:

export interface NotifyingBucketProps {
  prefix?: string;
}

export class NotifyingBucket extends Construct {
  constructor(scope: Construct, id: string, props: NotifyingBucketProps = {}) {
    super(scope, id);
    const bucket = new s3.Bucket(this, 'bucket');
    const topic = new sns.Topic(this, 'topic');
    bucket.addObjectCreatedNotification(new s3notify.SnsDestination(topic),
      { prefix: props.prefix });
  }
}

A couple of notes here:

  • All you have to do to create your own Construct is use extends Construct and instantiate!
  • This construct encapsulates an Amazon S3 bucket and a AWS SNS notification into a class named NotifyingBucket.
  • You can use this custom construct by simply creating a new instance new NotifyingBucket(this, 'MyNotifyingBucket');

Next Steps

I highly recommend giving the CDK Workshop a try! They go through a bigger example with fine details. Read Best practices for developing cloud applications with AWS CDK.

Conclusion

The AWS CDK is a great tool for provisioning your cloud infrastructure! You can easily set up and destroy resources with a few lines of code. Keep this code in source control to monitor changes. Reuse your code for new projects or clients! I hope you enjoyed this tutorial! I will be writing some more in-depth articles going forward.

Special Thanks

I would like to take a moment to thank both Nathan Peck and Adam Keller for helping me fine-tune this guide! They both gave me great feedback for improving this guide.

Cheers!

🐦 Follow me on Twitter if you would like to see more content like this! 🐦

Here is a link to my Github Repo with the fulling working example.

Did you find this article valuable?

Support Phillip Ninan by becoming a sponsor. Any amount is appreciated!