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)