如何实现Terraform多环境配置的DRY原则
Great question—managing Terraform code duplication across multiple environments is super common as your infrastructure scales, and there are several battle-tested approaches to clean this up effectively. Let’s break down the most impactful strategies tailored to your existing structure:
First, here’s your current directory structure for reference:
├─ modules │ ├── x │ └── y └─ environments ├── dev │ ├── main.tf │ ├── output.tf │ └── variables.tf ├── uat │ ├── main.tf │ ├── output.tf │ └── variables.tf └── prod ├── main.tf ├── output.tf └── variables.tf
1. Build a Shared Root Module for Environment-Agnostic Logic
The biggest win comes from moving all repeated module calls, provider configurations, and shared infrastructure into a single "root" module that each environment can reference. This way, each environment’s code only contains environment-specific variables and a single module call.
Example Setup:
Create a shared root module under
environments/_root(the underscore signals it’s a utility module, not a deployable environment):# environments/_root/main.tf provider "aws" { region = var.aws_region } module "x" { source = "../../modules/x" # Shared parameters across all environments common_tag = var.common_tag # Environment-specific parameters passed from the environment env_name = var.environment_name } module "y" { source = "../../modules/y" # Repeat pattern for module y, including cross-module references if needed dependent_resource_id = module.x.resource_id }Add shared variables and outputs to
environments/_root/variables.tfandenvironments/_root/output.tf.Simplify each environment’s
main.tfto just call this root module:# environments/dev/main.tf module "infrastructure" { source = "../_root" aws_region = "us-east-1" common_tag = "managed-by-terraform" environment_name = "dev" }
Now your dev/uat/prod directories only need minimal files for environment-specific overrides—no more repeating module calls or provider configs.
2. Centralize Variables and Outputs
Stop duplicating variable definitions across environments. Instead:
- Create an
environments/_common/variables.tffile with all shared variable definitions (e.g.,aws_regiondefault values, global tags). - In each environment’s
variables.tf, reference these shared variables using a common module or locals:# environments/dev/variables.tf module "common_vars" { source = "../_common" } locals { default_tags = merge(module.common_vars.global_tags, { Environment = "dev" }) }
For simpler setups, you can also use symlinks (note: cross-platform caveats apply) to link directly to the shared variables file instead of using a module.
3. Use Terraform Workspaces for Lightweight Environment Differentiation
If your environments only differ in variable values (not infrastructure structure), Terraform Workspaces can reduce file duplication. You’ll maintain a single set of root files, and workspaces handle environment-specific state and variable overrides:
Configure a backend that uses workspaces to isolate state:
# root terraform.tf (move this out of individual environments) terraform { backend "s3" { bucket = "my-terraform-state-bucket" key = "${terraform.workspace}/terraform.tfstate" region = "us-east-1" dynamodb_table = "terraform-state-lock" } }Create environment-specific variable files like
terraform.dev.tfvars,terraform.uat.tfvars, and apply with:terraform workspace select dev terraform apply -var-file=terraform.dev.tfvars
Note: Workspaces work best when environments are nearly identical. If you have structural differences (e.g., prod has extra security modules), stick with the shared root module approach instead.
4. Nest Modules to Reduce Repetition in modules/
If you find yourself calling modules x and y together across all environments, create a higher-level composite module in modules/ that wraps both:
# modules/combined_xy/main.tf module "x" { source = "../x" # Shared parameters for module x tag_prefix = var.tag_prefix } module "y" { source = "../y" # Shared parameters for module y, including cross-module references linked_resource = module.x.resource_arn }
Now each environment can call module "combined_xy" instead of repeating calls to x and y separately.
Key Best Practices to Maintain Cleanliness
- Keep environment differences minimal: Use variables for environment-specific values (e.g., instance sizes, tag values) instead of writing separate code paths.
- Automate with CI/CD: Use pipelines to deploy all environments from the same codebase, ensuring consistency and reducing manual errors.
- Regularly refactor: As you add new modules or environments, periodically audit for repeated code and move it to shared modules.
内容的提问来源于stack exchange,提问作者Kristof Jozsa

