CDK Best Practices
Tags: cdk, aws, devops
Published: 2022-05-17 08:40:25

In this post i want to show you my best practices while developing and deploying infrastructure using CDK. Let's start with a short intro about CDK. [CDK](https://aws.amazon.com/cdk/) - AWS Cloud Development Kit is a tool to manage AWS resources in a fully programmatic way. It is available in different popular programming languages like TypeScript, JavaScript, Python, C#/.NET and Java. 

## Key concepts In the following picture you can see the anatomy of CDK.


* **Apps** are the final artifact of code that define one or more [Stacks](https://docs.aws.amazon.com/cdk/latest/guide/stacks.html). 
* **Stacks** are equivalent to [CloudFormation stacks](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/stacks.html) and are made up of [Constructs](https://docs.aws.amazon.com/cdk/latest/guide/constructs.html). 
* **Constructs** define one or more concrete AWS resources, such as the S3 Bucket or your Lambda Function. 

If you want to learn more about how to get started with CDK take a look at the [AWS CDK Developer Guide](https://docs.aws.amazon.com/cdk/v2/guide/getting_started.html) or take a look at the [CDK workshop](https://cdkworkshop.com/). ## Best Practices Now let's jump into my Best Practices i am using on daily bases to build my automations with CDK. Just as a side note i am using CDK with Typescript. 

### Validation of your CDK APP variables 
Whenever you want your user to pass information to your CDK app ensure to validate the variables. I want my users to pass the information via JSON files always so I can use [`typescript-json-schema`](https://www.npmjs.com/package/json-schema-to-typescript) for my app. Here is an example how my config-validator looks like.

```
import Ajv, {JSONSchemaType} from "ajv";
import {Config} from "../types/config";
import { resolve } from "path";
import * as TJS from "typescript-json-schema";

const settings: TJS.PartialArgs = {
  required: true,
  noExtraProps: true
};

const compilerOptions: TJS.CompilerOptions = {
  strictNullChecks: true,
};

const program = TJS.getProgramFromFiles(
  [resolve("lib/types/config.ts")],
  compilerOptions
);

const schema = TJS.generateSchema(program, "Config", settings);

const ajv = new Ajv();

export const validate = ajv.compile(schema as JSONSchemaType<Config>);
```


### Clean your cdk.out directory before deployment 
When ever you want to deploy your cdk app to a new account or stage i can recommend you to clean your `cdk.out` directory before the next deployment. I am using [`taskfiles`](https://taskfile.dev/#/) for deployment so I created a small clean task which will be invoked before my deployment task.

```
tasks:
  deploy:
    desc: Deploy Stack
    cmds:
      - task: clean
      - task: cdkdeploy  
  clean:
    desc: Clean CDK Out
    cmds:
      - rm -rf ./cdk.out
    silent: true
  cdkdeploy:
    desc: CDK Deploy
    cmds:
      - cdk deploy --require-approval never {{.TAGS}} --strict
    vars:
      ACCOUNT:
        sh: aws sts get-caller-identity |jq -r .Account
      TAGS:
        sh: cat tags/tags.json | jq -j '.[]|"--tags " + (.Key)+"="+(.Value)+" "'
    silent: true
    interactive: true
```


### Structurize your app in multiple stacks 
When building a CDK App which contains a lot of constructs it make sense to structurize your project and set up multiple stacks when creating the Infrastructure. Therefore it's good to know how you can reference resources across stacks in AWS CDK. If you want to learn how to do this - look at this [blogpost](https://towardsthecloud.com/library/aws-cdk-share-resources-across-stacks). ### Use Grants AWS CDK help you to easily manage roles and security groups. The construct library `grant()` allows you to quick and simple create AWS Identity and Access Management roles granting access to one resource by another using minimally-scoped permissions. If you want to learn more about grants look at the [aws cdk documentation](https://docs.aws.amazon.com/cdk/v2/guide/permissions.html). ### Secure your CDK Resources Whenever you deploy resources with CDK and manage them, secure them with SCPs (You can use service control policies to manage the security and permissions that are available to your organization's AWS accounts) so that they are not accidentally customized manually by others. CDK will otherwise lose its state and it will be difficult to repair it. Below you can see an example of an SCP to secure Route53 HostedZones from manual adjustments.

```
{
      "Version": "2012-10-17",
      "Statement": [
            {
                  "Sid": "DenyRoute53fornoneCDK",
                  "Effect": "Deny",
                  "Action": [
                        "route53:ChangeResourceRecordSets",
                        "route53:DeleteHostedZone",
                        "route53:DisassociateVPCFromHostedZone",
                        "route53:AssociateVPCFromHostedZone"
                  ],
                  "Resource": [
                        "arn:aws:route53:::hostedzone/HOSTEDZONEID"
                  ],
                  "Condition": {
                        "StringNotLike": {
                              "aws:PrincipalArn": "arn:aws:iam::AWSACCOUNTID:role/cdk-*"
                        }
                  }
            }
      ]
}
```


If you want to learn more about SCPs look this [blogpost](https://globaldatanet.com/tech-blog/scp-best-practices). 

### Helpers Functions 
If you are using helpers functions ([SDK Calls](https://aws.amazon.com/tools/?nc1=h_ls)) like [Quota Checks](https://docs.aws.amazon.com/servicequotas/2019-06-24/apireference/API_GetServiceQuota.html), [importing CloudFormation outputs](https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/clients/client-cloudformation/classes/describestackscommand.html) as variables etc. I can recommend you to collect them in a file and import them into your bin / lib later. I call the file helpers.ts since I am using [Typescript](https://www.typescriptlang.org/) and but them under lib in to a own tools directory. 

### Renaming of logical IDs and resources 
As you may know some AWS resources don't have any update mechanism using AWS CloudFormation like WAF Rule groups or VPC PrefixLists. If the resource is changing in an update and the API does not support this, a rollback can occur. To avoid this, you store the states of the corresponding resource in the outputs of the stack and query them with a helper function before an update, if the state of the resource changes you rename the logical ID and the resource name within the synth process and the resource can be updated successfully. Here is an example: You are deploying a WAF Rule with a capacity of 10 - within the next update your are changing the rule that the capacity changed to 15. This requires you to update the logical ID in the CloudFormation Stack + the resource name of the rule. To generate new IDs and resource names I am using a `Date.now().toString(36);` as automated addition for my names.