# #10weeksofCloudOps - Week 2

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1692866872430/a870d478-a79b-4eb3-8cb8-1f721f99b11d.png align="center")

## Introduction

In the dynamic landscape of cloud computing, designing a highly available architecture is paramount to ensuring uninterrupted performance and user experience. This becomes even more crucial for modern applications like Node.js apps, where responsiveness and reliability are essential.

Within the intricate web of Amazon Web Services (AWS), a resilient 3-tier architecture comes to life, seamlessly integrating frontend public subnets, backend private subnets, and Aurora DB private subnets. This design ensures secure data handling, efficient traffic management, and continuous availability for Node.js applications.

# Architecture

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1692180439630/bc0cc89b-3b51-40aa-bc13-5cf72ec49fb5.png align="center")

## GitHub Repo

[melvincv/10weeksofcloudops-w2: #10weeksofcloudops challenge - Week 2 (](https://github.com/melvincv/10weeksofcloudops-w2)[github.com](http://github.com)[)](https://github.com/melvincv/10weeksofcloudops-w2)

# Procedure

## Create an S3 Bucket

Got to the **S3 service** &gt; Buckets &gt; Create Bucket.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1691639549441/08b71e5e-59b8-4712-a2bb-a62680e838fd.png align="center")

Block Public Access should be on.

Click **Create Bucket**

## Create an IAM Role

Go to the IAM service &gt; Roles &gt; Create role

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1691639737281/fe01e557-c7c6-4acc-af0e-992890a08343.png align="center")

Next &gt; Add Permissions to the Role:

* **AmazonSSMManagedInstanceCore**
    
* **AmazonS3ReadOnlyAccess**
    

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1691639786530/b3c20873-28a4-46bf-97a7-8d9638506734.png align="center")

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1691639842052/16fd3510-11ff-4c9c-8e46-227ab6f0afa9.png align="center")

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1691639901734/c6c984dd-17a6-4394-9a2f-18906c7f4697.png align="center")

Verify that the correct permissions are added:

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1691639932091/76a3586b-6c52-4141-9ac8-3bd248cd0481.png align="center")

Click **Create Role**

## Create a Custom VPC

Go to the 'VPC' service and select a region of your choice.

Create VPC &gt; Name: cloudops-week2

CIDR Block: 10.0.0.0/16

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1691640126574/3ae89ea6-bd18-4aae-a4fc-f76e371040ac.png align="center")

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1691640191020/36eb1e76-09af-49dd-8738-30cd9912ddbf.png align="center")

### Create public and private subnets

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1691467570414/ad879a0a-921a-40cd-9a4e-c14b907a434c.png align="center")

* The first pair of subnets, *Public /* ***Web***, will be accessible from the Internet and contain web tier ec2 instances, load balancers and NAT gateways.
    
* The ***App*** pair of subnets will contain app tier ec2 instances containing a nodejs backend app.
    
* The *Data /* ***DB*** pair of subnets will hold your active/passive Aurora database.
    

VPC &gt; Subnets &gt; Create Subnet &gt; Select VPC

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1691640359258/710967da-4ea6-4da1-9c9c-ab8c725dfc5d.png align="center")

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1691640689203/af2922ba-37d4-42e6-a901-3fdcaf1d732a.png align="center")

Create 6 subnets:

| Sl.No. | Subnet Name | Availability Zone | CIDR Value |
| --- | --- | --- | --- |
| 1. | web-public-1a | ap-southeast-1a | **10.0.0.0**/24 |
| 2. | web-public-1b | ap-southeast-1b | 10.0.1.0/24 |
| 3. | app-private-1a | ap-southeast-1a | 10.0.2.0/24 |
| 4. | app-private-1b | ap-southeast-1b | 10.0.3.0/24 |
| 5. | db-private-1a | ap-southeast-1a | 10.0.4.0/24 |
| 6. | db-private-1b | ap-southeast-1b | 10.0.5.0/24 |

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1691640733109/f9e30e65-01c9-453c-8fe1-3a297ee60795.png align="center")

Click **Create Subnet**

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1691640835399/fae68c12-93a0-48f6-97bf-4a37d7f4dbce.png align="center")

### Create an Internet Gateway

To give the web subnets in our VPC Internet access we need to create and attach an Internet Gateway.

VPC &gt; Internet Gateways &gt; Create

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1691640950325/25f64ac3-c372-4f6a-865d-64c8707c19d6.png align="center")

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1691640989938/955aed62-eff6-4bda-bad0-7017cc2de405.png align="center")

