How to Launch Ec2 instance using Terraform. Terraform Basics part:1

Venkat teja Ravi
Vitwit
Published in
13 min readApr 3, 2020
Image by HashiCorp

What is Terraform?

Terraform is an infrastructure as code software by HashiCorp. Terraform enables you to create, change, improve, and version infrastructure safely and efficiently.

Terraform is the first infrastructure configuration tool that supports multiple providers, including:

Terraform is a declarative language, and it was written in the GO language. See the difference between Procedure language and declarative language and why we are using Terraform instead of other technologies such as Ansible, Chef, and more.

Newbies who have not encountered the Terraform and AWS terminologies need not worry. We will walk you through every step that is required.

What we are going to do:

  1. Set up your AWS account
  2. Install Terraform
  3. Launch an EC2-Instance

Set up your AWS account:

Terraform supports multiple providers, but for this tutorial, we are going with AWS provider. AWS provides a massive range of scalable cloud hosting services which includes Elastic Cloud Computing (EC2), Auto-Scaling-Group(ASG), Elastic Load Balancer(ELB), and more

They have a strong community and reliability wise AWS is the pioneer.

One of the good things is AWS provides one-year free-tier services on limited resources. It will be helpful for learners in experimenting with the resources.

To set up a new AWS account open the console.

Source: AWS Console

After setting up the AWS account. You will be able to log in to the AWS console using Root credentials. It is not recommended to log in with the Root credentials. So create one IAM user with Administrative access. (IAM user Vs. Root User)

After creating the IAM user you will have one Access key and one Secret key. It is recommended to store the AWS IAM credentials in AWS configuration on the local machine(how to configure the settings that the AWS Command Line Interface (AWS CLI)).

Installation

To install Terraform, find the appropriate package for your system and download it. To download the Terraform visit this link.

Terraform is packaged as a Zip archive. After downloading Terraform, Unzip the package. Install wget package to download and install unzip to extract the files from zip files.

Ubuntu:

$ sudo apt-get install wget unzip -y

CentOS:

$ sudo yum install wget unzip -y

Download the Terraform archive and extract the Terraform binary.

$ export VERSION="0.12.16"
$ wget https://releases.hashicorp.com/terraform/${VERSION}/terraform_${VERSION}_linux_amd64.zip
$ unzip terraform_${VERSION}_linux_amd64.zip
$ ls
terraform terraform_0.12.16_linux_amd64.zip
$ rm terraform_0.12.16_linux_amd64.zip

Terraform runs as a single binary named terraform.

$ terraform
Usage: terraform [-version] [-help] <command> [args]
The available commands for execution are listed below.
The most common, useful commands are shown first, followed by
less common or more advanced commands. If you're just getting
started with Terraform, stick with the common commands. For the
other commands, please read the help and docs before usage.
........
..............

To make terraform accessible to every user, move to /usr/local/bin/

$ sudo mv terraform /usr/local/bin/

Exit from the terminal and re-login to verify:

$ terraform -v
Terraform v0.12.21

Launch a single Ec2-server:

Before getting your hands dirty, I recommend you look at The official Terraform Getting Started documentation. They have done a pretty good job introducing the individual elements of Terraform (i.e., resources, input variables, output variables, etc.). This tutorial will focus on putting those elements together to create a reasonably real-world example.

Required File Structure:

single-server
├── main.tf
├── variables.tf
├── outputs.tf

single-server here refers to the root directory; the remaining .tf files are Terraform binary files used to configure the infrastructure. main.tf will have the critical Configuration code for infrastructure where you declare the provider "aws" , variables.tf is the binary file that holds all the inputs required for main.tf and the file outputs.tf is used to store all the outputs generated by the provisioned infrastructure (For example: If we create an EC2 Instance, then the Instance’s public_ip and public_dns will be held in outputs.tf).

Terraform won’t care about the file names. But the naming convention should be developer-friendly.

Let’s get into the main task:

open main.tf file and write the following code to declare a provider:

provider "aws" {
region = "us-east-2"
access_key = "my-access-key"
secret_key = "my-secret-key"
}

The code snippet provider will be used to tell Terraform that the provider we are using is AWS. The argument region will provide the infrastructure at the given region in our case; it is "us-east-2" (Know about AWS regions). AWS provision resources across various regions. access_key and secret_key is where you hard code your IAM_user credentials.

Warning: Hard-coding credentials into any Terraform configuration is not recommended. Your credentials in the code may get compromised when you upload it to Git hub or Git lab. See how to set your credentials as Environment variables.

The syntax for the provider is:

provider "<provider_name>" {
region = "<region_code>"
[config....]
}

Adding EC2 as a resource:

Add the following code to main.tf in order to provide an EC2 server as a resource:

resource "aws_instance" "example" {
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t2.micro"
user_data = <<-EOF
#!/bin/bash
echo "Hello, World" > index.html
nohup busybox httpd -f -p 8080 &
EOF
tags = {
Name = "terraform-example"
}
}

Ohhh!! That was a lot of code. It is OK you need not worry about the syntax we will walk you through every line of the code. Now before we go through the code. Why don’t we just have a look at the syntax?

