How to deploy a high available and auto scale Apache Web Server on EC2 instances using AWS CloudFormation
Tags: cloudformation, infrastructureascode, devops, cloud
Published: 2023-01-12 14:30:36


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/53i1sam5usyp9ynyuuwo.png)

### Introduction

This article will guide you **deploy a high available and auto scale Apache Web Server** on EC2 instances using **AWS CloudFormation** on both the AWS Console and AWS CLI.

To achieve this, we will create the following resources:

* VPC with custom CIDR block

* 3 Public Subnets in 3 different availability zones

* Internet Gateway

* Security Group

* LaunchTemplate

* Auto Scaling Group

* Application Load Balancer

* Scaling UP and DOWN policy

---

### Let the fun begin!

According to [AWS CloudFormation best practices](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/best-practices.html) documentation, it is recommended to break the templates into smaller manageable templates. So I splitted the deploy on 2 different template files **vpc.yaml** and **asg_alb.yaml** listed below.

Let’s go through each item of the template:

* **Description** is a text string to describe the purpose of the template.

* **Parameters** are used to pass custom values at runtime. It is how we can make templates reusable to customize our stacks when we create them.

On the example below (**vpc.yaml**), we are including the name of the environment (that will be used later to tag the resources), the IPv4 CIDR of the VPC and subnets.

```
    Parameters:
     EnvName:
       Description: Name that will be used on resources
       Type: String
       Default: LUIT
     
     VPCCIDR:
       Description: Please enter the IPv4 CIDR for this VPC
       Type: String
       Default: 10.10.0.0/16
    
     PublicSubnetACIDR:
       Description: Please enter the IPv4 CIDR for this Public Subnet A
       Type: String
       Default: 10.10.1.0/24
     PublicSubnetBCIDR:
       Description: Please enter the IPv4 CIDR for this Public Subnet B
       Type: String
       Default: 10.10.2.0/24
     PublicSubnetCCIDR:
       Description: Please enter the IPv4 CIDR for this Public Subnet C
       Type: String
       Default: 10.10.3.0/24
```

---

* **Resources** are the only required object of a template. We are including the VPC with the “DnsSupport” and “DnsHostnames” options enabled, an Internet Gateway that will be attached to the new VPC created, the 3 public subnets and the default route to 0.0.0.0/0 using the Internet Gateway created.

```
    Resources:
     VPC:
       Type: AWS::EC2::VPC
       Properties:
         CidrBlock: !Ref VPCCIDR
         EnableDnsSupport: true
         EnableDnsHostnames: true
         Tags:
          - Key: Name
            Value: !Ref EnvName
    
     InternetGateway:
       Type: AWS::EC2::InternetGateway
       Properties:
         Tags:
          - Key: Name
            Value: !Ref EnvName
    
     InternetGatewayAttachment:
       Type: AWS::EC2::VPCGatewayAttachment
       Properties:
         InternetGatewayId: !Ref InternetGateway
         VpcId: !Ref VPC
    
     PublicSubnetA:
       Type: AWS::EC2::Subnet
       Properties:
         VpcId: !Ref VPC
         CidrBlock: !Ref PublicSubnetACIDR
         MapPublicIpOnLaunch: true
         AvailabilityZone: "us-east-1a"
         #AvailabilityZone: !Select [ 0, !GetAZs '' ]
         Tags:
         - Key: Name
           Value: !Sub ${EnvName} Public Subnet A
     PublicSubnetB:
       Type: AWS::EC2::Subnet
       Properties:
         VpcId: !Ref VPC
         CidrBlock: !Ref PublicSubnetBCIDR
         MapPublicIpOnLaunch: true
         AvailabilityZone: "us-east-1b"
         #AvailabilityZone: !Select [ 1, !GetAZs '' ]
         Tags:
         - Key: Name
           Value: !Sub ${EnvName} Public Subnet B
     PublicSubnetC:
       Type: AWS::EC2::Subnet
       Properties:
         VpcId: !Ref VPC
         CidrBlock: !Ref PublicSubnetCCIDR
         MapPublicIpOnLaunch: true
         AvailabilityZone: "us-east-1c"
         #AvailabilityZone: !Select [ 2, !GetAZs '' ]
         Tags:
         - Key: Name
           Value: !Sub ${EnvName} Public Subnet C
    
     PublicRouteTable:
       Type: AWS::EC2::RouteTable
       Properties:
         Tags:
          - Key: Name
            Value: !Sub ${EnvName} Public Route Table
         VpcId: !Ref VPC
    
     DefaultPublicRoute:
       Type: AWS::EC2::Route
       DependsOn: InternetGatewayAttachment
       Properties:
         DestinationCidrBlock: 0.0.0.0/0
         GatewayId: !Ref InternetGateway
         RouteTableId: !Ref PublicRouteTable
    
     PublicSubnetARouteTableAssociation:
      Type: AWS::EC2::SubnetRouteTableAssociation
      Properties:
        RouteTableId: !Ref PublicRouteTable
        SubnetId: !Ref PublicSubnetA
     PublicSubnetBRouteTableAssociation:
      Type: AWS::EC2::SubnetRouteTableAssociation
      Properties:
        RouteTableId: !Ref PublicRouteTable
        SubnetId: !Ref PublicSubnetB
     PublicSubnetCRouteTableAssociation:
      Type: AWS::EC2::SubnetRouteTableAssociation
      Properties:
        RouteTableId: !Ref PublicRouteTable
        SubnetId: !Ref PublicSubnetC
```

