Automating Cloud Infrastructure: Deploying a Scalable Web Application with AWS CloudFormation

Michael Johnson
14 min readJul 27, 2024

--

Today we are back with a new project. We will be looking at how to deploy AWS infrastructure using CloudFormation by assiting the company Aurora Digital.

Aurora Digital is an expanding online retailer specializing in luxury home goods, and has decided to transition its digital operations to AWS to leverage cloud computing’s benefits. The company is experiencing significant growth and needs a solution that can handle increasing traffic and scale during major sales events without sacrificing performance or security.

To do this, we will help Aurora Digital implement a cloud-based infrastructure using AWS services, specifically focusing on scalability, automation, security, and cost-efficiency. Therefore are objectives are as follows:

  • Enhance Scalability: Automatically adjust computing resources to meet demand, ensuring the platform remains operational and responsive during peak traffic periods.
  • Increase Reliability: Improve website uptime and reduce the risk of failures associated with physical hardware and manual interventions.
  • Boost Security: Implement advanced security protocols and isolation through AWS to protect sensitive customer data and transactions.
  • Reduce Operational Costs: Lower overall infrastructure costs by optimizing resource usage and eliminating the need for upfront hardware investments.

With that said after achieving our objectives will will go a little more in depth why CloudFormation could be an effective soulution for this. Without further ado let’s get into it.

Prerequisites:

  • AWS Account
  • Basic understanding of AWS infrastructure
  • Basic understanding of YAML
  • Basic understanding of AWS S3

Step One: Create a Foundational Template and a Stack

To begin we will create a foundational stack. For this first stack we will need to accomplish the following:

  1. Deploy an EC2 instance with an Apache Webserver.
  2. Create a Webserver Security Group with the needed permissions

To do this we will first need to write a template. As stated in the AWS documentation for CloudFormation. “You can create templates for the service or application architectures you want and have CloudFormation use those templates for quick and reliable provisioning of the services or applications (called “stacks”). You can also easily update or replicate the stacks as needed.

Also as stated in the AWS documentation:

“You can create templates using:

  • AWS Application Composer — A visual interface for designing templates.
  • AWS CloudFormation Designer — An older visual interface for template design.
  • Text Editor — Write templates directly in JSON or YAML syntax.
  • IaC generator — Generate templates from resources provisioned in your account that are not currently managed by CloudFormation. The IaC generator works with a wide range of resource types that are supported by the Cloud Control API in your Region.”

I will be using the Text Editor option, and will write my template using YAML format. I prefer YAML as I find it is easier to read and write. You can find how to format templated here and here

That stated, I used Visual Code Studio on my local device and created this template:

AWSTemplateFormatVersion: '2010-09-09'
Description: CloudFormation template for Aurora Digital to deploy an EC2 instance with Apache Webserver

Resources:
WebServerSecurityGroup:
Type: 'AWS::EC2::SecurityGroup'
Properties:
GroupDescription: Enable HTTP access
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 80
ToPort: 80
CidrIp: 0.0.0.0/0
- IpProtocol: tcp
FromPort: 22
ToPort: 22
CidrIp: 0.0.0.0/0

EC2Instance:
Type: 'AWS::EC2::Instance'
Properties:
InstanceType: t2.micro
SecurityGroups:
- !Ref WebServerSecurityGroup
ImageId: ami-0b72821e2f351e396 # Amazon Linux 2023 AMI (for us-east-1, replace with appropriate region's AMI ID)
UserData:
Fn::Base64: !Sub |
#!/bin/bash
dnf update -y
dnf install -y httpd
systemctl start httpd
systemctl enable httpd
Tags:
- Key: Name
Value: AuroraDigitalWebServer

Outputs:
InstanceId:
Description: The Instance ID of the EC2 instance
Value: !Ref EC2Instance

PublicIP:
Description: Public IP address of the EC2 instance
Value: !GetAtt EC2Instance.PublicIp

With our template created we will now navigate on the AWS Console to CloudFormation and click on Create Stack.

Starting process to create a Stack

Next we will choose how to upload our template. An important note is that all Cloudformation templates are ultimately stored in an S3 Bucket. This is because CloudFormation references the template that is stored in S3 when creating a stack. You can read more about that here.

That said I will pick the Upload template file and Choose file options. After selecting our file a URL path will be created automatically for the S3 Bucket that will be provisioned for our template.

S3 Bucket URL

Let’s click next and move to our next step. For our next step we need to give our stack a name. I will name our stack AuroraDigitalStack

Naming our Stack

