AWS Cloud Development Kit is a framework, or tool, for designing cloud Infrastructure as Code using familiar languages: JavaScript, TypeScript, Python, Java, and C#.

CDK is similar to AWS SAM and Serverless framework in a way that it uses CloudFormation to deploy resources and promotes a declarative approach for creating resources.

Without going any further, let’s look at a simple example that creates an SQS queue, SNS topic, and SNS subscription.

from aws_cdk import (
    aws_iam as iam,
    aws_sqs as sqs,
    aws_sns as sns,
    aws_sns_subscriptions as subs,
    core
)


class PythonStack(core.Stack):

    def __init__(self, scope: core.Construct, construct_id: str, **kwargs) -> None:
        super().__init__(scope, construct_id, **kwargs)

        queue = sqs.Queue(
            self, "PythonQueue",
            visibility_timeout=core.Duration.seconds(300),
        )

        topic = sns.Topic(
            self, "PythonTopic"
        )

        topic.add_subscription(subs.SqsSubscription(queue))

Note that there is no need to write policies and IAM statements explicitly — CDK provides high level abstraction that remove the need to write boilerplate code we would have to write using plain CloudFormation. This abstractions are called constructs.

The main components of CDK are apps, stacks and constructs. A stack is a unit of deployment which directly maps to CloudFromation stack. An app is a collections of stacks.

A construct represents a “cloud component”, such as queue or topic, and encapsulates everything CloudFormation needs to create the component. The AWS CDK includes constructs that represent all the resources available on AWS. For example, the s3.Bucket construct class represents an Amazon S3 bucket, and the dynamodb.Table class represents an Amazon DynamoDB table, the sqs.Queue — SQS queue.

In CDK you don’t reason in terms of resources as you do in CloudFormation but cloud components or constructs.

 

There is two types of constructs: low level(L1), and high level(L2).

L1 constructs are directly mapped to CloudFormation resourses and prefixed with Cfn, e.g. CfnBucket maps to AWS::S3::Bucket.

L2 constructs, on other hand, are high level concepts with intent-based API. They provide reasonable defaults, boilerplate, and logic to glue everything together. e.g. sqs.Queue is L2 construct that creates SQS queue.

Usually, you want to use L2 constructs all the time, but you may want to gain more control over the resources created. In this case, opt for L1.

You can also write your L2 constructs to encapsulate some common logic into reusable modules or provide some common standards over your entire infrastructure. The AWS Solutions Constructs patterns is open source collection os such constructs maintained by the community.

Now, let’s look at more sophisticated example using AWS solutions construct — an open-source extension of the AWS Cloud Development Kit that provides multi-service, well-architected patterns for quickly defining solutions in code to create predictable and repeatable infrastructure.

You may think of this as L3 constructs — construct composed of other constructs.

from aws_cdk import (
    aws_lambda as lambda_,
    core
)

from aws_solutions_constructs.aws_apigateway_lambda import ApiGatewayToLambda


class PythonStack(core.Stack):

    def __init__(self, scope: core.Construct, construct_id: str, **kwargs) -> None:
        super().__init__(scope, construct_id, **kwargs)

        ApiGatewayToLambda(self, "ApiGatewayToLambdaPattern",
            lambda_function_props=lambda_.FunctionProps(
                runtime=lambda_.Runtime.PYTHON_3_8,
                handler="index.handler",
                code=lambda_.Code.from_asset(f"python/lambda")
            )
        )

The code will create API Gateway, Lambda, Roles, and CloudWatch logs using AWS provided solution construct aws-apigateway-lambda.

 

You can create more complex architecture with minimum effort by combining multiple L3 constructs. Lets say we want to build an HTTP API to store data in a DynamoDB table. To keep the content of the table small, we can use DynamoDB TTL to expire items after a few days. After the TTL expires, data is deleted from the table and sent, via DynamoDB Streams, to a AWS Lambda function to archive the expired data on S3. How can we achieve this with CDK? Easy!

  • aws-apigateway-lambda, a Construct that implements an API Gateway REST API connected to a Lambda function.
  • aws-dynamodb-stream-lambda, a Construct implementing a DynamoDB table streaming data changes to a Lambda function with the least privileged permissions.

 

CLI

Lets install the CDK cli by running npm install command.

npm install -g aws-cdk

CLI usage is pretty simple. Run run cdk init to create a new project, cdk synth to generate CloudFormation template, and cdk deploy to deploy the app. There is one additional cdk bootstrap step that must be executed once per environment.

Each stack instance in your AWS CDK app is explicitly or implicitly associated with an environment. An environment is the target AWS account and region into which the stack is intended to be deployed.

 

Let’s run cdk init app --language python . This command will generate new app skeleton for us.

├── README.md
├── app
      ├── __init__.py
      └── app_stack.py
├── app.py
├── cdk.json
├── requirements.txt
├── setup.py
└── source.bat

Heres how app.py will look like.

#!/usr/bin/env python3
import os

from aws_cdk import core as cdk
from aws_cdk import core

from app.app_stack import AppStack

app = core.App()
AppStack(app, "AppStack",
    env=core.Environment(account='123456789012', region='us-east-1'),
)

app.synth()

The app define stacks it will deploy. As we already know, each stack consists of constructs, a.k.a cloud components. In this example I also specified the implicit environment. Alternatively you could read this parameters from env variables.

import os

env=core.Environment(
    account=os.environ["CDK_DEFAULT_ACCOUNT"],
    region=os.environ["CDK_DEFAULT_REGION"])
)

Run the bootstrap step and deploy the app. This will create CloudFormation stack with 1 resource — CDKMetadata, it contains come service information we don’t need to worry about.

 

Now it’s time to put some constructs into our empty stack.

from aws_cdk import core as cdk
from aws_cdk import core


class AppStack(cdk.Stack):

    def __init__(self, scope: cdk.Construct, construct_id: str, **kwargs) -> None:
        super().__init__(scope, construct_id, **kwargs)

        # put your constructs here

Road so far

At this point we know:

  • the purpose of CDK,
  • how to install CDK,
  • main components of CDK,
  • and how to use the CDK.

As a next step I recommend you go and checkout CDK examples repo. This will give you nice overview of CDK abilities.

Links