Effective AWS CDK for AWS CloudFormation

Infrastructure as Code is the trend to manage the resources of application. AWS CloudFormation is the managed serive offering the IaC capability on AWS since 2011. CloudFormation uses the declarative language to manage your AWS resources with the style what you get is what you declare.

However there are cons of CloudFormation as a declarative language,

  • the readability and maintanence for applications involving lots of resources
  • the reuseable of code, CloudFormation modules released in re:Invent 2020 might help mitgate it

AWS CDK provides the programming way to define the infra in code by your preferred programming languages, such as Typescript, Javascripte, Python, Java and C#. AWS CDK will synthesis the code to CloudFormation template, then deploying the stack via AWS CloudForamtion service. It benefits the Devops engineers manage the infra on AWS as programming application, having version control, code review, unit testing, integration testing and CI/CD pipelines, the deployment still depends on the mature CloudFormation service to rolling update the resources and rollback when failing.

For solution development, using CDK indeed improves the productivity then publish the deployment assets as CloudFormation templates.

Though CDK application can be synthesized to CloudFormation template, there are still some differences blocking the synthesized tempaltes to be deployed across multiple AWS regeions.

This post will share the tips on how effectively writing AWS CDK application then deploying the application by CloudFormation across multiple regions.

General

Environment-agnostic stack

Don’t specify env with account and region like below that will generate account/region hardcode in CloudFormation template.

1new MyStack(app, 'Stack1', {
2    env: {
3      account: '123456789012',
4      region: 'us-east-1'
5    },
6});

use CfnMapping/CfnCondition instead of if-else clause

CloudFormation does not have logistic processing like programming language. Use CfnMapping or CfnCondition instead.

Note: the CfnMapping does not support default value, you have to list all supported regions like below code snippet,

 1getAwsLoadBalancerControllerRepo() {
 2    const albImageMapping = new cdk.CfnMapping(this, 'ALBImageMapping', {
 3      mapping: {
 4        'me-south-1': {
 5          2: '558608220178',
 6        },
 7        'eu-south-1': {
 8          2: '590381155156',
 9        },
10        'ap-northeast-1': {
11          2: '602401143452',
12        },
13        'ap-northeast-2': {
14          2: '602401143452',
15        },
16        ...        
17        'ap-east-1': {
18          2: '800184023465',
19        },
20        'af-south-1': {
21          2: '877085696533',
22        },
23        'cn-north-1': {
24          2: '918309763551',
25        },
26        'cn-northwest-1': {
27          2: '961992271922',
28        },
29      }
30    }); 
31    return `${albImageMapping.findInMap(cdk.Aws.REGION, '2')}.dkr.ecr.${cdk.Aws.REGION}.${cdk.Aws.URL_SUFFIX}/amazon/aws-load-balancer-controller`;
32  }

never use Stack.region

Don’t rely on stack.region to do the logistic for China regions. Use additional context parameter or CfnMapping like below snippet,

 1const partitionMapping = new cdk.CfnMapping(this, 'PartitionMapping', {
 2    mapping: {
 3      aws: {
 4        nexus: 'quay.io/travelaudience/docker-nexus',
 5        nexusProxy: 'quay.io/travelaudience/docker-nexus-proxy',
 6      },
 7      'aws-cn': {
 8        nexus: '048912060910.dkr.ecr.cn-northwest-1.amazonaws.com.cn/quay/travelaudience/docker-nexus',
 9        nexusProxy: '048912060910.dkr.ecr.cn-northwest-1.amazonaws.com.cn/quay/travelaudience/docker-nexus-proxy',
10      },
11    }
12  });
13partitionMapping.findInMap(cdk.Aws.PARTITION, 'nexus');

Use core.Aws.region token refered to the region which region of the stack is deployed.

explicitly add dependencies on resources to control the creation/deletion order of resources

For example, when deploying a solution with creating a new VPC with NAT gateway, then deploying EMR cluster in private subnets of VPC. The EMR cluster might fail on creation due to network issue. It’s caused by the NAT gateway is not ready when initializing the EMR cluster, you have to manually create the dependencies among EMR cluster and NAT gateway.

EKS module(@aws-cdk/aws-eks)

specify kubectl layer when creating EKS cluster

NOTE: This tricky only applies for AWS CDK prior to 1.81.0. CDK will bundle kubectl, helm and awscli as lambda layer instead of SAR appliction since 1.81.0, it resolves below limitation.

EKS uses a lambda layer to run kubectl/helm cli as custom resource, the @aws-cdk/aws-eks module depends on the Stack.region to check the region to be deployed in synthesizing phase. It violates the principle of Environment-agnostic stack! Use below workaround to create the EKS cluster,

 1const partitionMapping = new cdk.CfnMapping(this, 'PartitionMapping', {
 2  mapping: {
 3    aws: {
 4      // see https://github.com/aws/aws-cdk/blob/60c782fe173449ebf912f509de7db6df89985915/packages/%40aws-cdk/aws-eks/lib/kubectl-layer.ts#L6
 5      kubectlLayerAppid: 'arn:aws:serverlessrepo:us-east-1:903779448426:applications/lambda-layer-kubectl',
 6    },
 7    'aws-cn': {
 8      kubectlLayerAppid: 'arn:aws-cn:serverlessrepo:cn-north-1:487369736442:applications/lambda-layer-kubectl',
 9    },
10  }
11});
12
13const kubectlLayer = new eks.KubectlLayer(this, 'KubeLayer', {
14  applicationId: partitionMapping.findInMap(cdk.Aws.PARTITION, 'kubectlLayerAppid'),
15});
16const cluster = new eks.Cluster(this, 'MyK8SCluster', {
17  vpc,
18  defaultCapacity: 0,
19  kubectlEnabled: true,
20  mastersRole: clusterAdmin,
21  version: eks.KubernetesVersion.V1_16,
22  coreDnsComputeType: eks.CoreDnsComputeType.EC2,
23  kubectlLayer,
24});

If you're interested on this issue, see cdk issue for detail.

manage the lifecycle of helm chart deployment

The k8s helm chart might create AWS resources out of CloudFormation scope. You have to manage the lifecycle of those resources by yourself.

For example, there is an EKS cluster with AWS load balancer controller, then you deploy a helm chart with ingress that will create ALB/NLB by the chart, you must clean those load balancers in deletion of the chart. Also the uninstallation of Helm chart is asynchronous, you have to watch the deletion of resource completing before continuing to clean other resources.

THE END

The tips will be updated when something new is found or the one is deprecated after CDK is updated.

HAPPY CDK :satisfied:

comments powered by Disqus