Attach to the VPC

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1691645150042/3541594f-2903-49de-b759-89b9d16b76b5.png align="center")

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1691645172693/d2396bf7-fbef-4ba6-8e9c-677627639ddf.png align="center")

### Create NAT Gateways in Public Subnets

<mark>DELETE AFTER USE TO AVOID HIGH CHARGES</mark>

For our instances in the **App** layer private subnet to be able to access the Internet their traffic will need to go through a NAT Gateway. For high availability, you’ll deploy one NAT gateway in each of your **public** subnets.

Go to VPC &gt; NAT Gateways &gt; Create

Name them:

* NAT GW A
    
* NAT GW B
    

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1691645349023/11901896-2203-4eb2-ae22-fbfb78c376d3.png align="center")

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1691645393545/6a418a9a-b3ac-49f6-83f9-b739648cd1f0.png align="center")

Click: **Create NAT Gateway**

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1691645463310/147d2e8f-5bdd-421e-a860-11d34d0dc792.png align="center")

Click: **Create NAT Gateway**

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1691645928492/5d8becb3-435e-4855-a61d-53522c9f2cc6.png align="center")

If you go to VPC &gt; Elastic IPs, you can see that there are 2 new elastic IPs with association ID's.

<mark>DELETE AFTER USE TO AVOID HIGH CHARGES</mark>

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1691645910922/c9242255-25f4-40d4-9c23-217daf25251f.png align="center")

### Create Route Tables

Go to VPC &gt; Route Tables &gt; Create

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1691646015348/b2960353-dccc-4314-91ce-93d3e1550a99.png align="center")

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1691646087245/0e3f2637-f330-4bd2-9833-7beb430c55fe.png align="center")

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1691646111671/8493e86e-ad33-4ffc-916b-52db871eec48.png align="center")

Add a Route for 0.0.0.0/0 (All traffic except local VPC traffic) to the Internet Gateway we created earlier.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1691646193392/61428dae-90f3-43ea-8fbe-f5b1d3b16dc2.png align="center")

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1691646215844/5fd069ef-2ea4-4b0f-a0ae-98850e7fe393.png align="center")

Edit Subnet Associations and add the 2 public web subnets.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1691646282423/7b7ff20c-8591-481e-ae0f-557793919af4.png align="center")

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1691646308299/a8100ca5-5edd-4f35-8f7c-2f22258f75ac.png align="center")

Create 2 more Route Tables to route Internet traffic from the APP Subnets to the respective NAT Gateways in each Zone.

* `private-app-rt-1a`
    

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1691646526905/a1da8a49-e9f6-4604-9541-636cec39ed35.png align="center")

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1691646639569/9aa733ab-da88-42cb-8590-f3612d8a8613.png align="center")

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1691646672844/aa4ab94b-96bd-49fb-a482-6acebfe58864.png align="center")

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1691646905287/67cb36b0-09f5-4511-bdac-08a634d86622.png align="center")

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1691646930388/eb89a910-103a-4cff-8da5-e3315b788426.png align="center")

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1691646960098/26380477-24a1-4a92-b797-c6127915eaf1.png align="center")

* Similarly, route `private-app-rt-1b` to `NAT GW B`
    

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1691646572398/bc631b4e-47a9-4f55-af59-889f3bdad203.png align="center")

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1691646716720/4284a27a-cc25-40a9-a9a2-e76665ed5761.png align="center")

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1691646739057/fb2dc04d-f65f-4193-b553-f4d72c5d86b5.png align="center")

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1691646812646/5d8e7fe3-85e8-4ffa-ae85-c50dfc922827.png align="center")

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1691646844321/38e482d6-2a99-451a-80f2-e7c895c9e32a.png align="center")

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1691646874109/c47b12d7-1f0b-420a-b059-596142b68eb0.png align="center")

### Create Security Groups (SG)

Go to VPC &gt; Security Groups &gt; Create

* (SG 1) Create an SG named `cloudops-w2-alb-sg` for the Internet-facing Application Load Balancer
    

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1691647092815/792d4897-c51c-4c04-b97e-8d0406d44481.png align="center")

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1691647158152/5b97a9fa-990e-4ab6-a85d-7c6e0d344c7b.png align="center")

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1691647264553/48ebb52f-d62f-4f8a-9473-65509a1c9304.png align="center")

Click **Create Security Group**

Copy the ID of the SG to use later

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1691647388400/3014a80a-3079-442b-974a-293579ee8d26.png align="center")

* (SG 2) Create a new SG named `cloudops-w2-web-sg` for the web tier ec2 instances. Add an inbound rule that allows HTTP traffic from your internet-facing load balancer security group you created in the previous step.
    

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1691647633507/a3884fcd-afc5-4bbf-807a-527cae0ff6a7.png align="center")

