Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Terraform With AWS

DZone's Guide to

Terraform With AWS

Terraform is an Infrastructure as a Code tool that allows you to create and improve infrastructure. Learn how to spin up Terraform instances with AWS.

· DevOps Zone ·
Free Resource

Planning to extract out a few microservices from your monolith? Read this free guide to learn the best practice before you get started.

In my previous article, I did give an overview of Terraform. This article will help us to understand how to spin up instances in AWS using the Infrastructure as a Code tool Terraform. Although it's beyond the scope to discuss all the commands, I will try to cover the basic commands, and other resources can be researched and tried from the Terraform website against the provider of your choice.

Amazon Web Services (AWS) is a secure cloud services platform provided by Amazon. There are millions of customers who use AWS for their daily computing needs. AWS offers:

  • Compute capacity,

  • Storage,

  • DB,

  • And much more.

Big organizations who use AWS or any other cloud service provider do need to use some IaaC tool to spin up EC2 instances for their applications. It would be tedious to spin up EC2 instances manually.

Terraform's purpose is to deploy the server itself, CHEF would configure items in your server once you have the infrastructure. Let's learn in brief how to manage infrastructure as code using Terraform.

You have to download Terraform or use Brew command to install Terraform if using MAC. Also, you need to have an AWS account and need to add the AWS credentials (AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY) as environment variables to start using the TF with AWS.

Terraform State

Terraform supports many cloud providers and has resources for each cloud provider:

Image title

Resources are defined as a code in Terraform:

provider "aws" {
 region = "ap-south-1"
}
resource "aws_instance"
"example" {
 ami = "ami-4fc58420"
 instance_type = "t2.micro"
 tags {
  Name = "terraform-example"
 }
}

 The above configuration creates a single EC2 instance in AWS. 

Use terraform init, a command to initialize download provider plugins to your local system. The output of the above command is shown below:

Initializing provider plugins...
 - Checking for available provider plugins
 - Downloading plugin for provider "aws" (0.1.4)...
* provider.aws: version = "~> 0.1"
Terraform has been successfully initialized!

After initializing, type the terraform plan command to see what are you going to deploy. The output of plan command is as below:

+ aws_instance.example
      id:                           <computed>
      ami:                          "ami-4fc58420"
      associate_public_ip_address:  <computed>
      availability_zone:            <computed>
      ebs_block_device.#:           <computed>
      ephemeral_block_device.#:     <computed>
      instance_state:               <computed>
      instance_type:                "t2.micro"
      ipv6_address_count:           <computed>
      ipv6_addresses.#:             <computed>
      key_name:                     <computed>
      source_dest_check:            "true"


Plan: 1 to add, 0 to change, 0 to destroy.

The plan command tells you what TF is about to do; a few will be filled in and some will be computed. Use the terraform apply command to apply the changes.

$ terraform apply
aws_instance.example: Creating...
  ami:                          "" => "ami-4fc58420"
  associate_public_ip_address:  "" => "<computed>"
  availability_zone:            "" => "<computed>"
  ebs_block_device.#:           "" => "<computed>"
  ephemeral_block_device.#:     "" => "<computed>"
  instance_state:               "" => "<computed>"
 aws_instance.example: Still creating... (10s elapsed)
aws_instance.example: Still creating... (20s elapsed)
aws_instance.example: Creation complete after 22s (ID: i-07fcaa5435207dac7)

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

The EC2 instance is up and running with the name "terraform-example."

Image title

Your configuration can be parameterized using variables:

variable "name" {
 description = "My EC2 instance"
}

Define a variable and its description. The default value is optional:

provider "aws" {
 region = "ap-south-1"
}

variable "name" {
 description = "My EC2 instance"
 value = "foo"
}

resource "aws_instance"
"example" {
 ami = "ami-4fc58420"
 instance_type = "t2.micro"
 tags {
  Name = "${var.name}"
 }
}

Running the plan command will show different information now.

~ aws_instance.example
      tags.Name: "terraform-example" => "foo"


Plan: 0 to add, 1 to change, 0 to destroy.

Notice the signs when you ran terraform plan the first time and now.

  • + says that a new configuration will be added.

  • ~ says that the configuration needs a change.

  • - says that the configuration will be deleted.

 You can clean all your resources using TF.

terraform destroy
aws_instance.example: Refreshing state... (ID: i-07fcaa5435207dac7)

An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
  - destroy

Terraform will perform the following actions:

  - aws_instance.example


Plan: 0 to add, 0 to change, 1 to destroy.


The State of Terraform

You must be wondering, how does TF know what needs to be changed or destroyed when we issue the plan/apply command? Usually, Terraform records the state of everything it has done, so it stores the state locally as .tfstate files.

$ ls -al
-rw-r--r--  1 suniljacob  staff   317 Nov 13 20:53 terraform.tfstate
-rw-r--r--  1 suniljacob  staff  3276 Nov 13 20:53 terraform.tfstate.backup

Usually, it's not advised to store the state in local hard, as it may contain sensitive data, so it would be good to use a backend like S3 or Consul to store Terraform state information.

Modules

These are blueprints for TF configuration. A module is just a folder within Terraform files.

module "myfrontend" { //give module identifier
 source = "../terraform-example" //path where the module lies
 instance_name = "frontend"
}


They are like functions and can be called many times, instead of writing the same configuration code for different requirements.

Loops

Since Terraform is declarative, very little logic needs to be written in it. There is a count parameter which helps us to create loops in TF. The below code creates one EC2 instance:

provider "aws" {
 region = "ap-south-1"
}
resource "aws_instance"
"example" {
 count = 1
 ami = "ami-4fc58420"
 instance_type = "t2.micro"

}

The below code creates three EC2 instances:

provider "aws" {
 region = "ap-south-1"
}
resource "aws_instance"
"example" {
 count = 3
 ami = "ami-4fc58420"
 instance_type = "t2.micro"

 tags {
  Name = "${var.instance_name}-${count.index+1}" //Interpolation Syntax
 }
}

Use count.index to modify each iteration. The above code creates three EC2 instances, each with a different name. Using count is like using for loop in other languages.

Layout of Files

We use three main files while using TF:

  • main.tf -> This is used to declare resources needed for your configuration.

  • vars.tf -> This will hold variables to be used in main.tf.

  • outputs.tf -> This file will hold any output variables.

Best Practices

It's good to use GitHub when working as a team with TF. Use pull requests, merge the code, and deploy.

Use the DEV environment and test your TF configuration code before moving to production. Always run a plan and be extremely careful with deletes. There is no rollback once the configuration is applied. 

Manage your secrets as environment variables and deploy to your shared environments from one master branch.

If you plan to use Terraform for setting up your infrastructure, this article will surely serve as a starting point. 

Learn how to measure the impact of every feature release on performance and customer experience metrics.

Topics:
devops ,terraform ,infrastructure as code ,cloud computing ,iaas ,automation

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}