Terraform recommendations for multi environment deployment

We have a cloud service based in Azure that uses Terraform,

  • As the IaC solution for defining the required cloud deployment resources

  • Deployment of the aforementioned resources in Azure cloud

We maintain 3 separate environments of this cloud service namely development, staging and production. And each environment is not completely identical to each other.

Each environment has a core group of Azure resources which are identical across each environment while there are differences in terms of resources across each environment, as well.

e.g.,

If you list down all the cloud infrastructure resources in each environment,

  • Development environment resources: A, B, C, D

  • Staging environment resources: A, B, C, D, E, F

  • Production environment resources: A, B, C, F, G, H

A, B and C being the core group of resources identical across all environments (e.g., Azure SQL Servers, Kubernetes service clusters, Log Analytics Workspaces).

Currently,

  • We have defined Terraform modules for Azure services

  • When defining an Azure resource to be used in a given environment, we reuse the relevant custom Terraform module, passing the required parameters as per requirements

  • For each environment, we have defined such a collection of required Azure resources reusing modules

Questions:

  • What are the recommended best practices for defining infrastructure resources in the above use case?

  • Can we use further modularization to avoid unnecessary code duplication across environments, taking recommended best practices into consideration (e.g., modularizing the core group of resources identical across all environments)?

  • We noticed that Terraform conditional expressions can be used at resource parameter level to control parameter definitions.
    Do we have a similar option for controlling resource level definitions (e.g., enable/disable an entire infrastructure resource in a given environment)?

Hi @chirangaalwis,

From what you have described it sounds like you have already chosen the approach I would have recommended: you’ve used shared modules to decompose your problem into smaller units and then used one root module per environment to describe how those units should be configured differently in each environment.

At this point I would expect that you already have minimal duplicate code because the only thing you are not sharing between environments is the description of how the components are connected in each environment, and that’s intentionally different between your environments and so it isn’t duplicate information even if the code that expresses that information has a similar structure.

When working with Terraform I’d typically recommend focusing on making your configuration a straightforward description of the desired state rather than adding lots of extra complexity just to shrink the amount of code.

If there is some complex logic in your root modules that you want to factor out then that could be a good candidate for another shared module, but if your root modules are mostly just a set of module blocks and some simple passing of values from one module’s outputs to another module’s inputs then I think you’ve probably already found the best compromise for your goals.

The remaining question then is whether the way you decomposed the problem into shared modules will make future changes harder or easier. Hopefully the modules are designed to be mostly independent of each other’s details so that for most changes you will only be changing one module at a time and not need to change any other modules or change the root modules.

You won’t know that until you start really maintaining the system, though. The good news is that if you find that the decomposition wasn’t quite right and you want to adjust later then you can use Terraform’s refactoring features to record in your configuration where you moved the resources from and to, and then Terraform will automatically adapt the state into the new shape on the next run.

1 Like