<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: GERALD IZUCHUKWU</title>
    <description>The latest articles on DEV Community by GERALD IZUCHUKWU (@gerald_izuchukwu).</description>
    <link>https://dev.to/gerald_izuchukwu</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F534710%2F7861ceac-feab-4927-84e6-21baa247740c.jpeg</url>
      <title>DEV Community: GERALD IZUCHUKWU</title>
      <link>https://dev.to/gerald_izuchukwu</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/gerald_izuchukwu"/>
    <language>en</language>
    <item>
      <title>Provisioning A Three-Tier Application on AWS using Infrastructure-As-Code (IaC)</title>
      <dc:creator>GERALD IZUCHUKWU</dc:creator>
      <pubDate>Sat, 07 Dec 2024 10:33:01 +0000</pubDate>
      <link>https://dev.to/gerald_izuchukwu/provisioning-a-three-tier-application-on-aws-using-infrastructure-as-code-iac-1a21</link>
      <guid>https://dev.to/gerald_izuchukwu/provisioning-a-three-tier-application-on-aws-using-infrastructure-as-code-iac-1a21</guid>
      <description>&lt;p&gt;This is a general architecture of what we will build. In case it isn't clear enough, please click &lt;a href="https://testserviceaandserviceb.s3.us-east-1.amazonaws.com/three-tier-main-s3-upload.png" rel="noopener noreferrer"&gt;here&lt;/a&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5srbifi49xwnxr3iplol.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5srbifi49xwnxr3iplol.png" alt="Project HLD" width="800" height="295"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;IaC stands for Infrastructure as code and for a while I struggled to understand this concept, and when it seemed like I was starting to get it, I started confusing it with some other thing. IaC allows you to build, change, and manage your infrastructure in a safe, consistent, and repeatable way by defining resource configurations that you can version, reuse, and share. Infrastructure as code is basically provisioning or creating your resources through code or configuration files, in other words, automating the process of creating them in one click, instead of creating them singly through GUI or CLI, and Terraform is a tool that helps us to do that. An AWS cloud native used to also achieve IaC is &lt;a href="https://aws.amazon.com/cloudformation/" rel="noopener noreferrer"&gt;AWS CLoudFormation&lt;/a&gt; and you can learn more about it &lt;a href="https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/Welcome.html" rel="noopener noreferrer"&gt;here&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Terraform is an open-source tool that efficiently helps us to provision infrastructure. It is owned by Hashicorp and uses its own language HCL (Hashicorp Configuration Language), for the configuration of these infrastructures. Terraform can be used for several things but since it is only an IaC provisioning tool, it also has its limitations. &lt;/p&gt;

&lt;p&gt;This article focuses on using Terraform to provision a three-tier application on AWS. There are a lot of three-tier apps out there, maybe more detailed than this but what I intend to do for this is to explain every concept used here, to help me understand further as it takes a while for me to grasp a concept fully and to help people like me.&lt;/p&gt;

&lt;p&gt;This writeup will use the following technologies&lt;/p&gt;
&lt;h4&gt;
  
  
  Network
&lt;/h4&gt;

&lt;ol&gt;
&lt;li&gt;VPC&lt;/li&gt;
&lt;li&gt;Subnet&lt;/li&gt;
&lt;li&gt;Route Table&lt;/li&gt;
&lt;li&gt;Internet Gateway&lt;/li&gt;
&lt;li&gt;Nat Gateway&lt;/li&gt;
&lt;li&gt;Security Groups&lt;/li&gt;
&lt;/ol&gt;
&lt;h4&gt;
  
  
  Compute
&lt;/h4&gt;

&lt;ol&gt;
&lt;li&gt;launch template&lt;/li&gt;
&lt;li&gt;Key pair&lt;/li&gt;
&lt;li&gt;Elastic Load Balancer&lt;/li&gt;
&lt;li&gt;Target Groups&lt;/li&gt;
&lt;li&gt;Auto Scaling Groups&lt;/li&gt;
&lt;/ol&gt;
&lt;h4&gt;
  
  
  Database
&lt;/h4&gt;

&lt;ol&gt;
&lt;li&gt;RDS Database&lt;/li&gt;
&lt;li&gt;Subnet Groups&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;
  
  
  Other AWS Resources
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;IAM Role&lt;/li&gt;
&lt;li&gt;S3 Bucket&lt;/li&gt;
&lt;li&gt;AWS SNS&lt;/li&gt;
&lt;li&gt;AWS CloudWatch&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;
  
  
  Other Non-AWS Resources
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Nginx&lt;/li&gt;
&lt;li&gt;Docker&lt;/li&gt;
&lt;li&gt;Nodejs&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;We are going to break this down into steps&lt;/p&gt;
&lt;h3&gt;
  
  
  STEP 1: Upload your static files and logic  code to Amazon S3 Bucket
&lt;/h3&gt;

&lt;p&gt;To do this, we need to create an S3 bucket, create two folders, and name them frontend and backend (can be named otherwise). In the frontend folder, upload all your static files as well as your nginx.conf file. In the backend folder, we upload all our logic code files. A link to the repo can be found &lt;a href="https://github.com/Gerald-Izuchukwu/three-tier-application" rel="noopener noreferrer"&gt;here&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F31nlt3lql989mwxh2ppn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F31nlt3lql989mwxh2ppn.png" alt="S3 Bucket folder" width="800" height="329"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Dont forget to name your bucket something unique as bucket names are specific per region&lt;/p&gt;
&lt;h3&gt;
  
  
  STEP 2: Set up IAM (User, Roles and Policies)
&lt;/h3&gt;

&lt;p&gt;You can use various ways to configure AWS authentication, I will walk you through using IAM &lt;/p&gt;

