Terraform to Provision AWS Resources

In this article, we will learn how we can provision AWS resources like VPC, EC2, NAT, etc with the help of the Terraform tool. To understand easily we will provision infrastructure as shows in the image. Here we will create a VPC and two subnets, one public subnet to host our web server and the private subnet for Database instance. Webserver instance can be accessed from the internet but DB instance can only be accessed from the Web-server (Not from the internet) only.

To download my complete Terraform code from Github execute below command.

git clone https://github.com/techiescorner/terra-works.git

To complete this article you need the below tools.

  • Terraform
  • AWS account and private key
  • Github accounts for version control.
  • Mac / Linux terminal.

 

To install the latest Terraform I recommend reading the below article.

Terraform Installation.

To store our terraform work files and for version control,  we will create a repository on Github.

Create a repository on Github:

Login to https://github.com/ and click “+” on the top right corner, select New repository to create a repository for Terraform works. I have created a repository named “Terra-works” with the following details.

 

 

Added a gitignore file named terraform and license as Apache License 2.0.

Download the repository to the local machine.

 

 

Copy the git URL from GitHub and use “git clone” to copy to the local machine.

 

:$ git clone https://github.com/techiescorner/terra-works.git

 Cloning into 'terra-works'... remote: Enumerating objects: 4, done. remote: Counting objects: 100% (4/4), done. remote: Compressing objects: 100% (4/4), done. remote: Total 4 (delta 0), reused 0 (delta 0), 
pack-reused 0 Unpacking objects: 100% (4/4), 
done.

 

To connect to the AWS platform we need to configure it in our terminal, for this we need to install awscli on the terminal. If you haven’t installed before please refer below link it.

 

https://docs.aws.amazon.com/cli/latest/userguide/install-bundle.html

 

Once you have installed awscli tool, you can configure the terminal to connect to the AWS cloud. For this we need a user and role, so create it first.

AWS user and role creation:

Login to AWS console and select IAM from all resources. Select the user from the left side panel and click add user button. Next, create a group and set a policy name as AdministratorAccess.

 

 

Click next and add tag and click on Create user button to complete the process. Once you are done you will get a window with “Access key ID” and “Secret access key”. Download and keep it in a safe place.

 

 

Now configure AWS by executing below command and give the required details.

$ aws configure

 

Terraform initialization:

Create a file under the repository folder named connect.tf to initiate the terraform. This file we will add some elements so we can confirm terraform can connect to the AWS cloud. Open the connect.tf file and add the below codes.

provider "aws" {

   region = "us-east-1"

}

 

Execute the “terraform init” command.

 

 

Now you can see a new folder created under the repository directory named .terraform this folder contains the plugin to connect to the AWS.

Push the files to Github:

Push all the files from our local repository to the Github, for this execute below commands.

MacBook-Pro:terra-works $ git init

Reinitialized existing Git repository in /Users/terraform-works/terra-works/terra-works/.git/

MacBook-Pro:terra-works $ git add .

MacBook-Pro:terra-works $ git commit -m "Terraform initialization" 

[master 1ad24f5] Terraform initialization

 1 file changed, 5 insertions(+)

 create mode 100644 connect.tf

MacBook-Pro:terra-works $ git push -u origin master

Enumerating objects: 4, done.

Counting objects: 100% (4/4), done.

Delta compression using up to 12 threads

Compressing objects: 100% (2/2), done.

Writing objects: 100% (3/3), 368 bytes | 368.00 KiB/s, done.

Total 3 (delta 0), reused 0 (delta 0)

To https://github.com/techiescorner/terra-works.git

   8a90269..1ad24f5  master -> master

Branch 'master' set up to track remote branch 'master' from 'origin'.

 

Provision AWS resources:

Create another file called awsvpc.tf to provision AWS VPC. It include a VPC, public subnet, private subnet, internet gateway and route table. Also, we will associate a route table with a public and private subnet. The NAT address needs a public IP address, so we will create one and associate it with NAT resources.

Add the below codes to the file and save.

resource "aws_vpc" "terraform-vpc" {
    cidr_block = "10.0.0.0/16"
    enable_dns_hostnames = true
    enable_dns_support   = true
    tags = {
       Name = "TC-vpc" 
    }    
}

resource "aws_internet_gateway" "terraform-IG" {
     vpc_id = "${aws_vpc.terraform-vpc.id}"
     tags = {
  Name = "TC-ig"
     }
}

resource "aws_subnet" "terraform-PubSN" {
     vpc_id = "${aws_vpc.terraform-vpc.id}"
     cidr_block = "10.0.1.0/24"
     availability_zone = "us-east-1a"
     tags = {
         Name = "TC-PublicSN"
     }
}      