---

Lastly, we are including the **Outputs** section. It is optional but important to print the information of some resources that we might need to know or to use on any other stack.
On the example below, we are printing and exporting the ID of created VPC and subnets, and the CIDR of public subnets created.

```
    Outputs:
      VPC:
        Description: ID of created VPC
        Value: !Ref VPC
        Export:
          Name: VPC
      PublicSubnets:
        Description: CIDR of public subnets created
        Value: !Join [ ",", [ !Ref PublicSubnetACIDR, !Ref PublicSubnetBCIDR, !Ref PublicSubnetCCIDR ] ]
      PublicSubnetA:
        Description: ID of created public subnet A
        Value: !Ref PublicSubnetA
        Export:
          Name: PublicSubnetA
      PublicSubnetB:
        Description: ID of created public subnet B
        Value: !Ref PublicSubnetB
        Export:
          Name: PublicSubnetB
      PublicSubnetC:
        Description: ID of created public subnet C
        Value: !Ref PublicSubnetC
        Export:
          Name: PublicSubnetC
```

---

Below is the full **vpc.yaml** template that we will use to create the stack.
{% gist https://gist.github.com/lucianobm/0307d42e5f8fa650836ba91a7e65aa7a.js %}

---

Now let’s go through each item of the **asg_alb.yaml** template:

We are including the following resources:

* **Security Group** to allow inbound HTTP and SSH traffic from the Internet;

* **Launch Template** with an Amazon Linux AMI, t2.micro instance type, my key pair “luciano” previous created, the security group created above and the UserData to install Apache Web Server;

* **Application Load Balancer**, Target Group and Listener;

* **Auto Scaling Group** with desired capacity of 2, minimum of 2 and maximum of 5.

* **Scale up** and **down** policies with alarms to scale in case of the utilization of CPU goes above or beyond 50% for 10 minutes.

Also we are including an **output** section to print the DNS name of the Application Load Balancer.

Below is the full **asg_alb.yaml** template that we will use to create the stack.
{% gist https://gist.github.com/lucianobm/99ecdc7728c72c757e33275a4ee273bd.js %}

---

Now let’s deploy the stacks using both the AWS Console and AWS CLI:

### Using AWS Console

After logging into your AWS account, search for **CloudFormation**.

![CloudFormation service](https://cdn-images-1.medium.com/max/2078/1*jvr6OiMekR8HlAs9viJdwA.png)

On the CloudFormation dashboard, click on “Create stack” button.

Select “Upload a template file” and click on “Choose file” button to browse the **vpc.yaml** file on your computer and then click on “Next”.

![Create stack](https://cdn-images-1.medium.com/max/2716/1*QHX7Ws69d6tm-DsPbfVswg.png)

Enter a stack name, the environment name and the IPv4 CIDR blocks for the VPC and the subnets, and click on “Next”.

![Specify stack details](https://cdn-images-1.medium.com/max/2964/1*0PmBLnJDYqxb4Ov7Y3dV8Q.png)

On **Configure stack options** section, we will leave everything as default and click on “Next”.
Review your stack details and click on “Submit” if everything seems correct.

You can repeat this process to create another stack using the **asg_alb.yaml** file.

---

### Using AWS CLI

To create the stacks using AWS CLI, follow the steps below:

**Validate our templates**

```
    aws cloudformation validate-template --template-body file://vpc.yaml
    
    aws cloudformation validate-template --template-body file://asg_alb.yaml
```

**Create stacks using our templates**

```
    aws cloudformation create-stack --stack-name LUIT --template-body file://vpc.yaml 
    
    aws cloudformation create-stack --stack-name LUIT2 --template-body file://asg_alb.yaml
```

**Check if the stack we created via template is completed successfully**

```
    aws cloudformation list-stack-resources --stack-name LUIT
    
    aws cloudformation list-stack-resources --stack-name LUIT2
```

**Describe stack and its resources to view its properties**

```
    aws cloudformation describe-stacks --stack-name LUIT
    
    aws cloudformation describe-stacks --stack-name LUIT2
    
    
    aws cloudformation describe-stack-resources --stack-name LUIT
    
    aws cloudformation describe-stack-resources --stack-name LUIT2
```

**Check events for stack formation**

```
    aws cloudformation describe-stack-events --stack-name LUIT
    
    aws cloudformation describe-stack-events --stack-name LUIT2
```

---

Now you should check if the Status is “CREATE_COMPLETE” and check the resources created.

If everything was created successfully, we will be able to reach the Application Load Balancer DNS on our browser and see the Apache running.

![ALB redirecting traffic to EC2 instance on us-east-1a](https://cdn-images-1.medium.com/max/2606/1*SI3feg3ve-avycKWWEcJYg.png)

![ALB redirecting traffic to EC2 instance on us-east-1c](https://cdn-images-1.medium.com/max/2602/1*fwR99s996OztvQSJfcjTGQ.png)

---

## Success!

We have successfully created 2 stacks using AWS CloudFormation to deploy a high available and auto scale Apache Web Server running on EC2 instances.

---

If this step-by-step guide was helpful to you, click the like button and drop a comment below! I can’t wait to hear from you!