&lt;p&gt;An IAM user represents a specific person or application that interacts with resources. This is the "user" that allows Terraform to perform certain tasks in our AWS account. A User's action is defined by the policies attached to that user. An IAM User is quite different from an IAM Role because, a user is mostly configured to perform long-term tasks as it has permanent credentials, whereas an IAM role is for short-term and immediate functions as its credentials are temporary. To create an IAM user for CLI, follow the following steps:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Visit &lt;a href="https://https://us-east-1.console.aws.amazon.com/console/home?region=us-east-1#" rel="noopener noreferrer"&gt;AWS Management Console&lt;/a&gt; &lt;/li&gt;
&lt;li&gt;Navigate to IAM&lt;/li&gt;
&lt;li&gt;Add User, give the user a name&lt;/li&gt;
&lt;li&gt;Attach &lt;strong&gt;AdminstratorAccess&lt;/strong&gt; Policy to the user &lt;/li&gt;
&lt;li&gt;Review and create user&lt;/li&gt;
&lt;li&gt;After creating a user, select the user and navigate to security credentials, scroll down to access keys, and click &lt;code&gt;create access keys&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Select a use case, add a description and the Access key and Secret Access key will be created, download the CSV and save it in a secure folder&lt;/li&gt;
&lt;li&gt;Now set the environment variables
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export AWS_ACCESS_KEY_ID="your_access_key"
export AWS_SECRET_ACCESS_KEY="your_secret_key"
export AWS_REGION="your_region
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Note: The reason why I gave this user Administrator Access policy privilege is because the user will interact with so many resources. The best practice is to compile all the rules the user will need to one policy and attach that policy to the user&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Set up IAM Role
We will need an IAM role to perform two basic functions, therefore we create an IAM role and attach two policies to it. The first policy will be for our instances to be able to read our uploaded code files from Amazon S3. The second policy we will need is the SSM managed instance core policy, to be able to connect to our instance instead of opening an SSH port. Both policies already exist in AWS so there will be no need to create them. The following steps are to be followed to create the IAM role with these two policies&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;Navigate to IAM, on the sidebar, click on roles&lt;/li&gt;
&lt;li&gt;Click on AWS Service (since we are using it for EC2)&lt;/li&gt;
&lt;li&gt;Choose EC2 for the use case, click next&lt;/li&gt;
&lt;li&gt;Add permissions, search for "AmazonS3ReadOnlyAccess" and AmazonSSMManagedInstanceCore, check them, and click next. &lt;/li&gt;
&lt;li&gt;Give the role a name and click "create role"&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  STEP 3: Set up your terraform
&lt;/h3&gt;

&lt;p&gt;I won't assume that you already have Terraform installed, if you do, that's fine, if you don't follow the steps below.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Visit &lt;a href="https://developer.hashicorp.com/terraform/install" rel="noopener noreferrer"&gt;Teraform Download File&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Select the download configuration for your operating system&lt;/li&gt;
&lt;li&gt;Test if the download was successful using &lt;code&gt;terraform -version&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Create a folder for this terraform project, call it whatever you want. I will call mine &lt;em&gt;"three-tier-app-projects"&lt;/em&gt;
change directory into the folder &lt;code&gt;cd three-tier-app-projects&lt;/code&gt; and create a file named &lt;em&gt;main.tf&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;Add the terraform block and aws provider block into the file and save
on the CLI, run the command &lt;code&gt;terraform init&lt;/code&gt; This command takes a while, so be patient
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~&amp;gt; 4.16"
    }
  }

  required_version = "&amp;gt;= 1.8.0"
}

