14.2 Terraform 与声明式基础设施

一句话

Terraform 把 “基础设施” 变成可 diff、可评审、可复现的代码:你写期望状态,它算出 “现状 → 期望” 的差异(plan)并执行(apply);真正的难点不是语法,而是 state 管理、模块边界与漂移治理。

三件套:代码、状态、计划

组件 作用 出问题时的表现
代码(.tf) 声明资源与依赖关系 改错一行,plan 里出现 “销毁重建”
state 记录 “代码 ↔ 真实资源” 的映射 丢失/冲突 → 重复创建或误删
plan 差异预览与评审依据 不看 plan 直接 apply = 盲改生产
resource "aws_db_instance" "orders" {
  engine         = "postgres"
  instance_class = "db.r6g.large"
  storage_gb     = 200
  multi_az       = true          # 改这一行会触发重建,plan 里必须看清
}

state:必须远端 + 加锁

做法 后果
state 提交进 git 多人并行时互相覆盖,且含敏感信息
state 放本地 换机器即 “资源丢失”,下次 apply 会重建
远端后端 + 锁(S3+DynamoDB / Terraform Cloud / OSS) 并发安全,可审计、可回滚

敏感数据(密码、密钥)会进 state,所以 state 存储必须加密 + 严格权限,必要时用 sensitive = true 并配合外部密钥系统(见 13.4 密钥与凭证管理)。

模块与目录:按 “变更半径” 切

切法 好处 坏处
按环境(dev/staging/prod 各一套) 变更互相隔离 代码重复
按资源层(网络/数据/应用) 影响面清晰 跨层依赖要显式传参
单一大 state 一次 plan 看全貌 任一处变更都要锁全量,风险大

推荐:环境隔离 + 按层拆分 + 模块复用。一个 state 管一个 “可独立重建的单元”,别把整个公司塞进一个 state。

漂移:代码与现实的裂缝

漂移来源 检测 处理
控制台手改 定期 terraform plan(应为空) 改回代码,或把变更写进代码
其他工具创建 同上 用 import 纳入管理
云厂商侧变更(如默认值) plan 出现 “无操作但有 diff” 显式声明,别依赖默认值

“plan 不为空” 应该是告警,而不是常态。

落到数字:plan 里的三种动作

plan 标记 含义 风险
~ update 原地更新 低(但要确认是否触发重启)
+ create 新建 低(注意配额)
-/+ replace 销毁重建 高:数据库/存储可能丢数据
- destroy 删除 最高(retain 保护是否生效)
terraform plan -out=tf.plan && terraform show -json tf.plan | jq '.resource_changes[] | select(.change.actions!=["no-op"]) | {addr:.address, act:.change.actions}'
# 判据:replace 与 destroy 必须逐条人工确认

反模式

反模式 后果
手动改控制台,不回写代码 下次 apply 把手工修复抹掉
不看 plan 直接 apply -auto-approve 销毁重建生产数据库
一个 state 管全部环境 改测试要锁生产,且误操作影响全公司
用 latest 镜像/版本 计划不可复现
把密钥写在 .tf 里 state 与 git 双重泄露
用 -target 频繁“精确修复” 状态与代码逐渐偏离,漂移难查

三条铁律

🔑 state 远端化 + 加锁;本地 state 等于没有基础设施代码。

🔑 plan 必须人工评审;-auto-approve 只用于非生产。

🔑 漂移即告警:定期 plan 为空,是 IaC 健康的唯一标志。

自检

Q1 为什么 “改了 multi_az 一行” 可能在 plan 里显示 “销毁并重建”?

▸ 答案:因为 Terraform 依据云 API 的能力判断 “该属性是否支持原地更新”:不支持时只能删除旧资源、创建新资源(-/+)。这类操作对数据库意味着停机与数据迁移,必须在 plan 阶段识别,改用 “先建新实例再切换” 的迁移方案。

Q2 state 文件为什么不能进 git?

▸ 答案:两个原因:① 并发冲突:多人同时 apply 会互相覆盖,导致 “资源在云上但 state 不知道”(下次重复创建);② 敏感信息:state 里保存了资源属性(含密码、密钥、连接串)。正确做法是远端后端 + 锁 + 加密 + 访问审计。

Q3 控制台手改了一个安全组,下次 apply 会怎样?

▸ 答案:会被改回代码里的状态(这就是漂移被纠正)。如果手改是应急修复,正确流程是 “先改代码 → 再 apply”,否则应急变更会在下一次发布时被静默抹掉,形成 “修复消失” 的诡异故障。

Q4 什么时候该拆 state?

▸ 答案:当满足任一条件:① 不同团队/环境需要独立发布节奏;② 单次 plan 超过几分钟(资源太多);③ 变更的影响面差异巨大(网络层 vs 应用层);④ 需要独立的重建/销毁生命周期。拆分的代价是跨 state 的依赖要显式传参(data/remote_state)。

Q5 Terraform 与 K8s 的声明式有什么共同点与区别?

▸ 答案:共同点是 期望状态 + 控制器收敛 + 可评审的 diff(见 7.1 声明式 API 与控制器模式)。区别在于:Terraform 是客户端批量收敛(apply 一次),K8s 是常驻控制器持续收敛;前者适合 “低频、强顺序” 的基础设施,后者适合 “高频、自愈” 的应用运行时。

Q6 为什么 “先 terraform import 再改代码” 比 “让它重建” 更安全?

▸ 答案:因为 import 把已存在的资源纳入 state 管理,之后 apply 只做真正的差异;直接重建意味着删除线上资源(数据、IP、证书),尤其对数据库与对象存储是灾难。import 的成本是手工写资源块,远低于重建的代价。

关联