Terraform is an open-source infrastructure as code tool that automates infrastructure deployment, ensures consistency, handles large-scale deployments, is cloud-agnostic, allows for version control, and enables quick deployment to the cloud.

Windows Installation-

choco install terraform

Linux Installation-

#Ubuntu/Debian
sudo apt-get update && sudo apt-get install -y gnupg software-properties-common

#CentOS/RHEL
sudo yum install -y yum-utils

In Terraform code, a provider is a plugin that lets Terraform talk to a specific cloud or infrastructure provider, like AWS, Azure, or Google Cloud, or a specific platform, like Kubernetes.

In your Terraform code, the provider configuration is written in the provider block. It has the name of the provider, the version, and any credentials like access keys or tokens that are needed.

The provider configuration can also include settings that are specific to the provider, such as the region or availability zone to use or the resource types to enable.

Each provider also has its own set of resource types that you can use to describe the infrastructure you want to manage. For instance, the AWS provider has resource types for EC2 instances, S3 buckets, and other resources that are only available on AWS.

First we have to choose the provider in Terraform and pass on access and secret for your IAM user account, which is not recommended practice.

Here i have terraform installed in Windows and the script can be executed in cmd. First we are writing all of the code in a “main.tf” file Notepad++.

provider "aws" {
    region = "us-east-1"
    access_key = "key"
    secret_key = "key"
}

A safer alternative will be to create a local credetial file with the below details

aws configure
AWS Access Key ID :
AWS Secret Access Key :
Default region name :
Default output format :

The information above will be saved in the file /.aws/credentials. Add this path to your aws provider block’s shared credentials file section. E.g.

provider "aws" {
shared_credentials_file = ~/.aws/credentials"
region = var.aws_region
}

An important thing, terraform doesn’t build up resources on AWS in a sequential manner so we will build the framework first.

Next we are creating VPC.

An important note: Terraform does not create this resource, but instead “adopts” it into management. If no default VPC exists, Terraform creates a new default VPC, which leads to the implicit creation of other resources.

#1 create a vpc
resource "aws_vpc" "t1vpc" {
  cidr_block = "10.0.0.0/16"
  tags = {
    Name = "production"
  }
}

Here a VPC name “t1vpc” with cidr_block attribute was used for the primary IPv4 CIDR block.

Next we are creating a VPC internet gateway, it helps VPC to connect to the Internet

#2create internet gateway
resource "aws_internet_gateway" "T1Igw" {
  vpc_id = aws_vpc.t1vpc.id
}

Here T1Igw VPC Internet Gateway was created and we are associating the VPC we created earlier with it.

Next we are creating a VPC route table. It defines how network traffic should be directed within a VPC. It maps the IP addresses of network resources to their corresponding network interfaces, and determines the next hop for the traffic based on its destination. In other words, a routing table in a VPC specifies the paths that traffic takes to travel between network resources within the VPC or to outside the VPC. It is an essential component in managing and securing network traffic in AWS.

#3 create a route table
resource "aws_route_table" "T1rtable" {
  vpc_id = aws_vpc.t1vpc.id

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

  route {
    ipv6_cidr_block = "::/0"
    gateway_id = aws_internet_gateway.T1Igw.id
  }

  tags = {
    Name ="Prod"
  }
}

This route table resource includes two routing rules defined using the “route” block.

The first routing rule directs all IPv4 traffic (identified by the CIDR block “0.0.0.0/0”) to an internet gateway “T1Igw”.

The second routing rule directs all IPv6 traffic (identified by the CIDR block “::/0”) to the same internet gateway as the first rule.

Next we are creating a subnet “subnet1” within VPC “t1vpc”.

#4 create a subnet
resource "aws_subnet" "subnet1" {
  vpc_id     = aws_vpc.t1vpc.id
  cidr_block = "10.0.1.0/24" 
  availability_zone = "us-east-1a"

  tags = {
    Name = "prodsubnet"
  }
}

The subnet resource is defined with a CIDR block of “10.0.1.0/24”, which means it has a range of IP addresses from 10.0.1.0 to 10.0.1.255.

The subnet is associated with a specific availability zone within the AWS region, in this case, “us-east-1a”. This ensures that the subnet is physically located within the specified availability zone, and resources that are created within this subnet will be launched in this availability zone.

Next we are creating a route table association “RTA1” to route table “T1rtable” with subnet “subnet1”

#5 assoc subnet with route table
resource "aws_route_table_association" "RTA1" {
  subnet_id      = aws_subnet.subnet1.id
  route_table_id = aws_route_table.T1rtable.id
}

Basically, we are created a route table association between a specified route table and a specified subnet, which enables us to control the routing behavior for resources in that subnet.

Next we are creating a security group “allowweb"“allowweb” that allows incoming web traffic on three ports: 22 (SSH), 80 (HTTP), and 443 (HTTPS).

