Easily deploying infrastructure as code via AWS CDK
When you have to deploy / create / manage infrastructure do you usually do it manually in a more traditional approach or do you use an IaC approach like Terraform, Cloudformation or Ansible ? If you do, you might have faced quite a few challenges and even have been overwhelmed by the many complex configurations, vast amounts of boilerplate code and the steep learning curve of these. I went through all these transitions from manually creating / managing infrastructure to Cloudformation/Serverless Framework, then onto Terraform and finally ended up on CDK which I think is great !
These are great frameworks and each has its own strengths. However one thing that has always bothered me was how complex some of these could get and if all that complexity from the get go was really necessary. Some of the frameworks attempts to simplify this, for example Serverless Framework allows you to easily define a serverless architecture(API Gateway --> Lambdas) quite easily. However when defining the same architecture in Terraform it can get quite complex with 6 times more configurations/code. This is understandable as Serverless Framework is opinionated and optimized for Serverless architecture while Terraform tries to be unopinionated and expose and allow quite a bit lot of configuration/customization from the get go.
CDK aims to help with these challenges and makes IaC very much more enjoyable and simpler. However you might be left with a few questions about CDK, I have managed to summarize them below covering as much ground as I can.
The AWS Cloud Development Kit (AWS CDK) is an open source software development framework to model and provision your cloud application resources using programming languages that you are familiar with. At time of writing this the supported languages for CDK are TypeScript/Javascript, Python , Java and C#.
How it works is very simple, you are provided with a set of available modules/packages to use with your favorite language. Which you can use and configure them to whatever shape or form you need your infrastructure to be. The AWS CDK can then be used to synthesize
a CloudFormation(CLF) template. This can then be deployed with all the benefits a CLF template has.
devops
and development
by quite a bit.unit testable
which is a whole new level from any other IaC framework, for more info click here.So as I was working on my latest project - hacking together a blogging template for Ravensoft which essentially a Headless CMS + Frontend. As an initial step for setting up the project I wanted to create a CI/CD pipeline along with IaC to generate the needed Infrastructure. This would allow me to easily deploy as many instances of this template as we want for multiple clients/users on demand with little to no manual intervention.
I was looking into some options to do this, the CMS was sort of a monolith which meant deploying it on a lambda is not the most ideal option, so my usual go to serverless architecture wasn't going to work. This needed a different architecture,which is scalable yet at a relatively low cost to start off with. So I went with the following architecture which is reletively simple and common.
What this essentially does is to allow us to push a dockerized application into a container registry(AWS ECR), which is added as a "Tasks" on the ECS layer which can scale on demand within the EC2 instances we specify.
Defining the infrastructure for this could be a nightmare in most other IaC frameworks. However AWS CDK simplifies this quite a bit as you would see below, so I have chosen my favorite language(JavaScript/TypeScript) to define the resources. However there are few prerequisites for this.
npm install -g aws-cdk
- Installs AWS-CDK globally.mkdir my-cdk-app && cd my-cdk-app
- Creates a new directory for your app and changes directory into it.cdk init --language typescript
- Creates an empty app with minimal boilerplate code needed to create a basic stack.checkout Getting started guide for more info.
Now that you have a boilerplate generated for you. You can open lib/my-cdk-app.ts
and start defining the infrastructure. The code would come to be something like this once completed...
import * as cdk from '@aws-cdk/core';
import * as path from 'path';
import * as ecs from '@aws-cdk/aws-ecs';
import {
TaskDefinition,
Compatibility,
ContainerImage,
} from '@aws-cdk/aws-ecs';
import * as ec2 from '@aws-cdk/aws-ec2';
import * as ecsPatterns from '@aws-cdk/aws-ecs-patterns';
import { SubnetType } from '@aws-cdk/aws-ec2';
export class CdkStack extends cdk.Stack {
constructor(scope: cdk.App, id: string, props?: cdk.StackProps) {
super(scope, id, props);
const vpc = new ec2.Vpc(this, 'VPC', {
subnetConfiguration: [{
cidrMask: 24,
name: 'Public',
subnetType: SubnetType.PUBLIC,
}]
});
// ECS cluster/resources
const cluster = new ecs.Cluster(this, 'app-cluster', {
clusterName: 'app-cluster',
vpc,
});
const taskDefinition = new TaskDefinition(this, 'Task', {
compatibility: Compatibility.EC2,
memoryMiB: '512',
cpu: '256',
});
taskDefinition
.addContainer('cms-img', {
image: ContainerImage.fromAsset(path.join(__dirname, '../../')),
memoryLimitMiB:256,
cpu: 256,
})
.addPortMappings({ containerPort: 1337 });
cluster.addCapacity('app-scaling-group', {
instanceType: new ec2.InstanceType('t2.micro'),
desiredCapacity: 1,
maxCapacity: 4
minCapacity: 1
});
new ecsPatterns.ApplicationLoadBalancedEc2Service(
this,
'app-service',
{
cluster,
cpu: 256,
desiredCount: 1,
minHealthyPercent: 50,
maxHealthyPercent: 300,
serviceName: 'cmsservice',
taskDefinition: taskDefinition,
publicLoadBalancer: true,
},
);
}
}
If you are wondering what the hell all this means...
const vpc = new ec2.Vpc(this, 'VPC', {
subnetConfiguration: [{
cidrMask: 24,
name: 'Public',
subnetType: SubnetType.PUBLIC,
}]
});
const cluster = new ecs.Cluster(this, 'app-cluster', {
clusterName: 'app-cluster',
vpc,
});
const taskDefinition = new TaskDefinition(this, 'Task', {
compatibility: Compatibility.EC2,
memoryMiB: '512',
cpu: '256',
});
taskDefinition
.addContainer('cms-img', {
image: ContainerImage.fromAsset(path.join(__dirname, '../../')),
memoryLimitMiB:256,
cpu: 256,
})
.addPortMappings({ containerPort: 1337 });
cluster.addCapacity('app-scaling-group', {
instanceType: new ec2.InstanceType('t2.micro'),
desiredCapacity: 1,
maxCapacity: 4
minCapacity: 1
});
@aws-cdk/aws-ecs-patterns
and then pasing our cluster and task definitions into it.new ecsPatterns.ApplicationLoadBalancedEc2Service(
this,
'app-service',
{
cluster,
cpu: 256,
desiredCount: 1,
minHealthyPercent: 50,
maxHealthyPercent: 300,
serviceName: 'cmsservice',
taskDefinition: taskDefinition,
publicLoadBalancer: true,
},
);
Once we are done we can run npm run build
and cdk bootstrap && cdk deploy
and then the relevant infrastructure would be created along with which your dockerized app would be deployed. An output like the following would be shown where it was deployed / accessible at.
In my next followup article I would be describing how we can set up a CI/CD pipeline for this using Github Actions. I hope that this would get you to explore and learn more about AWS CDK. Please do let me know your comments/ thoughts in the comments below.