resource "aws_subnet" "terraform-PriSN" {
     vpc_id = "${aws_vpc.terraform-vpc.id}"
     cidr_block = "10.0.2.0/24"
     availability_zone = "us-east-1a"
     tags = {
         Name = "TC-PrivSN"
     }
}     

resource "aws_route_table" "terraform-Route-Pub" {
      vpc_id = "${aws_vpc.terraform-vpc.id}"
      route {
      cidr_block = "0.0.0.0/0"
      gateway_id = "${aws_internet_gateway.terraform-IG.id}"
      }
      tags = {
           Name = "TC-PubRT"
    }
}

resource "aws_route_table_association" "terraform-PubSN_Route" {
    subnet_id = "${aws_subnet.terraform-PubSN.id}"
    route_table_id = "${aws_route_table.terraform-Route-Pub.id}"
    }

resource "aws_route_table" "terraform-Route-Pri" {
     vpc_id = "${aws_vpc.terraform-vpc.id}"
     route {
     cidr_block = "0.0.0.0/0"
     nat_gateway_id = "${aws_nat_gateway.nat.id}"
}
    tags = {
         Name = "TC-PriRT"
     }
   }  

resource "aws_route_table_association" "terraform-PriSN-Route" {
     subnet_id = "${aws_subnet.terraform-PriSN.id}"
     route_table_id = "${aws_route_table.terraform-Route-Pri.id}"    
}

resource "aws_nat_gateway" "nat" {
            subnet_id     = "${aws_subnet.terraform-PubSN.id}"
           allocation_id = "${aws_eip.ip_nat.id}"
           tags = {
                   Name = "TC-NAT"
       }
}

resource "aws_eip" "ip_nat" {
        vpc = true
       tags = {
           Name = "terraforms-works"
       }
} 

Below is the explanation for the above code.

Here resource keyword is to indicate a resource in AWS cloud and a name for it. For example

resource “aws_vpc” “terraform-vpc”.    Here resource is used to indicate the AWS VPC service and named as terraform-vpc this name is only used in terraform code and the name in the AWS console will be “TC-vpc” (tags = { Name = “TC-vpc” } ).

 

Execute terraform init command to check for any syntax error.

It will give an output with what terraform going to do. The complete list of resources and its details.

Refreshing Terraform state in-memory prior to plan...

The refreshed state will be used to calculate this plan, but will not be

persisted to local or remote state storage.

--------------------------------------------------------------------

An execution plan has been generated and is shown below.

Resource actions are indicated with the following symbols:

  + create