Also add a rule that allows HTTP traffic from your IP.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1691647736246/fc6aea9f-0888-4cad-a989-72154febf0f1.png align="center")

* (SG 3) Create a new SG named `cloudops-w2-int-alb-sg` for the internal load balancer. Add an inbound rule that allows HTTP traffic from your public instance SG. This will allow traffic from Web tier instances to hit the internal load balancer.
    

Copy the ID of `cloudops-w2-web-sg`

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1691648070947/9b961da8-813a-41f0-8da4-efc8109e206c.png align="center")

* (SG 4) Create a new SG named `cloudops-w2-private-ec2-sg` for the private ec2 instances (backend).
    
* Add an inbound rule that allows TCP traffic on port 4000 from the internal load balancer SG `cloudops-w2-int-alb-sg`
    
* Also add an inbound rule that allows TCP traffic on port 4000 from your IP for testing.
    

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1691648392567/9aecbd2d-fbbf-438e-8e4c-9c591e057904.png align="center")

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1691648454683/948c1b3b-4516-4ac9-8b06-525c95359730.png align="center")

* (SG 5) Create a new SG named `cloudops-w2-db-sg` to protect our private database instances.
    
* Add an inbound rule that allows traffic from the private instance SG to the MySQL port 3306.
    

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1691648731133/aa5fdf97-944f-4279-a9e7-b46e5f5cb330.png align="center")

Verify SG creation

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1691648890163/029b957e-44b2-43c4-a44f-7c563b24a4b0.png align="center")

## Create Aurora Database

<mark>DELETE AFTER USE TO AVOID HIGH CHARGES</mark>

### Create a Subnet Group

Go to **RDS** service &gt; Subnet Groups &gt; Create

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1691730915293/d4ed56f5-0016-4091-b7e1-656b48f2b90f.png align="center")

Select the AZ's and your DB Subnets in each AZ.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1691730971515/dbb798b0-e763-45c3-9028-67e792a8cf6a.png align="center")

Click Create

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1691730997929/6f6cf736-b892-4b51-a8fd-db77441cd226.png align="center")

### Create an Aurora Database instance

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1691731048502/b6fb10da-0d84-46c1-80b1-b253fae39940.png align="center")

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1691731093612/73a79ba9-eb85-448d-82d8-7b837296b20d.png align="center")

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1691731185079/d18e31bc-0d4c-45f0-915b-f60f1fb7a786.png align="center")

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1691731205577/f6a691e1-371d-4966-8711-bee2a042dd26.png align="center")

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1691731272249/ce7b71e9-8e8a-4f44-8d13-3e951ef4815b.png align="center")

Connectivity section:

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1691731324009/df41da6f-f103-4962-838a-2cb1c847804f.png align="center")

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1691731368566/2bfbb75a-0919-4516-b1b9-d1a072173a65.png align="center")

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1691731386208/a3dfc981-59a2-4ec3-86e0-9856ceb8bf57.png align="center")

Click **Create Database**.

Wait for 5 min for the Aurora Cluster creation...

Click: **View Credentials** and copy the DB Credentials.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1691731577976/73bf4594-4fe9-434f-97a1-e38a5a1065a7.png align="center")

When your database is provisioned, you should see a reader and writer instance in the database subnets of each availability zone. Note down the writer endpoint for your database for later use.

Select the database cluster &gt; Copy the endpoint of the Writer instance.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1691732724497/66d7d2c0-5d86-4c73-abf6-cc8c1b5bc790.png align="center")

```bash
cloudops-w2-db.cluster-cf6xjg8izaoi.ap-southeast-1.rds.amazonaws.com
```

## App tier instances

<mark>DELETE AFTER USE TO AVOID HIGH CHARGES</mark>

Create an EC2 instance for our app layer and make all necessary software configurations so that the app can run. The app layer consists of a Node.js application that will run on port 4000. We will also configure our database with some data and tables.

## App instance

Go to the EC2 service &gt; Instances &gt; Launch instance.

Give a Name tag: `cloudops-w2-app` and tag instances, volumes and network interfaces.

Choose the Amazon Linux 2 AMI at the top for 64-bit architecture.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1691733004568/3a923da2-5aba-4e1e-a811-f82263e988d7.png align="center")

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1691733087131/99b63661-2dbf-4b99-81a3-c4e6efc1cc62.png align="center")

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1691733128263/10b2b311-f936-4616-bc1c-c5d3039f00b9.png align="center")

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1691733163022/aa983433-0acd-4d4c-8bed-ad92252829ca.png align="center")

