Deploy a high-availability web app using CloudFormation

INTRODUCTION

I’m excited to discuss a fascinating project I had the chance to work on this year in this piece. I began a journey that not just expanded my comprehension but also gave me the ability to use cutting-edge cloud computing and DevOps techniques after being inspired by the in-depth curricula of Udacity Cloud DevOps course. This project specifically explored the world of Infrastructure as Code using AWS CloudFormation, giving me great practical experience in constructing scalable cloud infrastructure.
You can simply locate the project’s source code on my GitHub repository if you’re interested in looking through it.

Project Scenario

Your company is creating an Instagram clone called Udagram, and the requirement is to deploy this new application to the AWS infrastructure using Infrastructure as Code.

You have been tasked with provisioning the required infrastructure and deploying a dummy application, along with the necessary supporting software.

Since the underlying network infrastructure will be maintained by a separate team, you must create independent stacks for the network infrastructure and the application itself.

Infrastructure spin up and tear down needs to be automated so that each team can create and discard testing environments on demand.

Server specifications

Application servers have a launch configuration that allows you to deploy four servers—two in each of your private subnets. An auto-scaling group used the launch configuration. There were two virtual CPUs and 4GB of RAM. Ubuntu 18 serves as the operating system. The best instance size and machine image (AMI) for this specification was selected.

My Architecture Diagram

Here is the information in the parameter files and configuration files for the servers (EC2 instances), S3 buckets, and network infrastructure.

network-parameters.json

[
    {
      "ParameterKey": "EnvironmentName",
      "ParameterValue": "udagram-cf-network"
    }
  ]

Configuration of a network
A network infrastructure with public and private subnets, routing, and internet access is created via the network.yaml file below. It provides subnet CIDR blocks, VPC CIDR blocks, and environment customization settings. A VPC, an internet gateway, public and private subnets, NAT gateways, and route tables are all created by the code. Important data like VPC IDs, route table IDs, and subnet IDs are defined as outputs. By using this CloudFormation template, a network configuration that can route internet traffic to both public and private subnets can be built.

Description: >
  Charles Asirifi
  This template deploys a VPC with public and private subnets in two Availabilty Zones (AZs). The subnets are in pairs.
  Also deployed in an Internet Gateway, with a default route on the public subnets and a pair of NAT Gateways, one in each Availability Zone, 
  and default routes for them in the private subnets.

Parameters:

  EnvironmentName:
    Description: Environment name prefixed to resource names
    Type: String

  VpcCIDR: 
    Description: IP range in CIDR notation for this VPC
    Type: String
    Default: 10.0.0.0/16

  PublicSubnet1CIDR:
    Description: IP range in CIDR notation for the public subnet in the first AZ
    Type: String
    Default: 10.0.0.0/24

  PublicSubnet2CIDR:
    Description: IP range in CIDR notation for the public subnet in the second AS
    Type: String
    Default: 10.0.1.0/24

  PrivateSubnet1CIDR:
    Description: IP range in CIDR notation for the private subnet in the first AZ
    Type: String
    Default: 10.0.2.0/24

  PrivateSubnet2CIDR:
    Description: IP range in CIDR notation for the private subnet in the second AZ
    Type: String
    Default: 10.0.3.0/24