Nothing else to do here so let’s move on. Next we can configure our stack options by adding tags, assigning IAM roles, stack failure options etc. For our purposes we will keep all these options default and move to our next step.

Next we will review our selected options and create the stack. From here CloudFormation will reference our template, and then provision and create our infrastructure automatically. Thus eliminating the need to figure out ordering and orchestration of our infrastructure. That’s the power of declarative programming. So let’s press Submit and see what happens.

After waiting a few moments our Stack was created. Let’s take a look at the Events to see what exactly happened.

Our Events for our Foundational Stack

As we can see CloudFormation created an EC2 instance and a security group called WebServerSecurityGroup. Let’s navigate over to the EC2 console and take a look at our EC2 instance and the security group.

EC2 Instance and Security Group

As we can see there is now a running EC2 instance and a security group attached called WebServerSecurityGroup. Let’s see if the Apache Webserver test page loads up by going to the public IP of our EC2.

Success!!

Awesome the test page for Apache for Amazon Linux 2023 was loaded when using the public IP address of our instance. Just like that in a few short moments we have a foundational template that can be built on. This is one of the advantages of CloudFormation. No resources are manually created, which is excellent for control. The code can version controlled, and changes to the infrastructure are reviewed through code. For productivity, we also have the ability to destroy and re-create an infrastructure on the cloud quickly. That makes life so much easier!!

Let’s move on in developing Aurora Digital’s website. Next we will create an advance template.

Step Two: Create an Advance Template and Stack

Next we will modify our foundational stack to achieve the following objectives:

  1. Create an autoscaling group using t2.micro instances. All instances should have apache installed on each instance with a custom web page.
  2. The autoscaling min and max should be 2 and 5.
  3. The Autoscaling Group should span to at least 3 of the default public subnets.
  4. Deploy a Webserver Security Group with the necessary permissions.

To do so we will create a copy on our local device of our Foundational template and edit it. The new file verison will be named Advance.yaml

After editing it our new file looks like this:

AWSTemplateFormatVersion: '2010-09-09'
Description: CloudFormation template for Aurora Digital to deploy an Auto Scaling Group with Apache Webserver

Resources:
WebServerSecurityGroup:
Type: 'AWS::EC2::SecurityGroup'
Properties:
GroupDescription: Enable HTTP access
VpcId: vpc-0ce418xxxxb42a03 #Put your own VPC-ID here
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 80
ToPort: 80
CidrIp: 0.0.0.0/0
- IpProtocol: tcp
FromPort: 22
ToPort: 22
CidrIp: 0.0.0.0/0

LaunchTemplate:
Type: 'AWS::EC2::LaunchTemplate'
Properties:
LaunchTemplateName: AuroraDigitalLaunchTemplate
LaunchTemplateData:
InstanceType: t2.micro
ImageId: ami-0b72821e2f351e396 # Amazon Linux 2023 AMI (for us-east-1, replace with appropriate region's AMI ID)
SecurityGroupIds:
- !Ref WebServerSecurityGroup
UserData:
Fn::Base64: !Sub |
#!/bin/bash
dnf update -y
dnf install -y httpd
systemctl start httpd
systemctl enable httpd
echo "<h1>Hello World from $(hostname -f)</h1>" > /var/www/html/index.html

AutoScalingGroup:
Type: 'AWS::AutoScaling::AutoScalingGroup'
Properties:
VPCZoneIdentifier:
- subnet-072e1xxxxx514b86b
- subnet-09535xxxxxxx7ed23
- subnet-085dxxxxxxxxx3af6
LaunchTemplate:
LaunchTemplateId: !Ref LaunchTemplate
Version: !GetAtt LaunchTemplate.LatestVersionNumber
MinSize: '2'
MaxSize: '5'
DesiredCapacity: '3'
Tags:
- Key: Name
Value: AuroraDigitalASG
PropagateAtLaunch: true

Outputs:
AutoScalingGroupId:
Description: The Auto Scaling Group ID
Value: !Ref AutoScalingGroup

LaunchTemplateId:
Description: The Launch Template ID
Value: !Ref LaunchTemplate

This new template will create an AutoScaling Group, and assign the three public Subnets that I specified. Since we already have an EC2 running to prevent a failure to create the stack I used the default VPC. In addition I reused some of the same resources as before with an added custom webpage. So let’s head back to the CloudFormation console. We will first select our existing Stack then press Update.

Next we will select Replace existing template, then Upload template file, select our Advance.yaml file the click next. Since I already hardcoded my Subnets we don’t have to fill out any parameters so next. We will now as before keep the default options under the Configure stack options section, and move on to our Review step. One usefully feature is when updating a template CloudFormation will show the changes that will happen before you continue.

