AWS Developer Tools Blog

Working with the AWS Cloud Development Kit and AWS Construct Library

The AWS Cloud Development Kit (CDK) is a software development framework for defining your cloud infrastructure in code and provisioning it through AWS CloudFormation. The AWS CDK allows developers to define their infrastructure in familiar programming languages such as TypeScript, Python, C# or Java, taking advantages of the features those languages provide.

When I worked as an AWS Solutions Architect with Digital Native Businesses in the UK. I worked directly with many companies that tend to build their own solutions to the problems they encounter and commonly embrace Infrastructure-as-Code practices.

When I speak with these customers about the AWS CDK, the most common question I get is, “how much of AWS CloudFormation is covered by the AWS CDK?” The short answer is all of it. The long answer, as we explore in this post, is more nuanced and requires understanding the different layers of abstraction in the AWS Construct Library.

Layers in the AWS Construct Library

The AWS CDK includes the AWS Construct Library, a broad set of modules that expose APIs for defining AWS resources in CDK applications. Each module in this library contains constructs, the basic building blocks for CDK apps, that encapsulate everything CloudFormation needs to create AWS resources. There are three different levels of CDK constructs in the library: CloudFormation Resource Constructs, AWS Constructs, and Pattern Constructs. CloudFormation Resource Constructs and AWS Constructs are packaged together in the same module and named after the AWS service they represent, aws-s3 for example. Pattern Constructs are packaged in their own module and have the patterns suffix, like aws-ecs-patterns.

CloudFormation Resource Constructs are the lowest-level constructs. They mirror the AWS CloudFormation Resource Types and are updated with each release of the CDK. This means that you can use the CDK to define any resource that is available to AWS CloudFormation and can expect them to be up-to-date. When you use CloudFormation Resources, you must explicitly configure all of the resource’s properties, which requires you to completely understand the details of the underlying resource model. You can quickly identify this construct layer by looking for the ‘Cfn’ prefix. If it starts with those three letters, then it is a CloudFormation Resource Construct and maps directly to the resource type found in the CloudFormation reference documentation.

AWS Constructs also represent AWS services and leverage CloudFormation Resource Constructs under-the-hood, but they provide a higher-level, intent-based API. They are designed specifically for the AWS CDK and handle much of the configuration details and boilerplate logic required by the CloudFormation Resources. AWS Constructs offer proven default values and provide convenient methods that make it simpler to work with the resource, reducing the need to know all the details about the CloudFormation resources they represent. In some cases, these constructs are contributed by the open source community and reviewed by the AWS CDK team for inclusion in the library. A good example of this is the Amazon Virtual Private Cloud (VPC) construct which I cover in more detail below.

Finally, the library includes even higher-level constructs, which are called Pattern Constructs. These constructs generally represent reference architectures or design patterns that are intended to help you complete common tasks in AWS. For example, the aws-ecs-patterns.LoadBalancedFargateService construct represents an architecture that includes an AWS Fargate container cluster that sits behind an Elastic Load Balancer (ELB). The aws-apigateway.LambdaRestApi construct represents an Amazon API Gateway API that’s backed by an AWS Lambda function.

My expectation is for you to use the highly abstracted AWS Constructs and Patterns Constructs whenever possible because of the convenience and time savings they provide. However, the CDK is new and AWS service coverage at these upper layers is not yet complete. What do you do when high-level service coverage is absent for your CDK use cases? In the remainder of this post I will teach you how to use the CloudFormation Resource layer when AWS Constructs are not available. I will also show you how to use “overrides” for situations where a high-level AWS Construct is available, but a specific underlying CloudFormation property you want to configure is not directly exposed in the API.

Prerequisites

You will need an active AWS account if you want to use the examples detailed in this post. You’ll be using only a few items that have an hourly billing figure – specifically NAT Gateways. Please check the pricing pages for this feature to understand any costs that may be incurred.

A basic understanding of a Terminal/CLI environment is recommended to run through everything here, but even without it, following along to learn the concepts should be fine.

First, follow the Getting Started guide to set up your computer or AWS Cloud9 environment to use the AWS CDK. It will also guide you on initializing new templates which you’ll be doing a few times in this post.

You will also need a text editor. If you’re working within AWS Cloud9, you have one provided within the console. I used Visual Studio Code, which has Typescript support built in the default install, to write this post. If using Visual Studio Code, I recommend using the EditorConfig for VS Code extension as it will handle different file formats and their space/tab requirements automatically for you.

Building a VPC with the CloudFormation Resource Construct layer

You will start by building a very basic VPC using CFN Resource constructs only.

Walkthrough

  1. Open up a Terminal in the environment you configured in Getting Started
  2. You will call this Stack vpc, so make a folder named `vpc` and change to this folder in your terminal
  3. Then initialize a new AWS CDK project using the following command:

cdk init app --language=typescript

4. Now, install the aws-ec2 library which contains both the CloudFormation Resource layer and AWS layer constructs for VPCs.

npm install @aws-cdk/aws-ec2

5. In your favorite text editor, open up the vpc-stack.ts file in the lib folder

6. The structure is pretty straightforward, you have imports at the top – these are similar to imports in Python or Java where you can include different libraries and features and the AWS CDK template puts in a convenient method to start defining our stack, highlighted by the comment.

7. Import the ec2 library by adding the import statement on the second line:

import ec2 = require('@aws-cdk/aws-ec2');

