1972 字
10 分钟
Terraform 状态文件
本文行文:state 里到底存了什么 → 本地存储的局限 → 远程后端与状态锁的完整配置 → 多环境与仓库布局 → 冲突与漂移的处理
Terraform State 是什么?
terraform.tfstate 是 Terraform 的核心状态文件,使用 JSON 格式记录:
- Terraform 管理的资源与真实基础设施的映射
- 资源 ID、属性、依赖关系
- 元数据、版本、输出值
Terraform 通过 state 对比“期望状态”和“真实状态”,生成 plan,再执行 apply。
代码里只写”我想要什么”,state 里才记着”实际建出来的是什么”,两者靠资源地址对应:
# 只声明期望状态;真实资源 ID 与属性记在 state 里resource "alicloud_vpc" "this" { vpc_name = "prod-vpc" cidr_block = "10.0.0.0/16"}
resource "alicloud_vswitch" "this" { vpc_id = alicloud_vpc.this.id # 引用即隐式依赖 cidr_block = "10.0.1.0/24" zone_id = "cn-hangzhou-i"}
resource "alicloud_instance" "web" { instance_type = "ecs.c6.large" image_id = "centos_7_9_x64_20G_alibase_20240115.vhd" vswitch_id = alicloud_vswitch.this.id}apply 之后,state 里就有了这份对应关系的节选(真实文件比这长得多):
{ "version": 4, "terraform_version": "1.9.0", "serial": 12, "lineage": "6f2a1c3e-1f4b-4d2a-9c3e-7b8f0d5a1e42", "outputs": { "web_public_ip": { "value": "47.98.12.34", "type": "string" } }, "resources": [ { "mode": "managed", "type": "alicloud_instance", "name": "web", "provider": "provider[\"registry.terraform.io/aliyun/alicloud\"]", "instances": [ { "schema_version": 1, "attributes": { "id": "i-bp1xxxxxxxxxxxx", "instance_type": "ecs.c6.large", "vswitch_id": "vsw-bp1yyyyyyyy", "private_ip": "10.0.1.23" }, "dependencies": [ "alicloud_vpc.this", "alicloud_vswitch.this" ] } ] } ]}几个字段值得留意:
| 字段 | 含义 |
|---|---|
lineage | 这份 state 的”血统”标识,用来判断两份 state 是否同源(迁移、合并时很关键) |
serial | 每次写入自增,能看出写入次序;两个 serial 相同的副本说明发生了并发写 |
instances[].attributes.id | 真实资源 ID,丢了它就等于失去了对资源的跟踪 |
dependencies | 资源依赖关系,决定 destroy 的顺序 |
outputs | terraform output 的数据来源,也是 terraform_remote_state 对外暴露的内容 |
注意:
- 默认存在本地(
terraform.tfstate与备份terraform.tfstate.backup),不适合多人协作。 - 明文存储,包含敏感信息,不能提交到 Git。
sensitive = true只是隐藏命令行与日志里的输出,state 中依然是明文,别把它当脱敏手段。- 删除或损坏 state 会导致 Terraform 失去对资源的跟踪,
destroy也就无从谈起。
多人协作
核心:远程后端 + 状态锁 + CI/CD + 权限控制。
多人协作规范:
- 远程后端:所有人读写同一份远程 state。
- 状态锁:
apply时自动加锁,防止并发写坏 state;CI 里加-lock-timeout=5m让并发排队而不是直接失败。 - CI/CD 执行:通过流水线跑
terraform plan/apply,人工审批后 apply。 - 环境隔离:用 workspace 或独立目录隔离 dev/staging/prod。
- 权限控制:RAM 权限、OSS 加密、操作审计。
- 代码审查:Terraform 代码走 Git MR/PR。
- 禁止手动改 state:必须用
terraform import、state mv、state rm、apply -replace等命令。 - 定期备份 state:OSS 版本控制 + 生命周期。
配置示例
阿里云常用:
- OSS 作为远程后端 存 state
- Tablestore 作为状态锁
- 配置示例:
terraform { required_version = ">= 1.6"
backend "oss" { bucket = "tf-state-prod" key = "env/prod/terraform.tfstate" region = "cn-hangzhou"
# 私有读写 + 服务端加密 acl = "private" encrypt = true
# 状态锁:OSS 后端依赖 Tablestore 实现 tablestore_endpoint = "https://tf-lock.cn-hangzhou.ots.aliyuncs.com" tablestore_table = "tf_lock" }}多环境要复用同一份代码,就得把后端参数外置(partial configuration),在 init 时注入:
# 只声明"用哪个后端",具体参数由 -backend-config 提供terraform { backend "oss" {}}# 真正的连接参数,按环境各存一份bucket = "tf-state-prod"key = "env/prod/terraform.tfstate"region = "cn-hangzhou"encrypt = trueacl = "private"tablestore_endpoint = "https://tf-lock.cn-hangzhou.ots.aliyuncs.com"tablestore_table = "tf_lock"# 首次初始化:下载 provider 并连上远程后端terraform -chdir=envs/prod init -backend-config=backend.tfbackend
# 已经有本地 state(或换后端)时,把已有 state 迁移过去terraform -chdir=envs/prod init -migrate-state -backend-config=backend.tfbackendprovider、变量与资源分成几个文件,是 Terraform 里最常见的组织方式:
terraform { required_providers { alicloud = { source = "aliyun/alicloud" version = "~> 1.230" } }}
provider "alicloud" { region = "cn-hangzhou"}variable "env" { type = string description = "环境名,用于资源命名"}
variable "instance_type" { type = string default = "ecs.c6.large"}# 随环境变化的取值(含敏感值时不入库,见 .gitignore)env = "prod"instance_type = "ecs.c7.xlarge"仓库布局
Terraform 仓库布局
envs
dev
- backend.tf
- backend.tfbackend
- providers.tf
- main.tf
- variables.tf
- terraform.tfvars
prod
- backend.tf
- backend.tfbackend
- providers.tf
- main.tf
- variables.tf
- terraform.tfvars
modules
network
- main.tf
- variables.tf
- outputs.tf
ecs
- main.tf
- variables.tf
- outputs.tf
- .gitignore
.gitignore 至少要挡住三样东西:
# 本地 state 与崩溃日志:绝不入库*.tfstate*.tfstate.*crash.log
# 变量文件可能含明文密钥*.tfvars!*.tfvars.example
# provider 插件与本地缓存.terraform/CI/CD 流水线
流水线里最小可用的 plan 配置:
name: terraformon: pull_request: paths: ["envs/**", "modules/**"]
jobs: plan: runs-on: ubuntu-latest defaults: run: working-directory: envs/prod steps: - uses: actions/checkout@v4 - uses: hashicorp/setup-terraform@v3 - run: terraform fmt -check -recursive - run: terraform init -backend-config=backend.tfbackend - run: terraform plan -lock-timeout=5m -out=tfplan冲突处理
冲突分几种:
A. 锁冲突
- 现象:
Error acquiring the state lock - 处理:确认没有正在运行的
apply,等待锁释放;若锁残留,从报错信息里取出LOCK_ID,确认安全后用terraform force-unlock <LOCK_ID>,谨慎操作。
B. 状态漂移
- 现象:
plan显示资源要重建或修改,但真实环境已被人手动改过。 - 处理:先用只读的
terraform plan -refresh-only看清真实环境与 state 的差异;确认接受现状就terraform apply -refresh-only把真实属性写回 state,确认要收敛就正常apply让代码覆盖真实环境。能纳管就import,不能纳管就state rm后重新创建。
# 只比对、不改资源(推荐用它替代已废弃的 terraform refresh)terraform plan -refresh-only
# 确认"以真实环境为准"后,把属性写回 stateterraform apply -refresh-onlyC. 并发修改
- 远程后端 + 锁基本可避免。若两人同时改代码,先拉最新代码,解决 Git 冲突,再重新
plan。 - 不要手动编辑
.tfstate,除非紧急且先备份。
D. 资源已存在
- 用
terraform import把已有 ECS、SLB、VPC 等导入 state;Terraform 1.5+ 还可以把导入写成声明式代码:
# 声明式导入,apply 时生效import { to = alicloud_instance.web id = "i-bp1xxxxxxxxxxxx"}# 命令行方式(等价)terraform import alicloud_instance.web i-bp1xxxxxxxxxxxx- 导入只按 ID 读取真实资源并写入 state,不会生成配置,导完必须
terraform plan检查属性是否与代码一致;不想手写配置可以用terraform plan -generate-config-out=imported.tf生成骨架。 - 或
terraform state rm移除后重新创建。
常用命令速查
以下命令默认在环境目录中执行(如 envs/prod),在其他位置可以用全局选项 -chdir=envs/prod 指定。
| 命令 | 用途 |
|---|---|
terraform init -backend-config=... | 初始化后端、下载 provider |
terraform init -migrate-state | 切换后端并迁移已有 state |
terraform plan -refresh-only | 只比对真实环境与 state 的差异,不改资源 |
terraform apply -refresh-only | 把真实属性写回 state |
terraform state list / show <地址> | 查看纳管的资源 / 单个资源的记档 |
terraform state mv <源> <目标> | 资源改名或移动(重构代码后常用) |
terraform state rm <地址> | 从 state 移除(不会删除真实资源) |
terraform import <地址> <ID> | 纳管已存在的资源 |
terraform apply -replace=<地址> | 强制重建单个资源(替代已废弃的 taint) |
terraform force-unlock <LOCK_ID> | 释放残留锁 |
terraform output -json | 读取输出值,供其他环境或脚本消费 |
Terraform 状态文件
https://blog-l7wd3.pages.dev/posts/iac/terraform-state/分享文章
生成精美分享图或复制链接,与更多人分享本文。
继续阅读
换条路线
从其他文章中稳定抽取
最后更新于 ,距今已过 21 天
部分内容可能已过时