Do I need CDK.TF to provision my AWS resources?

Recently an ex-colleague of mine, Paul Silver asked me what do I think about Terraform CDK and to be honest it was the first time I heard about it.

Paul is a big fan of the original AWS CDK and he challenged me if I can create a simple reusable stack with Terraform CDK and whether it will be as convenient as using AWS CDK.

With a couple not very busy days during the holiday break I accepted the challenge and decided to take a look into it.

I decided first to build my stack through a classic Terraform code (HCL), after that I will try to repeat the same stack through the new Terraform CDK. Paul at the same time will create a very similar stack with the classic AWS CDK.

The question which I am trying to answer for myself through this experiment what are the benefits of the new Terraform CDK. In my head it was quite clear why AWS introduced AWS CDK as an alternative for Cloud Formation. I was wondering whether the same logic is applicable for Classic Terraform (HCL)— CDK.TF pair. Here are my initial thoughts/doubts.

1.CloudFormation is very verbose, AWS CDK is much more concise. In some cases you need only a few dozen lines of AWS CDK code to provision the same infrastructure which will easily require 1000 lines of CloudFormation JSON.

In my opinion the native Terraform language (HCL — Hashicorp Configuration Language) is also quite concise and does not require the same level of verbosity as CFN. Introducing a more concise alternative is probably not that compelling.

2. CloudFormation does not easily cater for the modular design and component reusability. AWS CDK addresses this challenge.

Terraform HCL also allows modules and provides its own native answer to the reusability problem

3.More complex programming concepts like loops are not easily achievable with CloudFormation. AWS CDK cracks it without any effort — it is using mainstream programming languages at the end of the day.

Terraform HCL has count and for_each.

Solution experiment

In order to test CDK.TF I picked a simple solution to experiment with.

The solution is a simple web server which returns static content and is hosted on EC2s inside an Autoscaling Group. The content is served through an Application Load Balancer. The diagram below provides a high level view of the infrastructure to experiment with:

What AWS elements need to be created for this solution:

  1. Security Group
  2. Launch Configuration
  3. Autoscaling Group
  4. Application Load Balancer
  5. ALB Target Group
  6. Autoscaling Attachment
  7. ALB Listener

Disclaimer: To simplify the task I decided to create this stack in the default VPC and the security group is rather opened.

How is it done with classic Terraform (HCL)

First we need to declare an AWS provider and a variable for the server port.

Next thing is to create a Security Group

After that declare a resource for the Launch Configuration and install Apache Web Server in the user data

In the next step I would like to create anAutoscaling Group. In order to do that I need to provide the TF script with a list of the availability zones. Of course for the purpose of experiment it can be simply hardcoded, but let’s do it right and query this data from the provider

data "aws_availability_zones" "all" {}

Once this information is queried, I can move on to creating the Autoscaling Group. It does not need to be placed in exactly the right order into the TF file. Even if the query is placed below the Autoscaling Group creation, Terraform anyway will understand the dependencies and will execute these lines in the right order.

After that let’s create an Application Load Balancer and an ALB Target Group. These elements will require VPC Id and Ids of all the subnets for the target group. In this example I am using the default VPC and all the subnets which are available in it. This information is queried from the provider and is stored in the local TF variables to simplify referencing them throughout the code.

Query for all the subnets require to provide VPC Id as a filter. On top of that I am also outputting the queried values to make them visible at the end of terraform apply or terraform plan scripts. Normally the output values can be used as input to the other scripts.

We are almost done — the only two elements which need to be added are an ALB Listener and an Autoscaling Attachment. They can be added pretty simply:

Let’s start with CDK TF

In order to start with CDK TF you need to install it following instructions at CDK TF GitHub page.

It currently provides support for 3 languages: TypeScript, Python and Java. JavaScript and C# are to come sometime later. You install cdktf-cli with either npm or brew. I used npm.

npm install -g cdktf-cli

After that you use the installed cli to generate a template in the chosen language. I picked TypeScript for my experiment.

mkdir hello-terraform
cd hello-terraform
cdktf init --template="typescript" --local

What is happening behind the scene?

Terraform team in their blog thanks AWS CDK team for providing recommendations on patterns for CDK design and the polyglot functionality.

CDK constructs is a concept used by AWS CDK. Constructs are the basic building block of the cloud component and constructs can be combined together in a higher level constructs and reused.

AWS CDK Constructs are implemented as an open-source software as a part of AWS CDK. Its source code can be found here. The constructs are implemented in TypeScript and AWS is using jsii to implement polyglot approach for the other languages but TypeScript.

Terraform CDK team is using the same design. Based on what I understand they generate TypeScript library with Terraform constructs and after that they use jsii to provide multi-language support based on this TS library.

The difference is that the constructs are auto-generated based on the Terraform provider schema defined in Terraform Registry.

The rest is very similar to AWS CDK — instead of cdk deploy/synth/diff you should use cdktf deploy/synth/diff.

Instead of CloudFormation output, cdktf will generate Terraform JSON format.

Let’s do it and see how difficult/easy it would be.