Save the PEM file securely.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1691733200835/44b767c1-00b8-4d72-9ee6-ebc301cb8fe5.png align="center")

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1691733334632/ade4897a-53f9-4c7b-a3fe-d260ac41a798.png align="center")

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1691733347541/dea8e67f-3246-488c-a6f5-9c632e910856.png align="center")

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1691733393066/75684918-50a2-4992-aa8a-49441056d40b.png align="center")

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1691733414110/fa64f171-9a98-4261-b0f4-927e5ffb2c03.png align="center")

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1691733434254/89b33179-6f23-42a5-9b8a-c1e0b44c5339.png align="right")

### Connect to the instance using Session Manager

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1691733583325/7dbf61b8-f3a2-4244-85bb-760012517e5f.png align="center")

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1691738310066/20095128-e928-4ca3-8ad9-24790ed11780.png align="center")

you will be logged in as ssm-user which is the default user. Switch to ec2-user

```bash
sudo su - ec2-user
```

I did not like using this terminal. I use it only for account recovery. Instead of typing commands here, I would deploy a bastion host as below. But it is optional and you may continue with Session manager.

## (Optional) Deploy a Bastion Host instance in a Public Subnet

Launch Instance &gt;

* Name: cloudops-w2-bastion
    
* AMI: Amazon Linux 2023
    
* Arch: 64 bit
    
* Type: t2.micro
    
* Key Pair: cloudops-w2
    

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1691736222208/6cf5d251-7b42-4dc0-b9d7-3316577eabb3.png align="center")

Inbound Rules &gt; Leave the default SSH rule as is.

Launch instance.

Log in to the bastion host:

(Using Git Bash on Windows 11)

```bash
ssh -i "G:\My Drive\vault\aws\cloudops-w2.pem" ec2-user@13.212.4.194
```

Log in success. Now create an OpenSSH client config and save it to `~/.ssh/config`

```bash
Host cloudops-w2-bastion
    HostName 13.212.4.194
    User ec2-user
    IdentityFile "G:\My Drive\vault\aws\cloudops-w2.pem"
    ForwardAgent yes
    ServerAliveInterval 60
    ServerAliveCountMax 10
```

* Start the SSH Agent in Git Bash
    
* add the private key (pem file) to the agent
    
* connect to the bastion
    
* connect to the app instance from the bastion using it's private IP
    

```bash
eval $(ssh-agent -s)
ssh-add "G:\My Drive\vault\aws\cloudops-w2.pem"
ssh cloudops-w2-bastion
```

Modify the SG of the App instance to allow SSH traffic from the Private IP of the bastion instance.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1691737188439/8069539b-ff7f-4672-893a-5370df236eb9.png align="center")

Save Rules.

Try to connect again:

```bash
$ ssh ec2-user@10.0.2.223
```

This time the connection is OK.

List running services to check if the AWS ssm-agent is running (optional)

```bash
$ sudo systemctl --type=service --state=running
  UNIT                            LOAD   ACTIVE SUB     DESCRIPTION
  amazon-ssm-agent.service        loaded active running amazon-ssm-agent
```

## App tier instance configuration

Search for and Install the mariadb client \[as the mysql client is no longer there\]

```bash
sudo yum search mariadb
sudo yum install -y mariadb105
```

Launch the mariadb client and connect to the Aurora RDS DB:

```bash
[ec2-user@ip-10-0-2-223 ~]$ mysql -h cloudops-w2-db.cluster-cf6xjg8izaoi.ap-southeast-1.rds.amazonaws.com -u melvincv -p
Enter password:
Welcome to the MariaDB monitor.  Commands end with ; or \g.
Your MySQL connection id is 701
Server version: 5.7.12 MySQL Community Server (GPL)

Copyright (c) 2000, 2018, Oracle, MariaDB Corporation Ab and others.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

MySQL [(none)]>
```

Create a Database and a Table within it:

