In this article I will go over how you can use Terraform to code an application's infrastructure. Specifically, you will code an AWS VPC, a publicly accessible subnet, and an EC2 instance which will reside in the subnet.

In order to create a publicly accessible subnet you will be using an AWS Internet Gateway. An Internet Gateway is a resource that allows the internet at large to connect to a subnet within your VPC.

Prerequisites

This tutorial assumes you have an understanding in the following areas:

  • Linux CLI (bash)
  • AWS Concepts

If you do not have the AWS CLI installed on your system you can reference my previous post for details.

Project Setup

Start by creating a project folder

mkdir terraform_demo
cd terraform_demo
Install Terraform

In order to install terraform on your system you need to download the appropriate package for your system here.

In the case of this tutorial it is assumed you are working on a Linux environment. So I would run the following command to add terraform to the PATH environment:

unzip terraform_0.11.11_linux_amd64.zip
sudo mv terraform /usr/local/bin/

The first command unzips the terraform zip package which will contain a single binary file named terraform.

The second command moves the binary file to the /usr/local/bin/ directory which should already be part of the PATH environment.
If the installation worked properly you should now be able to run terraform from the terminal.

If any issues arose during installation reference the official installation page here.

Setting up Terraform

Now that terraform is installed you can start coding the infrastructure.

Start by creating a terraform file.

touch infrastructure_code.tf

Note: The name of the of the file that is created does not matter as long as it ends with .tf.

Next, use your favorite editor to edit the infrastructure_code.tf file. Adding the following lines:

provider "aws" {
  access_key = "PASTE ACCESS KEY ID HERE"
  secret_key = "PASTE SECRET ACCESS KEY HERE"
  region = "us-west-2"
}

The lines above tell terraform to use the AWS specific API in order to create AWS resources.

The access_key and secret_key properties are AWS AMI credentials that allow terraform to interact with AWS.

It's best practice to create a new IAM User specifically for this application. That way you can more precisely control what permissions this User has. More information on IAM Users can be found here.

The User you create for this tutorial should have the policies AmazonEC2FullAccess and AmazonVPCFullAccess attached to it. These policies will give the User permission to create a VPC and EC2.

Before an EC2 Instance can be spun up a VPC (Virtual Private Cloud) needs to be created in which the instance will reside.

A VPC is essentially your own private network that exist within the AWS cloud network. AWS allows you to requisition your own network an specify it's size and allowing you to configure how you partition subnets within it.

In the case of this tutorial you will be creating a single subnet that can be accessed by the internet.

Open the infrastructure_code.tf file again and add the following lines:

resource "aws_vpc" "my_vpc" {
  cidr_block = "10.0.0.0/26"
}

resource "aws_subnet" "public" {
  vpc_id = "${aws_vpc.my_vpc.id}"
  cidr_block = "10.0.0.0/28"
  availability_zone = "us-west-2b"
}

The first resource that is specified, aws_vpc is the VPC, which we assign the name my_vpc.

Inside the resource block you specify the size of the VPC with the cidr_block property. cidr_block ="10.0.0.0/26" tells AWS to allocate the IP 10.0.0.0. With the size specified by /26, which should allow for a total of 64 hosts within this network.

Note: If CIDR notation is foreign to you a good explanation can be found here.

The second resource specified, aws_subnet, is our public subnet, which we named public. In the resource block, the subnet is associated with the VPC that was just created with the property vpc_id = "${aws_vpc.my_vpc.id}". Embedded within the string "${aws_vpc.my_vpc.id}" is terraform's interpolation syntax, ${}. In this instance it will solve the variable aws_vpc.my_vpc.id which references the id for the VPC that was just created.

The cidr_block = "10.0.0.0/28" property specifies the size of this subnet. In this case the subnet will consist of 16 hosts. With the first IP address starting at 10.0.0.0. And the last one being 10.0.0.15

The cidr_block could have just as easily been set to 10.0.0.0/26, which would make the subnet the size of the VPC. Allowing the subnet to have up to 64 hosts.

Note: Once again please reference the link provided above for an explanation of CIDR.

The final property, availability_zone = "us-west-2b", specifies the availability zone of the subnet.

Now that you have a VPC and subnet allocated you would be able to spin up an EC2 instance, but would not be able to remote into it or have access to it from outside of the VPC network. The VPC that was created has no way to access the internet at large, and vice versa.

