CREATING A CUSTOM VPC ON AWS USING TERRAFORM
In the age of cloud migration, establishing resilient and secure Virtual Private Cloud (VPC) networks is paramount. AWS offers a robust VPC service, enabling users to create virtual networks in the cloud. However, configuring and managing a VPC can be complex and time-consuming. Terraform plays a key role. Terraform is an open-source infrastructure as a code tool that steps in to simplify and automate the creation and management of AWS resources.
This article will explore how Terraform streamlines setting up a custom VPC on AWS.
Prerequisites
- AWS Cli: Installed and Configured
- Terraform
- AWS Account
GitHub Repository Link
The diagram above illustrates a fundamental VPC design that incorporates the following components:
- A Virtual Private Cloud with the specified CIDR block.
- Two public subnets, with each subnet associated with a specific availability zone.
- Two private subnets, with each subnet associated with a specific availability zone.
- Route Table Associations for Public Subnets
- Route Table Associations for Private Subnets
- Internet Gateway (IGW)
- Elastic IP (EIP) for NAT Gateway
- NAT Gateway
- Route Table for Private Subnets
- Route Table for Public Subnets
- EC2 Instances
Virtual Private Cloud (VPC): A Virtual Private Cloud with the specified CIDR block, DNS hostnames, and support enabled for the VPC.
# main.tf
# VPC
resource "aws_vpc" "vpc" {
cidr_block = var.vpc_cidr
enable_dns_hostnames = true
enable_dns_support = true
tags = {
Name = "${var.tag}-vpc"
}
}
Private Subnets: Two public subnets, each associated with a specific availability zone. Each public subnet is configured to allow instances launched with public IP addresses.
# main.tf
# Public subnet
resource "aws_subnet" "public_subnet" {
vpc_id = aws_vpc.vpc.id
count = length(var.public_subnets_cidr)
cidr_block = element(var.public_subnets_cidr, count.index)
availability_zone = element(var.public_AZ, count.index)
map_public_ip_on_launch = true
tags = {
Name = "${var.tag}-${element(var.public_AZ, count.index)}-public-subnet"
}
}
Private Subnets: Two private subnets, with each subnet associated with a specific availability zone, and Instances launched in these private subnets will not have public IP addresses by default.
# main.tf
# Private Subnet
resource "aws_subnet" "private_subnet" {
vpc_id = aws_vpc.vpc.id
count = length(var.private_subnets_cidr)
cidr_block = element(var.private_subnets_cidr, count.index)
availability_zone = element(var.private_AZ, count.index)
map_public_ip_on_launch = false
tags = {
Name = "${var.tag}-${element(var.private_AZ, count.index)}-private-subnet"
}
}
Route Table/Route Table Associations for Public Subnets: Route table associations link each public subnet to a familiar route table for handling their routing.
# main.tf
# Routing tables to route traffic for Public Subnet
resource "aws_route_table" "public" {
vpc_id = aws_vpc.vpc.id
tags = {
Name = "${var.tag}-public-route-table"
}
}
# Route table associations for both Public subnet
resource "aws_route_table_association" "public" {
count = length(var.public_subnets_cidr)
subnet_id = element(aws_subnet.public_subnet.*.id, count.index)
route_table_id = aws_route_table.public.id
}
Route Table/Route Table Associations for Private Subnets: Route table associations link each private subnet to a route table for handling their routing.
# main.tf
# Routing tables to route traffic for Private Subnet
resource "aws_route_table" "private" {
vpc_id = aws_vpc.vpc.id
tags = {
Name = "${var.tag}-private-route-table"
}
}
# Route table associations for both Private subnet
resource "aws_route_table_association" "private" {
count = length(var.private_subnets_cidr)
subnet_id = element(aws_subnet.private_subnet.*.id, count.index)
route_table_id = aws_route_table.private.id
}
Internet Gateway (IGW): An Internet Gateway associated with the VPC, allowing public subnets to communicate with the internet.
# main.tf
#Internet gateway
resource "aws_internet_gateway" "igw" {
vpc_id = aws_vpc.vpc.id
tags = {
"Name" = "${var.tag}-igw"
}
}
NAT Gateway/Elastic IP (EIP) for NAT Gateway: An Elastic IP is created for use with the NAT Gateway, allowing instances in the private subnets to communicate outbound internet.
# main.tf
# NAT Gateway
resource "aws_nat_gateway" "nat" {
allocation_id = aws_eip.NAT_eip.id
subnet_id = element(aws_subnet.public_subnet.*.id, 0)
tags = {
Name = "nat-gateway-${var.tag}"
}
}
# Elastic-IP (eip) for NAT
resource "aws_eip" "NAT_eip" {
vpc = true
depends_on = [aws_internet_gateway.igw]
}
aws_instance.public_instance: This block creates EC2 instances in the public subnets.
#main.tf
# EC2 instance in Public Subnet
resource "aws_instance" "public_instance" {
count = length(var.public_subnets_cidr)
ami = var.os
instance_type = var.instance
vpc_security_group_ids = [aws_security_group.public_sg.id]
subnet_id = element(aws_subnet.public_subnet.*.id, count.index)
key_name = "vpn-key"
tags = {
Name = "${var.tag}-public-instance-${count.index + 1}"
}
}
## ssh connectivity
resource "aws_key_pair" "vpn_key" {
key_name = "vpn-key"
public_key = file("~/.ssh/id_rsa.pub")
}
# Creating a security group named sg
resource "aws_security_group" "public_sg" {
# Name, Description and the VPC of the Security Group
name = "sg"
vpc_id = aws_vpc.vpc.id
description = "Security group"
# allowing traffic from our IP on port 22
ingress {
description = "Allow SSH from my computer"
from_port = 22
to_port = 22
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
#instance to being able to talk to the internet
egress {
description = "Allow all outbound traffic"
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
aws_instance.private_instance: This block creates EC2 instances in the private subnets.
# main.tf
# EC2 instance in Private Subnet
resource "aws_instance" "private_instance" {
count = length(var.private_subnets_cidr)
ami = var.os
instance_type = var.instance
subnet_id = element(aws_subnet.private_subnet.*.id, count.index)
key_name = "vpn-key" # Replace with your SSH key pair
tags = {
Name = "${var.tag}-private-instance-${count.index + 1}"
}
}
var.tf
# var.ft
# Replaced with desired values
variable "region" {
default = "us-east-1"
}
variable "tag" {
default = "vpc-setup"
}
variable "vpc_cidr" {
default = "10.0.0.0/16"
description = "CIDR block of the vpc"
}
variable "public_subnets_cidr" {
type = list(string)
default = ["10.0.1.0/24", "10.0.2.0/24"]
description = "CIDR block for Public Subnet"
}
variable "private_subnets_cidr" {
type = list(string)
default = ["10.0.3.0/24", "10.0.4.0/24"]
description = "CIDR block for Private Subnet"
}
variable "private_AZ" {
type = list(string)
default = ["us-east-1a"]
}
variable "public_AZ" {
type = list(string)
default = ["us-east-1b"]
}
variable "os" {
default = "ami-08a52ddb321b32a8c"
}
variable "instance" {
default = "t2.micro"
}
TERRAFORM COMMANDS
In the working directory, follow these steps:
RUN
1. terraform init
2. terraform validate
3. terraform plan
4. terraform apply
In summary, the configuration sets up a VPC with public and private subnets, enabling resources to be deployed in a segmented manner. Public subnets can access the internet, while private subnets use a NAT Gateway for outbound traffic. The routing tables ensure that traffic is routed correctly between the subnets and the internet. This architecture is suitable for hosting a variety of applications with different security and access requirements.
Full terraform configuration file can be found Here
Enjoy your learning journey!!