```bash
MySQL [(none)]> CREATE DATABASE webappdb;
Query OK, 1 row affected (0.015 sec)

MySQL [(none)]> SHOW DATABASES;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| mysql              |
| performance_schema |
| sys                |
| webappdb           |
+--------------------+
5 rows in set (0.002 sec)

MySQL [(none)]> USE webappdb;
Database changed
MySQL [webappdb]> CREATE TABLE IF NOT EXISTS transactions(id INT NOT NULL
    -> AUTO_INCREMENT, amount DECIMAL(10,2), description
    -> VARCHAR(100), PRIMARY KEY(id));
Query OK, 0 rows affected (0.049 sec)

MySQL [webappdb]> SHOW TABLES;
+--------------------+
| Tables_in_webappdb |
+--------------------+
| transactions       |
+--------------------+
1 row in set (0.001 sec)

MySQL [webappdb]> INSERT INTO transactions (amount,description) VALUES ('400','groceries');
Query OK, 1 row affected (0.009 sec)

MySQL [webappdb]> SELECT * FROM transactions;
+----+--------+-------------+
| id | amount | description |
+----+--------+-------------+
|  1 | 400.00 | groceries   |
+----+--------+-------------+
1 row in set (0.010 sec)

MySQL [webappdb]>
```

Create a DB User and grant all permissions on the `transactions` DB to it:

```bash
MySQL [webappdb]> CREATE USER 'melvin'@'%' IDENTIFIED BY 'RZe=urGbp4<-"F4T';
Query OK, 0 rows affected (0.013 sec)

MySQL [webappdb]> GRANT ALL ON transactions.* TO 'melvin'@'%';
Query OK, 0 rows affected (0.013 sec)

MySQL [webappdb]> SELECT User,Host FROM mysql.user;
+-----------+-----------+
| User      | Host      |
+-----------+-----------+
| melvin    | %         |
| melvincv  | %         |
| mysql.sys | localhost |
| rdsadmin  | localhost |
+-----------+-----------+
4 rows in set (0.001 sec)

MySQL [webappdb]> \q
Bye
```

### Git Repo and Code

Edit the below file in the git repo using a code editor and fill in the Aurora DB details:

`application-code/app-tier/DbConfig.js`

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1691821548310/86269bc4-6151-4eaf-9d43-8fdd931ed76d.png align="center")

Copy the repo code to your S3 bucket.

From the repo root folder,

```bash
cd application-code
aws s3 cp . s3://melvincv2023-cloudops-w2 --recursive
```

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1691820574533/907fae7a-2e8f-46a5-b0b9-6aa7663f0af7.png align="center")

install NVM

```bash
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.38.0/install.sh | bash
source ~/.bashrc
```

Install a compatible version of nodejs and use it

```bash
nvm install 16
nvm use 16
```

PM2 is a daemon process manager that will keep our node.js app running when we exit the instance or if it is rebooted. Install that as well.

```bash
npm install -g pm2
```

Download our code from our s3 buckets onto our instance.

```bash
cd ~
aws s3 cp s3://melvincv2023-cloudops-w2/app-tier/ app-tier --recursive
```

Install dependencies, and start the app with pm2.

```bash
cd ~/app-tier
npm install
pm2 start index.js
```

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1691820993037/e7f2f77f-e8d8-44ef-b7ba-31c0d22fb26a.png align="center")

Make sure the app is running corectly:

```bash
pm2 list
```

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1691821725369/35db86ce-ee30-4c43-9367-2bf8cf118ca8.png align="center")

To start our app at boot even if the server crashes or reboots:

```bash
pm2 startup
```

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1691821812443/60d2ff67-72af-477b-9d1b-2d9c03438aa3.png align="center")

save the current list of node processes:

```bash
pm2 save
```

### Testing

This is our simple health check endpoint that tells us if the app is simply running.

```bash
curl http://localhost:4000/health
```

The response should looks like the following:

```bash
"This is the health check"
```

Next, test your database connection. You can do that by hitting the following endpoint locally:

```bash
curl http://localhost:4000/transaction
```

You should see a response containing the test data we added earlier:

```bash
{"result":[{"id":1,"amount":400,"description":"groceries"}]}
```

If you see both of these responses, then your networking, security, database and app configurations are correct.

## Internal Load Balancing

<mark>DELETE AFTER USE TO AVOID HIGH CHARGES</mark>

An **internal load balancer** is a type of load balancer that distributes incoming network traffic to backend resources, such as Amazon EC2 instances, within a Virtual Private Cloud (VPC) network. Unlike external load balancers, which are accessible from the internet, internal load balancers are designed to handle traffic within the boundaries of a VPC and are not directly accessible from the public internet.

### Create \*\*Amazon Machine Image (\*\*AMI)

An **Amazon Machine Image (AMI)** is a pre-configured virtual machine image used as a template to create new instances (virtual servers) in Amazon Web Services (AWS). An AMI includes the operating system, application software, and any additional configurations necessary to launch instances with specific characteristics.

Create an AMI of the App tier instance to implement AutoScaling using an Internal App Load balancer.

