Single Repo: AWS CDK, Route53, APIGateway, Lambda, Cloudfront, Flask & Nuxt

Tech Rap
7 min readMay 23, 2021

--

TL;DR Step-by-step tutorial building a full-stack application using AWS-CDK, Flask & Nuxt using a unified code repository for all components: frontend, backend, and infrastructure.

Velocity

Every business is looking to accelerate product-driven businesses, many would say it is the most stable determinant of success.

For the last five years, as a CTO of a cloud-based startup, we worked with several cloud infrastructure deployment frameworks including Terraform, Cloudformation, and Serverless.com while we also programmed in different cloud application development frameworks like Django, Flask, Asyncio.

Having two work streams of cloud infrastructure and cloud application is the most common HR structure within R&D teams today; however, it is an antithesis of shift left methodology, not just from the security perspective. Furthermore, it creates misunderstanding in the role & responsibilities between operations and engineering.

The most common outcome of this misunderstanding is that most organizations have two code bases — one for the application and another for the infrastructure.

This paper will share my first attempt to merge both application code and infrastructure code into a unified pipeline utilizing AWS CDK.

Shift left the deployment pipeline

AWSCDK

The AWS Cloud Development Kit (AWS CDK) is an open-source software development framework to define your cloud application resources using familiar programming languages.

It helps R&D teams deploy cloud applications and infrastructure faster and with confidence, using a unified, shared pipeline and codebase.

AWS CDK is available in four different languages: Typescript, Python, .NET and Java. They publish documentation and examples in Typescript, however, I decided to use Python as it’s my preferred language.

Today alternatives to AWS CDK are CloudFormation or Terraform, forcing developers to learn these technologies in order to take ownership of the cloud infrastructure. For example, developers need to learn Arcane templating skills for CloudFormation or the Hashicorp Configuration Language (HCL) for Terraform.

As a CTO, I was unable to find candidates with the skill set to develop applications and deploy them in production.

Having the same language for applications and infrastructure with AWS CDK allows my developers to quickly start making changes to the infrastructure with a familiar language helping to reduce the DevOps team’s dependency dramatically.

Fullstack

To demonstrate a real-world environment, I used several technologies to build a full-stack environment, including backend and frontend components. Although all the technologies are combined into the same code-base, let’s take a look at each layer separately. It will help us create a baseline for what we are already familiar with: the infrastructure and application layers.

Application Layer

Nuxt — A free and open-source web application framework based on Vue.js which functions both as in-browser single page application (SPA) views as well as server-rendered web.

Flask — A micro web framework written in Python. It is classified as a microframework because it does not require particular tools or libraries except Flask itself.

Infrastructure Layer

Route53 — AWS managed Domain Name System (DNS) service

Lambda — AWS managed serverless compute service that lets you run code without provisioning or managing servers. I specifically used Docker Image to pack the python code and its dependencies.

Cloudfront — AWS managed a content delivery network service.

S3 — AWS managed object-based storage service.

Init

This tutorial requires the installation of the AWS CDK library. The CDK is written in Typescript and requires node and npm to be installed.

  1. Install AWS CDK
npm install -g aws-cdk

2. Clone my example project

git clone https://gitlab.com/eilon2/spa-flask-aws-cdk

3. Create & activate virtual env

python3 -m venv awscdk
. awscdk/bin/activate
pip install -r requirements.txt

Code Walkthrough

Directory tree

A quick look at AWS CDK project will highlight how easy it is:

  • The term ‘AWSGEN’ references to file that generated automatically by AWSCDK
.
├── app.py # AWSGEN
├── backend # The backend code of the project
│ ├── app.py
│ ├── Dockerfile
│ └── requirements.txt
├── cdk.json # AWSGEN
├── cdk.out # AWSGEN
├── frontend # The frontend code of the project
│ ├── package.json
│ └── pages
│ ├── about.vue
│ └── index.vue
├── package-lock.json
├── README.md
├── requirements.txt
├── setup.py # AWSGEN
├── source.bat # AWSGEN
└── spa_flask_aws_cdk # AWSGEN
├── __init__.py
├── spa_flask_aws_cdk.egg-info
│ ├── dependency_links.txt
│ ├── PKG-INFO
│ ├── requires.txt
│ ├── SOURCES.txt
│ └── top_level.txt
└── spa_flask_aws_cdk_stack.py
6 directories, 20 files

The most important file in our project is spa_flask_aws_cdk_stack.py; it contains all the Infrastructure as a code that we will use to build our infrastructure.

DNS & Certificates

Let’s start with making sure our users can browse the application using a domain name. I assume that there is already an existing root hosted zone — ROOT_ZONE; in our case, it will be saasment.com

We will create an already existing hosted zone as a reference in my AWS account, either by another CloudFormation Stack or by another CDK App.

To do so, I will use one of the CDK Constructs fromXXX() methods — from_hosted_zone_attributes. Creating the reference by using hosted_zone_id and zone_name.

IMPORTANT — unlike all classes we will create during the next steps — creating a reference by using formXXX() method will not create a component, it is just a reference.

# File: spa_flask_aws_cdk_stack.pyROOT_ZONE = 'saasment'# Create interface to already existing hosted zone
root_hosted_zone = route53.HostedZone.from_hosted_zone_attributes(self,
'RootHostedZone',
hosted_zone_id='Z05422531LZAQAHZETW5S',
zone_name=ROOT_ZONE)

Once we have the initial starting point, we can create our fully independent stack, starting with:

  1. Creating a new hosted zone ‘demo.saasment.com’
  2. Creating an NS record in the root hosted zone to point our new hosted zone
  3. Creating & validating certificates in ACM for both frontend ‘cdk-spa.demo.saasment.com’ and backend ‘cdk-spa-api.demo.saasment.com’ domains
