DEV Community

Erick Okal
Erick Okal

Posted on

Project #02: Building a 3-Tier AWS VPC Architecture Using Terraform

In this project, we’ll dive into building a 3-tier AWS VPC architecture using Terraform. This architecture demonstrates how to span resources across multiple Availability Zones (AZs), create both public and private subnets, and configure NAT Gateways to enable secure internet access (e.g downloading software updates) for instances in private subnets via the Internet Gateway.

Prerequisites

  1. AWS Account: Active AWS account with permissions to create resources like VPCs, subnets, and EC2 instances.
  2. Terraform Installed: Install Terraform from the official website.
  3. AWS CLI

Note on Costs

Deploying this architecture will incur AWS costs, even when using free-tier eligible resources, due to the following:

  • Elastic IPs: Costs associated with each NAT Gateway.
  • NAT Gateways: Charges for usage and data transfer.

Tip: Use the AWS Pricing Calculator to estimate costs.

Architectural Diagram

3-tier-architecture

Project Steps

Here’s a breakdown of how the 3-tier AWS VPC architecture was created.

Create the Network Infrastructure

  • A VPC was set up with a CIDR block of 10.0.0.0/16 to host all the resources.
  • Public subnets were created in two Availability Zones (AZs) to host NAT Gateways and allow public-facing resources to access the internet.
  • Private subnets were configured:
    • Application subnets to host the application instances.
    • Database subnets for securely hosting database instances.
  • An Internet Gateway was attached to the VPC to provide internet connectivity for resources in public subnets.
  • NAT Gateways were deployed in the public subnets to enable secure internet access for private subnets, allowing both application and database instances to download software updates and libraries from the internet.
  • Route tables were created and associated with the subnets:
    • Public subnets routed traffic directly through the Internet Gateway.
    • Private subnets routed outbound traffic through the NAT Gateways.

Clean Up Resources

  • Terraform’s destroy command ensured all resources (VPC, subnets, NAT Gateways, instances, and route tables) were systematically deleted once the deployment was no longer needed.

Terraform Implementation

Here’s the Terraform configurations used to set up the VPC, subnets, NAT gateways and route tables.


1. Providers and Variables

providers.tf:

terraform {
  required_version = ">= 1.7.5"

  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
  }
}

provider "aws" {
  region = var.aws_region
}
Enter fullscreen mode Exit fullscreen mode

variables.tf:
Defined key variables for modularity and flexibility.

variable "project_name" {
  type        = string
  description = "The name of the project"
  default     = "3-tier-architecture"
}

variable "aws_region" {
  type        = string
  description = "The AWS region to create resources in"
  default     = "us-east-1"
}

variable "azs" {
  type        = list(string)
  description = "The availability zones to create resources in"
  default     = ["us-east-1a", "us-east-1b"]
}

variable "vpc_cidr" {
  type        = string
  description = "The CIDR block for the VPC"
  default     = "10.0.0.0/16"
}

variable "public_subnets" {
  type        = list(string)
  description = "The public subnets to create"
  default     = ["10.0.1.0/24", "10.0.2.0/24"]
}

variable "app_subnets" {
  type        = list(string)
  description = "The private subnets to host the application"
  default     = ["10.0.3.0/24", "10.0.4.0/24"]
}

variable "db_subnets" {
  type        = list(string)
  description = "The private subnets to host the database"
  default     = ["10.0.5.0/24", "10.0.6.0/24"]
}
Enter fullscreen mode Exit fullscreen mode

2. VPC and Subnets

network.tf:

locals {
  common_tags = {
    Project = var.project_name
  }
}

# VPC
resource "aws_vpc" "main" {
  cidr_block           = var.vpc_cidr
  enable_dns_support   = true
  enable_dns_hostnames = true
  tags                 = merge(local.common_tags, { Name = "${var.project_name}-vpc" })
}

