3. Terraform变量与凭据注入基础

在 Terraform 中,变量是让同一份配置适配不同环境、不同账号、不同区域的关键机制。但变量一旦承载密码、Token、Access Key 这类敏感数据,它就不再只是“参数化工具”,而成为凭据注入链路的一部分。本节围绕 input 变量、TF_VAR_ 环境变量、变量优先级以及安全传参的初步实践展开,目标是让你既能正确传值,也能判断值从哪里来、会不会被写进状态文件或日志。

一、input 变量的三种声明与传值方式

Terraform 的 input 变量通过 variable 块声明,至少包含名称,通常还会声明类型、默认值、描述和是否敏感。例如声明一个数据库密码变量:

variable "db_password" {
  type        = string
  description = "RDS 主账号密码,仅通过安全渠道注入"
  sensitive   = true
}

variable "db_port" {
  type    = number
  default = 3306
}

声明之后,值可以通过多种途径传入,常见的有四种:交互式输入、-var 命令行参数、-var-file 变量文件、以及 TF_VAR_ 环境变量。交互式输入适合本地临时测试;-var 会把明文留在 shell 历史和进程列表中,不适合敏感值;-var-file 适合结构化传参,但文件本身必须妥善保护;TF_VAR_ 环境变量则适合 CI/CD 中由密钥管理系统动态注入。

需要特别注意命名规则:环境变量名必须是 TF_VAR_ 加上变量名。变量 db_password 对应的环境变量是 TF_VAR_db_password,大小写敏感。写成 TF_VAR_DB_PASSWORD 不会被识别,Terraform 会认为该变量未赋值,若没有默认值就会在 plan 阶段报错。

二、变量优先级:谁覆盖谁

当同一个变量被多个来源赋值时,Terraform 按固定优先级决定最终值。理解这个顺序,是排查“为什么我传的值没生效”的核心。优先级从低到高依次为:

  1. 环境变量 TF_VAR_name(最低)
  2. terraform.tfvars 文件
  3. terraform.tfvars.json 文件
  4. 任意 *.auto.tfvars 或 *.auto.tfvars.json 文件(按文件名字典序,后者覆盖前者)
  5. -var-file 指定的文件
  6. -var 命令行参数(最高)

也就是说,命令行 -var 永远赢,环境变量永远输。这个顺序带来一个重要实践结论:不要把敏感值放在优先级最高的 -var 里,因为它会进入 shell 历史;也不要把敏感值放在 terraform.tfvars 里并提交到 Git。相对安全的做法是让敏感值走 TF_VAR_,由 CI 的 secret 注入,同时用非敏感的 *.auto.tfvars 提供区域、实例规格等普通参数。

Terraform 变量优先级与凭据注入路径 优先级由下至上递增;敏感值应走左侧安全通道,避免右侧明文通道 ① TF_VAR_name 环境变量 优先级最低 · CI Secret 注入首选 ② terraform.tfvars 易被提交 Git · 不放敏感值 ③ *.auto.tfvars 按文件名字典序覆盖 ④ -var-file 指定文件 显式指定 · 可覆盖 auto ⑤ -var 命令行参数 优先级最高 · 明文进 shell 历史 优先级递增 安全通道(推荐) • 敏感值 → TF_VAR_ 环境变量 • 由 Vault / CI Secret 动态注入 • 变量声明 sensitive = true • 普通参数 → *.auto.tfvars 风险通道(避免) • -var 传密码 → shell 历史泄露 • tfvars 提交 Git → 明文入库 • 未标 sensitive → plan 输出明文 • 变量写入 state → 需加密后端 结论:敏感值走 TF_VAR_ + sensitive,普通值走 auto.tfvars,命令行只传非敏感覆盖项

三、sensitive 标记与状态文件的关系

把变量标记为 sensitive = true 后,Terraform 在 plan 和 apply 的输出中会用 (sensitive value) 替代明文,避免终端和 CI 日志泄露。但必须清楚它的边界:sensitive 只影响输出显示,不改变值的存储方式。如果这个变量最终被写进资源属性,它仍会以明文形式保存在 state 文件中。因此,state 后端必须启用加密与访问控制,例如 S3 后端配合 SSE-KMS 和桶策略,或使用 Terraform Cloud 的加密状态存储。

另一个常见误区是认为 sensitive 能阻止值出现在 terraform output 中。实际上,如果 output 引用了敏感变量且未标记 sensitive = true,Terraform 会直接报错拒绝输出;只有 output 也标记 sensitive,才会以掩码形式展示。这说明敏感信息需要在变量和输出两端都做标记。

四、安全传参的初步实践

下面给出一个可参考的实践结构。假设要部署一台 RDS 实例,密码来自 CI 的 secret,区域和规格来自版本库中的普通配置。

第一步,在 variables.tf 中声明变量,敏感项加标记:

variable "region" {
  type    = string
  default = "cn-hangzhou"
}

variable "db_instance_class" {
  type    = string
  default = "rds.mysql.c1.large"
}

variable "db_password" {
  type      = string
  sensitive = true
}

第二步,在 terraform.auto.tfvars 中放非敏感参数,该文件可以提交版本库:

region            = "cn-shanghai"
db_instance_class = "rds.mysql.c1.xlarge"

第三步,在 CI 流水线中通过环境变量注入密码,而不是写进任何文件:

export TF_VAR_db_password="$DB_PASSWORD_FROM_VAULT"
terraform plan -out=tfplan
terraform apply tfplan

执行后如何判断传参是否正确?可以在 plan 输出中观察:region 应显示为 cn-shanghai(来自 auto.tfvars,覆盖了默认值 cn-hangzhou);db_password 应显示为 (sensitive value),而不是明文。如果密码位置出现明文,说明 sensitive 未生效或变量名拼写不一致;如果 region 仍是 cn-hangzhou,说明 auto.tfvars 未被加载或文件名不符合 *.auto.tfvars 规则。

使用 terraform console 可以交互式检查变量最终取值,例如输入 var.region 查看解析结果。但不要在其中打印敏感变量,因为 console 输出同样可能进入日志。
不要把 TF_VAR_db_password 写进 Dockerfile、Makefile 或 CI 的明文变量配置中。环境变量在进程层面可被同主机其他进程读取,应尽量缩短其生命周期,并在使用后及时清理。

五、优先级冲突的排查思路

当实际值与预期不符时,按以下顺序排查:先确认变量名拼写与大小写;再确认 TF_VAR_ 前缀是否正确;然后检查是否存在多个 *.auto.tfvars 文件互相覆盖;最后确认命令行是否传了 -var 或 -var-file。由于 -var 优先级最高,一个遗留的调试参数就可能悄悄覆盖生产配置,这是实践中非常隐蔽的问题。