How to Deploy your Architecture as Infrastructure as Code using Terraform — Step By Step

Haris Aqeel
5 min readMar 20, 2023

--

Terraform is an open-source infrastructure as code tool that allows users to define, manage, and automate their cloud infrastructure. When combined with Amazon Web Services (AWS), Terraform can be a powerful tool for managing and deploying cloud resources.

One of the key benefits of using Terraform with AWS is the ability to provision and manage infrastructure across multiple AWS accounts and regions. Terraform enables users to write reusable code, which can be easily applied to various environments and ensures consistent configuration and deployment.

Terraform also offers a range of benefits for managing AWS resources, such as providing visibility into infrastructure changes, enabling resource versioning, and simplifying rollback and recovery processes. Furthermore, Terraform allows for fine-grained control over AWS resources, and users can define dependencies and manage complex configurations.

Overall, using Terraform with AWS can help streamline and automate cloud infrastructure management, increase efficiency, and reduce the risk of errors and misconfigurations.

In this post, we will discuss how to develop below architecture using Terraform.

Architecture

We will be using all the possible best practices today to develop the given architecture.

First of all I am providing variable.tf file that has all the variables we will be using during the whole project.

variable "ports" {
type = list(number)
default = [80, 22, 443]
}
variable "image_id" {
type = string
}
variable "region" {
type = string
}
variable "instance_type" {
type = string
}
variable "access_key" {
type = string
}
variable "secret_key" {
type = string
}
variable "vpc_cidr" {
type = string
}
variable "public_subnet_cidrs" {
type = list(string)
description = "Public Subnet CIDR values"
default = ["10.0.1.0/24", "10.0.2.0/24"]
}

variable "private_subnet_cidrs" {
type = list(string)
description = "Private Subnet CIDR values"
}

variable "public_subnet_cidrs_zones" {
type = list(string)
description = "Public Subnet AZ"
default = ["10.0.1.0/24", "10.0.2.0/24"]
}

variable "private_subnet_cidrs_zones" {
type = list(string)
description = "Private Subnet AZ"
}
variable "image_name" {
type = string
}

You can also define the variables in terraform.tfvars so you don’t have to provide them during execution.

Example (terraform.tfvars):

ports                      = [80, 22, 443]
region = "us-east-2"
instance_type = "t2.micro"
access_key = "abcd"
secret_key = "abcd"
vpc_cidr = "10.0.0.0/16"
public_subnet_cidrs = ["10.0.1.0/24", "10.0.3.0/24"]
private_subnet_cidrs = ["10.0.2.0/24", "10.0.4.0/24"]
public_subnet_cidrs_zones = ["us-east-2a", "us-east-2b"]
private_subnet_cidrs_zones = ["us-east-2a", "us-east-2b"]
image_name = "amzn2-ami-kernel-5.10-hvm-2.0.20230307.0-x86_64-gp2"

Defining provider:

provider "aws" {
region = "us-east-2"
access_key = var.access_key
secret_key = var.secret_key
}

First of all you have to define a provider for AWS with access key and secret key of an authorized user in provider.tf.

Creating VPC:

resource "aws_vpc" "terraform-vpc" {
cidr_block = var.vpc_cidr

tags = {
Name = "Project VPC (Through terraform)"
}
}

Now you have to create a VPC by providing the VPC CIDR .

Creating Subnets:

resource "aws_subnet" "public_subnets" {
count = length(var.public_subnet_cidrs)
vpc_id = aws_vpc.terraform-vpc.id
cidr_block = element(var.public_subnet_cidrs, count.index)
availability_zone = element(var.public_subnet_cidrs_zones, count.index)
map_public_ip_on_launch = true

tags = {
Name = "Public Subnet ${count.index + 1}"
}
}

