Modular Infrastructure with Terraform
I hate doing repetitive, finicky tasks. No matter how careful I am, something, sooner or later, always gets missed. Ansible scratched a particular itch for me and I've relied on it ever since for system rebuilds. I've recently discovered Terraform and I think I'm in love.
As with much of my use of infrastructure as code (IAC) technology, it seems like my use case is a bit to the left of where the designers figured most people would be. After some trial and error I've come up with a modular system that works well enough for my purposes so I wanted to get a post put together sharing what I've settled on.
There are many resources available for general Terraform usage, so I'm going to focus on my use cases: rapid deploy, modification, and redeploy of cloud infrastructure.
Pre-Flight Checklist
Here Be Dragons
Terraform works better than you'd expect in many ways, but I've had a few surprises along the way. Here's a short list:
- There isn't much in the way of support for changing a VM. If you update the configuration and reapply, you're looking at a destroy/create operation. A good way to avoid the headache here is to ensure your Terraform config contains as much of your actual configuration as possible.
- If you want to get outputs out of your modules, you need to output them at the module level and then pull them in at the parent using
"${module.<module name>.<output name>}"
- The Terraform plan file (
-out planfile
) contains all the relevant information required to configure your environment. Including credentials. Guard these like passwords, or just useterraform plan
.
High-Level Terraform Workflow
- Build environment-specific configuration.
- Select modules.
- Run
terraform init
to download the relevant providers. - Run
terraform plan
to preview your changes. - Run
terraform apply
to apply changes. - Run
terraform destroy
to tear things down.
Directory Structure
I've tried to keep the directory structure as simple as possible. Terraform will process any files ending in .tf
, so you can complicate things into many tiny files if you feel the need.
|
|
environments/
- Where the configuration for your assorted environments live (e.g. GoPhish, HTTP redirector, etc.). Each will get their own state file and be completely isolated. You can keep them all in one environment, but be careful with your destroys.main.tf
- Defines providers, sources modules, and generally defines what gets done.
modules/
- Your reusable configurations.main.tf
- Defines the configuration for the module. This does the actual work.outputs.tf
- Pulls in outputs from modules so they can be displayed.variables.tf
- Variables used by the module.
Authentication
Terraform for DevOps relies on a lot of automated provisioning, so it makes sense to have the API keys for all this infrastructure available on disk somewhere. I like to avoid having that stuff laying around, so I make use of the environment variable option if it's available:
|
|
Building a Module
For this blog post we're just going to use Microsoft's Azure configuration module and make a few minor changes.
When building my own, I like to keep things pretty basic. I define configuration for compute, network, and ssh. I can then pick choose the pieces I like and change them on the fly. Updating the firewall rules just means sourcing a different module.
Variables
The magic of modules is the variables you pass into them. Keep that in mind as you build your module; anything you'd like to be able to change on the fly should probably be a variable.
Any variables you define should live in variables.tf
and look something like this:
|
|
Consider the following:
|
|
Three of the above (ssh_key
, admin_username
, management_ip
) relate to the administration of the system itself, so the only thing I can really change here is which Azure region I deploy my VM to. For demonstration purposes, this is sufficient.
Interpolation
Interpolation refers to how variables are inserted in a Terraform configuration.
|
|
Building an Environment Configuration
This is the easy part. All you're really doing here is selecting modules and passing the appropriate values to them.
Here's an example:
|
|
You're now good to roll some infrastructure! terraform init
, terraform plan
, terraform apply
and you're one Azure resource richer.