正确做法是每个差异化参数独立声明为 variable 并通过 .tfvars 或 TFVAR* 环境变量注入;避免 default、workspace 分支、复杂映射,命名需体现业务含义与环境边界。
用
variable
块定义可复用的环境参数
不同环境(dev/staging/prod)的差异,本质是同一份 Terraform 代码里要注入不同的值。直接硬编码会破坏复用性,而
variable
是唯一标准方式来抽象这些变化点。Terraform 不允许在
variable
块里写逻辑判断或默认值依赖其他变量,所以别试图在
default
字段里调用
lookup
或拼接字符串。
常见错误是把环境名当变量值传进去,再用
local
做映射——这绕远了,且容易漏覆盖。正确做法是:每个需差异化配置的参数都独立声明为
variable
,并在外部通过
.tfvars
或环境变量注入。
variable "region"
和
variable "instance_type"
分开定义,而不是只定义一个
environment
变量再去查表
所有
variable
都应带
description
,说明该参数在各环境中的典型取值(如:
"prod: us-east-1, dev: us-west-2"
)
避免给
variable
设
default
,除非该值在所有环境都一致(比如统一用
aws
provider);否则默认值会掩盖缺失输入的问题
用
TF_VAR_*
环境变量注入不同环境的值
Terraform 会自动识别以
TF_VAR_
开头的环境变量作为对应
variable
的值,优先级高于
.tfvars
文件。这对 CI/CD 流水线最实用——你不需要维护多套
.tfvars
文件,只要在不同 stage 设置不同环境变量即可。
注意
TF_VAR_
后面必须是变量名全小写、下划线分隔(即使原
variable
声明用了驼峰,Terraform 也会强制转成蛇形)。例如,
variable "dbInstanceClass"
对应的环境变量是
TF_VAR_db_instance_class
,不是
TF_VAR_dbInstanceClass
。
在 GitHub Actions 中用
env:
设置:
TF_VAR_region: ${{ secrets.PROD_REGION }}
本地调试时,可用
export TF_VAR_region=us-east-1 && terraform apply
敏感值(如数据库密码)必须走
TF_VAR_*
或
tfvars
,绝不能写进
variables.tf
的
default
里
用
terraform.workspace
区分环境?别这么做
terraform.workspace
是早期用来隔离状态的机制,但它和
variable
没有绑定关系。很多人误以为可以靠
workspace
名称动态决定
region
或
ami_id
,结果写出类似
lookup({dev="ami-123", prod="ami-456"}, terraform.workspace)
这种脆弱逻辑。问题在于:
workspace
名字不强制和环境语义对齐,且无法被远程后端(如 S3 + DynamoDB)可靠区分。
真正可靠的环境隔离,靠的是独立的
state
文件路径(用
backend
的
key
参数控制),而非
workspace
。变量值应该由外部注入,而不是在代码里“猜”当前是什么环境。
删除所有对
terraform.workspace
的引用,尤其别在
variable default
或
locals
里用它做分支
如果已有 workspace 体系,先用
terraform workspace select
切换,再用
TF_VAR_*
注入对应值,二者解耦
CI 脚本里不要执行
terraform workspace new
,改用
-backend-config=key=prod/main.tfstate
复杂结构变量(map/list/object)怎么安全传入
当一组参数天然成组(如不同环境的 VPC CIDR、子网 ID 映射),可以用
type = map(string)
或
type = object({ ... })
定义单个变量,再用
jsonencode
或
.tfvars
传入完整结构。但要注意:Terraform 0.12+ 要求 map/object 类型必须显式声明
type
,否则报错
Attribute must be defined in the variable configuration
。
常见坑是本地测试时用
echo '{"dev":"vpc-123"}' > dev.tfvars
,却忘了
terraform apply -var-file=dev.tfvars
必须配合
variable "vpc_map" { type = map(string) }
声明,否则类型校验失败。
对象类型推荐用
object({ cidr = string, azs = list(string) })
,比裸 map 更易约束字段
避免嵌套过深(如
map(map(map(string)))
),解析和调试成本陡增
CI 中传复杂结构,优先用
TF_VAR_vpc_map='{"dev":{"cidr":"10.0.0.0/16"}}'
,而不是拆成多个扁平变量
多环境的本质不是“写一套能自动适配所有环境的代码”,而是“同一份代码,接受不同输入”。最容易被忽略的一点是:变量命名是否真的反映了它的业务含义?比如
variable "autoscaling_max"
看似合理,但如果 dev 和 prod 的最大实例数差异极大,这个变量就该拆成
autoscaling_max_dev
和
autoscaling_max_prod
——不是为了省事,而是让每次变更的影响范围一目了然。