DevOps Guide to Important Terraform Concepts

Reading Time: 7 minutesEstimated Reading Time: 7 minutes

This is an intermediate guide to the core Terraform concepts. If you can get your head around these core concepts, you will be well on your way to understanding Terraform. I have not included Terraform modules in the guide, modules are a big topic and I will tackle these in a separate post.

Before I begin. If you are looking for a beginners guide to Terraform, check out these articles I have already written:

Note: This guide will focus on Terraform on AWS but the code is repeatable on other Terraform providers such as Azure and Google Cloud

Terraform Advantages?

You may be wondering why you need Terraform in your life, why not just configure AWS manually? Ofcourse manual configuration has its place in the IT industry, but when you need to do anything at scale, using repeatable, infrastructure as code (IaC) is the easiest way to create large, complicated Stack Sets in AWS.

Here are some key reasons you should use IaC:

  • Manual configuration is Error-prone, often relying on trial and error
  • It’s very difficult to create multiple identical environments manually
  • Manual configurations are very Time Consuming, Terraform is very very quick!
  • If you are not using versioned IaC, keeping the environments up-to-date will be very hard
  • No written definition of what your infrastructure looks like, but using IaC provides an auditable source of truth

Why Use Terraform?

  • Terraform can compare your desired infrastructure to the infrastructure you actually have and automatically work out how to align the two
  • As your infrastructure is now defined as code you can check it into your version control
  • Reproducible, every time you create an environment it will be exactly the same
  • Fast
  • Massive online community
  • Uses provider model – allow you to define infrastructure on many sources in a consistent way
  • Plan before applying changes
  • Destroy infrastructure completely once you are finished with it
  • Import existing infrastructure 
  • Terraform Providers kept up to date by the community 
  • Open source so you can debug issues
  • Write your own providers

Terraform vs AWS Cloudformation

Terraform is slowly taking over from AWS Cloudformation, certainly in my day-to-day job. CloudFormations biggest weakest is that it is locked to AWS, which is understandable as AWS wrote it, but it can only be used on AWS infrastructure. Terraform is cloud-agnostic, you can use it on one or many providers:

Check out this list for all the latest providers: https://www.terraform.io/docs/providers/index.html

Here are some other reasons why Terraform should be your next cloud configuration tool:

  • Terraform is open source and generally moves faster, often releasing features before CloudFormation
  • It allows you to configure multiple clouds/resources with the same skillset allowing you to manage all of your infrastructures in the same way
  • Terraform allows you to plan before changing your infrastructure

Terraform Resources

A resource block describes one or more infrastructure objects, such as virtual networks, compute instances, or higher-level components such as DNS records.

  • Resources are a component of your infrastructure. They might be:
    • A low-level component like a physical server, virtual machine, or container
    • A high-level component like a DNS record or email provider
    • Think of it as a “Thing in your infrastructure”
  • Resources have a set of arguments
    • Required
    • Optional
  • Resources can have exported attributes (like a return value)

Example

resource “aws_s3_bucket” “my_bucket” {
bucket = “turbogeek-myfirst-bucket”
}

Amazon Resource Names (ARN)

Every single resource that is created in AWS is given an ARN. The Amazon Resource Name is a unique attribute given to your resource. We can use Interpolation Syntax to use some of these attributes.

  • The aws_s3_bucket above returns the ARN created as of of its exported attributes 

Interpolation Syntax

Interpolation syntax is a way of having an expression that will be evaluated when terraform runs, the value can be used or replaced with another value. For example, you may want the ARN of a created AWS object to use the AWS IAM policy, the ARN is unknown before you create the resource so you can use interpolation syntax to replace the ARN at runtime.

Importance of Interpolation Syntax

So why is interpolation syntax so important?

  • Interpolation Syntax helps terraform work out any dependency order in your code
  • Makes refactoring easier as you only have a value defined in a single place

Example:

“${<resource_type>.<resource_name>.<exported_attribute>}”
“${aws_s3_bucket.my_bucket.arn}”

To reference an exported attribute from a resource use the interpolation syntax format:

Data Types:

There are 4 main data types for resource attributes

  • Int – defined using – port = 21
  • String – defined using – host =“localhost”
  • List – defined using – security_groups = [“abc”,”def”]
  • Bool – defined using – enabled = false (true or false values)
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Action": [
                "s3:ListBucket"
            ],
            "Effect": "Allow",
            "Resource": [
                "arn:aws:s3:::richbailey-myfirst-bucket"
            ]
        }
    ]
}

Terraform Data Sources

Datasources are one of my favourite parts of Terraform, it essentially allows you to pull in code from AWS, other Repos and re-use the data that is already provisioned in AWS.

  • Data sources allow you to reference something created in AWS
  • Data sources allow data to be fetched or computed from external sources such as another Terraform project or a resource that already exists in AWS
  • The fetched data can then be used in your terraform project using interpolation syntax

Example:

data “aws_db_instance” “database” {
db_instamce_identifier = “my-test-databases”
}
  • The data resource above returns many attributes about the database (in this example) that can be used in your terraform projects such as the address of the instance, availability zone, and instance size.
  • To use an attribute from a data resource use this format
“${data.<resource_type>.<resource.name>.<exported_attribute>}”

Example:

data “aws_s3_bucket” “my_bucket”  {
  Bucket = “richards-bucket”
}

Reference the s3 bucket ARN using “{$data.aws_s3_bucket.my_bucket.arn}”

