AWS cloud development kit — A Turing complete solution for infrastructure
AWS Cloud Development Kit (CDK) was released in July 2019. AWS described it as a code-first approach to defining cloud application infrastructure. Back then, I was oblivious to the fact how ground-breaking this would be. In fact, I doubt many realised the potential of CDK. It was only recently when I attended a webinar from AWS and saw the potential of it. I was completely awestruck that this could redefine how we work with cloud resources.
This blog discusses how infrastructure in the cloud has evolved, why AWS CDK is a Turing Complete solution and how it can impact the infrastructure we design in the cloud.
First, we will explore some definitions:
- What is a Turing Complete solution?
- What is AWS CDK?
What is a Turing Complete Solution?
A general definition found onStackOverflow would be:
“A Turing Complete system means a system in which a program can be written to find an answer (although with no guarantees regarding runtime or memory). So, if somebody says, “my new thing is Turing Complete,” that means in principle (although often not in practice), it could solve any computation problem.”
A more adequate, simplerdefinition in my opinion would be:
“For example, an imperative language is Turing-complete if it has conditional branching (e.g., “if” and “goto” statements, or a “branch if zero” instruction; see one-instruction set computer) and the ability to change an arbitrary amount of memory (e.g., the ability to maintain an arbitrary number of data items).”
So you don’t need a loop. You just need a conditional jump (because with a conditional jump you can simulate loops). That’s ultimately how a compiler translates loops into assembler.c.
What is AWS CDK?
AWS CDK is a tool, which is used to define your cloud infrastructure as code in one of five supported programming languages: TypeScript, JavaScript, Python, Java, or C#.
Note: Go language is supported in a developer preview.
Prerequisites before starting with CDK
- Experience with popular AWS services,
- AWS SDK or the AWS CLI and experience working with AWS resources programmatically,
- Familiarity with AWS CloudFormation, and
- Proficient in the programming language you intend to use with the AWS CDK.
Concept
AWS CDK uses “Constructs” in its framework:
- Level 1 construct is a 1:1 cloud formation definition of a resource in AWS, for example:CfnBucket represents the CloudFormation AWS::S3::Bucket.
- Level 2 is an abstraction of level 1 for examples3.Bucket class. Finally, level 3 combines level 2 in further abstraction, which results in best practice patterns. These constructs are available in AWS CDK core — AWS Construct Library.
In this article, I’m using Python due to its easy-to-read syntax. Also I will assume you have a bash or shell terminal (i.e. WSL2 or Linux OS) at hand as it will be easier to follow this tutorial.
- Install Python 3+, (link toconfigure python prerequisite)
- Configure AWS CLI → you would need an AWS account (access key and secret),
- Install NPM, Node & Node.js, Hint: use NVM for ease, the version used here: v14.17.1
- Install AWS CDK using NPM
Let us start by creating a workspace:
mkdir cdk-helloworld
cd cdk-helloworld
cdk init app --language python
The above commands will set up your environment and multiple files will show up.
Next, execute python command to gather dependencies:
python -m pip install -r requirements.txt
Now we shall write some code. The below will be available to you after running the init command above. We are going to keep the level of difficulty at HelloWorld.
We need to import a level 2 curated construct now.
First, run:
python -m pip install aws-cdk.aws_ec2
and then add at the top:
from aws_cdk import aws_ec2 as ec2
Next, we will create a class and define a VPC and pass parameters inside:
When defining a VPC with a private subnet, we often need to define NAT Gateways as well. To define a NAT gateway, all we had to do was create the variable “nat_gateways=1”, which will pick the first available public subnet and automatically define a nat gateway. It is also required to define an IP block for the VPC using the “CIDR” variable and two availability zones using the “max_azs”. At the end, we have defined two subnets — private and public.
Now behind the scenes, AWS CDK is being a bit intuitive based on best practices. CDK now knows we want a VPC with one NAT gateway, Class C CIDR block and two availability zones. As mentioned before, we also require two subnets that are both public and private. CDK will automatically define two public and two private subnets in those two availability zones.
The subnets will encompass the following range: 10.0.0.0/16 , a definition for a private and a public subnet, so 2 subnets definition X 2 availability zones = 4 subnets. If we had written “max_azs=3” , it would have resulted in something like this: 2 subnets definition X 3 availability zones = 6 subnets.
You could also define the CIDR block inside the subnet configuration for each subnet or let CDK automatically divide the IP Block equally among all the four subnets.
Now we have enough to deploy something in the cloud using CDK successfully. If you execute “ cdk synth ”, it would build out a cloud formation template that you can review and execute with “ cdk deploy ” This step will fail if you have not configured AWS CLI locally. If the prerequisites are met and there is no syntax error, you could review the progress of the deployment or stack inside the cloud portal.
Next, let us define an instance:
First, we define a volume because it will be required when defining the instance. ec2.MachineImage is a good example of a level 2 construct. The second part includes defining the details for the instance level 2 construct. Instance_type, machine image construct is the volume we created above, in which VPC will be deployed. “VPC = VPC” is telling the ec2 instance object to deploy this resource in the mentioned VPC, we defined earlier.
If we deploy the stack now, the instance will be deployed successfully and by default, route tables and security groups will be created. But where will the instance be deployed?
There is no documented precedence for this — it chooses a subnet automatically. However, upon repeating the deployment multiple times, it seemed the subnet, which was defined firstly, took precedence and this will not work. Therefore, we need to define the subnet explicitly.
Defining the subnet was not as simple as expected. Since subnets are not defined yet, we need to find out their object.
This bit of code gave me the ability to filter, and now I have separate objects for the public and private subnet.
Now we define to which subnet this instance will be deployed and we can also further filter subnets based on ID or name.
You can now deploy the stack by executing “ cdk synth ” and “ cdk deploy ”, but you will realise that the naming of this resource seems a bit off and not really appealing. If we were in some other tool, i.e. terraform or CF, we would be defining every resource one by one. Let us try something different: the TRUE power of using a pure programming language.
Conclusion
Fairly simple, yes? CDK Tags add or update resource tags. The interesting part is the conditional jump, which makes infrastructure as code “Turing Complete”. Now we can define complex computational solutions and define workflows with much more complex logic than ever possible. AWS CDK gives us developers the ability to manipulate the infrastructure through code and provide more freedom and functionality compared to its predecessors. We can now use the same programming language to deploy our infrastructure as we use it for runtime code. We can also reference our runtime code assets. We do not have to reinvent the wheel, leveraging the full power of an already established programming language. We can now use software engineering principles in infrastructure. Using the familiar programming languages developers can now accelerate the development process. I would go as far as saying “ Now this is truly DevOps ”.
Using CDK we can create a higher level of abstraction, composed types and create interfaces. For example, if you were to deploy a lambda function, you could create it by default monitoring, alarms and API gateway instances.
I would also like to highlight that the more complex constructs we use, the more control we lose. How? Well, level 3 constructs are standardised libraries or interfaces, which means less defining of how the infrastructure is created. Rest assured level 3 constructs address a specific use case with best practices, but they will most likely haveissues. Integration can fail, e.g. the cloud formation between API and CDK Library breaks more often than compared to level 1 constructs as Amazon follows the premise: “release early, release often”. If you like to forge your own path, you can always fork() the libraries and define what you need. Or you can go down to level 1 or 2 and define the details required for the project.
Basically, CDK adds a computational meta-layer on top of a purely declarative infrastructure spec. I suppose this also allows you to make conditional decisions on the infrastructure setup based on variables or other factors, which could be a time-saver when defining infrastructure for different environments (Dev, QA, PRD).
I would like to close the blog with the thought that adding a Turing Complete language into the mix suddenly opens up opportunities for real “ Infrastructure as Code ”.
THANK YOU FOR READING 🙇
Find the full code below:
#!/usr/bin/env python3
from aws_cdk import core as cdk
from aws_cdk import core
from aws_cdk import aws_ec2 as ec2
from cdk-helloworld.cdk-helloworld-stack import CdkHelloworldStack
class CdkHelloworldStack(cdk.Stack):
def __init__ (self, scope: cdk.Construct, id: str, **kwargs) -> None:
super(). __init__ (scope, id, **kwargs)
# VPC
vpc = ec2.Vpc(self, "my-cdk-vpc",
nat_gateways=1,
cidr='10.0.0.0/16',
max_azs=2,
subnet_configuration=[
ec2.SubnetConfiguration(name="private",subnet_type=ec2.SubnetType.PRIVATE),
ec2.SubnetConfiguration(name="public",subnet_type=ec2.SubnetType.PUBLIC)
]
)
# Filter out subnets
private_subnets = vpc.select_subnets(
subnet_type=ec2.SubnetType.PRIVATE
)
public_subnets = vpc.select_subnets(
subnet_type=ec2.SubnetType.PUBLIC
)
# AMI
amzn_linux = ec2.MachineImage.latest_amazon_linux(
generation=ec2.AmazonLinuxGeneration.AMAZON_LINUX_2,
edition=ec2.AmazonLinuxEdition.STANDARD,
virtualization=ec2.AmazonLinuxVirt.HVM,
storage=ec2.AmazonLinuxStorage.GENERAL_PURPOSE
)
# Instance
instance = ec2.Instance(self, "Instance",
instance_type=ec2.InstanceType("t3.nano"),
machine_image=amzn_linux,
vpc = vpc,
vpc_subnets = ec2.SubnetSelection(subnets=public_subnets.subnets)
)
# Tagging
cdk.Tags.of(vpc).add("Name", "my-cdk-vpc")
index = 1
for subnet in public_subnets.subnets:
cdk.Tags.of(subnet).add("Name", "public subnet "+str(index))
index=index+1
index = 1
for subnet in private_subnets.subnets:
cdk.Tags.of(subnet).add("Name", "private subnet "+str(index))
index=index+1
app = cdk.App()
CdkHelloworldStack(app, "cdk-helloworld")
app.synth()
Top comments (0)