Resources:

  VPC: 
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: !Ref VpcCIDR
      EnableDnsHostnames: true
      Tags: 
        - Key: Name 
          Value: !Ref EnvironmentName
          
  InternetGateway:
    Type: AWS::EC2::InternetGateway
    Properties:
      Tags:
        - Key: Name
          Value: !Ref EnvironmentName
          
  InternetGatewayAttachment:
    Type: AWS::EC2::VPCGatewayAttachment
    Properties:
      InternetGatewayId: !Ref InternetGateway
      VpcId: !Ref VPC

  PublicSubnet1: 
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      AvailabilityZone: !Select [ 0, !GetAZs '' ]
      CidrBlock: !Ref PublicSubnet1CIDR
      MapPublicIpOnLaunch: true
      Tags: 
        - Key: Name 
          Value: !Sub ${EnvironmentName} Public Subnet (AZ1)

  PublicSubnet2: 
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      AvailabilityZone: !Select [ 1, !GetAZs '' ]
      CidrBlock: !Ref PublicSubnet2CIDR
      MapPublicIpOnLaunch: true
      Tags: 
        - Key: Name 
          Value: !Sub ${EnvironmentName} Public Subnet (AZ2)

  PrivateSubnet1: 
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      AvailabilityZone: !Select [ 0, !GetAZs '' ]
      CidrBlock: !Ref PrivateSubnet1CIDR
      MapPublicIpOnLaunch: false
      Tags: 
        - Key: Name 
          Value: !Sub ${EnvironmentName} Private Subnet (AZ1)

  PrivateSubnet2: 
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      AvailabilityZone: !Select [ 1, !GetAZs '' ]
      CidrBlock: !Ref PrivateSubnet2CIDR
      MapPublicIpOnLaunch: false
      Tags: 
        - Key: Name 
          Value: !Sub ${EnvironmentName} Private Subnet (AZ2)

  NatGateway1EIP:
    Type: AWS::EC2::EIP
    DependsOn: InternetGatewayAttachment
    Properties: 
      Domain: vpc

  NatGateway2EIP:
    Type: AWS::EC2::EIP
    DependsOn: InternetGatewayAttachment
    Properties:
      Domain: vpc

  NatGateway1: 
    Type: AWS::EC2::NatGateway
    Properties: 
      AllocationId: !GetAtt NatGateway1EIP.AllocationId
      SubnetId: !Ref PublicSubnet1

  NatGateway2: 
    Type: AWS::EC2::NatGateway
    Properties:
      AllocationId: !GetAtt NatGateway2EIP.AllocationId
      SubnetId: !Ref PublicSubnet2

  PublicRouteTable:
    Type: AWS::EC2::RouteTable
    Properties: 
      VpcId: !Ref VPC
      Tags: 
        - Key: Name 
          Value: !Sub ${EnvironmentName} Public Routes

  DefaultPublicRoute: 
    Type: AWS::EC2::Route
    DependsOn: InternetGatewayAttachment
    Properties: 
      RouteTableId: !Ref PublicRouteTable
      DestinationCidrBlock: 0.0.0.0/0
      GatewayId: !Ref InternetGateway

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

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

  PrivateRouteTable1:
    Type: AWS::EC2::RouteTable
    Properties: 
      VpcId: !Ref VPC
      Tags: 
        - Key: Name 
          Value: !Sub ${EnvironmentName} Private Routes (AZ1)

  DefaultPrivateRoute1:
    Type: AWS::EC2::Route
    Properties:
      RouteTableId: !Ref PrivateRouteTable1
      DestinationCidrBlock: 0.0.0.0/0
      NatGatewayId: !Ref NatGateway1

  PrivateSubnet1RouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref PrivateRouteTable1
      SubnetId: !Ref PrivateSubnet1

  PrivateRouteTable2:
    Type: AWS::EC2::RouteTable
    Properties: 
      VpcId: !Ref VPC
      Tags: 
        - Key: Name 
          Value: !Sub ${EnvironmentName} Private Routes (AZ2)

  DefaultPrivateRoute2:
    Type: AWS::EC2::Route
    Properties:
      RouteTableId: !Ref PrivateRouteTable2
      DestinationCidrBlock: 0.0.0.0/0
      NatGatewayId: !Ref NatGateway2

  PrivateSubnet2RouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref PrivateRouteTable2
      SubnetId: !Ref PrivateSubnet2