Here we see that the previous instance will be removed since it was launched without our custom webpage and was not assigned to our newly created ASG. Also we see an ASG will be created and it will have the ASG Launch Template we specified that will have our custom webpage. So let’s submit and see what happens.

Success

Awesome!!! Let’s go over to to EC2 Console and look at what was created.

ASG and it’s Instances

We can see our previous instance was terminated, and our desired three instances were launched from the ASG Launch template. Let’s also look at the ASG’s details

ASG Group Details

So now let’s visit the public DNS of one of our instances and see our new custom webpage shall we?

Our index.html test was successful

Things are looking good. Let’s go to our finally step and complete our project for Aurora Digital.

Step Three: Create a Complex Template and Stack

Our final step should look similar to our previous project. For this finally step our objectives will be to:

  1. Create a VPC with cidr 10.10.0.0/16 (Without using the VPC & More option)
  2. Create three public subnets with 10.10.1.0/24 & 10.10.2.0/24 & 10.10.3.0/24
  3. Create an autoscaling group using t2.micro instances. All instances should have apache installed on each instance with a custom web page. Ensure the autoscaling group is using the public subnets from #2.
  4. The autoscaling min and max should be 2 and 5.
  5. Create an Application Load Balancer to distribute traffic to the autoscaling group.
  6. Create web server security group that allows inbound traffic from HTTP from your Application Load Balancer. As a security precaution, no one should be able to use the public ip of your webserver to reach your website. All users should be using the Application Load Balancer.
  7. Create a load balancer security group that allows inbound traffic from HTTP from 0.0.0.0/0.
  8. Use the DNS url of the Application Load Balancer in a browser to verify you can reach your site.

Alrighty let’s get to it. We will now create a new index.html file and update our CloudFormation template. This time we will name the new file version. complex.yaml

After referencing the AWS documentation and using the Application Composer template validator(found within Cloudformation console) I created this template:

AWSTemplateFormatVersion: '2010-09-09'
Description: CloudFormation template to deploy a VPC, subnets, an Auto Scaling group, and an Application Load Balancer with necessary security groups for Aurora Digital

Resources:
# Create a VPC
VPC:
Type: 'AWS::EC2::VPC'
Properties:
CidrBlock: 10.10.0.0/16
EnableDnsSupport: true
EnableDnsHostnames: true
Tags:
- Key: Name
Value: auroradigital-vpc

PublicSubnet1:
Type: 'AWS::EC2::Subnet'
Properties:
VpcId: !Ref VPC
CidrBlock: 10.10.1.0/24
MapPublicIpOnLaunch: true
AvailabilityZone: "us-east-1a"
Tags:
- Key: Name
Value: AuroraDigitalSubnet-1

PublicSubnet2:
Type: 'AWS::EC2::Subnet'
Properties:
VpcId: !Ref VPC
CidrBlock: 10.10.2.0/24
MapPublicIpOnLaunch: true
AvailabilityZone: "us-east-1b"
Tags:
- Key: Name
Value: AuroraDigitalSubnet-2

PublicSubnet3:
Type: 'AWS::EC2::Subnet'
Properties:
VpcId: !Ref VPC
CidrBlock: 10.10.3.0/24
MapPublicIpOnLaunch: true
AvailabilityZone: "us-east-1c"
Tags:
- Key: Name
Value: AuroraDigitalSubnet-3

# Create an internet gateway and attach it to the VPC
InternetGateway:
Type: 'AWS::EC2::InternetGateway'
Properties:
Tags:
- Key: Name
Value: AuroraDigital-igw

AttachGateway:
Type: 'AWS::EC2::VPCGatewayAttachment'
Properties:
VpcId: !Ref VPC
InternetGatewayId: !Ref InternetGateway

# Create a route table for the public subnets
PublicRouteTable:
Type: 'AWS::EC2::RouteTable'
Properties:
VpcId: !Ref VPC
Tags:
- Key: Name
Value: AuroraDigitalPublic-rtb

PublicRoute:
Type: 'AWS::EC2::Route'
Properties:
RouteTableId: !Ref PublicRouteTable
DestinationCidrBlock: 0.0.0.0/0
GatewayId: !Ref InternetGateway

SubnetRouteTableAssociation1:
Type: 'AWS::EC2::SubnetRouteTableAssociation'
Properties:
SubnetId: !Ref PublicSubnet1
RouteTableId: !Ref PublicRouteTable

SubnetRouteTableAssociation2:
Type: 'AWS::EC2::SubnetRouteTableAssociation'
Properties:
SubnetId: !Ref PublicSubnet2
RouteTableId: !Ref PublicRouteTable

