跳到主要内容

State 管理

本章目标:理解 Terraform state 是什么、为什么不能只放本地,并用远程 backend 把状态存好、加锁、加密。state 是 Terraform 的「真相源」,管不好它,团队协作寸步难行。

完成后你应能配通一套 S3 + DynamoDB 的远程 state,并安全操作 terraform state 子命令。


1. state 是什么​

Terraform 的 state 是资源地址与真实对象的映射和最近一次已知属性。常规 plan 会先通过 provider 刷新真实状态,再把刷新后的 state 与配置中的目标状态比较;没有 state,Terraform 就无法可靠判断「当前对象是谁、该创建还是该更新」。

代码 (main.tf) ──plan──▶ state (映射表) ──diff──▶ 真实云资源
▲
└── apply 后写回
  • 本地 state:默认 terraform.tfstate(JSON 明文),在当前目录。
  • 远程 state:存在 backend(S3 / GCS / Azure / Terraform Cloud 等),团队共享。
本地 state 的致命问题
  1. 无法协作:两人各有一份本地文件,互相覆盖。
  2. 无锁:同时 apply 会交错写坏状态。
  3. 明文落盘:含密码、私钥等敏感属性,且没人备份。
  4. 易丢失:删了目录、换了电脑,state 就没了。

2. backend 配置​

把 state 交给远程 backend,在 terraform {} 块里声明(通常放 backend.tf):

terraform {
backend "s3" {
bucket = "my-org-terraform-state"
key = "envs/prod/network/terraform.tfstate"
region = "ap-guangzhou"
dynamodb_table = "terraform-locks"
encrypt = true
}
}
字段作用
bucket存 state 的桶(需开启版本控制 + 服务端加密)
keystate 在桶内的路径,不同环境/模块用不同 key 隔离
dynamodb_table加锁表,防止并发写
encrypt服务端加密存储
不同云的等价选择
  • AWS:s3 backend + DynamoDB 锁
  • GCP:gcs backend(原生状态锁)
  • Azure:azurerm backend(原生状态锁)
  • 跨云/自建:consul / terraform Cloud / etcd / http

初始化 backend​

terraform init
# 首次会从本地 state 迁移到远程;后续从这里直接读写远程

dynamodb_table 需提前建好(开启 PAY_PER_REQUEST 即可),建表不是 Terraform 管的——属于「bootstrapping」资源,往往用一次性脚本或另一个最小 Terraform 配置创建。


3. state 锁​

当两个人在同一份 state 上 apply,没有锁就会写出损坏状态。backend 提供乐观锁:

  • S3 用 DynamoDB 表(表名即上面 dynamodb_table)记录持有者。
  • GCS / Azure 用各自原生锁。
  • terraform apply 期间自动加锁,正常结束后释放;进程异常中断时锁可能残留,必须在确认无人操作后再人工处理。
# 若发现锁卡住(前一次异常退出未释放),先确认无人正在操作,再手动解锁
terraform force-unlock <LOCK_ID>
force-unlock 是危险操作

只有在确认没有别人/CI 正在跑时才解锁,否则可能造成两人同时写、state 损坏。解锁前先沟通。


4. 手动操作 state:terraform state​

绝大多数时候你不直接碰 state,但以下场景需要:

命令用途
terraform state list列出当前管理的资源地址
terraform state show aws_instance.web查看某资源的完整属性(含 state 中的值)
terraform state mv aws_instance.old aws_instance.new重命名资源地址(避免误判为删除+新建)
terraform state rm aws_instance.web从 state 移除(资源本身仍在云上,只是不再被管理)
terraform state pull / push导出 / 覆盖整份 state(仅应急,push 可能覆盖远程状态)
terraform state list
# aws_instance.web
# aws_security_group.web

terraform state show aws_instance.web
# 输出该资源的全部属性与当前 state 值
改了资源名怎么办

代码里把 aws_instance.web 改名成 aws_instance.app,直接 plan 会显示「销毁旧的 + 新建一个」。若它们其实是同一个东西,用 terraform state mv aws_instance.web aws_instance.app 把状态搬过去,plan 就变成「无变更」。这比删了重建安全得多。

state 操作前先备份并复核

state show 可能输出敏感属性;state mv / rm / push 会直接改变管理映射。先执行 terraform state pull > /tmp/state-before-change.tfstate,完成操作后立即跑一次完整 terraform plan。尤其不要把 state push 当作日常修复手段。


5. 敏感信息:state 里是明文!​

这是最容易踩的坑。sensitive = true 只让 plan/输出打码,并不会让 state 文件加密。只要资源属性里出现过密码、token、私钥,它们在 state 里就是明文 JSON。

{
"aws_db_instance.main": {
"password": "SuperSecret123!" // 明文!谁有 state 读权限谁就能看到
}
}

防护要点:

层面做法
传输/存储backend 开启服务端加密(S3 SSE-KMS / GCS CMEK)
访问state 桶/DynamoDB 走最小权限 IAM,按团队/角色细分
凭据来源密码类值尽量从密钥库(aws_secretsmanager_secret_version 等 data 源)在 apply 时读取,而不是在 .tf 里写死
兜底开启 state 桶版本控制以便误改回滚;定期审计谁有读权限
轮换一旦怀疑泄露,立即轮换凭据并重新 apply
别在 state 里存长期密钥

能走临时凭据(OIDC / 实例角色 / STS)的,绝不放长期 AK/SK 进 state。详见第 9 章。


6. backend 迁移​

当你要换桶、换 key、或本地 ↔ 远程互转时:

# 改 backend 配置后,init 会询问是否迁移已有 state
terraform init -migrate-state
# 确认后,旧 state 内容被复制到新位置

# 只改连接配置、不搬数据(如仅切换凭证 profile)
terraform init -reconfigure

迁移前务必先备份旧 state(terraform state pull > backup.tfstate)。


7. 大 state 与拆分​

单个 state 过大(成百上千资源)会变慢、且「一处改动要 plan 整个巨无霸」。按环境 + 业务边界拆成多个 root module,各自一份 state:

envs/prod/network/ # 网络层单独 state
envs/prod/compute/ # 计算层单独 state
envs/prod/data/ # 数据层单独 state

拆分的模块间通过 data 源读对方输出(如 network 暴露 vpc_id,compute 用 data 读取),而非共享同一份 state。


8. 练习与验收​

练习 A

  1. 准备一个启用了版本控制 + 加密的 S3 桶,和一个 DynamoDB 锁表
  2. 在 envs/staging/backend.tf 配好 s3 backend,terraform init 成功
  3. terraform state list 能看到已创建资源

练习 B

  1. 故意把 aws_instance.web 改名为 aws_instance.app,plan 观察「销毁+新建」
  2. terraform state mv aws_instance.web aws_instance.app,再 plan 应无变更
  3. terraform state pull > /tmp/state.bak 备份一份

验收清单

  • 远程 backend 配通,state 不再在本地
  • 并发写有锁保护(或理解锁机制)
  • 会 state list / show / mv / rm
  • 清楚 sensitive 不加密 state,已对 state 桶做加密 + 最小权限
  • 知道如何 -migrate-state 迁移 backend

下一章:模块化 —— 把重复逻辑抽成 module,定义清晰的输入输出,按版本复用。