engineering

October 29, 2020   |   7min read

Terraform Tutorial: Introduction to Infrastructure as Code

Introduction

Cloud computing is storming the IT world. It allows us to create scalable, resilient and cost-efficient applications. At the beginning, the main function of cloud services was to easily create, launch and terminate virtual machines at will. Nowadays, leading cloud providers have tens or even hundreds of services in the portfolio and these numbers are constantly growing along with their complexity.

Complexly distributed applications require a large set of infrastructure and developing them means frequently putting up and down various resources like networks (subnets, gateways, zones), load balancers, clusters and many, many more. Deploying and testing such an app can be problematic since provisioning infrastructure for it may consist of many dependent and intertwined steps. It makes a whole process fragile and prone to errors. The question is, how to make it better? The answer is: infrastructure as Code. In today’s tutorial, I’ll give you a brief introduction to infrastructure as a code and walk you through one of it’s tools: Terraform.

Infrastructure as Code

Infrastructure as Code [IaC] means that infrastructure configuration exists as fully versioned and documented code. It may bring many benefits, such as:

  • Speed—simplified and automated infrastructure provisioning is fast and efficient.
  • Reliability and consistency— big infrastructure configuration may drift away from the initial state over time. This can result in mismatched testing and production environments as well as risks of not complying with regulatory requirements. Infrastructure as a code ensures the same environment every time.
  • Faster development —DevOps and Software Engineers can quickly set up development, test and production environments within CI/CD pipelines. It allows for experimentation without worrying about coming back to the previously working infrastructure state.
  • Versioning and best practices—applying best practices leads to modular and configurable code which can be stored in the version control system.
  • Reduced costs — all points above lead to reduced time, effort and optimal configuration of the cloud services which have consumption-based cost structure.

Tools

There are plenty of infrastructure as a code tools. Here are some crucial differences between them:

  • Imperative or declarative— The imperative (procedural) approach means scripts contain steps executed one at a time to achieve the desired state. Users are responsible for designing optimal processes. The benefit of this approach is that already existing scripts can be leveraged. Examples of imperative infrastructure as a code tools are Chef and Puppet. In the declarative (functional) approach the desired state is defined and an infrastructure as a code tool is responsible for provisioning infrastructure components and solving all dependencies in the most efficient way. Tools like Terraform and Puppet use this approach.
  • Infrastructure mutability—The mutable infrastructure can be modified after provisioning. It allows easier experimentation, but sometimes creates configuration drift and compromises consistency between environments. Chef, Puppet and Ansible handle mutable infrastructure. The immutable infrastructure provided for e.g. by Terraform means that components are not modified after deployment. If there is a need for modification, they will be replaced with new ones. In the cloud-native world, this kind of orchestration can be performed in a fast and reliable manner. Immutability makes it easier to keep track of infrastructure versions and revert problematic changes when needed.

Approach

  • Terraform and Puppet — declarative
  • Ansible and Chef — imperative

Infrastructure type

  • Terraform — immutable
  • Puppet, Ansible, and Chef — mutable

Terraform

Terraform is one of the best and most popular Infrastructure as Code tools. It was created by HashiCorp and initially released in 2014. It is written in Go and reads infrastructure definitions written in declarative configuration language called HCL (HashiCorp Configuration Language).

Key features of Terraform:

  • platform-agnostic—the same workflow can be maintained when provisioning resources across a wide range of different providers.
  • open source—communities support not only the tool itself but also develop custom providers, plugins and modules.
  • execution plans—the “planning” step shows changes that will be made during the “apply” step, so there is a possibility to verify it and avoid surprises.
  • resource graph—a graph of all resources is built (and can be displayed in a graphic form), so Terraform knows which resources depend on each other and can efficiently manage them.

Hands On Terraform

Installation

Terraform can be installed in several ways. You can download the pre-compiled binary file from Hashi Corp website. You can also use package management tools:

  • Homebrew on OS X
brew tap hashicorp/tap
brew install hashicorp/tap/terraform
  • Chocolatey on Windows
