An introduction to Terraform

When you get into infrastructure as code (IaC), you have a variety of tools at your disposal. Some may be just for a specific service, like CloudFormation is for AWS. Although these platform-specific tools may be faster in adopting the latest features, the knowledge you gather does not transfer over to other platforms. In this post, I will explain how to get started with Terraform.

What is Terraform

Terraform is a tool for building, changing and versioning infrastructure safely and efficiently. Terraform can manage existing and popular service providers as well as custom in-house solutions.

Providers

Terraform has support for a variety of platforms. These integrations are called providers and can be used and contributed to by the community.

You can use Terraform to manage a list of major cloud vendors like AWS, Azure and Google Cloud Platform. You can manage your VCS installations and even your monitoring tools. The full list includes a whole lot of tools and if something is missing you can even create your own.

Concepts

To get started we first need to go over some basic concepts to understand how it all fits together.

HCL

Terraform uses configuration files in the HCL format to define the desired state you want to achieve. This HCL or Hashicorp Configuration Language is a basic and easy to understand syntax.

CLI

The Terraform CLI is an extensive tool. For simplicity, only the basic commands will be covered in this guide.

  • init: Initialize a Terraform working directory
  • plan: Generate and show an execution plan
  • apply: Build or change infrastructure

State

Terraform stores the state of your infrastructure. This state is used to map the HCL configuration to the real-world resources. The state file can be stored locally for a single user scenario. In most cases however you’ll want to store the state in one centralized location. You can choose from a list of backends to do this.

Modules

Although not required to get started, modules allow you to re-use parts of your configuration to make sure you don’t duplicate your code and to maximize the similarity between multiple environments.

Basic

First, let’s create a simple file called main.tf with the following content.

provider "aws" {
  profile    = "default"
  region     = "eu-west-1"
}

resource "aws_instance" "example" {
  ami           = "ami-07042e91d04b1c30d"
  instance_type = "t2.micro"
}

This example shows the AWS provider configuration and a definition of an EC2 instance.

Note In the example we will be using AWS. If you want to follow along, make sure you have the AWS CLI configured. If you have multiple profiles, you can update the profile setting in the provider setting.

Plan

In order to see what the result of our configuration will be, we can run terraform plan and see what resources will be created.

$ terraform plan
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_instance.example will be created
  + resource "aws_instance" "example" {
      + ami                          = "ami-07042e91d04b1c30d"
      + arn                          = (known after apply)
      ...
    }

Plan: 1 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.

Once we have validated that the generated output is what we expected, we can now run terraform apply to allow Terraform to make the changes to our AWS account.

$ 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-07042e91d04b1c30d"
      + arn                          = (known after apply)
      ...
    }

Plan: 1 to add, 0 to 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: yes

aws_instance.example: Creating...
aws_instance.example: Still creating... [10s elapsed]
...
aws_instance.example: Still creating... [3m30s elapsed]
aws_instance.example: Creation complete after 3m38s [id=i-013ab22e66500b50b]

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

You should see the changes reflect in your AWS console.

Making a change

As you can see in the console, our instance does not have a Name set, so let’s define it and see how changes work.

Add the following block to the aws_instance resource.

  tags = {
    Name = "TerraformBasic"
  }

Let’s run another plan to see what changes will be made.

$ terraform plan 
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.

aws_instance.example: Refreshing state... [id=i-013ab22e66500b50b]

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

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

Terraform will perform the following actions:

  # aws_instance.example will be updated in-place
  ~ resource "aws_instance" "example" {
        ...
      ~ tags                         = {
          + "Name" = "TerraformBasic"
        }
        ...
    }

Plan: 0 to add, 1 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 you can see the type of change indicated in front of the resource:

  • +: create
  • ~: update in place
  • -: delete
  • -/+: delete then create
  • +/-: create then delete
  • <=: read data

Let’s see if it works by running terraform apply.

$ terraform apply
aws_instance.example: Refreshing state... [id=i-013ab22e66500b50b]

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

Terraform will perform the following actions:

  # aws_instance.example will be updated in-place
  ~ resource "aws_instance" "example" {
        ...
      ~ tags                         = {
          + "Name" = "TerraformBasic"
        }
        ...
    }

Plan: 0 to add, 1 to 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: yes

aws_instance.example: Modifying... [id=i-013ab22e66500b50b]
aws_instance.example: Modifications complete after 2s [id=i-013ab22e66500b50b]

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

Destroy

Now that we have seen the basics at work, let’s do a cleanup of what we’ve just created. Terraform provides the destroy command for this action. Please note this is a very dangerous action and any data/state inside the resources can get lost.

$ terraform destroy 
aws_instance.example: Refreshing state... [id=i-013ab22e66500b50b]

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 will be destroyed
  - resource "aws_instance" "example" {
      - ami                          = "ami-07042e91d04b1c30d" -> null
      ...
    }

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

Do you really want to destroy all resources?
  Terraform will destroy all your managed infrastructure, as shown above.
  There is no undo. Only 'yes' will be accepted to confirm.

  Enter a value: yes

aws_instance.example: Destroying... [id=i-013ab22e66500b50b]
aws_instance.example: Still destroying... [id=i-013ab22e66500b50b, 10s elapsed]
aws_instance.example: Still destroying... [id=i-013ab22e66500b50b, 20s elapsed]
aws_instance.example: Still destroying... [id=i-013ab22e66500b50b, 30s elapsed]
aws_instance.example: Destruction complete after 31s

Destroy complete! Resources: 1 destroyed.

More

In a future post we will be covering more complex setups on how to structure your projects as well as making your setup maintainable in the future.

Dries De Peuter | February 12, 2020