Once I generated the TypeScript template this is the code which I got:

The first thing I tried to create was SecurityGroup and it happened to be almost a showstopper. I started by pretty much copying my HCL code:

cdktf synth on this code worked just fine, but when I tried to execute terraform plan in the folder ./cdktf.out with cdk.tf.json file in it, I received the following error.

Error: Incorrect attribute value typeon cdk.tf.json line 26, in resource.aws_security_group.sg-123456:26:         "egress": [..Inappropriate value for attribute "egress": element 0: attributes"ipv6_cidr_blocks", "prefix_list_ids", "security_groups", and "self" arerequired.

Apparently the following JSON which is a logical copy of a valid TF file (HCL) is not considered valid by Terraform.

  26:         "egress": [
27: {
28: "cidr_blocks": [
29: "0.0.0.0/0"
30: ],
31: "from_port": 0,
32: "protocol": "-1",
33: "to_port": 0
34: }
35: ],

After some investigation I have realised that it has something to do with “Attributes as Blocks” and the way how for some elements Terraform infers the type. The same issue is described in this GitHub issue. The suggestion is to make the JSON look the following:

26:         "egress": [
27: {
28: "cidr_blocks": [
29: "0.0.0.0/0"
30: ],
31: "from_port": 0,
32: "description": null,
33: "ipv6_cidr_blocks": null,
34: "prefix_list_ids": null,
35: "self": null,
36: "protocol": "-1",
37: "to_port": 0
38: }
39: ],

Now I just need to find how to convince CDK TF to output exactly this JSON. It is apparently more comlex than it seems. Compiler does not allow to assign null to neither array (securityGroups, ipv6CidrBlocks, prefixListIds) nor a boolean attribute (self).
If you try to assign Undefined, then JSON generator simply ignores this attribute.

const egress : AwsTypes.SecurityGroupEgress = {  fromPort : 0,  ipv6CidrBlocks: Undefined,

So I had to assign empty arrays to the the array attributes and some values to Description and Self.

The rest of the code is very straight forward and can be found here: https://github.com/yubelenky/tf_experiment

Comparing with AWS CDK

As Paul and I agreed at the beginning of this experiment he created a similar stack using AWS CDK: https://github.com/paulsilver2000/website

It is exactly the same architecture, but he created a separate VPC for this solution and instead of HTTP.D he is using NGINX. All the rest is pretty much the same:

The biggest difference I noticed is the way how an AutoscalingGroup is created. In AWS CDK example it also creates a LaunchConfiguration and a SecurityGroup under the hood. A SecurityGroup is inferred from the connectivity settings specified for the Load Balancer. It can be validated by checking the generated Cloud Formation YAML

NewsBlogAutoScalingGroupInstanceSecurityGroup3BB44488:Type: AWS::EC2::SecurityGroupNewsBlogAutoScalingGroupInstanceSecurityGroupfromWebsiteStackLBSecurityGroupB29745CF807D785AAB:Type: AWS::EC2::SecurityGroupIngress

This is where AWS CDK shows full power of its L2 constructs: https://docs.aws.amazon.com/cdk/latest/guide/constructs.html#constructs_using

It seems not to be available out of the box for CDK.TF, but you can obviously create your own L2 constructs for CDK.TF. The Out of the box constructor for AutoscalingGroup does not accept any LaunchConfiguration elements, but just the reference to LaunchConfiguration itself:

Conclusion

Some numbers:

  • Original TF code — ~140 lines.
  • CDK.TF code — ~140 lines.
  • Generated TF JSON — ~300 lines
  • AWS CDK code — 60–70 lines.
  • Generated Cloud Formation — ~450 lines

It is not an exact science. Paul’s solution is slightly different from mine and my TypeScript code could be more concise, but it should convey the gist of it. I think AWS CDK output is smaller due to the use of an L2 construct for AutoscalingGroup.

I personally find CDK.TF not as a big step forward from the classic Hashicorp Configuration Language as AWS CDK was to CloudFormation, but:

a) In terms of functionality classic Terraform provides more than classic Cloud Formation — so CDK.TF has a higher bar it is compared against than AWS CDK.

b) CDK.TF is still in the very beginning of their journey, currently Terraform team provides a disclaimer that the project is still in alpha and could not be used for production.
But they will definitely be able to offer more in the future, it seems like Hashicorp is building up a team to work on CDK.TF. https://twitter.com/skorfmann/status/1346023776870412288.

Still, if you are running a Terraform house and would like to use expressiveness of a proper programming language, then CDK.TF will be a good choice for you. I think it is a very nice initiative in IaaC/CDK space

References

Terraform blog with the CDK announcement: https://www.hashicorp.com/blog/cdk-for-terraform-enabling-python-and-typescript-support

Terraform CDK GitHub: https://github.com/hashicorp/terraform-cdk

AWS CDK docs:

https://docs.aws.amazon.com/cdk/latest/guide/home.html

AWS CDK GitHub: https://github.com/aws/aws-cdk

I am an ex-software engineer, who moved to management, but is still interested in tech. Cloud | AWS | Microsoft | Programming

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store