「资源与状态管理」

讲解 Terraform 状态管理:state 文件与格式、本地与远程 backend、状态锁、import 采纳存量资源、state mv 与 moved 重构、多环境隔离与备份。

1. State 是什么与为什么需要

一句话总结: Terraform 是声明式的,但它必须借助 state 这份「现实记录」来算出期望状态与现实状态之间的 diff,state 是声明式 IaC 得以成立的账本。

写 HCL 只描述了「期望状态」,真实云环境里到底创建了哪些资源、ID 是多少、属性如何,都记录在 state 中。terraform plan 的本质就是「期望 state 与现实 state 的差异计算」。

期望状态(.tf 配置)  ──┐
                        ├──► 对比 ──► 生成 plan(增/删/改)
现实状态(terraform.tfstate)──┘

1.1 State 承担的三类职责

职责说明
资源映射记录资源地址(aws_instance.web)与真实云资源 ID 的对应
元数据记录属性值、依赖关系、Provider 版本
性能优化无需调用云 API 即可得知已管理哪些资源
# 配置中引用另一资源的输出
resource "aws_instance" "web" {
  subnet_id = aws_subnet.main.id
}

如果没有 state,Terraform 就不知道 aws_subnet.main 到底对应哪个真实子网,也就无法构建依赖与 diff。

2. State 文件结构与格式

一句话总结: state 是一个带 serial 与 backend 元信息的 JSON 文件,核心是 resources 数组;读懂它的字段结构是排障与迁移的基础。

默认本地 state 存放在 terraform.tfstate。其顶层结构如下:

{
  "version": 4,
  "terraform_version": "1.7.0",
  "serial": 7,
  "lineage": "8a1b...",
  "outputs": {},
  "resources": [
    {
      "module": "module.vpc",
      "mode": "managed",
      "type": "aws_subnet",
      "name": "main",
      "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]",
      "instances": [
        {
          "schema_version": 1,
          "attributes": {
            "id": "subnet-0a1b2c3d",
            "cidr_block": "10.0.1.0/24"
          },
          "sensitive_attributes": [],
          "private": "bm9wZQ=="
        }
      ]
    }
  ]
}

2.1 字段含义

字段含义排障要点
versionstate 格式版本4 为当前主流
serial变更序号每次写操作递增,用于乐观锁
lineagestate 身份标识同名 state 若 lineage 不同说明被重建过
resources[].type资源类型与配置中 block 名对应
resources[].instances[]具体实例每个元素对应一个 count/for_each 实例
sensitive_attributes敏感字段路径标记为 sensitive 的字段在此列出

2.2 本地 state 的目录形态

terraform.tfstate        # 主状态文件
terraform.tfstate.backup # 上次成功写入前的备份
terraform.tfstate.d/     # 使用 -state 参数时的目录
.terraform/              # 缓存 Provider 与后端配置

避坑:本地 state 默认不加密,且会记录 sensitive 字段的明文值(只是渲染时打码)。单人演示可以用,团队协作必须切换到远程 backend。

3. Backend 本地与远程

一句话总结: 团队协作的底线是把 state 放到带锁的远程 backend(S3、GCS、Azure Storage、Terraform Cloud),本地 state 只适用于单人演示。

backend 定义了 state 存储在哪里。配置写在 terraform block 的 backend 内:

terraform {
  backend "s3" {
    bucket         = "my-infra-tfstate"
    key            = "prod/network/terraform.tfstate"
    region         = "ap-northeast-1"
    encrypt        = true
    dynamodb_table = "tf-state-lock"
  }
}

3.1 常用 backend 对比

Backend锁支持加密适用
local无无单人、演示
s3 + DynamoDB有SSE-S3/KMSAWS 生态
gcs有默认加密GCP 生态
azurerm有默认加密Azure 生态
terraform cloud有有HashiCorp 云平台
http视实现视实现自研/网关

3.2 避免在 HCL 中硬编码凭据

# 推荐:partial configuration,凭据与敏感信息由环境注入
terraform {
  backend "s3" {}  # 参数从命令行 / CI 环境读取
}
terraform init -backend-config="bucket=my-infra-tfstate" \
  -backend-config="key=prod/network/terraform.tfstate" \
  -backend-config="region=ap-northeast-1"

把 bucket 名、key、区域从 .tf 中剥离,留给 -backend-config 或环境变量注入,避免敏感信息进入版本库。

4. 状态锁与一致性

一句话总结: 状态锁防止两个人同时 plan/apply 导致 state 被并发写坏;锁被卡住时要先确认没有正在运行的 apply,再谨慎 force-unlock。

远程 backend(S3+DynamoDB)在 apply 前会自动获取锁,apply 结束后释放。锁记录存在 DynamoDB 表中:

# 正常流程:plan/apply 自动加锁
terraform apply

# 锁被占用时的报错
# Error: Error acquiring the state lock
# LockInfo: { "ID": "...", "Operation": "OperationTypeApply", "Info": "...", "Who": "...", "Created": "..." }

# 排查后强制解锁(谨慎!)
terraform force-unlock <LOCK_ID>

4.1 锁的生命周期

plan ──► 尝试加锁 ──► 读取 state ──► 计算 diff ──► 释放锁
apply ──► 加锁 ──► 写新 state ──► 释放锁
环节锁状态说明
读取短暂锁防止读到写一半的 state
apply长锁贯穿整个资源变更
写回短暂锁配合 serial 乐观锁防覆盖

4.2 锁被卡住的常见原因

现象原因对策
上次 apply 崩溃进程被 kill 未释放锁确认无运行中进程后 force-unlock
CI 超时残留流水线中断增加锁超时与清理任务
锁表被误删DynamoDB 表重建先重建表,再检查是否有在跑 apply
多人同时触发门禁缺失串行化流水线 + 锁等待策略

