Don't repeat yourself (DRY) with Terraform to create your infrastructure in multiple environments/regions/cloud-providers
Terraform has simplified the way we provision infrastructure in the cloud and manage it as code. But best practices like separating infrastructure into multiple environments (staging/QA/production) don't change. Maybe even depending on your business needs, you need to extend the infrastructure across multiple geographical areas. Maybe you are even thinking about adopting a multi-cloud strategy.
If you are in this situation this means that you need to be able to create multiple environments in your code. The challenge here is to factorize the code as much as possible to the DRY (Don't Repeat Yourself) principle. There are many ways of achieving it using Terraform.
In this article, we will see two strategies for doing this with Terraform. Each has its strengths and weaknesses and we'll compare them at the end. Let's go!
2 Strategies to Create Multiple Environments
In both presented strategies, we're using modules included in projects for convenience. They can be versioned in distinct GIT repositories. Each module is called in a specific layer and terraform remote states are stored in a versioned S3 backend. Separated layers improve consistency and make easier rollback.
In this project structure, each environment has its directory that contains layers. You can see below the network configuration of the staging environment :
As mentioned, we use a backend to persist remote states. The key definition, i.e. where the remote state is stored, varies according to the environment and the layer. In the module block, the different values can differ following the environment.
Dependencies between layers can be solved with data sources. The existing resources in the cloud or outputs from the remote states can be fetched :
For deployment, you have to perform a terraform init and apply on the layers in order :
Before we use an S3 backend to store remote states. Initially, there is only one workspace named
default and only one associated with the state. Some backends as S3 support multiple workspaces. They allow multiple states to be associated with a single Terraform configuration.
We are going to use this feature to define multiple environments. Each environment will be a workspace. In the backend definition, we add the
workspace_key_prefix parameter. It is specific to the S3 backend and it will define the S3 state path as
The remote states will look as follow in S3 :
The network layer stays the same as before :
The Terraform command manages the workspace. The first step is to create the new workspace:
Then you have to select it :
vars directory presented in the diagram above contains variable files to customize the environment. You have to load the good one when you apply the configuration :
$ terraform init -chdir="./network"
$ terraform apply -chdir="./network" -var-file="./vars/staging.tfvars"
Another way is to use locals instead and play with
The Comparison of Both Strategies
- Environments are separated and identifiable
- More granularity: you can customize environment layers
- Less chance of applying a configuration in a bad environment
- Need to duplicate a piece of file structure to create a new environment
- Several directory levels in the project
- Scalability with repeatable environments
- More possible to make errors by selecting a wrong workspace
- The customization of an environment layer is less obvious
There is no single solution for managing multiple environments in a Terraform project. The two approaches we have discussed both have their qualities and their shortcomings. It depends on your project expectations.
Do you need to scale quickly or is the pace of environment creation more extended in time? Do you want to have file isolation between environments or rely on the workspace abstraction mechanism?
In any case, you can compensate for the shortcomings of this choice with other solutions. By using a continuous deployment pipeline you can greatly reduce the error rate of selecting the wrong workspace. The creation of a new environment in a file structure can be generated on the fly using templating.
Also, the two methods we have seen are not exhaustive. You can take inspiration from them and create a new one that best fits your use case.