An Introduction to Terraform25 November 2015 · Filed in Education
In this post, I’m going to provide a quick introduction to Terraform, a tool that is used to provision and configure infrastructure. Terraform allows you to define infrastructure configurations and then have those configurations implemented/created by Terraform automatically. In this respect, you could compare Terraform to similar solutions like OpenStack Heat, AWS CloudFormation, and others.
Before I continue, though, allow me to first address this question: why Terraform?
This is a fair question, and one that you should be asking. After all, if Terraform is considered similar to OpenStack Heat or AWS CloudFormation, then why use Terraform instead of one of the comparable solutions? I believe there are a couple (related) reasons why you might consider Terraform over a similar solution:
Within a single Terraform definition, you can orchestrate across multiple cloud services. For example, you could create instances with a cloud provider (AWS, DigitalOcean, etc.), create DNS records with a DNS provider, and register key/value entries in Consul. Heat and CloudFormation are, quite naturally, designed to work almost exclusively with OpenStack and AWS, respectively. (Astute readers will know that Heat supports CloudFormation templates, but you get the idea.) Therefore, one reason to use Terraform would be because you need to orchestrate multiple services/providers in a single definition.
Because Terraform supports multiple services/providers (more on that in a moment), you might choose to use Terraform out of a desire to use a single solution that supports multiple services. Even if you only choose to use a single provider within any given definition, you still have only one solution to learn/master/use/manage/maintain in the event you add more cloud services or more providers at some point in the future. In other words, you might only be supporting OpenStack right now, but by choosing Terraform you’re building in a certain level of protection against having to switch solutions when you add more solutions later. (Note that this is subtly different from, although related to, the first reason.)
Now that I’ve covered the “why,” let’s take a quick look at how Terraform works.
As I’ve mentioned already, Terraform allows you to create infrastructure configurations that affect resources across multiple cloud services and cloud platforms. Terraform does this with a few different components:
- Configurations: A Terraform configuration is the text file that contains the infrastructure resource definitions. You can write Terraform configurations in either Terraform format (using the
.tfextension) or in JSON format (using the
.tf.jsonextension). In this article, I’m going to use the JSON format. (Why? One, I want to improve my skills in JSON. Two, I found very few good examples of JSON-formatted Terraform configurations, so I want to help remedy that oversight.)
- Providers: Terraform leverages multiple providers to talk to back-end platforms and services, like AWS, Azure, DigitalOcean, Docker, or OpenStack. In this post, I’m going to focus on using Terraform with OpenStack.
- Resources: Resources are the basic building blocks of a Terraform configuration. When you define a configuration, you are defining one or more (typically more) resources. Resources are provider-specific, so a resource for the AWS provider is different than a resource for OpenStack. This, in my opinion, is the one major flaw in Terraform. If you wanted to convert a configuration from using AWS to using OpenStack (or vice versa), you’d essentially have to re-create the configuration because all the resources are provider-specific.
- Variables: To help make configurations more portable and more flexible, Terraform supports the use of variables. By changing the value of the variables, you could potentially re-use a single configuration multiple times (but not across providers, since resources are provider-specific).
Perhaps an example will help. Let’s take a look at an example Terraform configuration that will spin up an environment in OpenStack.
Example Terraform Configuration
This example Terraform configuration will perform the following tasks:
- Create a new Neutron network and subnet.
- Create a new Neutron logical router connected the new network and with an uplink to an external network.
- Create a new instance, assign some security groups, and associate a floating IP.
To do this, we’ll split the information Terraform needs into four different files:
provider.tf.json vars.tf.json main.tf.json output.tf.json
provider.tf.json file contains information specific to Terraform’s OpenStack provider. Splitting this into a separate file makes it easier to share the Terraform configuration without sharing confidential credentials.
vars.tf.json file contains variables that will be used later by Terraform. Putting as much information as possible into a separate variables file makes the Terraform configuration more reusable.
main.tf.json file contains the bulk of the Terraform configuration.
output.tf.json file contains information that should be output to the operator.
Let’s start with the
provider.tf.json file. For the OpenStack provider, it should look something like this:
Technically, this file isn’t required; Terraform can leverage OpenStack-specific environment variables (like OS_AUTH_URL and similar) that are typically required by the OpenStack command-line clients anyway.
Next up is the variables file,
vars.tf.json. This is the file that would change most between uses of the configuration, since it contains the information specific to that particular use case. Here’s an example of what it might look like:
On its own, this file doesn’t make a whole lot of sense. However, when viewed in light of the main configuration file (
main.tf.json) it makes a lot more sense:
Let’s break this down a little bit:
openstack_networking_network_v2resource creates a Neutron network.
openstack_networking_subnet_v2resource creates a subnet and associates the subnet with the new Neutron network (via the
network_idvalue, which you can see refers back to the network object’s ID).
openstack_networking_router_v2resource creates a logical router. This is where our first variable comes into play, where Terraform uses the
var.external_gatewayvariable (taken from
vars.tf.json) to assign the uplink (external network) for the logical router.
openstack_networking_router_interface_v2resource creates a router interface connecting the logical router to the new Neutron network created earlier. It does this by referencing IDs from those resources (
openstack_networking_router_v2.tf-router.idreferences the logical router, and
openstack_networking_subnet_v2.tf-subnet.idreferences the subnet).
openstack_compute_floatingip_v2resource creates a new floating IP address from an external address pool (where it uses another variable again).
- Finally, the
openstack_compute_instance_v2resource creates a compute instance, leveraging variables to define the image, flavor, and SSH key pair. Other resources are referenced, like the Neutron network and floating IP address, and some values—the security groups—are hard-coded in the configuration.
Somewhat anti-climactically, the final file in our solution is
output.tf.json, which simply defines what information Terraform will display when the configuration is applied. Here’s a sample
In this case, the output is only the IP address created by the
openstack_compute_floatingip_v2 resource and assigned to the new compute instance. You could, naturally, include more information to be displayed here.
Using the Example Configuration
To use this example Terraform configuration, you’d place these files into a directory on a system that has Terraform installed, and simply run
terraform apply. (This is after you’ve made sure the files have the correct information for your environment, naturally.) Terraform will apply the configuration, creating and modifying resources as needed, and when it is finished it will display the information specified in
output.tf.json to the screen.
Later, if you needed to modify the environment, you could edit the configuration files and run
terraform apply again. Terraform would make the necessary changes to have the environment conform to the configuration. (Note that this may entail deleting and re-creating resources, so be sure to double-check the documentation for the resources you’re using to understand Terraform’s behavior.)
If you put the configuration files into a source control repository (using something like Git—see here for a Git intro), then now you have versioned infrastructure configurations, and you can track changes, rollback changes, etc.
When you’re finished with the environment, you run
terraform destroy and Terraform will tear down the resources contained in the configuration.
There’s more to Terraform than what I’ve covered here, but this should at least get you started. To help you along in case you want to experiment with Terraform, I’ve placed both Terraform-formatted and JSON-formatted configurations in the “terraform” directory of my GitHub learning-tools repository. Feel free to use those as a starting point for your own exploration.Tags: AWS · Automation · CLI · OSS · OpenStack · Terraform Previous Post: Bootstrapping Cloud Instances into Ansible Next Post: One Use Case for an SSH Bastion Host