大家好,我是程序员柏风,AI 最全的 Vibecoding 学习地图 http://coderoadmap.cn 作者。
Variables 输入变量、校验与敏感信息
这一课你将能做到
- 定义 string/number/bool/list/map/object 等类型的输入变量,并通过 tfvars 文件按环境赋值
- 用
validation块在 plan 前拦截非法输入,用sensitive控制敏感值展示 - 说清楚变量传入的多种方式与优先级,组织 dev/staging/prod 三套环境配置
为什么要学
没有变量的 Terraform 配置是「一次性脚本」:桶名写死、实例规格写死、CIDR 写死,换个环境就得复制一个目录改值,两份配置很快分叉。变量让同一份配置按参数长出不同形态——dev 用 t3.micro 单实例、prod 用 m5.large 多实例,代码只有一份。
更重要的是「左移」:非法的 CIDR、生产环境却用了单机配置、数据库密码太短,这些问题如果在 apply 到一半才暴露,可能留下半套资源。validation 让错误在 plan 之前就被挡住。而敏感信息如何在日志和 state 中处理,直接关系安全合规。
先建立图像
变量像 Java 方法的入参:
variable "env" { ... } ≈ void deploy(String env)
terraform apply -var 'env=dev' ≈ deploy("dev")
terraform.tfvars ≈ 默认参数文件
一次 apply 的变量来源与优先级(后者覆盖前者):
环境变量 TF_VAR_xxx
└─ 命令行 -var / -var-file(最高,覆盖一切)
terraform.tfvars / *.auto.tfvars(自动加载)
└─ 变量自身的 default(最低)
跟着做
步骤 1:定义基础变量
新建 vars-demo/variables.tf:
variable "aws_region" {
description = "部署区域"
type = string
default = "ap-northeast-1"
}
variable "env" {
description = "环境名:dev / staging / prod"
type = string
}
variable "instance_count" {
description = "Web 实例数量"
type = number
default = 1
}
variable "enable_deletion_protection" {
type = bool
default = false
}
variable "availability_zones" {
type = list(string)
default = ["ap-northeast-1a", "ap-northeast-1c"]
}
variable "common_tags" {
type = map(string)
default = {
Project = "coderoadmap"
}
}
用 var.env、var.instance_count 引用。有 default 的变量可以不传;没有 default 且非 nullable 的变量,不传会在 plan 时交互式提示输入(CI 环境没有交互终端,会直接失败,所以 CI 必须显式传值)。
步骤 2:object 与 optional 表达结构化配置
1.3 起可以在 object 类型里用 optional() 声明可选字段,这是描述「环境配置」的利器:
variable "app_config" {
type = object({
instance_type = string
disk_size = optional(number, 20) # 可选,默认 20
enable_https = optional(bool, false) # 可选,默认 false
allowed_cidrs = optional(list(string), ["0.0.0.0/0"])
})
default = {} # 整个对象可省略,内部字段全部走默认
}
output "demo" {
value = var.app_config
}
部署到不同环境时只覆盖关心的字段:app_config = { instance_type = "t3.micro" },其余字段自动取默认值。
步骤 3:validation 前置校验
variable "env" {
type = string
validation {
condition = contains(["dev", "staging", "prod"], var.env)
error_message = "env 只能是 dev、staging 或 prod。"
}
}
variable "instance_count" {
type = number
default = 1
validation {
condition = var.instance_count >= 1 && var.instance_count <= 10
error_message = "实例数量必须在 1-10 之间。"
}
}
variable "db_password" {
type = string
sensitive = true
validation {
condition = length(var.db_password) >= 12
error_message = "数据库密码长度至少 12 位。"
}
}
校验表达式可以用函数但不能引用其他变量/资源(1.7 限制)。条件为假时 apply/plan 直接中止,不会产生任何云资源。
步骤 4:用 tfvars 组织多环境
新建 dev.tfvars:
env = "dev"
instance_count = 1
availability_zones = ["ap-northeast-1a", "ap-northeast-1c"]
app_config = {
instance_type = "t3.micro"
}
db_password = "dev-only-123456"
新建 prod.tfvars:
env = "prod"
instance_count = 3
availability_zones = ["ap-northeast-1a", "ap-northeast-1c", "ap-northeast-1d"]
app_config = {
instance_type = "m5.large"
disk_size = 50
enable_https = true
allowed_cidrs = ["10.0.0.0/8"]
}
db_password = "从环境变量注入,不要写在这里"
使用方式:
terraform apply -var-file=dev.tfvars
terraform apply -var-file=prod.tfvars
名为 terraform.tfvars 或 *.auto.tfvars 的文件会被自动加载,无需显式指定。含密钥的 tfvars 不要提交 Git:提交 dev.tfvars 这种非敏感样例,敏感文件命名 secret.tfvars 并加入 .gitignore;更好的做法是根本不把密码放进变量——见步骤 5。
步骤 5:敏感变量的正确打开方式
三种传入方式(不在文件里留痕):
# 1) 环境变量(CI 的 secret 注入)
export TF_VAR_db_password='你的强密码'
terraform apply -var-file=prod.tfvars
# 2) 命令行 -var(会进 shell 历史,不推荐)
terraform apply -var 'db_password=你的强密码'
# 3) 根本不传密码:让 Terraform 引用 Secrets Manager / SSM
第 3 种是本站采用的架构:RDS 密码存进 AWS Secrets Manager,配置中只引用其 ARN;ECS 任务启动时由平台注入。注意:sensitive = true 只影响 CLI/日志中的显示(输出显示为 (sensitive value)),它仍然以明文存在于 state 里。所以 state 必须加密、访问收口(第 7、12 课),敏感值不能因为标了 sensitive 就当成已加密。
讲清楚要点
- 类型约束即文档:看 variable 的 type 就知道要传什么,比注释可靠;类型不符在加载期直接报错。
- default 决定「可选还是必传」:无 default 的变量必传(除非 nullable 且显式传 null)。
- validation 是纯输入校验:不能访问资源、其他变量和网络,适合值域、格式、长度这类检查;跨资源约束用 preconditions/lifecycle(后续课讲)。
-var-file与自动加载别混淆:只有固定文件名自动加载,环境文件一律显式-var-file,避免「我以为加载了 prod」。- sensitive 是显示掩码不是加密:state 明文存储这一事实决定了 state 加密(S3 SSE-KMS)和权限控制必不可少。
你可能会懵的地方
-
nullable = true是干什么的? 默认变量可以接收 null。传 null 时资源参数相当于「未设置」,会走 provider 默认值。想强制调用方必须给值,写nullable = false。 -
map(string) 和 object 有什么区别? map 的所有值必须同类型、键数量任意;object 每个字段独立类型、字段名固定。配置项固定但类型混合(有字符串有数字有布尔)用 object,纯标签类用 map。
-
.auto.tfvars多个文件同时存在会怎样? 按文件名字典序依次加载,后加载的覆盖先加载的。团队里少用这个特性,显式传文件更不容易出错。 -
变量改了默认值,为什么 plan 没变化? tfvars 或命令行显式赋了值时,default 根本不参与;排查优先级链路即可。
-
为什么 validation 里不能引用另一个变量? 校验在变量图求值阶段执行,变量间相互引用会引入求值顺序问题。需要跨变量/资源的断言,用资源/lifecycle 的
precondition(第 10 课)。
错法对照
| 错法 | 问题 | 正解 |
|---|---|---|
| 生产密码写进 prod.tfvars 并提交 | 凭据永久留在 Git 历史 | TF_VAR 环境变量注入或 Secrets Manager |
| 以为 sensitive 会加密 state | state 中仍是明文 | state 远程加密 + 权限收口 |
| 无 default 变量靠交互输入跑 CI | CI 无 TTY,直接失败 | CI 中 -var-file/-var 显式传值 |
一个 config.tfvars 里塞所有环境 | 切环境靠手改文件,易错 | dev/staging/prod 分文件显式选择 |
| 校验报错写得含糊 | 使用者不知道怎么改 | error_message 给出合法取值范围/示例 |
卡点急救
Required variable not set:变量没有 default 且当前没传任何值;检查 tfvars 文件名(只有 terraform.tfvars/auto.tfvars 自动加载)或补上 -var-file。Invalid value for variable(类型不符):常见于 number 传了"1"带引号、list 传成单个字符串;terraform console里用type(var.xxx)思路核对,或对照 type 定义修正。- CI 日志里打印出了敏感值:确认变量与相关 output 都标了
sensitive = true;但任何通过nonsensitive()或普通 output 间接暴露的路径都要排查,掩码跟着「值」走而不是跟着名字走。
面试常问
-
Terraform 变量怎么传?tfvars 和 -var 的优先级? default < 自动加载的 terraform.tfvars/*.auto.tfvars < 环境变量 TF_VAR_ < 命令行 -var/-var-file,后者覆盖前者;多环境用独立 tfvars 显式 -var-file 选择。
-
sensitive = true 能防止什么、不能防止什么? 防止值出现在 CLI 输出和 plan 日志里(掩码跟着值传播);不能加密 state,state 文件中仍为明文,必须依靠 state 存储加密和最小访问权限。
-
如何在 Terraform 中管理数据库密码这类机密? 首选托管机密服务(Secrets Manager/SSM Parameter Store/Vault),Terraform 只管理和引用机密的 ARN,运行时注入;次选 TF_VAR 从 CI secret 传入。避免写进 tfvars 入库。
自测三问
- 让同一份配置在 dev/prod 长出不同实例规格,文件和命令分别怎么组织?
- validation 能挡住「密码小于 12 位」,为什么挡不住「密码和另一个变量重复」?
- 同事说「我标了 sensitive 为什么 state 里还能看到密码」,你怎么解释?
小结一句话
变量让一份配置参数化地覆盖所有环境:靠类型约束形状、靠 validation 前置拦截、靠 tfvars 分环境、靠机密托管而非明文文件处理密码,并始终记住 sensitive 只是显示掩码。
延伸
- Input Variables 官方文档
- Custom Validation Rules
- Optional Object Attributes(1.3+)
- 下一课:Outputs——如何把资源创建后的关键信息(IP、域名、ARN)有选择地暴露给人和模块。