When managing infrastructure, it's common to configure multiple environments for deploying code. For example,
production. In these cases, it's important for each environment to be as close as possible to the others. This ensures that minute differences in architecture—as minor as a PostgreSQL patch version or as major as using RabbitMQ in
staging and IBM MQ in
production -don't introduce problems when applications are promoted to
production. Using Terraform to manage environments can help. Terraform is declarative, and the same declaration can be used to manage multiple environments. To do so, we'll explore two common solutions: Terraform Workspaces, and Terraform Modules.
This is a great way to isolate different versions of your state. The declarative infrastructure is still read by the same
.tf files within your directory, while the state used to reconcile against your currently deployed infrastructure is isolated from other workspaces (Similar to the way version managers allow multiple language versions without altering the project's code).
When using workspaces, your directory structure might look something like this:
├── README.md ├── main.tf ├── variables.tf ├── outputs.tf ├── eks.tf ├── vpc.tf ├── iam.tf
With this structure, you can create a workspace for each environment:
$ terraform workspace new stage $ terraform workspace new prod $ terraform workspace select stage
These commands will create a separate subdirectory for each workspace within your terraform back end, and use this directory to store the current state. This means when you run
terraform plan in an environment that is currently up to date, you will have no changes to apply because the state within this workspace's directory is in sync with your infrastructure. By contrast, the same command when using a different workspace could show pending changes.
- Your infrastructure definitions are identical for each environment.
- Directory structure is easy to navigate and manage.
- It's trivial to spin up additional environments with
terraform workspace new [environment]
- While each environment is identical, that's not always what you want.
- You may have conditional operators in your code, which can get messy. IE:
instance_count = terraform.workspace == "prod" ? 10 : 2
- It's actually not recommended by HashiCorp.
As an alternative, Terraform also offers modules. This approach encourages you to abstract the complex configuration of your environment into a module, and then reference that module in separate terraform "environment" folders within your project. In this case, your directory structure might look something like this:
├── README.md ├── modules/ │ ├── eks/ │ │ ├── eks.tf │ │ ├── iam.tf │ │ ├── main.tf │ │ ├── outputs.tf │ │ ├── variables.tf │ │ ├── vpc.tf ├── stage/ │ ├── eks.tf │ ├── main.tf │ ├── variables.tf ├── prod/ │ ├── eks.tf │ ├── main.tf │ ├── variables.tf
In the above example, the
main.tf file within both
prod would have its own terraform configuration, including its own backend. This allows you to store your
tfstate in a completely isolated location for each environment. Additionally, the
eks.tf within both
prod would simply call the
eks module (where most of the infrastructure is defined) and pass in its own unique set of variables specific to that environment.
- Complex infrastructure constructs are abstracted.
- You may want some resources in
- Increased upfront investment.
- More complex code and directory structure.
- There aren't good conventions for how to structure your module.
- It can sometimes be confusing where to make changes to your infrastructure.
So why might you use one method over the other? Use whichever method makes the most sense for your use case. A project with 1-3 engineers and relatively few cloud needs may benefit from Terraform Workspaces, while a much larger engineering team could benefit from the rigidity of modules, and know that the upfront investment will pay dividends as their team and needs grow. In short: be pragmatic! I've found that pragmatism and considering my current needs is always the best approach.