#6 create security group to allow 22, 80, 443 
resource "aws_security_group" "allowweb" {
  name        = "allow_web_traffic"
  description = "Allow Web traffic on 3 ports"
  vpc_id      = aws_vpc.t1vpc.id

  ingress {
    description      = "HTTPS"
    from_port        = 443
    to_port          = 443
    protocol         = "tcp"
    cidr_blocks      = ["0.0.0.0/0"]
    
  }
  ingress {
    description      = "HTTP"
    from_port        = 80
    to_port          = 80
    protocol         = "tcp"
    cidr_blocks      = ["0.0.0.0/0"]
    
  }
  ingress {
    description      = "SSH"
    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"]
    ipv6_cidr_blocks = ["::/0"]
  }

  tags = {
    Name = "allow_web"
  }
}

The security group resource includes three “ingress” blocks that define rules for incoming traffic. Each ingress block specifies a port range (from “from_port” to “to_port”) and a protocol (in this case, “tcp”) that are allowed to enter the security group. The “cidr_blocks” attribute specifies the IP address ranges that are allowed to access the security group (in this case, “0.0.0.0/0” which allows any IP address to access the security group).

Additionally, the security group includes an “egress” block that allows all outbound traffic to any destination IP address range.

Next we are creating a Network interface “WbserverNIT1” that can be attached to an instance in our VPC.

#7 network interface with an ip in the subnet that was created earlier
resource "aws_network_interface" "WbserverNIT1" {
  subnet_id       = aws_subnet.subnet1.id
  private_ips     = ["10.0.1.50"]
  security_groups = [aws_security_group.allowweb.id]
}

The subnet_id parameter specifies the ID of the subnet where the network interface should be created. The private_ips parameter assigns a static private IP address to the network interface, in this case, 10.0.1.50. The security_groups parameter associates the network interface with the security group created earlier, allowing traffic on the specified ports.

Next we are creating an Elastic IP Address and associating it to the Network Interface created earlier.

An Elastic IP address is a public, static IP address that you can assign to your AWS account and link to an instance, network interface, or NAT Gateway in your VPC. Elastic IP addresses are useful when you need to keep the same public IP address, even if the instance or network interface that goes with it is stopped or shut down.

#8 create an elastic IP to NIT1
resource "aws_eip" "eip" {
  vpc                       = true
  network_interface         = aws_network_interface.WbserverNIT1.id
  associate_with_private_ip = "10.0.1.50"
  depends_on = [aws_internet_gateway.T1Igw]
}

Here, the vpc parameter specifies that the Elastic IP should be associated with a VPC. The network_interface parameter associates the Elastic IP address with the network interface “WbserverNIT1”. The associate_with_private_ip parameter specifies the private IP address of the network interface to associate with the Elastic IP address.

Finally, the depends_on parameter specifies that the Elastic IP address resource should wait for the Internet Gateway to be created before creating the Elastic IP address, to ensure that the gateway is available before the Elastic IP address is created.

Finally, we are all set to build up our EC2 instance and install Apache2 in it.

#9 create an ubuntu server and install/enable apache2
resource "aws_instance" "ubuntuserver" {
    ami = "ami-00874d747dde814fa"
    instance_type =  "t2.micro"
    availability_zone = "us-east-1a"
    key_name = "T1"

    network_interface {
      device_index = 0
      network_interface_id = aws_network_interface.WbserverNIT1.id
    }
    user_data = <<-EOF
                #!/bin/bash
                sudo apt update -y
                sudo apt install apache2 -y
                sudo systemctl start apache2
                sudo bash -c 'echo My Very first web server on AWS with Terraform > /var/www/html/index.html'
                EOF
    tags = {
        Name = "web-server"
    }
}

Here, EC2 instance named ubuntuserver is being build up using Amazon Machine Image ID belonging to Ubuntu. Here t2.micro instance type is used. t2.micro instances have 1 virtual CPU (vCPU) and 1 GB of memory. We also specified our availability zone “us-east-1a” and also create a key pair “T1”. Then we have added script so when the instance is running Apache2 web server will installed, ran and creating an index.html file at the web directory as well.

After the terraform configuration file “main.tf” is ready, we can run couple of terraform commands, such as -

  • Terraform init - To create a working directory, containing all configuration files.

  • Terraform validate - To check for errors in the configuration

  • Terraform plan - To show the changes to be made as per current configuration

  • Terraform apply - To create or update the deployment, as per current configuration

  • Terraform destroy - To destroy previously created infrastructure

This was my first Terraform project and will be creating another terraform project on a different use case.

EC2 Network Diagram

References-

https://medium.com/@knoldus/add-aws-credentials-in-terraform-b43efa7b934d

https://blog.gruntwork.io/a-comprehensive-guide-to-managing-secrets-in-your-terraform-code-1d586955ace1#c49b

https://registry.terraform.io/providers/hashicorp/aws/latest/docs

https://docs.aws.amazon.com/vpc/latest/userguide/configure-your-vpc.html