# File: spa_flask_aws_cdk_stack.pyZONE = 'demo.saasment.com'
DOMAIN_NAME_FRONT = 'cdk-spa.demo.saasment.com'
DOMAIN_NAME_BACKEND = 'cdk-spa-api.demo.saasment.com'
# Create a new zone
hosted_zone = route53.HostedZone(self,
'HostedZone',
zone_name=ZONE)
# Create NS record in the root zone, it will point to our new zone
route53.NsRecord(self,
'HostedZoneNS',
values=hosted_zone.hosted_zone_name_servers,
zone=root_hosted_zone,
record_name=ZONE)
# Create & validate SSL certificates for frontend & backend domains
front_certificate = acm.Certificate(self,
'staticWebCert',
domain_name=DOMAIN_NAME_FRONT,
validation=acm.CertificateValidation.from_dns(hosted_zone))
backend_certificate = acm.Certificate(self,
'apiCert',
domain_name=DOMAIN_NAME_BACKEND,
validation=acm.CertificateValidation.from_dns(hosted_zone))

Static Frontend

One of the most exciting features of AWS CDK is the option to use standard development/build/deployment patterns.

In this case, I utilized ‘BundlingDockerImage’ pattern to deploy into S3 Bucket static HTML pages generated from the Nuxt project (located at ‘./frontend’). As you can see there, I don’t create any docker registry to support the S3deployment — it is fully transparent to us.

Having the build and deployment of the application layer inside the infrastructure highlights an essential advantage of using AWS CDK, it helps us have immutable and idempotent components across all our microservices, infrastructures, and applications.

Now we will create a CloudFront distribution that will fetch the content from the S3 static pages deployment and DNS record with the CloudFront as a target.

Our frontend infro + app layers
# File: spa_flask_aws_cdk_stack.py# Create bucket for static pagesstatic_bucket = s3.Bucket(self,
'staticWebBucket',
website_index_document='index.html',
website_error_document='index.html',
public_read_access=True)
# Upload nuxt static pages to S3
core_image=core.BundlingDockerImage.from_registry(image='node:lts')
build_cmd=['bash', '-c', ' && '.join(['ls -la ', 'npm install', 'npm run build', 'npm run generate', 'cp -r /asset-input/dist/* /asset-output/'])]
s3deployment = s3deploy.BucketDeployment(self,
'staticDeployment',
destination_bucket=static_bucket,
sources=[s3deploy.Source.asset('./frontend',
bundling=core.BundlingOptions(image=core_image,
command=build_cmd))])
# Init view policy
view_policy = cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS
# Init error responses
error_403 = cloudfront.ErrorResponse(http_status=403, response_http_status=200, response_page_path='/index.html')
error_404 = cloudfront.ErrorResponse(http_status=404, response_http_status=200, response_page_path='/index.html')distribution = cloudfront.Distribution(self,
'staticWebBucketDistribution',
default_behavior={
'origin': origins.S3Origin(static_bucket),
'viewer_protocol_policy': view_policy
},
domain_names=[DOMAIN_NAME_FRONT],
certificate=front_certificate,
error_responses=[error_403, error_404])
# Init alias
front_target = route53.RecordTarget.from_alias(targets.CloudFrontTarget(distribution))
# Create A & AAA records to point cloudfront and serves frontend
frond_r53_record = route53.AaaaRecord(self,
'frontendAlias',
zone=hosted_zone,
target=front_target,
record_name=DOMAIN_NAME_FRONT)

Serverless Backend

Flask is THE most popular Python web application development framework because of its simplicity, extensibility, and community. For the same reasons, AWS Lambda is THE most popular function as a service framework.

Bundling both to support our backend enables us to create low cost, pay-per-execution, and instant scale architecture.

Similar to the frontend S3 deployment, I am using an existing pattern, in this case — DockerImageFunction. Instead of uploading a ZIP file to my lambda function and losing time on structuring all the dependencies, I can use Dockerfile to build it in a standalone environment.

In the backend, instead of using Cloudfront, I will use APIGateway to forward the HTTP request to the flask application.

Our backend infra + app layers
# File: spa_flask_aws_cdk_stack.py# Pack backend code as docker
backend = lambda_.DockerImageFunction(self,
"ECRFunction",
code=lambda_.DockerImageCode.from_image_asset("./backend")
)
# Create API Gateway instance
apigwdomain = apigateway.DomainNameOptions(
certificate=backend_certificate,
domain_name=DOMAIN_NAME_BACKEND)
# Trigger lambda by APIGateway
apigw = apigateway.LambdaRestApi(self,
"myapi",
handler=backend,
domain_name=apigwdomain)
backend_target = route53.RecordTarget.from_alias(targets.ApiGateway(apigw))backend_alias = route53.AaaaRecord(self,
'backendAlias',
zone=hosted_zone,
target=backend_target,
record_name=DOMAIN_NAME_BACKEND)

Leapfrogging

Probably the most famous leapfrog is the mobile revolution, which put phones in the hands of millions of people while allowing developing nations to skip directly to mobile phones without the need to invest in landline infrastructure.

From my perspective, Cloudformation and Terraform are the landlines infrastructure of the Cloud Infrastructure technology. Any new business looking to build an application or any legacy business that will migrate its on-prem infrastructure to the cloud using AWS CDK is probably the right way.

Link to Gitlab repository — https://gitlab.com/eilon2/spa-flask-aws-cdk

--

--