I like tools. As opposed to people, a tool is something you can like only for what it does for you, at least without emotional damage. In both cases, though, they have the potential to either raise yours or leave you in tears on the sidewalk while they throw all your stuff through the window.

Terraform is a tool, and I like it. I haven’t been kicked to the curb yet, but I’m also not sure if I would bring it home to meet my folks. Regardless, I have been using it quite consistently for a few years now. I’d be lying if I claimed that terraform destroy hasn’t occasionally destroyed every ounce of joy and comfort in my being, but I do think it’s a worthy candidate for its purpose.

Basics

The primary purpose is to manage infrastructure in IaaS (Infrastructure as a Service) providers. This is done via APIs that can provision compute resources or higher level services. As a user, you describe what you want the final result of your infrastructure to be, and Terraform makes it so. You can make changes, e.g. modify the definition of or replicate a server, and Terraform obediently complies unless your definition is invalid, in which case you may have just taught computers the emotion of anger. Good luck convincing a sentient datacenter to create a VPS so that you can play Minecraft with your friends.

Your set of infrastructure components is defined using HCL, HashiCorp’s Configuration Language. Frankly I’m not a fan, and I think Pulumi does it way better by letting me use a legitimate programming language, but life is better not having to look at any more YAML than is already inevitable. You Ain’t My Language of choice in any situation ever, except when the only other option is JSON.

State

“I’ve heard of configuration management and tools like Ansible, Chef, or Salt. What makes Terraform so, err… chill?” Well listen up. Terraform hits the sweet spot between simplicity, statefullness, and ease of use. Now I’m not exactly saying that Terraform is simple or easy to use, but the whole problem space is so complicated that it stands out to me.

State is merely the answer to the question, “what currently exists?” There are four basic operations we need to perform on state: create, read, update, and delete. For any object that Terraform has knowledge of, it can generally perform these actions on. Speaking at a conceptual level, this means that Terraform can programmatically manage anything that can be consistently automated via computers, even something like a smart light in your home, which has basic state of being on or off. That’s pretty powerful.

Terraform creates a local cache of state, meaning it doesn’t need to ask the IaaS provider for updated information on each run, though it does have facilities for doing just that or even adopting changes made outside its use. That takes some care and getting used to, but it is a huge boon for iteration and experimentation. Under normal circumstances, this makes Terraform fairly fast at computing what changes need to happen.

Secondly, Terraform does doesn’t require any sort of persistent master service. You can run it from your development machine or a CI server. Either way it does what it needs to and exits, meaning the operational burden is quite low. All you have to do is securely store that state file.

At the end of the day, I really can’t say any one tool in this space is a clean winner over the rest, and my goal here is hardly to compare them. I mostly enjoy the ergonomics and simple mentality of Terraform doing differentiation between states and taking care of the “how” for me. While not quite my taste, Atlantis can at least give you a sense for the workflows that are possible when Terraform is combined with Git to change infrastructure.

Plugins

Terraform is multi-cloud. In today’s climate, that statement will have Fortune 500 CTO’s drooling. You can just as easily create an EC2 instance on AWS as you can a DigitalOcean Droplet, or even both at once! In order to support independent developers or IaaS employees who want Terraform to work with their provider of choice, Terraform can be extended via plugins. You can see the full list of mainstream, supported plugins in the registry, but anyone can make one and distribute it on their own too. With this potential Terraform can be used to define your entire infrastructure, no matter where it lives.

The design of Terraform’s plugin system is the result of a microservice architecture applied to the one place it shouldn’t be: a single-purpose program running on a single machine. Yes, there are many processes on your personal computer that communicate, but they each (hopefully) serve a different purpose, relying on each other to achieve their goal. And they are all active - programs that run on their own. A passive program would be a library, which doesn’t serve a user-facing purpose but must be included as part of some other program. Terraform plugins are conceptually passive but implemented as active RPC servers. Maybe shared libraries aren’t awesome, but a library-as-a-web-server is a new depth of insanity.

Silly technical details aside, plugins allow you to expand Terraform’s knowledge of state. It can track new objects that aren’t included into the core code base or any other plugin.

Example

Terraform is a tool, after all, so users care about how it’s used in practice. Let’s check it out! First you create a file describing your desired end state:

# main.tf
provider "digitalocean" {}

resource "digitalocean_droplet" "example" {
  name   = "example"
  image  = "fedora-31-x64"
  ipv6   = true
  region = "nyc1"
  size   = "s-1vcpu-1gb"
}

I’m purposefully skipping over authentication because setting an environment variable with your API token is boring. Just invoke the tool to make it happen:

$ 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:

  # digitalocean_droplet.example will be created
  + resource "digitalocean_droplet" "example" {
      + backups              = false
      + created_at           = (known after apply)
      + disk                 = (known after apply)
      + id                   = (known after apply)
      + image                = "fedora-31-x64"
      + ipv4_address         = (known after apply)
      + ipv4_address_private = (known after apply)
      + ipv6                 = true
      + ipv6_address         = (known after apply)
      + ipv6_address_private = (known after apply)
      + locked               = (known after apply)
      + memory               = (known after apply)
      + monitoring           = false
      + name                 = "example"
      + price_hourly         = (known after apply)
      + price_monthly        = (known after apply)
      + private_networking   = false
      + region               = "nyc1"
      + resize_disk          = true
      + size                 = "s-1vcpu-1gb"
      + status               = (known after apply)
      + urn                  = (known after apply)
      + vcpus                = (known after apply)
      + volume_ids           = (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          <------- Terraform paused here for an interactive prompt

digitalocean_droplet.example: Creating...
digitalocean_droplet.example: Still creating... [10s elapsed]
digitalocean_droplet.example: Still creating... [20s elapsed]
digitalocean_droplet.example: Creation complete after 24s [id=176205713]

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

$ doctl compute d list
ID           Name            Public IPv4        Private IPv4    Public IPv6                    Memory    VCPUs    Disk    Region    Image             Status    Tags    Features    Volumes
176205713    example         67.205.172.198                     2604:a880:400:d0::9a4:3001     1024      1        25      nyc1      Fedora 31 x64     active            ipv6

Pretty sweet! That level of detail and response is valuable for operators who desparately need to know what will or has happened. I have worked on a handful of Terraform plugins by now and will be introducing my own in a follow up post.