Outputs: 

  VPC: 
    Description: A reference to the created VPC
    Value: !Ref VPC
    Export:
      Name: !Sub ${EnvironmentName}-VPCID

  VPCPublicRouteTable:
    Description: Public Routing
    Value: !Ref PublicRouteTable
    Export:
      Name: !Sub ${EnvironmentName}-PUB-RT

  VPCPrivateRouteTable1:
    Description: Private Routing AZ1
    Value: !Ref PrivateRouteTable1
    Export:
      Name: !Sub ${EnvironmentName}-PRI1-RT

  VPCPrivateRouteTable2:
    Description: Private Routing AZ2
    Value: !Ref PrivateRouteTable2
    Export:
      Name: !Sub ${EnvironmentName}-PRI2-RT

  PublicSubnets:
    Description: A list of the public subnets
    Value: !Join [ ",", [ !Ref PublicSubnet1, !Ref PublicSubnet2 ]]
    Export:
      Name: !Sub ${EnvironmentName}-PUB-NETS

  PrivateSubnets:
    Description: A list of the private subnets
    Value: !Join [ ",", [ !Ref PrivateSubnet1, !Ref PrivateSubnet2 ]]
    Export:
      Name: !Sub ${EnvironmentName}-PRIV-NETS

  PublicSubnet1:
    Description: A reference to the public subnet in the 1st Availability Zone
    Value: !Ref PublicSubnet1
    Export:
      Name: !Sub ${EnvironmentName}-PUB1-SN

  PublicSubnet2: 
    Description: A reference to the public subnet in the 2nd Availability Zone
    Value: !Ref PublicSubnet2
    Export:
      Name: !Sub ${EnvironmentName}-PUB2-SN

  PrivateSubnet1:
    Description: A reference to the private subnet in the 1st Availability Zone
    Value: !Ref PrivateSubnet1
    Export:
      Name: !Sub ${EnvironmentName}-PRI1-SN

  PrivateSubnet2: 
    Description: A reference to the private subnet in the 2nd Availability Zone
    Value: !Ref PrivateSubnet2
    Export:
      Name: !Sub ${EnvironmentName}-PRI2-SN

udagram-parameters.json

[
    {
      "ParameterKey": "EnvironmentName",
      "ParameterValue": "cloudformation-server"
    },
    {
      "ParameterKey": "NetworkEnvironmentName",
      "ParameterValue": "cloudformation-network"
    }
  ]

udagram.yml

This template deploys a High-Availability Web Application in the cloud
using Load Balancers, AutoScaling Groups and related infrastructure.

Description:
  Charles Asirifi
  This template deploys a High-Availability Web Application in the cloud
  using Load Balancers, AutoScaling Groups and related infrastructure.

Parameters:

  EnvironmentName:
    Description: Environment name prefixed to server resource names
    Type: String

  NetworkEnvironmentName:
    Description: Environment name prefixed to network resource names
    Type: String

  AMI:
      Description: 'The AWS Machine Image used.'
      Type: String
      Default: ami-053b0d53c279acc90

  InstanceType:
    Description: EC2 instance type
    Type: String
    Default: t3.medium

