大家好,我是程序员柏风,AI 最全的 Vibecoding 学习地图 http://coderoadmap.cn 作者。
State 远程状态与锁
这一课你将能做到
- 说清楚 state 里到底存了什么,以及本地 state 在团队协作中的四个致命问题
- 用 S3 + DynamoDB 搭建带加密和锁的远程后端,并理解初始化的引导顺序
- 使用 state 子命令(list/show/mv/rm/pull)和
-refresh-only处理状态不一致
为什么要学
前 6 课的 state 都在你本机的 terraform.tfstate 文件里。单人学习没问题,一旦团队协作就四处漏风:两个人同时 apply,谁后写谁覆盖,state 与真实资源错乱;state 文件里有 RDS 密码等明文,散落在各人硬盘上;换台电脑、误删文件,整套资源就「失联」了——资源还在云上花钱,Terraform 却不认识它们。
远程 state 是 Terraform 从「个人工具」变成「团队生产工具」的分水岭。本站的所有环境都必须走远程后端:加密存储、访问留痕、加锁防并发。这一课也是故障处理密度最高的一课,state 乱了怎么救,面试和实战都绕不开。
先建立图像
┌─────────┐ ┌─────────┐
│ 开发者 A │ │ 开发者 B │
└────┬────┘ └────┬────┘
│ plan/apply │
▼ ▼
┌──────────────────────────────┐
│ S3 桶 coderoadmap-tfstate │ 版本化 + SSE-KMS 加密
│ env:/dev/terraform.tfstate │
│ env:/prod/terraform.tfstate│
└───────────────┬──────────────┘
│ 锁:谁在 apply,谁持有
▼
┌──────────────────────────────┐
│ DynamoDB 表 terraform-locks │ LockID = 状态文件路径
└──────────────────────────────┘
state 的双重身份:记账本(配置地址 ↔ 云资源 ID 的映射)+ 缓存(上次 apply 的属性值,用来对比差异)。
跟着做
步骤 1:先引导(bootstrap)后端基础设施
「鸡生蛋」问题:装 state 的 S3 桶本身也可以用 Terraform 建,但那时还没有远程后端。解法是一个独立的 bootstrap 配置,用本地 state(只执行一次):
新建 bootstrap/main.tf:
terraform {
required_providers {
aws = { source = "hashicorp/aws", version = "~> 5.0" }
}
}
provider "aws" { region = "ap-northeast-1" }
resource "aws_s3_bucket" "tfstate" {
bucket = "coderoadmap-tfstate-2026"
}
resource "aws_s3_bucket_versioning" "tfstate" {
bucket = aws_s3_bucket.tfstate.id
versioning_configuration { status = "Enabled" }
}
resource "aws_s3_bucket_server_side_encryption_configuration" "tfstate" {
bucket = aws_s3_bucket.tfstate.id
rule {
apply_server_side_encryption_by_default {
sse_algorithm = "aws:kms"
}
}
}
resource "aws_dynamodb_table" "locks" {
name = "terraform-locks"
billing_mode = "PAY_PER_REQUEST"
hash_key = "LockID"
attribute {
name = "LockID"
type = "S"
}
}
执行:
cd bootstrap
terraform init && terraform apply
cd ..
四件套缺一不可:桶本身、版本化(误改可回滚)、KMS 加密(state 有机密)、DynamoDB 锁表(防并发写)。
步骤 2:在业务配置中声明 backend
在主配置里写:
terraform {
required_version = ">= 1.7.0"
required_providers {
aws = { source = "hashicorp/aws", version = "~> 5.0" }
}
backend "s3" {
bucket = "coderoadmap-tfstate-2026"
key = "env/dev/terraform.tfstate"
region = "ap-northeast-1"
dynamodb_table = "terraform-locks"
encrypt = true
}
}
prod 环境用另一个目录/仓库,key 改为 env/prod/terraform.tfstate,桶可以共用。
步骤 3:init 迁移本地 state
terraform init
# 提示:Do you want to copy existing state to the new backend?
# 输入 yes,本地 tfstate 被上传到 S3
backend 配置里不能使用变量或引用(它在求值早期就要确定),需要参数化时用「部分配置」:
terraform init \
-backend-config="key=env/dev/terraform.tfstate" \
-backend-config="dynamodb_table=terraform-locks"
CI 里普遍用这种方式,配置文件只写 bucket,环境信息由流水线注入。
步骤 4:观察锁的工作过程
正常执行 terraform apply,开第二个终端同时跑 apply,第二个会立刻报:
Error: Error acquiring the state lock
... Lock Info: ID: 3a4f..., Path: coderoadmap-tfstate-2026/env/dev/...
正常结束后锁自动释放。异常退出留下死锁时,确认没有真正的 apply 在跑,再手动解锁:
terraform force-unlock 3a4f... # 参数是报错里的 Lock ID
force-unlock 是高危操作,前提是你 100% 确认持有者已经死亡,否则两边同时写 state 必坏。
步骤 5:state 急救命令全家桶
terraform state list # state 里管了哪些地址
terraform state show aws_instance.web # 看某资源在 state 中的真实属性
terraform state pull > backup.tfstate # 下载当前远程 state(备份/审计)
terraform state mv aws_s3_bucket.old aws_s3_bucket.new
# 只改 state 不碰云资源:重命名资源后用它把旧绑定搬到新地址,避免重建
terraform state rm aws_s3_bucket.legacy
# 从 state 摘除绑定(资源继续留在云上!):用于放弃管理或准备重新 import
有人在控制台改了资源、只想把 state 同步成现实而不纠正任何东西:
terraform plan -refresh-only
terraform apply -refresh-only
-refresh-only 是 1.1 引入的,替代旧的 terraform refresh(该命令仍在但已不推荐,因为它无计划直接写 state)。它让你看清「云上现实与 state 的差异」并只更新 state,不做任何资源变更。
讲清楚要点
- state 不是配置的一部分,是运行产物:它不该进 Git(远程后端 + 本地忽略),但必须被备份(S3 versioning)。
- 锁保护的是写操作:plan 默认也会短暂取锁(可用
-lock=false,不要在写操作上用);锁机制由后端提供,local backend 无锁。 - 每个环境独立 state:dev/prod 用不同 key(甚至不同桶),一次 plan 只影响一个环境;workspace 也能隔离 state,但可见性弱,正式项目多按目录/仓库隔离。
state mv/rm只动 state 不动云:它们是「调整记账本」的手术刀,执行前state pull备份,执行后立刻 plan 验证。- 权限要读写分离收口:多数角色只需
plan(读 state + 读云),apply 权限少数人/CI 持有;state 桶策略禁止公网、开启访问日志。
你可能会懵的地方
-
state 里到底有什么?为什么文件那么大? 每个受管资源的类型、地址、云 ID,以及 apply 后读到的全部属性(相当于一份完整云资源快照)。它是 diff 的基准,所以属性越多文件越大。
-
S3 桶里直接看到旧 state,能手工下载改了再传吗? 绝对不要。绕过锁和格式校验极易写坏;任何修改都用 terraform 命令完成。S3 版本历史是给你「回滚到历史版本」做灾难恢复用的。
-
terraform workspace和按 key 分环境怎么选? workspace 适合同一配置的轻量多实例(如临时演示环境),命令切换快但容易「忘记当前在哪」;本站的 dev/staging/prod 用目录 + 独立 backend key,显式且安全。 -
state 丢了(桶被删)怎么办? 资源都还在云上,只是失联。用 S3 版本/备份恢复 state;没有备份就重新写配置并用 import 逐个重新绑定。所以桶版本化和跨区复制不是可选项。
-
远程 state 引用(terraform_remote_state)是什么? 一个配置只读另一个 state 的 root outputs(见第 10 课 data 块),用来跨仓库共享信息,如网络仓库输出的 VPC ID 被应用仓库引用。
错法对照
| 错法 | 问题 | 正解 |
|---|---|---|
| state 留在本地、提交 Git | 明文机密 + 覆盖写冲突 | S3 后端 + 本地 state 忽略 |
| 不加 DynamoDB 锁 | 并发 apply 互相覆盖 | backend 配置 dynamodb_table |
| 改了资源本地名直接 apply | Terraform 要删旧建新 | 配合 terraform state mv 搬绑定 |
| 看到锁就 force-unlock | 可能撞上正在跑的 apply | 先确认持有者已死,再解锁 |
用 state rm 以为资源会被删 | rm 只摘账本,资源继续计费 | 想删资源改配置走 apply/destroy |
卡点急救
Error loading state/ state 文件损坏:立刻从 S3 历史版本恢复到上一个正常版本;操作前停止一切 apply。恢复后terraform plan验证。- plan 报资源要新建,但资源明明在云上:state 里丢了绑定(可能被 rm 过)。写 import 块把现有资源重新导回正确地址,plan 即可归零。
- apply 卡在锁等待、锁确实是僵尸:DynamoDB 控制台核对该条目(含持有者 IP/时间),确认无进程后
terraform force-unlock <ID>;仍残留就删 DynamoDB 对应行(最后手段)。
面试常问
-
为什么要远程 state?你们怎么配置的? 本地 state 无法协作:并发覆盖、无锁、易丢、明文散落。我们用 S3(版本化 + KMS 加密)存状态、DynamoDB 提供锁、CI 以部分配置注入环境 key,读写权限按角色分离。
-
Terraform state 锁是什么?获取不到锁怎么办? 锁保证同一 state 同时只有一个写操作,防止并发写损坏。获取不到先排查是否有人在 apply;确认是异常残留的死锁才 force-unlock,绝不无脑强解。
-
重构代码改了资源地址,如何避免资源被重建? 用
terraform state mv 旧地址 新地址把绑定平移到新地址,云资源完全不动;随后 plan 应显示 No changes。涉及模块内部地址移动也可用同样方式。
自测三问
- bootstrap 配置为什么用本地 state 也能接受?它多久执行一次?
- 同事改了资源名导致 plan 要删旧建新,不碰云资源如何挽救?
-refresh-only和直接再跑一次 plan 的区别是什么?
小结一句话
State 是 Terraform 的记账本与缓存:用 S3 加密版本化解决存储与协作、用 DynamoDB 解决并发锁、用 state mv/rm/refresh-only 这些只动账本的命令处理各类不一致。
延伸
- S3 Backend 官方配置文档
- State 命令全集(mv/rm/pull/list/show)
- Locking 与 force-unlock 说明
- 下一课:Modules——模块的输入输出、版本化调用、Registry 使用与模块设计原则。