Terraform is a tool for building, changing, and versioning infrastructure safely and efficiently. We can use Terraform to design, implement and manage the AWS infrastructure.
There are many articles out there explain this in detail so I will dive straight into the example.
If you want to learn Terraform basics, please check out my other blog: https://packetswitch.co.uk/terrafrom-aws/
Assumptions
- AWS account is set up and correct permission is assigned to Terraform user
- Terraform is installed on your machine and have access to AWS
Let's say you want to deploy the below infrastructure in AWS. You can of course use the web console, however, what if you want to do the same again for another VPC? That's where Terraform comes in to play.
Terraform will outline exactly what will happen when we run the code. Using Terraform is faster than manually navigating an interface when you need to deploy resources across multiple VPCs / accounts. I will discuss how can we accomplish this using Terraform in AWS.

Steps
- Create a VPC with 10.22.0.0/16 CIDR
- Create public, private and intra subnets in two Availability zones.
- Create an Internet Gateway for public subnets
- Create 3x routing tables and associate it with private, public and intra subnets
- Pubic subnets should have access to both 20.20.20.0/24 and 30.30.30.0/24 via IGW
- Create 2x Elastic IPs
- Create 2x NAT gateways and associate the EIPs created on the previous step.
Note - In this example, I am not going to associate the NAT gateway with the private subnets routing table.
I created three files vars.tf , main.tf and vpc-subnets.tf
vars.tf
Input variables serve as parameters for a Terraform module, allowing aspects of the module to be customized without altering the module's own source code, and allowing modules to be shared between different configurations.
variable "region" {
default = "eu-west-2"
}
variable "vpc-cidr" {
default = "10.22.0.0/16"
}
variable "azs" {
type = list
default = ["eu-west-2a" , "eu-west-2b"]
}
variable "prod-intra-subnets" {
type = list
default = ["10.22.0.0/26" , "10.22.0.64/26"]
}
variable "prod-private-subnets" {
type = list
default = ["10.22.8.0/24" , "10.22.9.0/24"]
}
variable "prod-public-subnets" {
type = list
default = ["10.22.16.0/24" , "10.22.17.0/24"]
}
#this is just for testing
variable "public-subnets" {
type = list
default = ["20.20.20.0/24" , "30.30.30.0/24"]
}
main.tf
It contains provider, region and profile. I work with multiple accounts hence using the profiles. AWS provider is used to interact with the many resources supported by AWS.
provider "aws" {
region = var.region
profile = "suresh-stage"
}
vpc-subnets.tf
It contain the main set of configuration for the deployment.
resource "aws_vpc" "prod-vpc" {
cidr_block = var.vpc-cidr
tags = {
Name = "prod-vpc"
}
}
resource "aws_subnet" "prod-intra-subnets" {
vpc_id = aws_vpc.prod-vpc.id
count = length(var.azs)
cidr_block = element(var.prod-intra-subnets , count.index)
availability_zone = element(var.azs , count.index)
tags = {
Name = "prod-intra-subnet-${count.index+1}"
}
}
resource "aws_subnet" "prod-private-subnets" {
vpc_id = aws_vpc.prod-vpc.id
count = length(var.azs)
cidr_block = element(var.prod-private-subnets , count.index)
availability_zone = element(var.azs , count.index)
tags = {
Name = "prod-private-subnet-${count.index+1}"
}
}
resource "aws_subnet" "prod-public-subnets" {
vpc_id = aws_vpc.prod-vpc.id
count = length(var.azs)
cidr_block = element(var.prod-public-subnets , count.index)
availability_zone = element(var.azs , count.index)
tags = {
Name = "prod-public-subnet-${count.index+1}"
}
}
#IGW
resource "aws_internet_gateway" "prod-igw" {
vpc_id = aws_vpc.prod-vpc.id
tags = {
Name = "prod-igw"
}
}
#route table for public subnet
resource "aws_route_table" "prod-public-rtable" {
vpc_id = aws_vpc.prod-vpc.id
tags = {
Name = "prod-public-rtable"
}
}
#add routes to public-rtable this is just for testing
resource "aws_route" "ner-subnets-public-rtable" {
count = length(var.public-subnets)
route_table_id = aws_route_table.prod-public-rtable.id
destination_cidr_block = element(var.public-subnets , count.index)
gateway_id = aws_internet_gateway.prod-igw.id
}
#route table association public subnets
resource "aws_route_table_association" "public-subnet-association" {
count = length(var.prod-public-subnets)
subnet_id = element(aws_subnet.prod-public-subnets.*.id , count.index)
route_table_id = aws_route_table.prod-public-rtable.id
}
EIP
resource "aws_eip" "nat-eip" {
count = length(var.azs)
vpc = true
tags = {
Name = "EIP--${count.index+1}"
}
}
NAT gateway
resource "aws_nat_gateway" "prod-nat-gateway" {
count = length(var.azs)
allocation_id = element(aws_eip.nat-eip.*.id , count.index)
subnet_id = element(aws_subnet.prod-public-subnets.*.id , count.index)
tags = {
Name = "NAT-GW--${count.index+1}"
}
}
Few things to consider
count - The number of identical resources to create. Above you can see I have used count parameter to create two subnets. I could have said count = 2, but what if in the future I wanted another subnet in the third AZ. That's why I have used length function with count.
length determines the length of a given list, map, or string. In our case, length of the variable called"azs" is 2 (eu-west-2a and eu-west-2a), so two subnets are created. If I add one more AZ to the list then count will become 3 and three subnets will be created.
Element retrieves a single element from a list. In our example, we have a variable called prod-intra-subnets with two subnets (list). We need to provide these subnet CIDRs one by one while creating a subnet. By using element function, I'm instructing Terraform to pick the subnet CIDR one by one in a loop.
Let's take a look at the NAT Gateway tag, for "tag Name", I am using ${count.index} otherwise I will have a same tag for all resources. I have used count.index to get the index of each “iteration” in the “loop”.
Verification






Thanks for reading
As always, your feedback and comments are more than welcome.
Reference