resource syntax:

resource "<provider>_<resource_name>" "identifier" {
[config...]
}

a resource is not only one EC2 Instance it can be iam_user or AWS autoscaling_group and more ..(Read about AWS resources)

Let’s breakdown the resource code:

aws_instance is the resource_name that we want to provision. example is the identifier for the resource. Using this identifier this particular resource can be called anywhere in Terraform. ami is the image_id that represents an Amazon Machine Image. An Amazon Machine Image is a special type of virtual appliance that is used to create a virtual machine within the Amazon Elastic Compute Cloud. It serves as the basic unit of deployment for services delivered using EC2.

instance_type is t2.micro . Instance types comprise varying combinations of CPU, memory, storage, and networking capacity and give you the flexibility to choose the appropriate mix of resources for your applications. Each instance type includes one or more instance sizes, allowing you to scale your resources to the requirements of your target workload. AWS provides a wide variety of instance types. The reason why we are using instance_type = "t2.micro" is t2.micro comes under free-tier service. So this instance type can be useful for learning and experimenting.

If you want to install any services on your computer you do it by running a set of commands. Suppose if I want to run a python server I have to install the python package first. Then, I have to run the command sudo apt-get install python3 in Ubuntu or sudo yum install python3 in Linux and to run python server I will run the following command python3 -m http.server 80 . So everything is fine but what if we want to run these commands before launching the remote EC2-server. AWS provides a facility called user_data where we write all the commands that should be run by a server. In our case, We want to run the script that runs a web server which displays Hello world .

The script is:

<<-EOF
#!/bin/bash
echo "Hello, World" > index.html
nohup busybox httpd -f -p 8080 &
EOF

<<-EOF allows you to create multi-line strings without the need of \n . #!/bin/bash refer that this is a bash script. echo "Hello, world" > index.html defines that The word Hello world should be placed in a .html file called index.html . The word nohup says that this server should be up and running on port 8080even though this bash script is closed with the help of busybox (which is installed by default on Ubuntu) to serve that file at the URL “/”.

tags is an optional data used to identify a resource in the AWS Console.

Now save the code and run the command terraform init inside the root folder i.e single-server .

The output will be like this:

Initializing the backend...Initializing provider plugins...The following providers do not have any version constraints in configuration,
so the latest version was installed.
To prevent automatic upgrades to new major versions that may contain breaking
changes, it is recommended to add version = "..." constraints to the
corresponding provider blocks in configuration, with the constraint strings
suggested below.
* provider.aws: version = "~> 2.54"Terraform has been successfully initialized!You may now begin working with Terraform. Try running "terraform plan" to see
any changes that are required for your infrastructure. All Terraform commands
should now work.
If you ever set or change modules or backend configuration for Terraform,
rerun this command to reinitialize your working directory. If you forget, other
commands will detect it and remind you to do so if necessary.

What does it mean?

On running terraform init Terraform will bring all the required plugins of the provider. In our case, it is an AWS provider plugin. Note that the command is inevitable meaning that no matter how many times you run this command. Your AWS deployed infrastructure won’t be affected or duplicated or destroyed.

Now type terraform plan

Your output will be as follows:

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.
...........
Plan: 1to add, 0 to change, 0 to destroy.

Terraform mainly provides infrastructure based on state. For example, If you want to create some infrastructure using the AWS console and some infrastructure using Terraform. You may have some confusion that the deployed infrastructure may get duplicated or affected. To solve this problem Terraform has come up with a solution called state management. Terraform will remember the state of provisioned infrastructure. It will take the information from a `state` file about the already provisioned infrastructure. Before running terraform plan or terraform apply .

This state file can be shared locally if one person is working on Terraform. If a team is working on the same set of Terraform configuration files. Then it is recommended that the state file should be stored in remote back-end such as s3 .

Plan: 1 to add, 0 to change, 0 to destroy this line says that only one resource will be added, 0 resources to be updated and 0 resources to be destroyed.

It is recommended to run terraform plan before you run terraform apply . This is safety checking before deploying the infrastructure.

Now after running the terraform plan . Run terraform apply See the following output:

$ terraform apply
aws_instance.example: Refreshing state...
(...)Terraform will perform the following actions: # aws_instance.example will be updated in-place
~ resource "aws_instance" "example" {
ami = "ami-0c55b159cbfafe1f0"
availability_zone = "us-east-2b"
instance_state = "running"
(...) + tags = {
+ "Name" = "terraform-example"
} (...)
}Plan: 1to add, 0to change, 0 to destroy.
Do you want to perform these actions?
Terraform will perform the actions described above.
Only 'yes' will be accepted to approve.Enter a value:

It will again ask for confirmation again: type yes .

Now go to the console to see whether the Instance is created or not.

AS you can see our Instance has been created and it is up and running. Now copy the public_ip(IPv4 Public IP) of the Instance. and try to ping the server at port 8080.

$ curl 3.135.222.108:8080

You may get the output like this The connection has been timed out . Why what went wrong?

AWS provides a default security group. We have not specifically attached any security group to our Instance. So AWS will attach a default security group to our Instance. That default security group won’t allow any traffic flow into the Instance.