choco install terraform
  • Linux (Ubuntu/Debian)
curl -fsSL https://apt.releases.hashicorp.com/gpg | sudo apt-key add -

sudo apt-add-repository "deb [arch=amd64] https://apt.releases.hashicorp.com $(lsb_release -cs) main"


sudo apt-get update && sudo apt-get install terraform

To verify installation, run terraform -help.

Google Cloud Platform infrastructure configuration

To follow the example below, you should have :

  • basic knowledge of Google Cloud Platform
  • empty Google Cloud Platform project with enabled billing and Compute Engine API
  • Service Account with permissions to to Google Compute Engine and key saved as JSON file

Terraform configuration and initialization

Create a directory for the examples. Inside create file main.tf. Terraform recognizes .tf and tf.json files and loads them automatically when it runs.

main.tf

terraform {
  required_version = "= 0.13.3"
}

provider "google" {
  project     = "terraform-post-project"
  credentials = file("sa.json")
  region      = "us-central1"
  zone        = "us-central1-c"
}

Let’s analyze blocks:

terraform {
  required_version = "= 0.13.3"
}

The terraform block describes the configuration of Terraform itself, including backend specifying provider requirements, experimental features, and like in this case, specifying a required Terraform version. Specifying it is a good practice. It ensures that every collaborator is working with compatible versions of Terraform and related plugins and modules.

provider "google" {
  project     = "terraform-post-project"
  credentials = file("/path/to/service_account.json")
  region      = "us-central1"
  zone        = "us-central1-c"
}

A provider is responsible for creating and managing resources. In our case, it is Google where some basic attributes are set. The proper path to the Service Account key file is an important part.

Initialize Terraform by typing terraform init in a terminal. Information about successful initialization should appear pretty soon. Congratulations! You are ready to spin up new infrastructure services.

Type terraform plan. The message there are no changes to be applied should be visible now. Let’s create our first resource.

Creating first resource

Create gce.tf file with configuration of a basic Google Compute Engine instance:

resource "google_compute_instance" "vm_instance" {
  name         = "terraform-instance"
  machine_type = "f1-micro"

  boot_disk {
    initialize_params {
      image = "debian-cloud/debian-9"
    }
  }

  network_interface { 
    network = google_compute_network.vpc_network.name
    access_config {
    }
  }
}

Your folder structure should look like this now:

terraform
├── gce.tf
└── main.tf

Now, the command terraform plan should give you information that one service will be added. If you are happy with the output, type terraform apply and confirm. After a few seconds, your Google Compute Instance should be available.

Resource modification

Let’s modify vm_instance by changing its boot disc image from Debian to Ubuntu. gce.tf

resource "google_compute_instance" "vm_instance" {
  name         = "terraform-instance"
  machine_type = "f1-micro"

  boot_disk {
    initialize_params {
      image = "ubuntu-2004-focal-v20201008"
    }
  }

  network_interface { 
    network = google_compute_network.vpc_network.name
    access_config {
    }
  }
}

Since Terraform treats infrastructure components in an immutable manner, it will replace the current instance with the new one. That’s why the terraform plan command will inform us that one resource will be destroyed and one added. If you are happy with that, type terraform apply and wait for a message about the successful completion of your operation.

Teardown infrastructure

The terraform destroy command is responsible for folding down the infrastructure. The plan of execution can be previewed with terraform plan -destroy command.

Summary

Terraform is powerful. This short Terraform tutorial is just the tip of the iceberg when it comes to the infrastructure as a code features that may help you significantly improve infrastructure management. Next time, resist the temptation to use CLI for creating a new infrastructure setup and give Terraform a try!

This Terraform tutorial doesn’t cover more advanced topics like managing remote state instead of local or running in automation. For more detailed information sources I recommend the official documentation and tutorials.

If you have a project you want to migrate to the cloud or improve an existing one, do not hesitate to contact us. Happy Terraforming!

Tobiasz Kędzierski

Software Engineer

Did you enjoy the read?

If you have any questions, don’t hesitate to ask!