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 等),团队共享。
- 无法协作:两人各有一份本地文件,互相覆盖。
- 无锁:同时
apply会交错写坏状态。 - 明文落盘:含密码、私钥等敏感属性,且没人备份。
- 易丢失:删了目录、换了电脑,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 的桶(需开启版本控制 + 服务端加密) |
key | state 在桶内的路径,不同环境/模块用不同 key 隔离 |
dynamodb_table | 加锁表,防止并发写 |
encrypt | 服务端加密存储 |
- AWS:
s3backend + DynamoDB 锁 - GCP:
gcsbackend(原生状态锁) - Azure:
azurermbackend(原生状态锁) - 跨云/自建:
consul/terraformCloud /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>
只有在确认没有别人/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 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 |
能走临时凭据(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
- 准备一个启用了版本控制 + 加密的 S3 桶,和一个 DynamoDB 锁表
- 在
envs/staging/backend.tf配好s3backend,terraform init成功 terraform state list能看到已创建资源
练习 B
- 故意把
aws_instance.web改名为aws_instance.app,plan观察「销毁+新建」 terraform state mv aws_instance.web aws_instance.app,再plan应无变更terraform state pull > /tmp/state.bak备份一份
验收清单
- 远程 backend 配通,state 不再在本地
- 并发写有锁保护(或理解锁机制)
- 会
state list / show / mv / rm - 清楚
sensitive不加密 state,已对 state 桶做加密 + 最小权限 - 知道如何
-migrate-state迁移 backend
下一章:模块化 —— 把重复逻辑抽成 module,定义清晰的输入输出,按版本复用。