8. Now define your VPC using the following code where it says ‘The code that defines your stack goes here’:

new ec2.CfnVPC(this, "MyVPC",{
      cidrBlock: "10.0.0.0/16",
    });

9. Your code should now look like this:

10. Next you need to compile your TypeScript code to JavaScript for the CDK tool to then deploy it. You’ll first run the build command to do this, and then go ahead and deploy your stack. So, using your Terminal window again:

npm run build

> vpc@0.1.0 build /Users/leepac/Code/_blog/vpc
> tsc

cdk deploy

11. Head to the AWS Console’s CloudFormation Section, and select VpcStack and then Resources. If the stack doesn’t appear, you might have the wrong region selected, and you can use the Region drop down to select the right one.

12. Click on the VPC resource Physical ID link and you get taken to the VPC Dashboard section of the Console. You can then filter the VPCs listed in the console by selecting the same Physical ID in the filter by VPC box:

13. In this filtered view, take a look at your VPC and click on Subnets in the left hand pane to see your subnets. You’ll find it empty – why?

Because you used a CloudFormation Resource Construct, a VPC Resource Type (the CfnVpc) only deploys an empty VPC rather than the resources needed to start deploying things like Amazon EC2 instances. You could start adding Subnets and the like to your VPC using the ‘Cfn*’ constructs available, but you need to work out things like the CIDRs and references yourself.

Next you will move up to the higher-level VPC construct and see what it does.

Moving to the higher-level AWS Construct layer

The Amazon EC2 Module of the AWS CDK provides a way to make a useable VPC with the same amount of code you wrote just to deploy a VPC object with no subnets. The CDK documentation covers all the various options – by default, it creates a Well-architected VPC with both Private and Public Subnets in up to three Availability Zones within a region with NAT Gateways in each AZ.

Walkthrough

1. Change the ‘CfnVPC’ to just `Vpc` and `cidrBlock` to `cidr`:

Go back to the Terminal and run the build and then look at what this will do by using the CDK’s `diff` command:

npm run build

> vpc@0.1.0 build /Users/leepac/Code/_blog/vpc
> tsc

cdk diff

3. That’s a lot more stuff! The great thing with the ‘diff’ command is you can see quickly what the deploy will do when it goes to deploy a stack. Go ahead and deploy using the `cdk deploy` command – this will take up to 15 minutes to fully deploy as the NAT Gateways need to be fully provisioned.

4. Head over to the AWS Console’s CloudFormation section again and select the VpcStack and Resources – you’ll see there’s a lot more now!

5. Feel free to explore the VPC section of the console again by clicking on the VPC Physical ID link and having a browse. You now have a VPC that can be used for deploying resources.

As you can see, it’s worth looking at higher-level AWS Constructs! However, as an engineer, I always find high level abstractions are just that, abstractions. It can feel like I’m giving up the flexibility I get with lower level resources.

In the last section, you’ll find out how the AWS CDK gives you the flexibility to override the high-level abstractions when necessary.

Overriding parts of AWS Constructs

The AWS CDK provides a way to break out the AWS CloudFormation Constructs that make up AWS Construct resources to quickly extend functionality. This is best illustrated in a code example. In the lib folder of your CDK project, create a new file called s3-stack.ts, then copy or type the following code:

import cdk = require('@aws-cdk/core');
import s3 = require('@aws-cdk/aws-s3');

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

    // Create a logging bucket
    const loggingBucket = new s3.Bucket(this, "LoggingBucket", {
      bucketName: "leerandomexamplelogging",
    });

    // Create my bucket to be logged
    const higherLevelBucket = new s3.Bucket(this, "MyBucket", {
      bucketName: "leerandomexamplebucket",
    });

    // Extract the CfnBucket from the L2 Bucket above
    const bucketResource = higherLevelBucket.node.findChild('Resource') as s3.CfnBucket;

    // Override logging configuration to point to my logging bucket
    bucketResource.addPropertyOverride('LoggingConfiguration', {
      "DestinationBucketName": loggingBucket.bucketName,
    });
  }
}

Because the AWS CDK builds a virtual tree of resources, you can take advantage of the findChild() method to traverse the tree of your higherLevelBucket to get the CloudFormation resource (in this case a CfnBucket). You can then use the addPropertyOverride method to set the specific property you wish to use. In this case, you add a LoggingConfiguration that points to your loggingBucket. The AWS CDK will take care of referencing for you – use the cdk synth subcommand to look at this in CloudFormation YAML format:

In the output you can see lines called aws:cdk:path: and this tells you quickly where to find the Cfn* type. This is how you find out that the CfnBucket is part of MyBucket and called Resource, giving you the parameter to pass into findChild().

Conclusions

Today you learned about the differences between AWS CloudFormation Constructs and higher-level AWS Constructs. You also learned that AWS CloudFormation Constructs are automatically generated and updated from the CloudFormation reference with the AWS Constructs being more curated with opinionated patterns.

You have also learned how to tell which type a Construct is by its prefix, with AWS CloudFormation Constructs being prefixed with ‘Cfn’. You then used one of these constructs, CfnVpc, to deploy a VPC and discovered that because of this exact mapping that you would need to use multiple constructs to build a VPC.

You then looked at the higher-level ‘Vpc’ AWS Construct and how it builds out a full VPC which contained both private and public subnets with NAT Gateways, a common deployment amongst customers looking to deploy Amazon EC2 applications.