# Public Subnets
resource "aws_subnet" "public" {
  for_each                = toset(var.public_subnets)
  vpc_id                  = aws_vpc.main.id
  cidr_block              = each.key
  availability_zone       = element(var.azs, index(var.public_subnets, each.key))
  map_public_ip_on_launch = true
  tags                    = merge(local.common_tags, { Name = "public-subnet-${each.key}" })
}

# App Subnets
resource "aws_subnet" "app-subnet" {
  for_each          = toset(var.app_subnets)
  vpc_id            = aws_vpc.main.id
  cidr_block        = each.key
  availability_zone = element(var.azs, index(var.app_subnets, each.key))
  tags              = merge(local.common_tags, { Name = "app-subnet-${each.key}" })
}

# DB Subnets
resource "aws_subnet" "db-subnet" {
  for_each          = toset(var.db_subnets)
  vpc_id            = aws_vpc.main.id
  cidr_block        = each.key
  availability_zone = element(var.azs, index(var.db_subnets, each.key))
  tags              = merge(local.common_tags, { Name = "db-subnet-${each.key}" })
}
Enter fullscreen mode Exit fullscreen mode

3. Internet Gateway and NAT Gateways

nat_gateway.tf:

# Internet Gateway
resource "aws_internet_gateway" "igw" {
  vpc_id = aws_vpc.main.id
  tags   = merge(local.common_tags, { Name = "${var.project_name}-igw" })
}

# Elastic IPs for NAT Gateways
resource "aws_eip" "nat_eip" {
  for_each = toset(var.azs)
  tags     = merge(local.common_tags, { Name = "nat-eip-${each.key}" })
}

# NAT Gateways
resource "aws_nat_gateway" "nat" {
  for_each      = toset(var.azs)
  allocation_id = aws_eip.nat_eip[each.key].id
  subnet_id     = aws_subnet.public[element(var.public_subnets, index(var.azs, each.key))].id

  tags = merge(local.common_tags, { Name = "nat-gateway-${each.key}" })
}

Enter fullscreen mode Exit fullscreen mode

4. Route Tables

routes.tf:

# Public Route Table
resource "aws_route_table" "public" {
  vpc_id = aws_vpc.main.id

  route {
    cidr_block = "0.0.0.0/0"
    gateway_id = aws_internet_gateway.igw.id
  }

  tags = {
    Name = "public-rt"
  }
}

# Associate Public Subnets with Public Route Table
resource "aws_route_table_association" "public" {
  for_each = aws_subnet.public
  subnet_id      = each.value.id
  route_table_id = aws_route_table.public.id
}

# Private Route Tables for App and DB Subnets
resource "aws_route_table" "private" {
  for_each = toset(var.azs)
  vpc_id = aws_vpc.main.id

  route {
    cidr_block     = "0.0.0.0/0"
    nat_gateway_id = aws_nat_gateway.nat[each.key].id
  }

  tags = {
    Name = "private-rt-${each.key}"
  }
}

# Associate Private Subnets with Private Route Tables
resource "aws_route_table_association" "app" {
  for_each = aws_subnet.app
  subnet_id      = each.value.id
  route_table_id = element(aws_route_table.private[*].id, index(var.app_subnets, each.key))
}

resource "aws_route_table_association" "db" {
  for_each = aws_subnet.db
  subnet_id      = each.value.id
  route_table_id = element(aws_route_table.private[*].id, index(var.db_subnets, each.key))
}
Enter fullscreen mode Exit fullscreen mode

Next Steps

  1. Deploy application EC2 instances in the private application subnets to host the application layer.
  2. Set up a database instance (e.g., Amazon RDS) in the private database subnets.
  3. Integrate an Elastic Load Balancer (ELB) and Auto Scaling Group (ASG) to ensure high availability and scalability for instances in the application layer.

Try It Yourself

  • Clone the repository:
git clone https://github.com/bokal2/terraform-projects.git
cd 3-tier-architecture
Enter fullscreen mode Exit fullscreen mode
  • Follow the steps in README to deploy resources.

Thank you and please feel free to share your thoughts. Cheers!

Top comments (0)