一句话:force-unlock 是「原子弹」,用之前必须确认没有别的进程正在写 state,否则会造成状态损坏。

5. Import 采纳存量资源

一句话总结: terraform import 把「手工创建的存量资源」纳入 state 管理,让 IaC 覆盖范围可以从零开始接管历史资产。

云上已经存在大量手工创建的资源(旧控制台点的、历史脚本建的),用 import 把它们纳入 state,之后即可用 Terraform 统一管理。

# 先写好对应的资源块(属性可先留空,import 后补全)
resource "aws_instance" "legacy" {
  # 只写必填字段,其余属性 import 后会自动填充
  ami           = "ami-0c55b159cbfafe1f0"
  instance_type = "t3.micro"
}
# 导入:把真实实例 i-0a1b2c3d 映射到 aws_instance.legacy
terraform import aws_instance.legacy i-0a1b2c3d

# 验证
terraform state show aws_instance.legacy

5.1 Import 的完整流程

步骤命令目的
1 编写资源块手写声明纳入范围
2 导入terraform import <addr> <id>绑定 state
3 检查差异terraform plan找出属性漂移
4 修正配置手改让配置匹配现实
5 收敛terraform plan 无 diff完成采纳
# 用 import block 声明式导入(Terraform 1.5+ 推荐)
import {
  to = aws_instance.legacy
  id = "i-0a1b2c3d"
}

resource "aws_instance" "legacy" {
  ami           = "ami-0c55b159cbfafe1f0"
  instance_type = "t3.micro"
}

避坑:import 只把资源纳入 state,不会自动把配置写成「零 diff」。之后必须 terraform plan 逐项对齐属性,否则可能误判为资源会被重建。

6. Refactor 与 Move 资源重构

一句话总结: 资源改名、挪进模块、count 改 for_each 都靠 terraform state mv 或 moved block 完成「地址迁移」,让重构不产生「删旧建新」的破坏性 diff。

6.1 命令行迁移

# 资源改名
terraform state mv aws_instance.web aws_instance.web_server

# 挪进模块
terraform state mv aws_subnet.main module.vpc.aws_subnet.main

# count 索引迁移
terraform state mv 'aws_instance.web[0]' 'aws_instance.web["a"]'

6.2 用 moved block 记录历史迁移

# 把地址迁移写进配置,团队成员 plan 时自动重映射,无需逐个跑命令
moved {
  from = aws_instance.web
  to   = aws_instance.web_server
}

moved {
  from = aws_subnet.main
  to   = module.vpc.aws_subnet.main
}
重构场景命令 / moved注意事项
资源改名terraform state mv同时更新配置
挪入模块moved { from/to }模块 source 版本保持一致
count 改 for_eachmoved 按 key 迁移原索引地址需逐一列出
拆分大资源手写 import + state mv先拆 state 再拆配置

6.3 拆 state 与挪 state

# 从当前 state 中导出子集到独立文件
terraform state pull > full.tfstate
terraform state rm aws_instance.worker   # 从原 state 移除

# 在新目录中导入
terraform state push full.tfstate        # 覆盖写入(谨慎)

一句话:任何「地址变了但资源不能变」的重构,优先用 moved block 表达,它是可提交、可追溯、可 Review 的迁移记录。

7. 状态管理最佳实践与避坑

一句话总结: 远程存储 + 环境隔离 + 定期备份 + 禁止手改 state 文件,这四条是状态管理的黄金法则。

实践做法收益
远程存储S3/GCS/Azure Storage + 锁团队协作、防丢失
环境隔离每个环境独立 bucket/key互不干扰、权限隔离
定期备份版本化 + 跨区域复制防误删、防损坏
禁止手改全部走 CLI避免损坏 state
最小权限state 桶只给流水线角色读写安全

7.1 环境隔离的 key 设计

terraform {
  backend "s3" {
    bucket = "my-infra-tfstate"
    key    = "${var.env}/network/terraform.tfstate"  # 通过 -backend-config 注入
    region = "ap-northeast-1"
  }
}
prod/network/terraform.tfstate
prod/app/terraform.tfstate
staging/network/terraform.tfstate
staging/app/terraform.tfstate

7.2 常见避坑清单

坑现象对策
state 入库版本库明文泄露密钥立即迁远程 + 轮换密钥
多人共用本地 state互相覆盖统一远程 backend
手改 tfstate格式损坏、重建风暴只用 terraform state 系列命令
忘配 DynamoDB 锁并发写坏 state配锁表
state 与配置严重漂移plan 显示重建先 import / state mv 收敛
生产与测试同 key环境互相污染按环境拆分 bucket/key
# 安全查看 state(字段会被打码)
terraform state show aws_instance.web

# 导出与恢复备份
aws s3 cp s3://my-infra-tfstate/prod/network/terraform.tfstate ./backup.tfstate

8. 总结

State 是 Terraform 声明式引擎的「现实账本」,管理好它等于管理好整套 IaC 的可靠性:

环节要点
本质记录期望与现实之间 diff 所需的资源映射
结构JSON 文件,serial 与 resources 数组是核心
存储团队必须用远程 backend + 锁
锁防并发写坏,force-unlock 须先确认无运行中 apply
import采纳存量资源,完成后 plan 收敛
refactor地址迁移用 state mv / moved block
黄金法则远程存储、环境隔离、定期备份、禁止手改

一句话收尾:先让 state 落在一个安全、可锁、可备份的远程位置,再谈模块与流水线。下一篇「模块化设计模式」将讲解如何用模块封装复用逻辑,把散落的资源组织成可组合的组件。

延伸阅读

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「terraform」更多文章

  1. 「漂移检测与收敛」
  2. 「资源重构与迁移」
  3. 「数据源与远程数据读取」