Terraform locals

A local value assigns a name to an expression, so you can use it multiple times within a module without repeating it. If you’re familiar with traditional programming languages, it can be useful to compare Terraform modules to function definitions:

  • Terraform locals allow you to assign a name to an expression, you can think of them like a variable
  • You can use a local value multiple times within a module
  • Local values can be combined to make more local values
  • Locals are created in a locals block, you can have one or more values in a locals block and one or more locals blocks in a module
  • Locals allow you to define a value in one place and then you can use the local in your project. This means if you change the value of the local you only have to update it in a single place
  • Locals only have to be updated once in a .tf files

Example:

locals {
  bucket_name_prefix = “rich”
}
locals {
  default_instance_tag = “my-instance”
}

Interpolation Syntax for Locals is:   

”${local.<variable_name>}”
locals {
  bucket_name_prefix = “rich -“
}

Would be referenced by:

“${local.bucket_name_prefix}”

Combining locals

  • Locals can be combined to make more locals
resource “aws_s3_bucket” “my_bucket” {
  bucket = “richbailey-first-bucket”
}
locals {
  bucket_arn = “${aws_s3_bucket.my_bucket.arn}”
}

Example:

locals {
  first = “rich”
  last = “bailey’
  name = “${local.first}-${local.last}”
}

Locals as a value of Exported Attribute

  • Locals can be assigned to the value of an exported attribute from a resource.
  • Its good practice to keep output in their own terraform file such as output.tf

Output

Output displays to screen, good for keeping track of code

locals {
   name = “richard
   }
   output "my_name" {
   value = "${local.name}"
   }

Outputs can be the results of expressions

For example:

output “my_value” {
  value = “${aws_s3_bucket.my_bucket.arn}”
}

Terraform Templates and Files

  • Terraform allows you to use a file as a parameter to a resource 
  • This can be useful for a big block of data eg. For a config file or a big block of JSON for an AWS IAM policy 
  • To use a file’s contents as a value for a resource attribute you can use the “file” function which is accessible using
“${file(“path_to_file”)}”

For example:

resource “aws_iam_user_policy” “my_bucket_policy” {
  name = “my-bucket-policy”
  user = “Richard-Bailey”
  policy = “${file(policy.json)}”
    }
  • If you want to have values that change in a file then you can do this using a terraform template data source
  • Anywhere in your file you can add a placeholder using the syntax ${place_holder_name} where place_holder_name is any name you want
  • To set the value for the place holders you set them using the vars property on the template data source

        Hello ${name}   -example of templated file

data “template_file” “say_hello” {
   Template = “${file(“say_hello.tpl”)}”
      vars {
     Name = “Richard”
 }
 }

You can use Interpolation syntax to get the value “${data.template_file.<name>.rendered}” -template is a data file

For the above example it will be “$data.template_file.say_hello.rendered}”

Terraform Providers

  • A terraform provider enables Terraform to talk to an API to manage resources
  • Multiple providers can be included into a single project
  • Terraform allows you to define a single provider more than once. If you do thus you must provide and an alias for the second user
  • Allows you to manage resources that live in multiple AWS accounts in a single project
  • Allows you create resources in multiple AWS regions in a single project
  • To configure a second AWS provider you need to give it an alias
        provider “aws” {
          region = “eu-west-1”
          alias = “ireland”
          access_key = “AAAAA”
          secret_key = “BBBBBB”
        }

Please Note: Its not a good idea to check in keys to version control tools like bitbucket

You can also set the provider version by default. If you do not specify a provider on your resource then the default provider will be used to tell Terraform to use a different instance of a provider for a resource, use the “provider” properties with the syntax “<provider_name>.<alias>”

Terraform Variables

  • Variables server as parameters to a terraform module
  • when used at the top level they enable you to pass parameters into your Terraform project
    • command line
    • file
    • environmental variables
  • 3 properties can be defined on a variable:
    • Type (Optional) : if this is set it defines the type of the variable, if no type is set then the type of the default value will be used. If neither are set the type will be assumed to be a string. Allowed values:
      • string
      • list
      • map
    • Default (optional) : if this is set then the variable will take this value if you do not pass one in. If no default value is set and a value is not passed in then Terraform will raise and error
    • Description (optional): purely to give the user of the terraform project some information as to what this variable is used for. Terraform ignores this field
  • Variables are great for passing in secrets to projects
  • To use the valuee of a variable in your project you use interpolation syntax of the format                          “${var.<variable_name}”
variable “my_name” {}
          “${var.my_name}”

Map Variables

  • Map variables allow you to define value to terraform to using in different cases. To use a lookup for a map value is the function:
variable “instance_size_map” {
          type = “map”
          default = {
             dev = “t2.micro”,
             test = “t2.medium”,
              prod = “m4.large”
        }
        }
        variable “instance_size” {}
        “${lookup(var.instance_size_map, var.instance_size)}”
  • to set the value of a variable using a file, create a file with the extension “.tfvars” then give the variable a value using “name” = “value”
variable “my_name” {}
  inside myvaules.tfvars
  my_name=“Richard”

Thankyou for taking time to read this page. Check out other cool Terraform content below:

Terraform in Azure

Terraform Core Commands

Hashicorp Terraform for AWS, Google Cloud, and Azure

That’s it, thanks for taking the time to read this article. if you have any questions or feedback please write in the comment section below.

You may also like...

Leave a Reply

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