Skip to content

Terraform

In the previous exercise we have discussed how to set up an infrastructure manually. For the purposes of this lecture Terraform is our tool of choice when it comes to cloud automation. There are, of course, many other open source tools like Ansible, or provider-dependent tools like AWS CloudFormation. We specifically chose Terraform because it is provider-independent and can be used to provision cloud resources with a wide range of providers.

You can download Terraform to your own computer. As it is a program written in Go you can simply unpack it and use it. We recommend adding it to your PATH environment variable for easy access. Since Terraform is capable of using multiple files we also recommend using an IDE or an editor with a directory listing function. The Intellij IDEA Community Edition has a plugin for Terraform that also offers code completion.

At it's core Terraform reads all *.tf files in a directory and merges them together. This provides an efficient way of structuring a project.

Each Terraform file contains a number of data and resource sections which describe the cloud environment. For example, you could create the Exoscale instance from the previous exercise using the following Terraform code:

data "exoscale_compute_template" "ubuntu" {
  zone = "at-vie-1"
  name = "Linux Ubuntu 20.04 LTS 64-bit"
}

resource "exoscale_compute" "mymachine" {
  zone         = "at-vie-1"
  display_name = "test"
  template_id  = data.exoscale_compute_template.ubuntu.id
  size         = "Micro"
  disk_size    = 10
  key_pair     = ""

  affinity_groups = []
  security_groups = ["default"]

  user_data = <<EOF
#!/bin/bash
set -e
apt update
apt install -y nginx
EOF
}

This exercise will guide you through the basics of using Terraform to provision cloud servers.

Tip

The entire source code for this exercise is available on GitHub.

Setting up the Exoscale provider#

Before you can begin you will need to configure your cloud provider. In our case that will be Exoscale, so our first piece of code in a file called provider.tf will be as follows:

terraform {
  required_providers {
    exoscale = {
      source  = "terraform-providers/exoscale"
    }
  }
}

provider "exoscale" {
  key = "EXO..."
  secret = "..."
}

This configures the Exoscale provider. We can make sure the provider loads correctly by running terraform init in the directory of the project.

Tip

You need to run terraform init every time the provider configuration changes.

The above example is not ideal as it contains hard-coded credentials. As any code Terraform should be stored in a code versioning system such as Git so having hard-coded credentials is not ideal. Instead, let's create a separate variable section:

variable "exoscale_key" {
  description = "The Exoscale API key" 
  type = string
}
variable "exoscale_secret" {
  description = "The Exoscale API secret"
  type = string
}
provider "exoscale" {
  key = var.exoscale_key
  secret = "${var.exoscale_secret}"
}

As you can see variables and other resources can be referenced in Terraform. This is important as it lets us write reusable and modular cloud manifests. Variables can be referenced in two formats: either by directly writing their name, or by including them in a string with the dollar sign.

Creating a virtual machine#

Unlike in our previous exercise, we will take a little more elaborate route and actually create our own security group this time. We start by creating a file called sg.tf to hold the following security group configuration:

resource "exoscale_security_group" "sg" {
  name = "exercise-2"
}

We will add the rules in a separate step:

resource "exoscale_security_group_rule" "http" {
  security_group_id = exoscale_security_group.sg.id
  type = "INGRESS"
  protocol = "tcp"
  cidr = "0.0.0.0/0"
  start_port = 80
  end_port = 80
}

Once you have this small amount of code complete you can try and run the Terraform config:

terraform apply

This step will query you for the variables:

$ terraform apply
var.exoscale_key
  The Exoscale API key

  Enter a value: EXO...

var.exoscale_secret
  The Exoscale API secret

  Enter a value: ...

If you wish to automate this step you can create a file called terraform.tfvars with the following content:

exoscale_key = "EXO..."
exoscale_secret = "..."

After inserting the variables Terraform will present you with a plan. It is very important that you always read this plan as it is a list of things Terraform intends to do. This may very well include destroying and re-creating a server! In our case the plan looks like this:

Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  # exoscale_security_group.sg will be created
  + resource "exoscale_security_group" "sg" {
      + id   = (known after apply)
      + name = "exercise-2"
    }

  # exoscale_security_group_rule.http will be created
  + resource "exoscale_security_group_rule" "http" {
      + cidr                = "0.0.0.0/0"
      + end_port            = 80
      + id                  = (known after apply)
      + protocol            = "tcp"
      + security_group      = (known after apply)
      + security_group_id   = (known after apply)
      + start_port          = 80
      + type                = "INGRESS"
      + user_security_group = (known after apply)
    }

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

This looks good, so you can enter "yes" to have Terraform execute the plan.

exoscale_security_group.sg: Creating...
exoscale_security_group.sg: Creation complete after 1s [id=beac3948-dc6f-44a9-89cb-d3ff8ea1c319]
exoscale_security_group_rule.http: Creating...
exoscale_security_group_rule.http: Creation complete after 3s [id=33b86aea-a64e-4708-82c8-43e88242f73f]

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

Sweet! Now, on to creating a virtual machine:

data "exoscale_compute_template" "ubuntu" {
  zone = "at-vie-1"
  name = "Linux Ubuntu 20.04 LTS 64-bit"
}

resource "exoscale_compute" "mymachine" {
  zone         = "at-vie-1"
  display_name = "test"
  template_id  = data.exoscale_compute_template.ubuntu.id
  size         = "Micro"
  disk_size    = 10
  key_pair     = ""

  affinity_groups = []
  security_groups = [exoscale_security_group.sg.name]

  user_data = <<EOF
#!/bin/bash
set -e
apt update
apt install -y nginx
EOF
}

As you can see we are referencing the previously-created security groups by name. This reference is important as Terraform attempts to execute resource creation in parallel and uses variables to determine which resources depend on each other. In other words, if you do not use a variable reference but hard-code the name Terraform will not be able to execute the instructions in the correct order.

Tip

You can force Terraform to destroy and recreate a resource by using terraform taint exoscale_compute.ubuntu.

About your state file#

You may have noticed that a new file called terraform.tfstate appeared in your project folder. This file is very important as it contains links to the cloud resources you created. If you lose it Terraform will not know what servers it already created and attempt to re-create everything. Also, do not put this file in Git! It contains sensitive information like passwords!

Destroying resources#

Once you are done with this exercise please run terraform destroy to tear down the infrastructure you just created.