Mark Pollmann's blog

Infrastructure as Code with the AWS CDK

Creating cloud infrastructure nowadays is done via Infrastructure as Code (IaC). The big players here are Terraform, Pulumi and CloudFormation. You write configuration files (usually in YAML or JSON) which can be checked into git, reviewed and be part of the software lifecycle of your company. With a single command those files are translated into real resources by your cloud provider.

Why do we use IaC?

  • To make things consistent and reproducible (you can destroy and re-create your infrastructure with a few commands).
  • You can write tests that what you expect to get deployed actually gets deployed.
  • You have actual documentation of your architecture that is not out of date.
  • Teams can work together and learn from each other.

Writing YAML files is kind of tedious, though. You basically go back and forward between the documentation and your text editor to add all needed resources and configuration options and hope you don’t have any typos which you will find when you try to apply your files.

But can we do better?

What if there was a way to use an actual programming language instead of YAML? Insanity? Not so much.

Enter the AWS Cloud Development Kit (CDK):

The CDK is a tool released by AWS to use a high-level language like TypeScript, Python, Java, Go and C# to describe your infrastructure and outputs CloudFormation templates.

It’s best explained by looking at some code:

// imports
// [...]

// A Stack is a collection of resources
// You are free to put all resources in one stack or abstract it in multiple ones
export class CdkWorkshopStack extends cdk.Stack {
  constructor(scope: cdk.App, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

// Creating a new S3 Bucket
    new Bucket(this, 'MyBucket');

// we create a new SQS Queue
    const queue = new sqs.Queue(this, 'MyQueue', {
      visibilityTimeout: cdk.Duration.seconds(300)
    });

// we create a new SNS Topic
    const topic = new sns.Topic(this, 'myTopic');

// we subscribe our Queue to our Topic
    topic.addSubscription(new subs.SqsSubscription(queue));
  }

// A new DynamoDB Table
    const dynamoTable = new Table(this, "MyTable", {
      partitionKey: {
        name: "PK",
        type: AttributeType.STRING,
      },
      sortKey: {
        name: "SK",
        type: AttributeType.STRING,
      },
      tableName: "myTable",
// Destroy the table when the Stack is destroyed
      removalPolicy: RemovalPolicy.DESTROY,
    });

// A Lambda function. Its code is defined in a separate file
    const handlerFunction = new NodejsFunction(this, "ProjectsFunction", {
      handler: "handler",
      entry: path.join(__dirname, "..", "lambdas", "mylambda.ts"),
      runtime: Runtime.NODEJS_20_X,
      logRetention: RetentionDays.ONE_MONTH,
      environment: {
// hand in the DynamoDB Table name as an environment variable
// so we can use it in the lambda via the AWS SDK
        TABLE_NAME: dynamoTable.tableName,
      },
    });

// Handle permissions that our lambda can read and write 
//to our table (and only exactly this table)
    dynamoTable.grantReadWriteData(handlerFunction);
}

This example is completely functional, we can deploy it as is. (Note that we don’t have to write any IAM logic.) The CDK creates reasonable default values so you don’t have to specify every single property. We didn’t give our S3 Bucket a name, for example.

You can customize anything, of course. As you can see in the code, the configuration is defined in the third parameter of any Resource which are called props.

Let’s say you want to configure something in your SQS Queue. Just ask your IDE what is possible:

Scenario 1: Across columns

to be continued…