Scenario
Here’s a real-world scenario…imagine “Aurora Digital, an expanding online retailer specializing in luxury home goods, 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.”
Now, your goal is to “help Aurora Digital implement a cloud-based infrastructure using AWS services, specifically focusing on scalability, security, cost-efficiency, and automation.” -Level Up In Tech, CDA03-CloudFormation
Objectives
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.
Why use CloudFormation (IaC)
Automation: Reduce human error and free up developer time to focus on other tasks by automating the provisioning and management of a complex cloud environment.
Consistency: Ensure consistent environments are created every time, thereby eliminating the “it works on my machine” problem.
Version Control: Enhance collaboration among team members by allowing infrastructure to be version-controlled and reviewed as part of the application code.
Reusability: Reuse templates across the company or community, speeding up future deployments and ensuring best practices are followed.
Prerequisites
- Be familiar with IaC
- Be familiar with YAML
- Be familiar with the AWS CLI
- Have a text editor (I use VSC)
Create the Solution Architecture
So the first step when receiving an objective is designing the solution. Before you start building, you have to know what you are trying to build. To do that, it’s always best practice to create a diagram. I use Draw.io.
For this objective, we need to create a web server for Aurora Digital to host their website. We also need this web server to be automatically scalable in the event of spikes, secure to protect the company’s assets, and resilient in the event of disaster.
This architecture will have multiple EC2 instances, running Apache web server. It will have an auto scaling group to automatically scale the number of instances required to handle the traffic. This will make it scalable. The instances will be spread across three public subnets in three availability zones all within a VPC, making it resilient to any disasters in the area. We will use a load balancer to, guess what… balance the load, of traffic between the instances. This will help with the resilience because if one instance in one availability zone goes down, the auto scaling group will create another instance in another availability zone and if one instance is not performing well or getting overwhelmed with traffic, the load balancer will re-distribute the load to make sure we optimize the utilization of all the instances. Also, we will use security groups to restrict access to the web server only from the load balancer. There will be two security groups (the security group for the load balancer is not displayed in the diagram), one for the webservers that restricts inbound traffic to only allow HTTP traffic from the load balancer and another security group for the load balancer that allows HTTP traffic from anywhere. Doing this makes sure all traffic is routed through the same path, making it easier to control and secure.
Create the YAML Script
Now, that you have the diagram and you know what you are building, build it. Create the script using YAML or JSON ( I will use YAML).
Below is my complete YAML script that I created in Visual Studio Code (VSC).
#cda03-cloudformation
AWSTemplateFormatVersion: "2010-09-09"
Description: The CloudFormation template in YAML for CDA03-CloudFormation use case.
Resources:
#vpc
CDA03VPC:
Type: AWS::EC2::VPC
Properties:
CidrBlock: 10.10.0.0/16
EnableDnsSupport: true
EnableDnsHostnames: true
Tags:
- Key: Name
Value: CDA03VPC
#subnet 1
CDA03PublicSubnet1:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref CDA03VPC
CidrBlock: 10.10.1.0/24
MapPublicIpOnLaunch: true
AvailabilityZone: us-east-1a
Tags:
- Key: Name
Value: CDA03PublicSubnet1
#subnet 2
CDA03PublicSubnet2:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref CDA03VPC
CidrBlock: 10.10.2.0/24
MapPublicIpOnLaunch: true
AvailabilityZone: us-east-1b
Tags:
- Key: Name
Value: CDA03PublicSubnet2
#subnet 3
CDA03PublicSubnet3:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref CDA03VPC
CidrBlock: 10.10.3.0/24
MapPublicIpOnLaunch: true
AvailabilityZone: us-east-1c
Tags:
- Key: Name
Value: CDA03PublicSubnet3
#igw
CDA03InternetGateway:
Type: AWS::EC2::InternetGateway
Properties:
Tags:
- Key: Name
Value: CDA03InternetGateway
#igw attachment
AttachGateway:
Type: AWS::EC2::VPCGatewayAttachment
Properties:
VpcId: !Ref CDA03VPC
InternetGatewayId: !Ref CDA03InternetGateway
#route table
CDA03RouteTable:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref CDA03VPC
Tags:
- Key: Name
Value: CDA03RouteTable
#route to internet
PublicRoute:
Type: AWS::EC2::Route
Properties:
RouteTableId: !Ref CDA03RouteTable
DestinationCidrBlock: 0.0.0.0/0
GatewayId: !Ref CDA03InternetGateway
# Subnet Route Table Associations
Subnet1RouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref CDA03PublicSubnet1
RouteTableId: !Ref CDA03RouteTable
Subnet2RouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref CDA03PublicSubnet2
RouteTableId: !Ref CDA03RouteTable
Subnet3RouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref CDA03PublicSubnet3
RouteTableId: !Ref CDA03RouteTable
#key pair
CDA03KeyPair:
Type: AWS::EC2::KeyPair
Properties:
KeyName: cda03-keypair
KeyType: rsa
#launch template
CDA03LaunchTemplate:
Type: AWS::EC2::LaunchTemplate
Properties:
LaunchTemplateData:
InstanceType: t2.micro
KeyName: cda03-keypair
ImageId: ami-0c02fb55956c7d316 # Amazon Linux 2 AMI ID (specific to region)
SecurityGroupIds:
- !Ref CDA03WebserverSecurityGroup
UserData:
Fn::Base64: |
#!/bin/bash
sudo yum update && sudo yum upgrade
sudo yum install httpd -y
sudo systemctl start httpd
sudo systemctl enable httpd
cd /usr/share/httpd/noindex/
chown apache:apache /usr/share/httpd/noindex/index.html
chmod 644 /usr/share/httpd/noindex/index.html
echo "hello world, from $HOSTNAME" | sudo tee /usr/share/httpd/noindex/index.html > /dev/null
TagSpecifications:
- ResourceType: instance
Tags:
- Key: Name
Value: CDA03-ApacheInstance
LaunchTemplateName: CDA03LaunchTemplate
#webserver sg
CDA03WebserverSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: Allow HTTP access only from the ALB
VpcId: !Ref CDA03VPC
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 80
ToPort: 80
SourceSecurityGroupId: !Ref CDA03ALBSecurityGroup
Tags:
- Key: Name
Value: CDA03WebserverSecurityGroup
#asg
CDA03ASG:
Type: AWS::AutoScaling::AutoScalingGroup
Properties:
LaunchTemplate:
LaunchTemplateId: !Ref CDA03LaunchTemplate
Version: !GetAtt CDA03LaunchTemplate.LatestVersionNumber
MinSize: 2
DesiredCapacity: 2
MaxSize: 5
VPCZoneIdentifier:
- !Ref CDA03PublicSubnet1
- !Ref CDA03PublicSubnet2
- !Ref CDA03PublicSubnet3
TargetGroupARNs:
- !Ref CDA03TargetGroup
Tags:
- Key: Name
Value: CDA03ASG
PropagateAtLaunch: true
# Target Group
CDA03TargetGroup:
Type: AWS::ElasticLoadBalancingV2::TargetGroup
Properties:
Name: CDA03TargetGroup
Protocol: HTTP
Port: 80
VpcId: !Ref CDA03VPC
TargetType: instance
HealthCheckEnabled: true
HealthCheckPath: /
HealthCheckIntervalSeconds: 30
HealthCheckTimeoutSeconds: 5
HealthyThresholdCount: 3
UnhealthyThresholdCount: 2
Matcher:
HttpCode: 200
#alb
CDA03LoadBalancer:
Type: AWS::ElasticLoadBalancingV2::LoadBalancer
Properties:
Name: CDA03ApplicationLoadBalancer
Scheme: internet-facing
Subnets:
- !Ref CDA03PublicSubnet1
- !Ref CDA03PublicSubnet2
- !Ref CDA03PublicSubnet3
SecurityGroups:
- !Ref CDA03ALBSecurityGroup
Tags:
- Key: Name
Value: CDA03LoadBalancer
#alb sg
CDA03ALBSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: Allow HTTP traffic from anywhere to ALB
VpcId: !Ref CDA03VPC
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 80
ToPort: 80
CidrIp: 0.0.0.0/0
Tags:
- Key: Name
Value: CDA03ALBSecurityGroup
#alb listener
CDA03ALBListener:
Type: AWS::ElasticLoadBalancingV2::Listener
Properties:
DefaultActions:
- Type: forward
TargetGroupArn: !Ref CDA03TargetGroup
LoadBalancerArn: !Ref CDA03LoadBalancer
Port: 80
Protocol: HTTP
#outputs (dns url)
Outputs:
ALBDNSName:
Description: "DNS name of the Application Load Balancer"
Value: !GetAtt CDA03LoadBalancer.DNSName
Export:
Name: "ALBDNSName"
Troubleshoot
Once I created the script, I used the AWS CLI to validate the code. In the terminal within VSC, I ran this command, aws cloudformation validate-template --template-body file://cda03-cloudformation-code.yml
to see if the code was all correct and ready to be deployed.
This was my response:
“An error occurred (ValidationError) when calling the ValidateTemplate operation: Invalid template resource property ‘ALBDNSName’”
There was an error with the output. I wanted the CloudFormation stack to output the DNS name of the load balancer so I don’t have to look around for it. After a little troubleshooting, I realized I had the outputs block within the resource block. The outputs block is not a resource and is supposed to be on par with the resource block. So after a minor adjustment, I saved the changes to the file and ran the validation again.
Success
Now that the code is validated, and confirmed working. We can create the CloudFormation stack. You will need to run this command aws cloudformation create-stack --stack-name CDA03Stack --template-body file://cda03-cloudformation-code.yml
and kick back and relax while CloudFormation creates everything. Wait a couple of minutes for all the services to finish provisioning.
If you try to query the stack for the load balancer DNS name before it is ready you will see a result of “none”, which just means it’s not done provisioning, so wait and try again.
Go to that address to confirm the web server is up and running. Make sure you use HTTP not HTTPS, because that is what we specified in the YAML code. Refresh the page a couple of times to verify that the load balancer is balancing between different instances.
Lastly, let’s confirm that you can only access the web server via the load balancer and not via the instance’s IP addresses directly. To find the instance’s IP addresses from the AWS CLI, use this command aws ec2 describe-instances --region us-east-1
to list all the instances in the us-east-1 region. The public IP address should be listed there, copy it and paste into a browser (using HTTP) to confirm that you cannot access the web server.
Congratulations!
If you followed along and reached this point, you should now see why IaC is better to use than manually creating everything in the console. You created a web server that is scalable, resilient, and secure, and you now can automate its creation process.
To delete the stack from the AWS CLI, use this command aws cloudformation delete-stack --stack-name CDA03Stack
. You should not get any response from the terminal but wait a couple minutes until the entire stack gets de-provisioned.
Originally published on Medium
Find me in Linkedin
For similar projects, check out my Github
Top comments (0)