provider "aws" {
  region  = "us-east-1"
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Providers are plugins that help manage and create your resources in IaC. providers could include aws, docker, nginx, etc.&lt;br&gt;
N/B For a simple project such as this, we can simply initialize &lt;code&gt;terraform&lt;/code&gt; with only the &lt;code&gt;provider&lt;/code&gt; block, without the terraform block&lt;/p&gt;
&lt;h3&gt;
  
  
  STEP 4: Setup VPC Network Aspect
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Create a &lt;strong&gt;&lt;em&gt;terraform.tfvars file&lt;/em&gt;&lt;/strong&gt;. This file is used to store all our static variables&lt;/li&gt;
&lt;li&gt;Create the VPC, which is a network house that houses all our resources. Store the vpc_cidr block in the &lt;strong&gt;&lt;em&gt;terraform.tfvars&lt;/em&gt;&lt;/strong&gt;, access it using the variable keyword before calling it in the aws_vpc resource block. Your &lt;strong&gt;&lt;em&gt;main.tf&lt;/em&gt;&lt;/strong&gt; and &lt;strong&gt;&lt;em&gt;terraform.tfvars&lt;/em&gt;&lt;/strong&gt; files should look like this
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;provider "aws" {
  region  = "us-east-1"
}

variable "env_prefix" {}
variable "avail_zone" {}
variable "vpc_cidr" {}

resource "aws_vpc" "main" {
  cidr_block           = var.vpc_cidr
  enable_dns_support   = true
  enable_dns_hostnames = true
  tags = {
    Name = "${var.env_prefix}_vpc"
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;In the &lt;code&gt;terraform.tfvars&lt;/code&gt; file, we have&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;env_prefix    = "three-tier-demo"
avail_zone    = ["us-east-1a", "us-east-1b"]
vpc_cidr      = "10.0.0.0/16"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Run the following command 
-&lt;code&gt;terraform fmt&lt;/code&gt;
-&lt;code&gt;terraform validate&lt;/code&gt;
-&lt;code&gt;terraform plan&lt;/code&gt;
-&lt;code&gt;terraform apply&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;terraform fmt&lt;/code&gt; formats the terraform files in the current directory it was ran, to follow a particular structure,&lt;br&gt;
&lt;code&gt;terraform validate&lt;/code&gt; checks if the configuration is syntactically valid and internally consistent, &lt;br&gt;
&lt;code&gt;terraform plan&lt;/code&gt; checks and outputs all the resources that will be added by the configuration and&lt;br&gt;
&lt;code&gt;terraform apply&lt;/code&gt; goes ahead to apply the configuration after the prompt-"yes" is entered. These are some of the most used terraform commands.&lt;/p&gt;

&lt;p&gt;If everything goes well, a VPC named three-tier-demo_vpc will be provisioned in our us-east-1 zone&lt;/p&gt;

&lt;h3&gt;
  
  
  STEP 5: Let's finish creating our Network resources
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Create Subnets for Public and Private resources in two availability zones for redundancy
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;resource "aws_subnet" "public" {
  count                   = 2
  vpc_id                  = aws_vpc.main.id
  cidr_block              = cidrsubnet(var.vpc_cidr, 8, count.index)
  availability_zone       = var.avail_zone[count.index]
  map_public_ip_on_launch = true

  tags = {
    Name = "${var.env_prefix}_public_subnet-${count.index + 1}"
  }
}

resource "aws_subnet" "private" {
  count                   = 2
  vpc_id                  = aws_vpc.main.id
  cidr_block              = cidrsubnet(var.vpc_cidr, 8, count.index + 2)
  availability_zone       = var.avail_zone[count.index]
  map_public_ip_on_launch = false

  tags = {
    Name = "${var.env_prefix}_private_subnet-${count.index + 1}"
  }
}

resource "aws_subnet" "db_private" {
  count                   = 2
  vpc_id                  = aws_vpc.main.id
  cidr_block              = cidrsubnet(var.vpc_cidr, 8, count.index + 4)
  availability_zone       = var.avail_zone[count.index]
  map_public_ip_on_launch = false

  tags = {
    Name = "${var.env_prefix}_db_private_subnet-${count.index + 1}"
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Create Internet Gateway and Nat Gateway
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;resource "aws_internet_gateway" "this" {
  vpc_id = aws_vpc.main.id
  tags = {
    Name = "${var.env_prefix}_igw"
  }
}
resource "aws_nat_gateway" "nat" {
  allocation_id = aws_eip.nat.id
  subnet_id     = aws_subnet.public[0].id

  tags = {
    Name = "${var.env_prefix}_nat_gateway"
  }
}

resource "aws_eip" "this" {
  vpc = true
  tags = {
    Name = "${var.env_prefix}_eip_nat"
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;N/B: NatGW and ElasticIP (eip) are paid for, there is no free tier available for this. NatGW costs &lt;strong&gt;$0.05/hr&lt;/strong&gt; and EIP costs &lt;strong&gt;$0.005/hr&lt;/strong&gt; when it is not attached to an ec2 instance&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create a Public and Private RouteTable and associate them to a subnet
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;resource "aws_route_table" "public_route_table" {
  vpc_id = aws_vpc.main.id
  route {
    cidr_block = "0.0.0.0/0"
    gateway_id = aws_internet_gateway.this.id
  }


  tags = {
    Name = "${var.env_prefix}_public_route_table"
  }
}

resource "aws_route_table_association" "public" {
  count          = 2
  subnet_id      = aws_subnet.public[count.index].id
  route_table_id = aws_route_table.public_route_table.id
}

resource "aws_route_table" "private_route_table" {
  vpc_id = aws_vpc.main.id
  route {
    cidr_block = "0.0.0.0/0"
    gateway_id = aws_nat_gateway.nat.id
  }

  tags = {
    Name = "${var.env_prefix}_private_route_table"
  }
}

resource "aws_route_table_association" "private" {
  count          = 2
  subnet_id      = aws_subnet.private[count.index].id
  route_table_id = aws_route_table.private_route_table.id
}

resource "aws_route_table_association" "db_private" {
  count          = 2
  subnet_id      = aws_subnet.db_private[count.index].id
  route_table_id = aws_route_table.private_route_table.id
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What did we just do?&lt;br&gt;
We created a VPC for all our resources to live in with a CIDR block of &lt;strong&gt;10.0.0.0/16&lt;/strong&gt;&lt;br&gt;
We created subnets which are like smaller houses in the VPC that house one or more resources. &lt;br&gt;
The public subnets (internet-facing) have an internet gateway route configured in the route table, to allow internet access.&lt;br&gt;
The private subnets (internal facing) have NAT gateway route configured in the route table to allow internet access when needed.&lt;br&gt;
The elastic IP is for Controlled Access, it allows us to use a particularly defined public IP address that doesn't change even if the resource it is attached to goes down. With a NAT Gateway, instances in a private subnet do not have direct public IPs, enhancing security. By using an EIP with the NAT Gateway, you maintain control over how and when outbound internet access is granted without exposing private instances directly to the internet&lt;/p&gt;

&lt;h3&gt;
  
  
  STEP 6: Security
&lt;/h3&gt;

&lt;p&gt;For security, we can set security on the subnet level(NACL) or instance level(Security groups). For this project, we will be using security groups. We need to create Security Groups for the Elastic Load Balancer, the Instances, and the RDS instance. To make our solution highly secure, we allow the web tier instance to receive traffic only from the external load balancer. We also allow the app tier instances to receive HTTP traffic only from the internal load balancer. Since the web tier and app tier has to communicate, we allow the internal load balancer to allow HTTP traffic only from the web-tier instances.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create Security Group for external Load Balancer, that allows HTTP traffic on port 80 and HTTPS on port 443
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;resource "aws_security_group" "externalLoadBalancerSG" {
  vpc_id = aws_vpc.main.id
  ingress {
    from_port   = 80
    to_port     = 80
    protocol    = "tcp"
    cidr_blocks = [var.my_ip_address]
  }

  ingress {
    from_port   = 443 // https
    to_port     = 443
    protocol    = "tcp"
    cidr_blocks = [var.my_ip_address]
  }
  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }

  tags = {
    Name = "01. External LoadBalancer Security Group"
  }

}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Create SG for Web tier Instance, that allows traffic on Port 80 only from the external load balancer security group. You can add an SSH security group rule too, but it's best to use the SSM manager to access the instance terminal. If you prefer ssh, uncomment the ssh security group rule
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;resource "aws_security_group" "webserverSG" {
  vpc_id = aws_vpc.main.id
  ingress {
    from_port       = 80
    to_port         = 80
    protocol        = "tcp"
    security_groups = [aws_security_group.externalLoadBalancerSG.id]

  }
    #ingress {
    #from_port       = 22
    #to_port         = 22
    #protocol        = "tcp"
    #cidr_blocks = ["0.0.0.0/0"]
  }
  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }
  tags = {
    Name = "02. Web Server Security Group"
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Create a security group for the internal load balancer, that allows HTTP traffic on port 80 only from the web-tier security groups
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;resource "aws_security_group" "internalLoadBalancerSG" {
  vpc_id = aws_vpc.main.id
  ingress {
    from_port       = 80
    to_port         = 80
    protocol        = "tcp"
    security_groups = [aws_security_group.webserverSG.id]
  }

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }

  tags = {
    Name = "03. Internal Load Balancer Security Group"
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Create a security group for app-tier instances that allows HTTP traffic on port 9662 (our NodeJS server port) only from the internal load balancer security group. If you will be needing ssh access, you can add the ssh rule like the one we added in the web tier security group
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;resource "aws_security_group" "appserverSG" {
  vpc_id = aws_vpc.main.id

  ingress {
    from_port       = 9662
    to_port         = 9662
    protocol        = "tcp"
    security_groups = [aws_security_group.internalLoadBalancerSG.id]
  }

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }
  tags = {
    Name = "04. App Server Security Group"
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Finally, we will create a security group for our database instances that allow inbound traffic on port 3306 (AURORA/MYSQL) port
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;resource "aws_security_group" "dbserverSG" {
  vpc_id = aws_vpc.main.id

  ingress {
    from_port       = 3306
    to_port         = 3306
    protocol        = "tcp"
    security_groups = [aws_security_group.appserverSG.id]
  }

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }
  tags = {
    Name = "04. Database Server Security Group"
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The created security groups as seen on AWS console&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbs0zba27lz1o91lcdcy8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbs0zba27lz1o91lcdcy8.png" alt="Security Groups" width="800" height="219"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  STEP 7: Provision Web Tier Instances
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;h5&gt;
  
  
  IAM Role
&lt;/h5&gt;

&lt;p&gt;We will be needing the IAM Role we created earlier with two policies - &lt;code&gt;S3ReadOnly&lt;/code&gt; and &lt;code&gt;SSManagerProfile&lt;/code&gt;. This role will be attached to the instances and it will enable them to read the uploaded files on S3 as well as connect to the instance's terminal through SSMManagedInstanceCore  rather than opening port 22 for SSH-ing. We already created this role via the console but it can also be created using Terraform&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;h5&gt;
  
  
  Launch Template
&lt;/h5&gt;

&lt;p&gt;Since we are trying to automate as much as possible and we want auto-scaling of instances, we use auto-scaling group. Auto-scaling group works with launch templates, so we create a launch template with keypair for SSH, entry-script i.e. a list of commands the instances will run after it is launched, and attach the IAM Profile created earlier. The frontend entry-script is &lt;a href="https://github.com/Gerald-Izuchukwu/three-tier-application/blob/main/frontend_script.sh" rel="noopener noreferrer"&gt;here&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;h5&gt;
  
  
  Load Balancer
&lt;/h5&gt;

&lt;p&gt;Next, we create a load balancer, we want this load balancer to be able to receive traffic from outside the VPC, so we make it internet-facing. The job of the load balancer is to distribute traffic evenly across all instances in order not to overwhelm a particular instance. For the load balancer to know the instances to distribute traffic evenly, the instances must be added to the load balancer's target group. The load balancer also checks the health of an instance before it routes traffic to it. The part of the ALB responsible for listening for incoming traffic requests, processing them, and routing them to the target group is called the &lt;code&gt;listener&lt;/code&gt;. It listens on port 80 or 443 and routes these traffic requests based on the rules specified.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;h5&gt;
  
  
  Auto-Scaling Group
&lt;/h5&gt;

&lt;p&gt;The auto-scaling group is used to scale instances up or down based on traffic demand. For this to work, we specify the launch template, the minimum and maximum amount of instances to create, the subnets to launch these instances in, the target group to place these instances in for the ALB, as well as the health check. We also set an autoscaling policy to scale up or down the instances.&lt;br&gt;
The link to the nginx server configuration is &lt;a href="https://github.com/Gerald-Izuchukwu/three-tier-application/tree/main/frontend" rel="noopener noreferrer"&gt;here&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  STEP 8: Provision App Tier Instances
&lt;/h4&gt;

&lt;p&gt;Now for the backend configuration, we do something similar but with little but extremely important changes. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;h5&gt;
  
  
  IAM Role
&lt;/h5&gt;

&lt;p&gt;We also attach the same IAM role we created earlier to the backend instances&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;h5&gt;
  
  
  Launch Template
&lt;/h5&gt;

&lt;p&gt;The launch template configuration is the same as earlier but the entry-script should be different as we want different commands to run in our backend instances. The backend entry-script is &lt;a href="https://github.com/Gerald-Izuchukwu/three-tier-application/blob/main/backend_script.sh" rel="noopener noreferrer"&gt;here&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;h5&gt;
  
  
  Load Balancer
&lt;/h5&gt;

&lt;p&gt;This time, we create an internal load balancer(not internet facing) as we don't want internet traffic to hit our instances directly.&lt;br&gt;
This load balancer also listens on port 80&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;h5&gt;
  
  
  Auto-Scaling Group
&lt;/h5&gt;

&lt;p&gt;The Autoscaling group configuration is also similar to the previous, the only difference is the subnets where the instances should be placed in is the private subnets, and the target group should be that of the internal ALB. &lt;br&gt;
The link to the backend express server configuration is &lt;a href="https://github.com/Gerald-Izuchukwu/three-tier-application/tree/main/backend" rel="noopener noreferrer"&gt;here&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We can see our created web-tier and app-tier instances to serve our frontend and backend files respectively&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftckf0sq3vdl5c4pjklym.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftckf0sq3vdl5c4pjklym.png" alt="Created Instances" width="800" height="219"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  STEP 9: Provision Database Tier Instances
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;h5&gt;
  
  
  Database Subnet Group
&lt;/h5&gt;

&lt;p&gt;First, we create a subnet group in two of the private instances already created earlier&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;h5&gt;
  
  
  DB Instance
&lt;/h5&gt;

&lt;p&gt;Then we create our DB instances in those DB subnet groups. We specify the name, the engine, the username and password of the default user, the security group, and the Availability Zone, (do not use Multi-AZ as that will incur costs outside the free-tier. You can deploy the DB instance in one AZ and deploy a read replica in another AZ. Data from the read replica that way, we can still achieve redundancy&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Step 10: Run Terraform Commands
&lt;/h4&gt;

&lt;p&gt;We need to run the following command to test our configuration&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;terraform fmt&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;terraform validate&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;terraform plan -output=tf.plan&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;terraform apply tf.plan&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If there are any errors, terraform will update you on these errors so you can correct them.&lt;br&gt;
After successfully provisioning the infrastructure, visit the external load balancer DNS to view the hosted website&lt;/p&gt;

&lt;p&gt;Navigate to EC2 page on AWS, and scroll the sidebar down for LoadBalancer. &lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvtmkxwq72tqhjy35gtys.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvtmkxwq72tqhjy35gtys.png" alt="Load Balancer" width="800" height="351"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Select the external load balancer, copy the DNS, and paste this into a browser to see the website and interact with the three-tier application. If you used my frontend code, it should look like this&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft4hb0bj1eglf969gjv3r.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft4hb0bj1eglf969gjv3r.png" alt="Display Page" width="800" height="217"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Up Next: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We configure Amazon CloudWatch and SNS to notify us when there is any change in our project and Amazon Route53 for our DNS&lt;/li&gt;
&lt;li&gt;We modularize the terraform configuration, which is a best practice.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The &lt;a href="https://github.com/Gerald-Izuchukwu/three-tier-application/tree/main" rel="noopener noreferrer"&gt;GitHub Repository&lt;/a&gt; for this Project&lt;/p&gt;

&lt;p&gt;Refs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://developer.hashicorp.com/terraform/tutorials/aws-get-started/infrastructure-as-code" rel="noopener noreferrer"&gt;https://developer.hashicorp.com/terraform/tutorials/aws-get-started/infrastructure-as-code&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.hashicorp.com/terraform/tutorials/aws-get-started/aws-build" rel="noopener noreferrer"&gt;https://developer.hashicorp.com/terraform/tutorials/aws-get-started/aws-build&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=spt3Grgfvu0" rel="noopener noreferrer"&gt;https://www.youtube.com/watch?v=spt3Grgfvu0&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>terraform</category>
      <category>aws</category>
      <category>infrastructureascode</category>
    </item>
    <item>
      <title>IaC Provisioning A Three-Tier Application on AWS</title>
      <dc:creator>GERALD IZUCHUKWU</dc:creator>
      <pubDate>Mon, 14 Oct 2024 10:54:53 +0000</pubDate>
      <link>https://dev.to/gerald_izuchukwu/iac-provisioning-a-three-tier-application-on-aws-2b5g</link>
      <guid>https://dev.to/gerald_izuchukwu/iac-provisioning-a-three-tier-application-on-aws-2b5g</guid>
      <description>&lt;p&gt;IaC Provisioning A Three-Tier Application on AWS&lt;/p&gt;

&lt;p&gt;IaC means Infrastructure as code and for a while I struggled to understand this concept, and when it seemed like I was starting to get it, I started confusing it with platform as code. IaC allows you to build, change, and manage your infrastructure in a safe, consistent, and repeatable way by defining resource configurations that you can version, reuse, and share. Infrastructure as code is basically provisioning or creating your resources through code or configuration files, in other words, automating the process of creating them in one click, instead of creating them singly through GUI or CLI, and Terraform is a tool that helps us to do that. &lt;/p&gt;

&lt;p&gt;Terraform is an open-source tool that efficiently helps us to provision infrastructure, it is owned by Hashicorp and it also uses its own language HCL (Hashicorp Configuration Language), for the configuration of these infrastructures. Terraform can be used for several things but since it is only an IaC provisioning tool, it also has its limitations. &lt;/p&gt;

&lt;p&gt;This write-up focuses on using Terraform to provision a three-tier application on AWS. There are a lot of three-tier apps out there, maybe more detailed than this but what I intend to do for this is to explain every concept used here, to help me understand further as it takes a while for me to grasp a concept fully and to help people like&lt;/p&gt;

&lt;p&gt;This writeup will use the following technologies&lt;/p&gt;

&lt;h4&gt;
  
  
  Network
&lt;/h4&gt;

&lt;ol&gt;
&lt;li&gt;VPC&lt;/li&gt;
&lt;li&gt;Subnet&lt;/li&gt;
&lt;li&gt;Route Table&lt;/li&gt;
&lt;li&gt;Internet Gateway&lt;/li&gt;
&lt;li&gt;Nat Gateway&lt;/li&gt;
&lt;li&gt;Security Groups&lt;/li&gt;
&lt;/ol&gt;

&lt;h4&gt;
  
  
  Compute
&lt;/h4&gt;

&lt;ol&gt;
&lt;li&gt;launch template&lt;/li&gt;
&lt;li&gt;Key pair&lt;/li&gt;
&lt;li&gt;Elastic Load Balancer&lt;/li&gt;
&lt;li&gt;Target Groups&lt;/li&gt;
&lt;li&gt;Auto Scaling Groups&lt;/li&gt;
&lt;/ol&gt;

&lt;h4&gt;
  
  
  Database
&lt;/h4&gt;

&lt;ol&gt;
&lt;li&gt;RDS Database&lt;/li&gt;
&lt;li&gt;Subnet Groups&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Others
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;IAM Role&lt;/li&gt;
&lt;li&gt;S3 Bucket&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;We are going to break this down into steps&lt;/p&gt;

&lt;h3&gt;
  
  
  STEP 1: Upload our static files and logic  code to Amazon S3 Bucket
&lt;/h3&gt;

&lt;p&gt;To do this, we need to create an S3 bucket, create two folders, and name them frontend and backend. In the frontend folder, upload all our static files as well as our nginx.conf file. In the backend folder, we upload all our logic code files&lt;/p&gt;

&lt;h3&gt;
  
  
  STEP 2: Configure AWS auth
&lt;/h3&gt;

&lt;p&gt;You can use various ways to configure AWS authentication, I will walk you through using IAM &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Visit &lt;a href="https://https://us-east-1.console.aws.amazon.com/console/home?region=us-east-1#" rel="noopener noreferrer"&gt;AWS Management Console&lt;/a&gt; &lt;/li&gt;
&lt;li&gt;Navigate to IAM&lt;/li&gt;
&lt;li&gt;Add User, give the user a name&lt;/li&gt;
&lt;li&gt;Attach AdminstratorAccess Policy to the user &lt;/li&gt;
&lt;li&gt;Review and create user&lt;/li&gt;
&lt;li&gt;After creating a user, select the user and navigate to security credentials, scroll down to access keys, and click create access keys&lt;/li&gt;
&lt;li&gt;Select a use case, add a description and the Access key and Secret Access key will be created, download the CSV and save it in a secure folder&lt;/li&gt;
&lt;li&gt;Now set the environment variables
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export AWS_ACCESS_KEY_ID="your_access_key"
export AWS_SECRET_ACCESS_KEY="your_secret_key"
export AWS_REGION="your_region
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  STEP 3: Set up your terraform
&lt;/h3&gt;

&lt;p&gt;I won't assume that you already have Terraform installed, if you do, that's fine, if you don't follow the steps below.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Visit &lt;a href="https://developer.hashicorp.com/terraform/install" rel="noopener noreferrer"&gt;Teraform Download File&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Select the download configuration for your operating system&lt;/li&gt;
&lt;li&gt;Test if the download was successful using "terraform -version"&lt;/li&gt;
&lt;li&gt;Create a folder for this terraform project, call it whatever you want. I will call mine &lt;em&gt;"three-tier-app-projects"&lt;/em&gt;
change directory into the folder &lt;code&gt;cd three-tier-app-projects&lt;/code&gt; and create a file named &lt;em&gt;main.tf&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;Add the terraform block and aws provider block into the file and save
on the CLI, run the command &lt;code&gt;terraform init&lt;/code&gt; This command takes a while, so be patient
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~&amp;gt; 4.16"
    }
  }

  required_version = "&amp;gt;= 1.2.0"
}

provider "aws" {
  region  = "us-east-1"
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Providers are plugins that help manage and create your resources in IaC. providers could include aws, docker, nginx, etc.&lt;br&gt;
N/B For a simple project such as this, we can simply initialize &lt;code&gt;terraform&lt;/code&gt; with only the &lt;code&gt;provider&lt;/code&gt; block, without the terraform block&lt;/p&gt;

&lt;h3&gt;
  
  
  STEP 4: Setup Network Aspect
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Create a &lt;strong&gt;&lt;em&gt;terraform.tfvars file&lt;/em&gt;&lt;/strong&gt;. This file is used to store all our static variables&lt;/li&gt;
&lt;li&gt;Create the VPC, which is a network house that houses all our resources. Store the vpc_cidr block in the &amp;gt;terraform.tfvars, access it using the variable keyword before calling it in the aws_vpc block. Your &lt;strong&gt;&lt;em&gt;main.tf&lt;/em&gt;&lt;/strong&gt; and &lt;strong&gt;&lt;em&gt;terraform.tfvars&lt;/em&gt;&lt;/strong&gt; files should look like this
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;provider "aws" {
  region  = "us-east-1"
}

variable "env_prefix" {}
variable "avail_zone" {}
variable "vpc_cidr" {}

resource "aws_vpc" "main" {
  cidr_block           = var.vpc_cidr
  enable_dns_support   = true
  enable_dns_hostnames = true
  tags = {
    Name = "${var.env_prefix}_vpc"
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;env_prefix    = "three-tier-demo"
avail_zone    = ["us-east-1a", "us-east-1b"]
vpc_cidr      = "10.0.0.0/16"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Run the following command 
&lt;code&gt;terraform fmt&lt;/code&gt;
&lt;code&gt;terraform validate&lt;/code&gt;
&lt;code&gt;terraform plan&lt;/code&gt;
&lt;code&gt;terraform apply&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;terraform fmt formats the file, to follow a particular structure&lt;br&gt;
terraform validate checks if the configuration is syntactically valid and internally consistent &lt;br&gt;
terraform plan checks and outputs all the resources that will be added by the configuration&lt;br&gt;
terraform apply goes ahead to apply the configuration after the prompt is entered&lt;/p&gt;

&lt;p&gt;If everything goes well, a VPC named three-tier-demo_vpc will be provisioned in our us-east-1 zone&lt;/p&gt;

&lt;h3&gt;
  
  
  STEP 5: Let's finish creating our Network resources
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Create Subnets for Public and Private resources in two availability zones for redundancy
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;resource "aws_subnet" "public" {
  count                   = 2
  vpc_id                  = aws_vpc.main.id
  cidr_block              = cidrsubnet(var.vpc_cidr, 8, count.index)
  availability_zone       = var.avail_zone[count.index]
  map_public_ip_on_launch = true

  tags = {
    Name = "${var.env_prefix}_public_subnet-${count.index + 1}"
  }
}

resource "aws_subnet" "private" {
  count                   = 2
  vpc_id                  = aws_vpc.main.id
  cidr_block              = cidrsubnet(var.vpc_cidr, 8, count.index + 2)
  availability_zone       = var.avail_zone[count.index]
  map_public_ip_on_launch = false

  tags = {
    Name = "${var.env_prefix}_private_subnet-${count.index + 1}"
  }
}

resource "aws_subnet" "db_private" {
  count                   = 2
  vpc_id                  = aws_vpc.main.id
  cidr_block              = cidrsubnet(var.vpc_cidr, 8, count.index + 4)
  availability_zone       = var.avail_zone[count.index]
  map_public_ip_on_launch = false

  tags = {
    Name = "${var.env_prefix}_db_private_subnet-${count.index + 1}"
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Create Internet Gateway and Nat Gateway
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;resource "aws_internet_gateway" "this" {
  vpc_id = aws_vpc.main.id
  tags = {
    Name = "${var.env_prefix}_igw"
  }
}
resource "aws_nat_gateway" "nat" {
  allocation_id = aws_eip.nat.id
  subnet_id     = aws_subnet.public[0].id

  tags = {
    Name = "${var.env_prefix}_nat_gateway"
  }
}

resource "aws_eip" "nat" {
  vpc = true
  tags = {
    Name = "${var.env_prefix}_eip_nat"
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;N/B: NatGW and ElasticIP (eip) are paid for, there is no free tier available for this. NatGW costs &lt;strong&gt;$0.05/hr&lt;/strong&gt; and EIP costs &lt;strong&gt;$0.005/hr&lt;/strong&gt; it is not attached to an ec2 instance&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create a Public and Private RouteTable and associate them to a subnet
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;resource "aws_route_table" "public_route_table" {
  vpc_id = aws_vpc.main.id
  route {
    cidr_block = "0.0.0.0/0"
    gateway_id = aws_internet_gateway.this.id
  }


  tags = {
    Name = "${var.env_prefix}_public_route_table"
  }
}

resource "aws_route_table_association" "public" {
  count          = 2
  subnet_id      = aws_subnet.public[count.index].id
  route_table_id = aws_route_table.public_route_table.id
}

resource "aws_route_table" "private_route_table" {
  vpc_id = aws_vpc.main.id
  route {
    cidr_block = "0.0.0.0/0"
    gateway_id = aws_nat_gateway.nat.id
  }

  tags = {
    Name = "${var.env_prefix}_private_route_table"
  }
}

resource "aws_route_table_association" "private" {
  count          = 2
  subnet_id      = aws_subnet.private[count.index].id
  route_table_id = aws_route_table.private_route_table.id
}

resource "aws_route_table_association" "db_private" {
  count          = 2
  subnet_id      = aws_subnet.db_private[count.index].id
  route_table_id = aws_route_table.private_route_table.id
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What did we just do?&lt;br&gt;
We created a VPC for all our resources to live in with a CIDR block of &lt;strong&gt;10.0.0.0/16&lt;/strong&gt;&lt;br&gt;
We created subnets which are like smaller houses in the VPC that house one or more resources. &lt;br&gt;
The public subnets (internet-facing) have an internet gateway route configured in the route table.&lt;br&gt;
The private subnets (internal facing) have NAT gateway route configured in the route table.&lt;br&gt;
The elastic IP is for Controlled Access. With a NAT Gateway, instances in a private subnet do not have direct public IPs, enhancing security. By using an EIP with the NAT Gateway, you maintain control over how and when outbound internet access is granted without exposing private instances directly to the internet&lt;/p&gt;

&lt;h3&gt;
  
  
  STEP 6: Security
&lt;/h3&gt;

&lt;p&gt;For security, we can set security on the subnet level(NACL) or instance level(Security groups). For this project, we will be using security groups. We need to create Security Groups for the Elastic Load Balancer, the Instances, and the RDS instance. To make our solution highly secure, we allow the web tier instance to receive traffic only from the external load balancer. We also allow the app tier instances to receive HTTP traffic only from the internal load balancer. Since the web tier and app tier has to communicate, we allow the internal load balancer to allow HTTP traffic only from the web-tier instances.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create SG for external Load Balancer, that allows HTTP traffic on port 80 and HTTPS on port 443
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;resource "aws_security_group" "externalLoadBalancerSG" {
  vpc_id = aws_vpc.main.id
  ingress {
    from_port   = 80
    to_port     = 80
    protocol    = "tcp"
    cidr_blocks = [var.my_ip_address]
  }

  ingress {
    from_port   = 443 // https
    to_port     = 443
    protocol    = "tcp"
    cidr_blocks = [var.my_ip_address]
  }
  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }

  tags = {
    Name = "01. External LoadBalancer Security Group"
  }

}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Create SG for Web tier Instance, that allows traffic on Port 80 only from the external load balancer security group. You can add ssh security group rule too, but it's best to use the SSM ssh manager to access the instance terminal
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;resource "aws_security_group" "webserverSG" {
  vpc_id = aws_vpc.main.id
  ingress {
    from_port       = 80
    to_port         = 80
    protocol        = "tcp"
    security_groups = [aws_security_group.externalLoadBalancerSG.id]

  }
    ingress {
    from_port       = 22
    to_port         = 22
    protocol        = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }
  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }
  tags = {
    Name = "02. Web Server Security Group"
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Create a security group for the internal load balancer, that allows HTTP traffic on port 80 only from the web-tier security groups
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;resource "aws_security_group" "internalLoadBalancerSG" {
  vpc_id = aws_vpc.main.id
  ingress {
    from_port       = 80
    to_port         = 80
    protocol        = "tcp"
    security_groups = [aws_security_group.webserverSG.id]
  }

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }

  tags = {
    Name = "03. Internal Load Balancer Security Group"
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Create a security group for app-tier instances that allows HTTP traffic on port 9662 (our NodeJS server port) only from the internal load balancer security group
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;resource "aws_security_group" "appserverSG" {
  vpc_id = aws_vpc.main.id

  ingress {
    from_port       = 9662
    to_port         = 9662
    protocol        = "tcp"
    security_groups = [aws_security_group.internalLoadBalancerSG.id]
  }

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }
  tags = {
    Name = "04. App Server Security Group"
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Finally, we will create a security group for our database instances that allows inbound traffic on port 3306 (AURORA/MYSQL) port
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;resource "aws_security_group" "dbserverSG" {
  vpc_id = aws_vpc.main.id

  ingress {
    from_port       = 3306
    to_port         = 3306
    protocol        = "tcp"
    security_groups = [aws_security_group.appserverSG.id]
  }

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }
  tags = {
    Name = "04. Database Server Security Group"
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  STEP 7: Provision Web Tier Instances
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;h5&gt;
  
  
  IAM Profile
&lt;/h5&gt;

&lt;p&gt;We will need to create an IAM Profile with two roles - &lt;code&gt;S3ReadOnly&lt;/code&gt; and &lt;code&gt;SSManagerProfile&lt;/code&gt;. This profile will be attached to the instances and it will enable them to read the uploaded files on S3 as well as connect to the instance's terminal through SSMManagedInstanceCore  rather than opening port 22 for ssh-ing&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;h5&gt;
  
  
  Launch Template
&lt;/h5&gt;

&lt;p&gt;Since we are trying to automate as much as possible and we want auto-scaling of instances, we use auto-scaling group. Auto-scaling group works with launch templates, so we create a launch template with keypair for ssh, entry-script, and attach the IAM Profile created earlier&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;h5&gt;
  
  
  Load Balancer
&lt;/h5&gt;

&lt;p&gt;Next, we create a load balancer, we want this load balancer to be able to receive traffic from outside the VPC, so we make it internet-facing. The job of the load balancer is to distribute traffic evenly across all instances in order not to overwhelm a particular instance. For the load balancer to know the instances to evenly distribute traffic to, the instances must be added to the load balancer's target group. The load balancer also checks the health of an instance before it routes to it. The part of the ALB that is responsible for listens for incoming traffic requests, processes them, and routes them to the target group is called the &lt;code&gt;listener&lt;/code&gt;. It listens on a port, 80 or 443, and routes these traffic requests based on the rules specified.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;h5&gt;
  
  
  Auto-Scaling Group
&lt;/h5&gt;

&lt;p&gt;The auto-scaling group is used to scale instances up or down based on traffic demand. For this to work, we specify the launch template, the minimum and maximum amount of instances to create, the subnets to launch these instances in, the target group to place these instances in for the ALB, as well as health check. We also set an autoscaling policy to scale up or down the instances.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  STEP 8: Provision App Tier Instances
&lt;/h4&gt;

&lt;p&gt;Now for the backend configuration, we do something similar but with little but extremely important changes. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;h5&gt;
  
  
  IAM Profile
&lt;/h5&gt;

&lt;p&gt;We also attach the same IAM profile we created earlier to the backend instances&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;h5&gt;
  
  
  Launch Template
&lt;/h5&gt;

&lt;p&gt;The launch template configuration is the same as earlier but the entry-script should be different as we want different commands to run in our backend instances&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;h5&gt;
  
  
  Load Balancer
&lt;/h5&gt;

&lt;p&gt;This time, we create an internal load balancer(not internet facing) as we don't want internet traffic to hit our instances directly.&lt;br&gt;
This load balancer also listens on port 80&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;h5&gt;
  
  
  Auto-Scaling Group
&lt;/h5&gt;

&lt;p&gt;The Autoscaling group configuration is also similar to the previous, the only difference is the subnets where the instances should be placed in is the private subnets, and the target group should be that of the internal ALB. &lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  STEP 9: Provision Database Tier Instances
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;h5&gt;
  
  
  Database Subnet Group
&lt;/h5&gt;

&lt;p&gt;First, we create a subnet group in two of the private instances already created earlier&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;h5&gt;
  
  
  DB Instance
&lt;/h5&gt;

&lt;p&gt;Then we create our DB instances in those DB subnet groups. We specify the name, the engine, the username and password of the default user, the security group, the Availability Zone, (do not use Multi-AZ as that will incur costs outside the free-tier. You can deploy the DB instance in one AZ and deploy a read replica in another AZ. Data from the read replica that way, we can still achieve redundancy&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Step 10: Run Terraform Commands
&lt;/h4&gt;

&lt;p&gt;We need to run the following command to test our configuration&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;terraform fmt&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;terraform validate&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;terraform plan -output=tf.plan&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;terraform apply tf.plan&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If there are any errors, terraform will update you on these errors so you can correct them.&lt;br&gt;
After successfully provisioning the infrastructure, visit the external load balancer DNS to view the hosted website&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://github.com/Gerald-Izuchukwu/three-tier-application/tree/main" rel="noopener noreferrer"&gt;GitHub Repository&lt;/a&gt; for this Project&lt;/p&gt;

&lt;p&gt;Refs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://developer.hashicorp.com/terraform/tutorials/azure-get-started/infrastructure-as-code" rel="noopener noreferrer"&gt;https://developer.hashicorp.com/terraform/tutorials/azure-get-started/infrastructure-as-code&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=spt3Grgfvu0" rel="noopener noreferrer"&gt;https://www.youtube.com/watch?v=spt3Grgfvu0&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>terraform</category>
      <category>configuration</category>
    </item>
  </channel>
</rss>
