Terraform is a great tool but it is also a scary monster to tame. If used correctly it can empower your team by hiding complex infrastructure setups.
This blog post focuses on what has worked better in my organisation regarding the structure of terraform projects and how to write infrastructure as code.
Terraform is code, just like your Python, Golang, or anything else, so it lives with the project code in a folder called tf.
whenever you go to any of our projects you will see a
tf folder , and it will follow this structure:
We decided not to use environments feature provided by terraform, we found it very confusing.
Instead we have a folder per environment (dev, stg, prd).
In the section below I will explain details on each folder and how Terraform state is managed
Modules folder contains terraform modules inherently related to the project.
Your project should not be a list of terraform files, but rather it should be a composition of modules.
I treat Terraform modules like functions. A Terraform Module should:
- Glue infrastructure
- Abstract complex gluing of infrastructure
- Do one single thing
A good module should let someone in my team build an ECS cluster without knowing too many unnecessary details, however it allows an advance user to provide complex setups if needed.
Modules are powerful, for the same reason functions are powerful:
- They make code more readable: “aha! that bit of code is creating an ECS cluster, I don’t know the details”. In case of needing to know the details, you know exactly what set of files to look at (instead of jumping across all files in your project)
- They empower team members: a team member who is bootstrapping a new project can get infrastructure done without learning every single detail.
Maybe that person is a data scientist who just want to bootstrap a cluster to analyse some data and then destroy the infrastructure.
If you have created something like an ECS cluster you know that it has many complex components: VPC, ECS Task, etc.…
Many people are tempted to just leave all of these definitions living at the top level of their project. for example:
As your project evolves, these files get really complex, glue connecting different parts is all over the place.
In terms of readability it is preferable to group your infrastructure taking into account the higher level pieces of your application.
For example, if you are building a Twitter clone application, It could probably be split into two higher level services: one for publishing tweets and another for consuming tweets.
Publishing service might be made of Lambda Functions, Kinesis and a ECS cluster. A reasonable structure might look like:
To anybody coming to check my code, the
tf folder would not just be a bunch of resources that are glued somehow, that person can see clearly that my service is made of two other internal services, whose name might sound familiar.
Does that person want to know more about the
publishing service ? great, take a look at that module folder, instead of the whole project.
Terraform state is a json file describing the infrastructure managed by terraform.
Terraform State is a scary part for many who tried Terraform before version 0.9. Back then you had to manage the state yourself, many people have told me how this turn them off from using it.
All I can say is, terraform state has never been a problem for me after 0.9, make sure you define a place to store it remotely and that’s it.
Each environment folder corresponds to the terraform defining the infrastructure for each environment.
By separating our terraform in the mentioned folders every environment will have its own terraform state file.
By having a different terraform state file we isolate environments.
Wanna run terraform for staging environement?
sure, just place your terminal in the
stg folder and then run your terraform commands.
Each folder contains at least the following files:
.version: Contains the terraform version that this environment uses. This file is not used by terraform. It is used by our CI system to fetch the intended terraform binary.
main.tf: Usually module imports, either from `modules` or from our external repository with modules. Some gluing resources are of course included but generally we tried to avoid doing very complex things.
vars.tf: Constants related to this environment.
At this point you should be wondering, why not using ‘terraform workspaces’. I got two reasons for that:
1. since most of our projects moved from older terraform versions to newer ones that was harder to introduce.
2. It is easier, and simpler just to have all the code of an environment in a folder: you can copy a folder, cd to it, your terminal shows you in which folder you are