Resources:
  
  UdacityS3ReadOnlyEC2:
    Type: AWS::IAM::Role
    Properties:
      ManagedPolicyArns: 
        - "arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess"
      AssumeRolePolicyDocument: 
        Version: "2012-10-17"
        Statement: 
          - 
            Effect: "Allow"
            Principal: 
              Service: 
                - "ec2.amazonaws.com"
            Action: 
              - "sts:AssumeRole"
      Path: "/"

  ProfileWithRolesForOurApp:
    Type: AWS::IAM::InstanceProfile
    Properties: 
      Roles:
        - !Ref UdacityS3ReadOnlyEC2

  LBSecGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: Allow http to our load balancer
      VpcId:
        Fn::ImportValue:
          !Sub "${NetworkEnvironmentName}-VPCID"
      SecurityGroupIngress:
      - IpProtocol: tcp
        FromPort: 80
        ToPort: 80
        CidrIp: 0.0.0.0/0
      SecurityGroupEgress:
      - IpProtocol: tcp
        FromPort: 80
        ToPort: 80
        CidrIp: 0.0.0.0/0

  WebServerSecGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: Allow http to our hosts and SSH from local only
      VpcId:
        Fn::ImportValue:
          !Sub "${NetworkEnvironmentName}-VPCID"
      SecurityGroupIngress:
      - IpProtocol: tcp
        FromPort: 80
        ToPort: 80
        CidrIp: 0.0.0.0/0
      SecurityGroupEgress:
      - IpProtocol: tcp
        FromPort: 0
        ToPort: 65535
        CidrIp: 0.0.0.0/0

  WebAppLaunchConfig:
    Type: AWS::AutoScaling::LaunchConfiguration
    Properties:
      UserData:
        Fn::Base64: !Sub |
          #!/bin/bash
          apt-get update -y
          apt-get install apache2 -y
          systemctl start apache2.service
          cd /var/www/html
          echo "it works! Udagram, Udacity" > index.html
         
      ImageId: !Ref AMI
      IamInstanceProfile: !Ref ProfileWithRolesForOurApp
      SecurityGroups:
      - Ref: WebServerSecGroup
      InstanceType: !Ref InstanceType
      BlockDeviceMappings:
      - DeviceName: "/dev/sdk"
        Ebs:
          VolumeSize: '10'

  WebAppGroup:
    Type: AWS::AutoScaling::AutoScalingGroup
    Properties:
      VPCZoneIdentifier:
      - Fn::ImportValue: 
          !Sub "${NetworkEnvironmentName}-PRIV-NETS"
      LaunchConfigurationName:
        Ref: WebAppLaunchConfig
      MinSize: '4'
      MaxSize: '6'
      TargetGroupARNs:
      - Ref: WebAppTargetGroup

  WebAppLB:
    Type: AWS::ElasticLoadBalancingV2::LoadBalancer
    Properties:
      Subnets:
      - Fn::ImportValue: !Sub "${NetworkEnvironmentName}-PUB1-SN"
      - Fn::ImportValue: !Sub "${NetworkEnvironmentName}-PUB2-SN"
      SecurityGroups:
      - Ref: LBSecGroup

  Listener:
    Type: AWS::ElasticLoadBalancingV2::Listener
    Properties:
      DefaultActions:
      - Type: forward
        TargetGroupArn:
          Ref: WebAppTargetGroup
      LoadBalancerArn:
        Ref: WebAppLB
      Port: '80'
      Protocol: HTTP

  ALBListenerRule:
      Type: AWS::ElasticLoadBalancingV2::ListenerRule
      Properties:
        Actions:
        - Type: forward
          TargetGroupArn: !Ref 'WebAppTargetGroup'
        Conditions:
        - Field: path-pattern
          Values: [/]
        ListenerArn: !Ref 'Listener'
        Priority: 1

  WebAppTargetGroup:
    Type: AWS::ElasticLoadBalancingV2::TargetGroup
    Properties:
      HealthCheckIntervalSeconds: 10
      HealthCheckPath: /
      HealthCheckProtocol: HTTP
      HealthCheckTimeoutSeconds: 8
      HealthyThresholdCount: 2
      Port: 80
      Protocol: HTTP
      UnhealthyThresholdCount: 5
      VpcId: 
        Fn::ImportValue:
          Fn::Sub: "${NetworkEnvironmentName}-VPCID"

Outputs: 

  LoadBalancerUrl: 
    Description: The URL to the Load Balancer
    Value: !Join [ '', [ http://, !GetAtt WebAppLB.DNSName ] ]
    Export:
      Name: !Sub ${EnvironmentName}-LBID

Script Usage

This Repository contains some scripts that will be used to create, update and delete the CloudFormation stack.

Create.sh

./create.sh (stackName) (script.yml) (parameters.json) (profile)

Below is the content of the create.sh script

aws cloudformation create-stack \
--stack-name $1 \
--template-body file://$2 \
--parameters file://$3 \
--region=us-east-1 \
--capabilities CAPABILITY_NAMED_IAM

Example:

./create.sh Udagram infrastructure/network.yaml parameters/network.json charles_user

Update:

./update.sh (stackName) (script.yml) (parameters.json) (profile)

Example:

./update.sh Udagram infrastructure/network.yaml parameters/network.json charles_user

Below is the content of the update.sh script

aws cloudformation update-stack \
--stack-name $1 \
--template-body file://$2 \
--parameters file://$3 \
--region=us-east-1 \
--capabilities CAPABILITY_NAMED_IAM

Delete.sh

aws cloudformation delete-stack \
--stack-name $1
aws cloudformation delete-stack \
--stack-name $1


These are the screenshots for the project

Leave a Comment

Your email address will not be published. Required fields are marked *