AWS CDK
What is it?
The AWS Cloud Development Kit (CDK) is an Infrastructure as Code (IaC) tool that allows AWS resources to be defined using familiar programming languages (TypeScript, JavaScript, Python, Java, C# and Go) and provisioned using AWS CloudFormation.
When should you use it?
CDK lowers the barrier to entry for making infrastructure changes for teams that may not have a huge amount of platform experience. It allows them to make use of the same languages that they use on a day-to-day basis, rather than having to learn the often quirky syntax used in CloudFormation and Terraform.
For operations teams with more limited coding experience, CloudFormation or Terraform may still be the more efficient choice if team members have previous experience with them.
Why should you use it?
- No config-like languages such as with CloudFormation or Terraform, instead you can use actual code to create infrastructure
- Full functionality of programming languages including conditionals, loops, string interpolation, etc.
- Type checking for infrastructure code results in compile time errors for invalid properties
- IDE code completion
- Create and share your own high level abstractions as patterns
- The ability to test your infrastructure code using industry-standard protocols
How does it work?
CDK uses the concept of constructs for defining resources, these constructs come in three different types: Level 1 (L1), Level 2 (L2) and Patterns. Every construct is documented as part of the AWS Construct Library.
L1 - Low-level
L1 (low-level) constructs are generated automatically and map directly to CloudFormation resources. Whenever a new resource type is made available in CloudFormation, CDK will automatically be updated with a new L1 construct to represent that resource.
For example, an S3 Bucket defined using a L1 construct would look like:
const bucket = new s3.CfnBucket(this, 'bucket', {
bucketName: 'Bucket',
corsConfiguration: {
corsRules: [
{
allowedOrigins: ['*'],
allowedMethods: ['*'],
},
],
},
});
On some occasions, when an L1 construct is not available for a particular resource, you can use a generic CfnResource
which allows CloudFormation to be written directly, for example:
new cdk.CfnResource(this, 'bucket', {
type: 'AWS::S3::Bucket',
properties: {
AnalyticsConfigurations: [
{
Id: 'Config',
// ...
},
],
},
});
L2 - High-level
L2 (high-level) constructs provide the capability to generate AWS resources with sane defaults without needing to understand every detail about the underlying resource type. They also provide convenience methods that make it easier to work with the resource.
An example of some L2 constructs would be:
const vpc = new Vpc(this, 'vpc');
const asg = new AutoScalingGroup(this, 'asg', {
vpc,
instanceType: InstanceType.of(InstanceClass.T3, InstanceSize.MICRO),
machineImage: new AmazonLinuxImage(),
});
const lb = new ApplicationLoadBalancer(this, 'alb', {
vpc,
internetFacing: true,
});
const listener = lb.addListener('listener', {
port: 80,
});
listener.addTargets('target', {
port: 80,
targets: [asg],
});
listener.connections.allowDefaultPortFromAnyIpv4();
asg.scaleOnCpuUtilization('scalingpolicy', {
targetUtilizationPercent: 80,
});
This creates a VPC and sets up an auto-scaling group behind an application load balancer which is available publicly on port 80, all in less than 30 lines of code. The CloudFormation generated from this is over 800 lines! Note the convenience functions such as addListener
and allowDefaultPortFromAnyIpv4
which are available on these L2 Constructs.
Patterns
Patterns are designed to complete common tasks or create common architectures and will glue together multiple resources.
An example of a CDK pattern would be:
const loadBalancedFargateService =
new ecsPatterns.ApplicationLoadBalancedFargateService(this, 'albservice', {
memoryLimitMiB: 1024,
cpu: 512,
taskImageOptions: {
image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'),
},
});
loadBalancedFargateService.targetGroup.configureHealthCheck({
path: '/custom-health-path',
});
This creates an ECS Fargate service running behind an Application Load Balancer, including all of the underlying resources required for this.
An example of a pattern performing a common task would be:
const websiteBucket = new s3.Bucket(this, 'websitebucket', {
websiteIndexDocument: 'index.html',
publicReadAccess: true,
});
new s3deploy.BucketDeployment(this, 'deploywebsite', {
sources: [s3deploy.Source.asset('./dist')],
destinationBucket: websiteBucket,
});
The BucketDeployment
construct here will handle uploading the given source folder into the S3 bucket automatically, if given a CloudFront distribution it would also handle the invalidation of the cache.
Are there any common gotchas?
- High-level constructs do not always support every option for a particular resource, in this case you can use a property override or a low-level construct instead
- While the defaults provided with high-level constructs generally adhere to best practice from a security and reliability perspective, they can be overkill for small projects and you should always double-check what they are. Creating a VPC with default settings results in a VPC that spans an entire region, including public and private subnets in each availability zone in the region. It will also create a NAT Gateway per AZ. If you were to use us-east-1, which has 6 availability zones, this would end up costing over $195.00 every month from the NAT Gateways alone!
- Some constructs use raw strings for certain properties which aren't checked at compile time and will lead to a deploy time error if they are incorrect
- CDK is built on top of CloudFormation which means anything that CloudFormation doesn't support is also not supported in CDK
Other CDKs
It is worth noting that there are other Cloud Development Kits other than the AWS CDK, these include:
- CDK for Terraform - Provision infrastructure with Terraform rather than CloudFormation, supports multiple cloud providers
- cdk8s - Define Kubernetes applications and generate manifests using familiar programming languages