In order to allow outside access into your network you will need to attach an internet gateway to the VPC that was just created and set the subnet's routing table to point to the internet gateway.

Add the following code:

resource "aws_internet_gateway" "iw" {
  vpc_id = "${aws_vpc.my_vpc.id}"
}

resource "aws_route_table" "public_rt" {
  vpc_id = "${aws_vpc.my_vpc.id}"

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

resource "aws_route_table_association" "public_route_assoc" {
  subnet_id = "${aws_subnet.public.id}"
  route_table_id = "${aws_route_table.public_rt.id}"
}

The first resource that's created is the internet gateway which is associated with the VPC.

The second and third resources created are a route table and its association with the subnet. The aws_route_table resource routes traffic going anywhere to the internet gateway, effectively connecting the subnet to the internet.

Now that the subnet is all setup you can code an EC2 instance resource which will be attached to the subnet.

resource "aws_instance" "ec2_server" {
  ami = "ami-03c652d3a09856345"
  instance_type = "t2.micro"
  subnet_id = "${aws_subnet.public.id}"
  associate_public_ip_address = "true"
  key_name = "EnterTheKeyNameYouWantToUse"
  vpc_security_group_ids = [
    "${aws_security_group.allow_ssh.id}"]
}

In order to spin up an EC2 instance resource you need to specify the virtual machine image (AMI). In this case it's ami-03c652d3a09856345 which is an Amazon Linux virtual machine.

The instance type is also specified as t2.micro. A full list of EC2 instance types can be found here.

As you have probably noticed this instance has been associated with the public subnet that was specified earlier, subnet_id = "${aws_subnet.public.id}".

The associate_public_ip_address = "true" property associates a public elastic IP address with the instance. This means that the instance will have a private and public IP address associated with it. The private address is used to access the instance from within the VPC. While the public IP address is the one used to access the instance from the internet at large. This needs to be set to true because you need to be able to access the instance remotely.

The key_name property is set to which ever key name you would like to use to access the instance. More info about AWS Key Pairs can be found here.

The last property to notice is vpc_security_group_ids. Security groups are virtual firewalls that control the inbound and outbound traffic within the VPC.

In this case we associate it with an aws_security_group resource which is specified below:

resource "aws_security_group" "allow_ssh" {
  name = "allow_ssh"
  vpc_id = "${aws_vpc.my_vpc.id}"

  ingress {
    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"]
  }
}

Ingress and egress rules have been specified. The ingress rule allows traffic using the TCP protocol coming in from any address (0.0.0.0/0) access to port 22.

The egress rule allows all outbound traffic, using any protocol, and from any port, to be able to access the internet.

These rules allow us to ssh to the instance remotely. While also allowing any outbound traffic to be able to leave the VPC.

Finally we add the following to the file:

output "ec2_server_ip" {
  value = "${aws_instance.ec2_server.public_ip}"
}

This last bit of code will print the IP address of the EC2 instance that was just created to the terminal. This is the IP that we will remote into using ssh

Now that the infrastructure_code.tf file is all set all you need to do is run the following commands to run terraform and create the resources specified:

terraform init
terraform apply

The first command initialize the working directory which contains the Terraform configuration files.

The second command creates an execution plan and ff everything works correctly you will be prompted to give the okay to perform all of the actions. Type in yes.

Do you want to perform these actions?
  Terraform will perform the actions described above.
  Only 'yes' will be accepted to approve.

  Enter a value: 

After this finishes you should be able to ssh into the instance using the key pair that was specified.

To test that you can access the instance run the following command:

ssh -i "EnterTheKeyNameYouWantToUse" ec2-user@IP_ADDRESS

You will replace EnterTheKeyNameYouWantToUse with the key pair name that you specified within the aws_instance resource.

You will also replace the IP_ADDRESS placeholder with the IP that was printed to the terminal.

If it worked you should see something like the following:


       __|  __|_  )
       _|  (     /   Amazon Linux 2 AMI
      ___|\___|___|

https://aws.amazon.com/amazon-linux-2/
No packages needed for security; 1 packages available
Run "sudo yum update" to apply all updates.
[ec2-user@ip-10-0-0-8 ~]$ 

That's it for this tutorial. Hopefully it helped you understand Terraform a little better.