SubnetRouteTableAssociation3:
Type: 'AWS::EC2::SubnetRouteTableAssociation'
Properties:
SubnetId: !Ref PublicSubnet3
RouteTableId: !Ref PublicRouteTable

# Create a security group for the load balancer
LoadBalancerSecurityGroup:
Type: 'AWS::EC2::SecurityGroup'
Properties:
GroupDescription: Enable HTTP access
VpcId: !Ref VPC
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 80
ToPort: 80
CidrIp: 0.0.0.0/0
Tags:
- Key: Name
Value: auroradigital-lb-sg

# Create a security group for the EC2 instances
InstanceSecurityGroup:
Type: 'AWS::EC2::SecurityGroup'
Properties:
GroupDescription: Enable HTTP access from the load balancer only
VpcId: !Ref VPC
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 80
ToPort: 80
SourceSecurityGroupId: !Ref LoadBalancerSecurityGroup
Tags:
- Key: Name
Value: auroradigital-instance-sg

# Create a launch template for the Auto Scaling group
LaunchTemplate:
Type: 'AWS::EC2::LaunchTemplate'
Properties:
LaunchTemplateData:
ImageId: ami-0b72821e2f351e396
InstanceType: t2.micro
SecurityGroupIds:
- !Ref InstanceSecurityGroup
UserData: !Base64 |
#!/bin/bash
dnf update -y
dnf install -y httpd
systemctl start httpd
systemctl enable httpd
cat <<EOF > /var/www/html/index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Aurora Digital</title>
<style>
body {
font-family: Arial, sans-serif;
margin: 0;
padding: 0;
background-color: #f4f4f4;
color: #333;
}
header {
background-color: #222;
color: white;
padding: 1rem 0;
text-align: center;
}
nav {
display: flex;
justify-content: center;
background-color: #444;
}
nav a {
color: white;
padding: 1rem;
text-decoration: none;
}
nav a:hover {
background-color: #333;
}
.hero {
background-image: url('https://img.freepik.com/free-photo/modern-apartment-interior-with-elegant-decor-comfort-generative-ai_188544-12683.jpg?t=st=1721418005~exp=1721421605~hmac=8a63bf459658e0c56ba4c34f75005eec78b0fd6123c2453b676730369c7659a5&w=2000');
background-size: cover;
background-position: center;
height: 400px;
color: white;
display: flex;
justify-content: center;
align-items: center;
text-align: center;
}
.hero h1 {
font-size: 3rem;
margin: 0;
}
.container {
padding: 2rem;
}
.products {
display: flex;
flex-wrap: wrap;
gap: 1rem;
justify-content: center;
}
.product {
background-color: white;
border: 1px solid #ddd;
padding: 1rem;
width: calc(33.333% - 2rem);
box-sizing: border-box;
text-align: center;
}
.product img {
max-width: 100%;
}
footer {
background-color: #222;
color: white;
text-align: center;
padding: 1rem 0;
position: relative;
bottom: 0;
width: 100%;
}
.address {
margin: 0;
}
</style>
</head>
<body>
<header>
<h1>Aurora Digital</h1>
</header>
<nav>
<a href="#">Home</a>
<a href="#">Shop</a>
<a href="#">About Us</a>
<a href="#">Contact</a>
</nav>
<div class="hero">
<h1>Discover Your Luxury</h1>
</div>
<div class="container">
<h2>Featured Products</h2>
<div class="products">
<div class="product">
<img src="https://img.freepik.com/free-photo/digital-lavender-interior-design_23-2151561200.jpg?t=st=1721417603~exp=1721421203~hmac=ffa933208e3e5dce9f6ea36c292fb76574b41c4d72bcacfc3d39344b0b91c288&w=1800" alt="Luxury Sofa">
<h3>Luxury Sofa</h3>
<p>$999.99</p>
</div>
<div class="product">
<img src="https://img.freepik.com/free-photo/dark-style-lamp-indoors_23-2151561298.jpg?t=st=1721417603~exp=1721421203~hmac=f7c563ff49e1ee1d133720e6a80a1b63df5c22928a65ae6de7a144c59b9ee4c6&w=1800" alt="Designer Lamp">
<h3>Designer Lamp</h3>
<p>$199.99</p>
</div>
<div class="product">
<img src="https://img.freepik.com/free-photo/minimalist-interior-design-with-armchair-generative-ai_188544-12520.jpg?t=st=1721418005~exp=1721421605~hmac=1878d7227f64ae42e3507d5d74247b6dcf0eaa2b19953c2885db2bca4f014177&w=2000" alt="Elegant Armchair">
<h3>Elegant Armchair</h3>
<p>$299.99</p>
</div>
</div>
</div>
<footer>
<p>&copy; 2024 Aurora Digital. All rights reserved.</p>
<p class="address">1234 Luxury Ave, New York, New York, 10001</p>
</footer>
</body>
</html>
EOF