EC2 &gt; Instances &gt; Rt.Click App instance

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1691823489505/7cbd3b14-b427-4807-b60c-ce179574016d.png align="center")

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1691823546492/850fd922-6525-47c5-9402-5b67df96f058.png align="center")

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1691823580246/8aac147b-49a9-45a3-abc7-64b9eb769a72.png align="center")

Click: Create Image

### Create a Target Group

Create a target group for the internal load balancer.

A **Target Group** is a logical grouping of targets, which are typically instances or IP addresses that receive incoming traffic from the load balancer. The load balancer uses the target group to determine where to direct incoming requests. It plays a crucial role in routing traffic to healthy instances while also allowing you to manage the distribution of traffic based on different rules and conditions.

EC2 &gt; Target Groups &gt; Create

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1691823751446/5f34e362-ed69-41cc-b77f-6c05c4afd43d.png align="center")

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1691823837762/f9a5a946-4589-4b40-87ae-60d4b621f377.png align="center")

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1691823855402/068fb1f8-077c-4a62-aa17-e41d26d61ca7.png align="center")

Next

Do NOT register any targets for now.

Click: **Create Target Group**

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1691823928997/81969da1-1be1-42bf-a178-0bebc35d31f4.png align="center")

EC2 &gt; Load Balancers &gt; Create load balancer

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1691824006259/4ce875b3-c0ea-4315-9df9-f5dba5b6719f.png align="center")

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1691824118257/c5ca6ad3-6915-41e3-929b-b64640ec358b.png align="center")

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1691824155777/10c72a9c-0696-4608-b68a-6e329cc0ce98.png align="center")

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1691824172366/a45eba16-8a52-490b-98b1-7fb600fae61b.png align="center")

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1691824191747/adfabc72-ce87-4694-8881-4f713322d3df.png align="center")

Click: Create Load Balancer

### Create a Launch Template

Before we configure Auto Scaling, we need to create a Launch template with the AMI we created earlier.

A **launch template** is a resource that defines the configuration for launching instances (virtual servers). It provides a convenient way to specify the various settings needed to create and launch instances consistently, including the Amazon Machine Image (AMI), instance type, networking options, security groups, storage configurations, and more.

EC2 &gt; Launch Templates &gt; Create

* Name the Launch Template
    
* Select the App Tier AMI
    

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1691825306277/b5a11522-b040-43c1-92f3-5fa311b1f2b7.png align="center")

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1691825341237/d9873827-c48a-461d-96ac-bce30500ecf7.png align="center")

* Instance Type: t2.micro
    
* Key pair: Don't include
    
* Network Settings: Don't include
    

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1691825374831/49546a61-e0ff-42e6-a391-b6c987aa5cbc.png align="center")

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1691825450754/b4bcd599-9b2c-4fab-9ebe-4b67b4c42a3f.png align="center")

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1691825502903/96fdca94-37f6-4577-a5de-9aaa69581dbd.png align="center")

User Data &gt; Add the below script

```bash
#!/bin/bash
cd ~
aws s3 sync s3://melvincv2023-cloudops-w2/app-tier/ app-tier
```

Click: **Create Launch Template**

### Create Auto Scaling Group

<mark>DELETE AFTER USE TO AVOID HIGH CHARGES</mark>

An **Auto Scaling group** is a key feature of Amazon Web Services (AWS) that enables you to automatically scale the number of instances in a group based on demand. It helps ensure that your application can handle varying workloads and maintains availability and performance by dynamically adjusting the number of instances up or down as needed.

EC2 service &gt; Auto Scaling groups &gt; Create

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1691826321290/d5bfe68c-75bd-4665-afbd-bfbe63d98f5e.png align="center")

Next

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1691826371804/66a137c0-6cb3-40c9-a59b-752e0e4f6877.png align="center")

Next

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1691826460505/d0c5e2b6-2afd-4574-96e5-5fe9e67404ad.png align="center")

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1691826482492/11db5ead-ed38-4653-9e87-0d04ae46c65b.png align="center")

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1691826536818/516b978d-3f83-40cf-8e13-dcf7b8bf539d.png align="center")

Optional: Add a 'Target Scaling Policy to scale up if a certain no. of requests hit the internal load balancer:

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1691826683480/fe55b97b-0adf-48a6-95be-0876897b664d.png align="center")

**How to set the right Target Value?**

1. **Observability and Monitoring**: Use AWS CloudWatch or other monitoring tools to track metrics related to the ALB and the instances. This data can provide insights into request patterns and help you set appropriate target values.
    
2. **Iterative Approach**: Start with a conservative target value and monitor your application's performance. Gradually adjust the target value based on observed behavior and performance metrics.
    