Terraform will perform the following actions:

  # aws_eip.ip_nat will be created

  + resource "aws_eip" "ip_nat" {

      + allocation_id     = (known after apply)

      + association_id    = (known after apply)

      + domain            = (known after apply)

      + id                = (known after apply)

      + instance          = (known after apply)

      + network_interface = (known after apply)

      + private_dns       = (known after apply)

      + private_ip        = (known after apply)

      + public_dns        = (known after apply)

      + public_ip         = (known after apply)

      + public_ipv4_pool  = (known after apply)

      + tags              = {

          + "Name" = "TC-NAT-IP"
        }
      + vpc               = true
    }

  # aws_internet_gateway.terraform-IG will be created

  + resource "aws_internet_gateway" "terraform-IG" {

      + id       = (known after apply)

      + owner_id = (known after apply)

      + tags     = {

          + "Name" = "TC-ig"
        }
      + vpc_id   = (known after apply)
    }

  # aws_nat_gateway.nat will be created

  + resource "aws_nat_gateway" "nat" {

      + allocation_id        = (known after apply)

      + id                   = (known after apply)

      + network_interface_id = (known after apply)

      + private_ip           = (known after apply)

      + public_ip            = (known after apply)

      + subnet_id            = (known after apply)

      + tags                 = {

          + "Name" = "terraform-works"
        }
    }

  # aws_route_table.terraform-Route-Pri will be created

  + resource "aws_route_table" "terraform-Route-Pri" {

      + id               = (known after apply)

      + owner_id         = (known after apply)

      + propagating_vgws = (known after apply)

      + route            = [

          + {

              + cidr_block                = "0.0.0.0/0"

              + egress_only_gateway_id    = ""

              + gateway_id                = ""

              + instance_id               = ""

              + ipv6_cidr_block           = ""

              + nat_gateway_id            = (known after apply)

              + network_interface_id      = ""

              + transit_gateway_id        = ""

              + vpc_peering_connection_id = ""

            },
        ]

      + tags             = {

          + "Name" = "TC-PriRT"
        }
      + vpc_id           = (known after apply)
    }

  # aws_route_table.terraform-Route-Pub will be created

  + resource "aws_route_table" "terraform-Route-Pub" {

      + id               = (known after apply)

      + owner_id         = (known after apply)

      + propagating_vgws = (known after apply)

      + route            = [

          + {

              + cidr_block                = "0.0.0.0/0"

              + egress_only_gateway_id    = ""

              + gateway_id                = (known after apply)

              + instance_id               = ""

              + ipv6_cidr_block           = ""

              + nat_gateway_id            = ""

              + network_interface_id      = ""

              + transit_gateway_id        = ""

              + vpc_peering_connection_id = ""
            },
        ]
      + tags             = {
          + "Name" = "TC-PubRT"
        }
      + vpc_id           = (known after apply)

    }

  # aws_route_table_association.terraform-PriSN-Route will be created

  + resource "aws_route_table_association" "terraform-PriSN-Route" {

      + id             = (known after apply)

      + route_table_id = (known after apply)

      + subnet_id      = (known after apply)
    }
  # aws_route_table_association.terraform-PubSN_Route will be created
  + resource "aws_route_table_association" "terraform-PubSN_Route" {

      + id             = (known after apply)

      + route_table_id = (known after apply)

      + subnet_id      = (known after apply)
    }

  # aws_subnet.terraform-PriSN will be created

  + resource "aws_subnet" "terraform-PriSN" {

      + arn                             = (known after apply)

      + assign_ipv6_address_on_creation = false

      + availability_zone               = "us-east-1a"

      + availability_zone_id            = (known after apply)

      + cidr_block                      = "10.0.2.0/24"

      + id                              = (known after apply)

      + ipv6_cidr_block                 = (known after apply)

      + ipv6_cidr_block_association_id  = (known after apply)

      + map_public_ip_on_launch         = false

      + owner_id                        = (known after apply)

      + tags                            = {

          + "Name" = "TC-PrivSN"
        }
      + vpc_id                          = (known after apply)
    }

  # aws_subnet.terraform-PubSN will be created
  + resource "aws_subnet" "terraform-PubSN" {

      + arn                             = (known after apply)

      + assign_ipv6_address_on_creation = false

      + availability_zone               = "us-east-1a"

      + availability_zone_id            = (known after apply)

      + cidr_block                      = "10.0.1.0/24"

      + id                              = (known after apply)

      + ipv6_cidr_block                 = (known after apply)

      + ipv6_cidr_block_association_id  = (known after apply)

      + map_public_ip_on_launch         = false

      + owner_id                        = (known after apply)

      + tags                            = {

          + "Name" = "TC-PublicSN"
        }
      + vpc_id                          = (known after apply)
    }

  # aws_vpc.terraform-vpc will be created

  + resource "aws_vpc" "terraform-vpc" {

      + arn                              = (known after apply)

      + assign_generated_ipv6_cidr_block = false

      + cidr_block                       = "10.0.0.0/16"

      + default_network_acl_id           = (known after apply)

      + default_route_table_id           = (known after apply)

      + default_security_group_id        = (known after apply)

      + dhcp_options_id                  = (known after apply)

      + enable_classiclink               = (known after apply)

      + enable_classiclink_dns_support   = (known after apply)

      + enable_dns_hostnames             = true

      + enable_dns_support               = true

      + id                               = (known after apply)

      + instance_tenancy                 = "default"

      + ipv6_association_id              = (known after apply)

      + ipv6_cidr_block                  = (known after apply)

      + main_route_table_id              = (known after apply)

      + owner_id                         = (known after apply)

      + tags                             = {

          + "Name" = "TC-vpc"
        }
    }
Plan: 10 to add, 0 to change, 0 to destroy.
---------------------------------------------------------------------
Note: You didn't specify an "-out" parameter to save this plan, so Terraform
can't guarantee that exactly these actions will be performed if

"terraform apply" is subsequently run.

As the above output mentioned execute terraform apply command to provision the resources and it will ask for a confirmation enter “yes” to continue.

$ terraform apply

An execution plan has been generated and is shown below.

Resource actions are indicated with the following symbols:

  + create

Terraform will perform the following actions:

aws_route_table_association.terraform-PriSN-Route: Creating...

aws_route_table_association.terraform-PriSN-Route: Creation complete after 1s [id=rtbassoc-0271b652a17395d6d]

Apply complete! Resources: 10 added, 0 changed, 0 destroyed.

Yes, the terraform execution completed successfully. Now go to the AWS console and make sure the resource is provisioned successfully.

First go to VPC:-

Subnet:


Route table:-

Internet gateway:-

NAT:-

On the next page, we will create 2 more files, One is to provision a web server and other for DB instance. Also, we will add a public IP address to the webserver EC2 instance.

Go to the next page.

Leave a Reply

Your email address will not be published. Required fields are marked *