resource "aws_subnet" "private_subnets" {
count = length(var.private_subnet_cidrs)
vpc_id = aws_vpc.terraform-vpc.id
cidr_block = element(var.private_subnet_cidrs, count.index)
availability_zone = element(var.public_subnet_cidrs_zones, count.index)
tags = {
Name = "Private Subnet ${count.index + 1}"
}
}

This creates one private and one public subnets in two region respectively (us-east-2a and us-east-2b).

Creating Internet Gateway:

resource "aws_internet_gateway" "gw" {
vpc_id = aws_vpc.terraform-vpc.id

tags = {
Name = "Project VPC IGW"
}
}

Above code snippet creates Internet Gateway in the given VPC.

Creating Route Table:

resource "aws_route_table" "second_rt" {
vpc_id = aws_vpc.terraform-vpc.id

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

tags = {
Name = "2nd Route Table"
}
}

resource "aws_route_table_association" "public_subnet_asso" {
count = length(var.public_subnet_cidrs)
subnet_id = element(aws_subnet.public_subnets[*].id, count.index)
route_table_id = aws_route_table.second_rt.id
}

Above code snippet creates a route table and attaches Internet Gateway to it and also associates public subnets to route table.

Note : Similarly you can create a NAT gateway and attach private subnets with it and an elastic IP . Remember that NAT gateway and elastic IP is a paid service and doesn’t come under the free tier of AWS.

Creating an Instance In Public Subnet:

Following steps are necessary to create an instance.

Creating a Security Group:

resource "aws_security_group" "terraform_sg" {
name = "terraform_sg"
description = "Allow TLS inbound traffic"
vpc_id = aws_vpc.terraform-vpc.id
dynamic "ingress" {
for_each = var.ports
iterator = port
content {
description = "TLS from VPC"
from_port = port.value
to_port = port.value
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 = "allow_tls"
# }
}

The following code open the ingress ports given in variables and also creates an egress rule for outbound traffic to anywhere.

Creating Key Value Pair:

resource "aws_key_pair" "terraform_key" {
key_name = "terraform-key"
public_key = file("${path.module}/test_rsa.pub")
}

You can create a key value pair and give it’s path in public key variable.

Creating Instance:

data "aws_ami" "linux" {
most_recent = true
owners = ["137112412989"]
filter {
name = "name"
values = ["${var.image_name}"]
}
filter {
name = "root-device-type"
values = ["ebs"]
}

filter {
name = "virtualization-type"
values = ["hvm"]
}
}

resource "aws_instance" "web" {

ami = data.aws_ami.linux.id
vpc_security_group_ids = ["${aws_security_group.terraform_sg.id}"]
instance_type = var.instance_type
key_name = aws_key_pair.terraform_key.key_name
subnet_id = aws_subnet.public_subnets[0].id
availability_zone = var.public_subnet_cidrs_zones[0]
tags = {
Name = "Learning terraform"
}
user_data = file("${path.module}/script.sh")
}

Following above code snippets create an ec2 instance in a public subnet with given security group , instance_type , ami and key value pair . It also installs Nginx so you check after creating the infrastructure by accessing the web through it’s public IP.

#!/bin/bash -ex

amazon-linux-extras install nginx1 -y
echo "<h1>$(curl https://api.kanye.rest/?format=text)</h1>" > /usr/share/nginx/html/index.html
systemctl enable nginx
systemctl start nginx

Now we have completed all the scripts and it’s time to test it.

terraform fmt

This will solve all the indentation problems.

terraform init

This will initialize the terraform and download required providers and modules.

terraform plan

This will tell you if you have any issues related to code and how many resources will be added or destroyed using the terraform script

terraform apply

This will apply all the changes and start creating resources , if everything works fine you can check your AWS management console to validate the creation of resources and you can also SSH into your ec2 instance using your private key.

Conclusion:

Congratulations! You have now created a VPC with four subnets (one public and one private each in two regions), attached a NAT gateway and internet gateway, added them to the route table, created a route table, allowed automatic public IP assignment in public IP subnets, and associated private IP with the NAT gateway.

--

--