Add Notifications if needed &gt; Add Tags

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1691826899719/1dfa1e59-987d-4c2a-b70f-72d6113841e8.png align="center")

Skip to Review &gt; Create ASG

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1691826785713/338ff01f-f77d-4af2-b79e-3f6ec36dc00d.png align="center")

## Create Web Tier instances

<mark>DELETE AFTER USE TO AVOID HIGH CHARGES</mark>

### Edit nginx config

From the Repo root, go to `app-code/nginx.conf` and edit line 58 to include the DNS name of the Internal load balancer we created earlier.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1692076585740/8a1d663a-e914-460d-93dd-36625b0d2c66.png align="center")

Commit and upload `nginx.conf` and `web-tier` folder to the S3 bucket.

### Create an EC2 instance for the web tier

EC2 &gt; Create instance &gt; Name: cloudops-w2-web &gt; Type: t2.micro &gt; Select Key Pair `cloudops-w2` &gt; Network Settings:

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1692076820739/bdc3b076-8dc9-4c29-a103-2b265ffb6cf9.png align="center")

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1692076891532/18923f2e-4287-4123-9f82-f7714290dd67.png align="center")

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1692076905818/99bc1ae7-f7a2-43e1-be75-019e69d49b11.png align="center")

Launch instance.

### Connect to the instance

Add this to `~/.ssh/config`

```bash
Host cloudops-w2-web
    HostName 13.212.149.128
    User ec2-user
    IdentityFile "cloudops-w2.pem"
    ForwardAgent no
	ServerAliveInterval 60
    ServerAliveCountMax 10
```

Add an Inbound Rule to the Security Group of the Web instance that allows SSH from your IP: `cloudops-w2-web-sg`

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1692077304488/8c2d3510-464e-45a4-b580-17e16f6cc2a8.png align="center")

Connect using openssh

```bash
ssh cloudops-w2-web
```

### Set up the Web instance

Install npm and node 16.

```bash
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.38.0/install.sh | bash
source ~/.bashrc
nvm install 16
nvm use 16
```

Copy the web tier code from our S3 bucket.

```bash
cd ~/
aws s3 cp s3://melvincv2023-cloudops-w2/web-tier/ web-tier --recursive
```

Create the build folder for the react app by building it.

```bash
cd ~/web-tier
npm install 
npm run build
```

Install nginx...

```bash
sudo yum install -y nginx
```

Replace `nginx.conf` on the instance with our edited one from the S3 bucket.

```bash
cd /etc/nginx
sudo rm nginx.conf
sudo aws s3 cp s3://melvincv2023-cloudops-w2/nginx.conf .
```

Restart NGINX, see that nginx has permissions to access our files and start it on boot.

```bash
sudo service nginx restart
chmod -R 755 /home/ec2-user
sudo chkconfig nginx on
```

The demo website would now be available when you enter the IP address of the Web instance:

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1692078454294/3e3eb80c-7beb-4211-b709-0816897c3320.png align="center")

Create an AMI of the Web instance, similar to that of the App tier...

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1692079048475/bae31608-0537-4dcb-a21f-f44ee26e9969.png align="center")

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1692079091038/a54b6a46-2dd9-455d-8824-38387f399f49.png align="center")

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1692079132445/d17e5377-4783-4554-bf67-771de9fb5dd9.png align="center")

Create Image.

### Create a Target Group

For balancing the load across the public Web instances.

EC2 &gt; Target Groups &gt; Create

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1692079262120/b4f8ff7c-1dc2-4a71-9543-5849456fbb8d.png align="center")

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1692079318485/e1ffc7c4-0861-44b9-a58d-cdff1a20953c.png align="center")

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1692079343511/90fa6938-bd33-4c4e-bd3e-c4f351f51524.png align="center")

Next &gt; Create Target Group.

### Create a Public Load Balancer

<mark>DELETE AFTER USE TO AVOID HIGH CHARGES</mark>

EC2 &gt; Load Balancers &gt; Create

Select `Application Load Balancer` &gt; Create

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1692079484871/bf412180-9e98-42e9-bf3c-a55c3c133bd3.png align="center")

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1692079519862/1fe96017-f86d-41e0-9b55-f17678dbf52e.png align="center")

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1692079540164/47561e33-2def-4380-bce7-04db9c9fa51c.png align="center")

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1692079564171/f753aece-6981-4bc8-bbd5-e555c584b976.png align="center")

Create Load Balancer.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1692079679797/4db322cc-aabb-4a3e-90fc-6cbbe81556f1.png align="center")

### Create Launch Template

Create a launch template for use in Auto Scaling.

