CodeRoad Map 程序员的学习成长路线图。内容免费开放,登录后可同步路线图与课时进度。
由 程序员柏风 独立维护 · 关于作者
学习 更多
State 管理 — CodeRoadMap
State 管理 Remote state 与 lock。
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 这些只动账本的命令处理各类不一致。
延伸