4. 远程状态与状态锁定
Terraform 的本地状态文件 terraform.tfstate 记录了资源 ID、属性、依赖关系与元数据,是 Terraform 判断“现实世界是否与配置一致”的唯一依据。一旦多人协作、CI/CD 流水线自动执行,本地状态就会立刻暴露三个致命问题:状态文件只存在于某台机器上、并发执行会互相覆盖、状态中可能包含数据库密码等敏感明文。远程状态(Remote State)与状态锁定(State Locking)正是为解决这三类问题而设计的工程机制。
一、后端选型:状态存到哪里
Terraform 通过 backend 块声明状态存储位置。常见后端包括 local、s3、azurerm、gcs、consul、terraform cloud 等。在 AWS 技术栈中,最主流的是 S3 后端:S3 提供高持久性对象存储、版本控制与服务器端加密,配合 DynamoDB 实现锁定。选型时需关注四个维度:是否支持锁定、是否支持版本回滚、是否支持加密、是否支持工作区隔离。
| 后端 | 锁定机制 | 版本/回滚 | 加密 | 适用场景 |
|---|---|---|---|---|
| local | 无 | 手动备份 | 无 | 单人学习、临时验证 |
| s3 + dynamodb | DynamoDB 条件写入 | S3 版本控制 | SSE-S3/KMS | AWS 生产环境、CI/CD |
| azurerm | Blob Lease | Blob 版本 | 服务端加密 | Azure 环境 |
| gcs | 对象元数据 | 对象版本 | Google 托管密钥 | GCP 环境 |
| Terraform Cloud | 内置 | 内置 | 内置 | 托管式协作 |
二、S3 后端与状态文件存储
一个生产可用的 S3 后端配置通常包含以下字段:
terraform {
backend "s3" {
bucket = "acme-tfstate-prod"
key = "network/vpc/terraform.tfstate"
region = "ap-southeast-1"
dynamodb_table = "acme-tf-lock"
encrypt = true
kms_key_id = "arn:aws:kms:ap-southeast-1:123456789012:key/abcd-..."
}
}
其中 bucket 与 key 共同决定状态文件的唯一路径;encrypt = true 启用 SSE,配合 kms_key_id 使用客户托管密钥,可满足合规审计要求。建议同时开启 S3 版本控制,这样每次 terraform apply 写入新状态时都会保留历史版本,一旦误操作可通过指定版本号恢复。状态文件本身不应被手工编辑,任何修改都应通过 Terraform 命令完成。
三、DynamoDB 锁:并发控制的实现原理
DynamoDB 锁表需要一张主键为 LockID(字符串)的表。Terraform 在执行 plan、apply 等写操作前,会向该表写入一条记录,主键值等于状态文件路径(如 acme-tfstate-prod/network/vpc/terraform.tfstate)。写入使用条件表达式 attribute_not_exists(LockID),若记录已存在则写入失败,Terraform 报错并终止,从而保证同一时刻只有一个执行者能修改状态。
锁记录包含 Info 字段,记录执行者、操作类型与时间戳,便于排查“谁锁住了状态”。当操作完成或失败时,Terraform 会删除该记录释放锁。若流水线被强制中断导致锁未释放,可使用 terraform force-unlock <LOCK_ID> 手动解锁,但必须先确认没有其他进程正在运行,否则可能造成状态损坏。
四、状态隔离:按环境与模块拆分
状态隔离的核心原则是“爆炸半径最小化”。如果把 VPC、数据库、应用全部放在同一个状态文件里,一次 apply 失败可能影响所有资源,且 plan 需要刷新全部资源,速度极慢。推荐按“环境 × 层”拆分:
- 环境维度:dev、staging、prod 各自独立状态,互不影响。
- 层级维度:网络层(VPC/子网)、数据层(RDS/Redis)、应用层(ECS/Lambda)分别独立状态。
- 路径约定:
env/层/terraform.tfstate,例如prod/network/terraform.tfstate、prod/app/terraform.tfstate。
层与层之间通过 terraform_remote_state 数据源引用输出值,而不是直接读取对方状态文件。例如应用层需要网络层的 VPC ID:
data "terraform_remote_state" "network" {
backend = "s3"
config = {
bucket = "acme-tfstate-prod"
key = "prod/network/terraform.tfstate"
region = "ap-southeast-1"
}
}
resource "aws_ecs_service" "app" {
network_configuration {
subnets = data.terraform_remote_state.network.outputs.private_subnet_ids
}
}
这种方式的优点是依赖显式、可审计;缺点是远程状态数据源会读取对方全部输出,若对方输出敏感值需谨慎。更严格的做法是使用 SSM Parameter Store 或 Consul 传递少量关键值。
五、跨环境状态管理实践
跨环境管理有两种主流模式:目录隔离与工作区隔离。目录隔离为每个环境建立独立目录,各自拥有 backend 配置与变量文件,隔离彻底、易于理解,是生产环境首选。工作区隔离使用 terraform workspace,同一份配置通过 terraform.workspace 变量区分环境,适合差异极小的场景,但容易因切换工作区误操作,且所有环境共享同一后端配置。
terraform force-unlock 只能在确认没有其他进程运行时使用;若在 CI 中并发触发流水线,应通过流水线层面的并发控制(如 GitHub Actions 的 concurrency、GitLab 的 resource_group)与 DynamoDB 锁双重防护,避免锁被误释放。最后,状态文件迁移是常见操作。当需要把本地状态迁移到 S3 时,先配置好 backend 块,执行 terraform init -migrate-state,Terraform 会提示是否将本地状态复制到远程,确认后本地状态被上传并保留备份。迁移完成后,务必验证 terraform plan 输出为 “No changes”,以确认状态与真实资源一致。