EC2 &gt; Launch Templates &gt; Create

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1692080141567/844d1930-5f56-42b1-904c-292c07e7e75a.png align="center")

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1692080161831/3c95f9df-34f3-4615-860c-7da22c984243.png align="center")

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1692080183763/11fa2948-b8b0-4d7c-928a-13987cc0e861.png align="center")

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1692080195240/43d1bcd6-13da-4d49-8110-0f6670358d93.png align="center")

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1692080245625/42ce00d9-77a4-4d25-bc05-cb3f2b8874de.png align="center")

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1692080262908/3ab46228-f87b-4e15-a383-1aa8e8426807.png align="center")

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1692080275252/904ccdc9-84c0-443d-b76c-fcec50c7351f.png align="center")

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1692080411462/90590355-b094-46ff-8ff4-fbbe6eac7a15.png align="center")

User Data &gt; Add the below script:

```bash
#!/bin/bash
cd ~
aws s3 sync s3://melvincv2023-cloudops-w2/web-tier/ web-tier
sudo aws s3 cp s3://melvincv2023-cloudops-w2/nginx.conf /etc/nginx
sudo systemctl reload nginx
```

Create Launch Template.

### Create Auto Scaling Group

<mark>DELETE AFTER USE TO AVOID HIGH CHARGES</mark>

EC2 &gt; Auto Scaling Groups &gt; Create

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1692080504228/38e68fa0-09d0-42ce-a73f-3cb3fe486ad8.png align="center")

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1692080520470/aaf6d65d-e60d-4e05-a710-e1d7e8b7c464.png align="center")

Next

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1692080559403/eea29439-6f17-4c54-b02a-21b403581553.png align="center")

Next

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1692080596699/fca3c0c1-60ed-4c9e-9dbe-bb1716efb224.png align="center")

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1692080617145/a53b09f5-aca8-4022-9ba8-a6851af7fcf8.png align="center")

Next

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1692080659301/750813b9-8298-4508-960f-38f5a71a55eb.png align="center")

(Next step is optional)

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1692080704471/95317942-7f85-4fe3-b525-0e69e58eed10.png align="center")

Next

Add notifications (optional) &gt; Skip to review

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1692080796597/6700e177-e092-4438-bb06-d14c619b8e65.png align="center")

Create Auto Scaling Group.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1692080875235/91e80a31-15f4-41f4-a98b-e9641be468f8.png align="center")

Go to the App LB's Security group &gt; Inbound Rules &gt; Add a rule to allow HTTP traffic to all IPv4

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1692081128105/b7685f3c-2158-493d-9ce6-2c42309b6e33.png align="center")

Open the DNS name of the App LB in a browser. You should be able to see the web page.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1692081173377/aba61c76-2341-482e-a32d-981506cb85c3.png align="center")

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1692081216988/f4f58db9-9ea0-42fb-80bf-ca65df83fd20.png align="center")

## Route 53 DNS config

Go to the Route 53 service &gt; Select your hosted zone &gt; Create Record

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1692081627996/6908d47b-0534-4f3a-b702-9eaeb850e541.png align="center")

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1692081668851/30d995d7-3af9-4c89-b2c7-9312e32cc363.png align="center")

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1692081710335/51260ec0-0a62-4b62-b475-e2e8011720e7.png align="center")

Create Records.

# Extras

## GitHub Actions

I also added a GitHub workflow to deploy the repo code to the S3 bucket, from which newly created instances in the app and web tier get their code. A commit and push to the main branch will trigger the workflow.

```yaml
name: Deploy App Code to S3

on:
  push:
    branches:
      - main

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
    - name: Checkout code
      uses: actions/checkout@v3

    - name: Configure AWS credentials
      uses: aws-actions/configure-aws-credentials@v2
      with:
        aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
        aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
        aws-region: ap-southeast-1

    - name: Sync files to S3
      run: |
        aws s3 sync app-code/ s3://melvincv2023-cloudops-w2
```

## Conclusion

Crafting a highly available 3-tier architecture for a Node.js app in AWS, with a blend of frontend public subnets, backend private subnets, and Aurora DB private subnets, yields a robust and efficient infrastructure.

The frontend layer engages users, the backend layer processes data, and the database layer stores critical information. This harmonious orchestration, combined with AWS's reliability and scalability, culminates in an architecture that can weather the demands of modern applications.

Connect with me on Linkedin and Twitter for more projects! Please find my social links at the top:

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1692179071969/5c1f7563-367b-482c-b33f-50efcd61aae4.png align="center")

Also, consider subscribing to my blog 😊