LaunchTemplateName: auroradigital-launch-template

# Create an Application Load Balancer
LoadBalancer:
Type: 'AWS::ElasticLoadBalancingV2::LoadBalancer'
Properties:
Name: AuroraDigitalLoadBalancer
Scheme: internet-facing
Subnets:
- !Ref PublicSubnet1
- !Ref PublicSubnet2
- !Ref PublicSubnet3
SecurityGroups:
- !Ref LoadBalancerSecurityGroup

# Create a target group
TargetGroup:
Type: 'AWS::ElasticLoadBalancingV2::TargetGroup'
Properties:
VpcId: !Ref VPC
Port: 80
Protocol: HTTP
TargetType: instance
HealthCheckIntervalSeconds: 30
HealthCheckProtocol: HTTP
HealthCheckTimeoutSeconds: 5
HealthyThresholdCount: 5
UnhealthyThresholdCount: 2
Matcher:
HttpCode: 200
Name: AuroraDigitalTG

# Create a listener for the load balancer
Listener:
Type: 'AWS::ElasticLoadBalancingV2::Listener'
Properties:
LoadBalancerArn: !Ref LoadBalancer
Port: 80
Protocol: HTTP
DefaultActions:
- Type: forward
TargetGroupArn: !Ref TargetGroup

# Create an Auto Scaling group
AutoScalingGroup:
Type: 'AWS::AutoScaling::AutoScalingGroup'
Properties:
VPCZoneIdentifier:
- !Ref PublicSubnet1
- !Ref PublicSubnet2
- !Ref PublicSubnet3
LaunchTemplate:
LaunchTemplateId: !Ref LaunchTemplate
Version: !GetAtt LaunchTemplate.LatestVersionNumber
MinSize: 2
MaxSize: 5
DesiredCapacity: 3
TargetGroupARNs:
- !Ref TargetGroup
Tags:
- Key: Name
Value: AuroraDigitalASG
PropagateAtLaunch: true

Outputs:
VPCId:
Description: "VPC Id"
Value: !Ref VPC
PublicSubnet1Id:
Description: "Public Subnet 1 Id"
Value: !Ref PublicSubnet1
PublicSubnet2Id:
Description: "Public Subnet 2 Id"
Value: !Ref PublicSubnet2
PublicSubnet3Id:
Description: "Public Subnet 3 Id"
Value: !Ref PublicSubnet3
LoadBalancerDNSName:
Description: "DNS Name of the load balancer"
Value: !GetAtt LoadBalancer.DNSName

Speaking of the Application Composer template validator I find it to be a really helpful tool. The first time I ran this template I had this error

So I went to Application Composer to validate my template and corrected the error, and sure enough after doing so my CloudFormation template came back as valid. But before we continue I would like to share a little further back story on this error. For the Complex Step, it took me a couple days to complete. While Application Composer helped me get my template valid and able to launch, I wasn’t seeing the custom webpage I had created. I tried manually combing VCS whenever I had a free moment outside of work and personal errands trying to figure it out, until finally realized I was missing dnf update -y That is a lesson that while Application Composer is usefully, don’t completely rely on it. So let’s continue and visit the final website version. First we will try using the Public IP address of the EC2 instance

Our ALB is working as intened. Traffic should only go through the ABL. Let’s use the DNS of our ABL now

And here we go…

HOOOORAYYYY

Our website has loaded. Aurora Digital now has a functioning website to fulfill their desired needs!

Let’s summarize what we have accomplished

Summary

I hope this has been a useful case to show the strengths of CloudFormation. Using AWS CloudFormation provided us several advantages over manual resource creation such as:

  • Automation: Automates the provisioning and management of a complex cloud environment, reducing human error and freeing up developer time to focus on other tasks.
  • Consistency: Ensures consistent environments are created every time, crucial for testing and production stages, thereby eliminating the “it works on my machine” problem.
  • Version Control: Allows infrastructure to be version-controlled and reviewed as part of application code, enhancing collaboration among team members and rollback capabilities.
  • Reusability: Enables reusing templates across the company or community, speeding up future deployments and ensuring best practices are followed.

Alright, that’s it for now until next time.. Bye!!

--

--

No responses yet