So go back to the file main.tf and add a security group resource and attach it to our Instance.

resource "aws_security_group" "instance" {
name = "terraform-example-instance"

ingress {
from_port = 8080
to_port = 8080
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
}

name = “terraform-example-instance” is the name of the security group.

ingress {
from_port = 8080
to_port = 8080
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
}

ingress defines the incoming traffic to that security group. from_port and to_port indicates that we are passing traffic at the port 8080 . protocol = “tcp” indicates that we are allowing the only protocol tcp . cidr_blocks = [“0.0.0.0/0”] is a list of IPs from which the traffic can be allowed.

Now the security group has been created. But we still have to attach it to our Instance.

Go to our Instance resource and update the code.

resource "aws_instance" "example" {
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t2.micro"
vpc_security_group_ids = [aws_security_group.instance.id]
user_data = <<-EOF
#!/bin/bash
echo "Hello, World" > index.html
nohup busybox httpd -f -p 8080 &
EOF using busybox (which is installed by default on Ubuntu) to serve that file at the URL “/”
tags = {
Name = "terraform-example"
}
}

If you notice vpc_security_group_ids = [aws_security_group.instance.id] this particular line has been added to the existing code.

This is called attribute_referencing. We are using the security group attribute aws_security_group.instance.id for referencing.

The syntax for attribute referencing is:

<provider>_<resource>.<identifier>.attribute, The attribute here is id .

Now the security group has been attached. Our Instance should accept the traffic to port 8080.

Try to run terraform apply and notice that the output contain Plan: 1to add, 1to change, 0 to destroy. 1 to add is a security group and 1to change is our Instance.

Now check the console that the security group has been created and successfully attached.

Source: AWS console

As you can see that a security group is created and attached to our Instance.

Now copy the public ip of the Instance. Wait a second why do you always want to copy manually. What if you are provisioning several instances. Then that would be very tough to copy each of them manually.

Now Go to the outputs.tf file and write the following code:

output "public_ip" {
value = aws_instance.example.public_ip
description = "The public IP of the web server"
}

We are assigning out Instance public_ip to the output variable “public_ip” with the help of argument referencing.

The syntax for the output variable is:

output "<identifier>" {
value = <provider>_<resource>.<resource_identifier>.<argument>
description = "........"
}

value is mandatory field and description is optional.

If you run terraform apply then the output will be :

$ 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_instance.example will be created
+ resource "aws_instance" "example" {
+ ami = "ami-0c55b159cbfafe1f0"
+ arn = (known after apply)
+ associate_public_ip_address = (known after apply)
+ availability_zone = (known after apply)
.....
Outputs:
public_ip = 3.135.222.108

As you can see the public_ip = 3.135.222.108 .

Copy the public_ip and try to ping in any browser like this 3.135.222.108:8080

We have successfully created one Instance and attached a security group to it. And that Instance is up and running a server.

If you check our instance resource block and security group:

resource "aws_instance" "example" {
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t2.micro"
vpc_security_group_ids = [aws_security_group.instance.id]
user_data = <<-EOF
#!/bin/bash
echo "Hello, World" > index.html
nohup busybox httpd -f -p 8080 &
EOF
tags = {
Name = "terraform-example"
}
}
resource "aws_security_group" "instance" {
name = "terraform-example-instance"

ingress {
from_port = 8080
to_port = 8080
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
}

If you notice that we are using the value 8080 in several places. It is not recommended to hard code the values.

Go to variables.tf file and write the following code:

variable "server_port" {
description = "The port the server will use for HTTP requests"
type = number
default = 8080
}

This defines that we are assigning a value 8080 to the variable server_port .

The syntax for this is:

variable "identifier" {
description = "........."
type = number | string | list(string) | map(string)
default = value
}

Now come back to the main.tf file and replace the value 8080 with var.server_port like this:

resource "aws_security_group" "instance" {
name = "terraform-example-instance"

ingress {
from_port = var.server_port
to_port = var.server_port
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
}
resource "aws_instance" "example" {
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t2.micro"
vpc_security_group_ids = [aws_security_group.instance.id]
user_data = <<-EOF
#!/bin/bash
echo "Hello, World" > index.html
nohup busybox httpd -f -p ${var.server_port} &
EOF
tags = {
Name = "terraform-example"
}
}

Now save the code and type terraform apply .

Now try to ping the server in any browser:

Viola !!!! the server is up and running.

Don’t forget to destroy the infrastructure which we have created by running this command terraform destroy which will destroy the resources and update the state file.

Conclusion

So far in this part, We have seen how to set up an AWS account, How to install Terraform, How to run a server with a security group attached, and how to use outputs.tf and variables.tf files. In the second part, we will see how to run a terraform configuration with the remote-backed state.

If you need help with Terraform, DevOps practices, or AWS at your company, feel free to reach out to us at Vitwit.

--

--

Venkat teja Ravi
Vitwit
Writer for

Software Engineer at Vitwit Technologies. A technology company helping businesses to transform, automate and scale with AI, Blockchain and Cloud computing.