Infrastructure as Code with Terraform
Blueprint
Gone are the days when you spend nights preparing your infrastructure for new software releases. Development cycles are getting shorter and shorter, and development teams are becoming more agile. Another concern is automation, which you can implement with configuration management. For example, just describe the configuration of virtual machines (VMs) to deliver and update them later according to a blueprint.
Terraform [1] by HashiCorp uses this idea to provision or adapt infrastructures. They also provide Vagrant, which handles the deployment of development environments. Terraform not only supports clean provisioning of the infrastructure, it also lets you change already provisioned environments. Terraform expects text configuration files, which are simply known as "configurations," with a .tf
file extension. They can be versioned with tools like Git or SVN.
Choose between HashiCorp Configuration Language (HCL) and JSON formats. Although HCL is based on JSON, it supports comments and some other extensions to simplify coding. The code examples in this article use the HCL format.
Blueprint
Terraform does almost all the work involved in deploying the virtual infrastructure, starting with provisioning VMs with VMware and OpenStack in your data center, as well as with Amazon and Oracle cloud providers, extending all the way to adjustments of the DNS or monitoring server. For this purpose, Terraform uses different providers [2] to provide resources for the corresponding platforms, which in turn feed into the configurations.
In this article, I use DigitalOcean [3] to provide insight into how Terraform works and, using the example, to show that the Infrastructure as Code paradigm does not have to be such a big hurdle.
Basics
First, you need accounts for DigitalOcean and Cloudflare, including valid API keys. You also have to configure your own domain with Cloudflare.
Installing Terraform is a very simple procedure. Once you have downloaded the appropriate ZIP archive [4], all you have to do is unpack the binary in the archive and copy it to a location in the filesystem that the $PATH
variable covers. For example, you can use /usr/local/bin
and then simply run the terraform
command.
The goal of the exercise is to place a droplet that is nothing more than a VM with the DigitalOcean cloud provider – in this case, with a preinstalled application, a Docker daemon. A container that provides a web service then starts on the VM. After successful provisioning with DigitalOcean, Terraform should create a suitable DNS entry so that third parties can access the web application automatically by domain and not just IP address.
The plan first requires a folder in which all further configurations are stored. The most important file is variables.tf
(Listing 1), which declares all variables used. These can be default values, types, or a description of the variable value.
Listing 1
variables.tf
01 variable "site_name" { 02 description = "Description" 03 type = "string" 04 default = "DEMO-SITE" 05 } 06 variable "site_author" { 07 type = "string" 08 default = "Jon Doe" 09 } 10 variable "site_container" {} 11 variable "do_token" { 12 type = "string" 13 } 14 variable "key_path" {} 15 variable "ssh_priv_key" {} 16 variable "ssh_pub_key" {} 17 variable "cloudflare_email" {} 18 variable "cloudflare_token" {} 19 variable "cloudflare_domain" {}
The next file sets declared variables. It not only depends on the file extension, but also on the file name: terraform.tfvars
(Listing 2). Among other things, it contains the API keys for DigitalOcean and Cloudflare. Information on where the you can find such API tokens is provided online [5] [6].
Listing 2
terraform.tfvars
01 do_token = "01189998819991197253" 02 ssh_priv_key = "/home/jon.doe/.ssh/id_rsa" 03 ssh_pub_key = "/home/jon.doe/.ssh/id_rsa.pub" 04 cloudflare_email = "<jon.doe@example.com>" 05 cloudflare_token = "<01189998819991197253>" 06 cloudflare_domain = "<example.com>" 07 site_name = "<mysite.example.com>" 08 site_author = "John Doe" 09 site_container = "dockersamples/static-site"
Resource Planning
Now create one file for the DigitalOcean and Cloudflare configurations. Distributing this information across two files is not necessary, nor is it a convention, but it makes it easier for teams to keep track of versioning and work on the infrastructure.
Terraform automatically reads all files with the .tf*
extension. From the digitalocean.tf
file (Listing 3), it first loads the digitalocean
provider and then transfers the API token from the do_token
variable in the terraform.tfvars
file. Terraform accesses variables with the "${var.<Variablenname>}"
string. In the next step, Terraform calls the digitalocean_ssh_key
resource with the jondoe
name (line 4), reads the public key from a file on the local filesystem, and uploads it to DigitalOcean. The VM needs it later.
Listing 3
digitalocean.tf
01 provider "digitalocean" { 02 token = "${var.do_token}" 03 } 04 resource "digitalocean_ssh_key" "jondoe" { 05 name = "Jons Key" 06 public_key = "${file("${var.ssh_pub_key}")}" 07 } 08 resource "digitalocean_droplet" "mywebapp" { 09 image = "docker-16-04" 10 name: guest 11 region = "fra1" 12 size = "512mb" 13 ssh_keys = ["${digitalocean_ssh_key.jondoe.id}"] 14 provisioner "remote-exec" { 15 inline = [ 16 "docker run -p 80:80 --name ${var.site_name} -e AUTHOR=\"${var.site_author}\" -d -P ${var.site_container}", 17 ] 18 connection { 19 type = "ssh" 20 user = "root" 21 private_key = "${file("${var.ssh_priv_key}")}" 22 } 23 } 24 } 25 output "IP" { 26 value = "${digitalocean_droplet.mywebapp.ipv4_address}" 27 }
The second digitalocean_droplet
resource named mywebapp
(line 8) creates a droplet from the docker-16-04
image containing Ubuntu 16.04 and a preinstalled Docker daemon. Thanks to line 13, Terraform accesses an id
value of the digitalocean_ssh_keys
resource and the jondoe
name. The id
attribute stores Terraform after successfully perfoming the digitalocean_ssh_keys
resource and provides it to all other resources.
At the same time, the file defines a dependency: Terraform only executes digitalocean_droplet
if it knows the digitalocean_ssh_key.jondoe.id
value. When it accesses resource attributes, Terraform automatically establishes these dependencies and calls the resources accordingly in the correct order. The available attributes can be found in the resource documentation [7]. If Terraform does not automatically recognize other dependencies because they do not use attributes of other resources, you must set them explicitly [8].
From line 25 onward, Listing 3 finally defines an output variable and declares it with an attribute. This variable (IP
here), explicitly outputs Terraform on the console when the program has completed its task successfully.
Buy this article as PDF
(incl. VAT)