上周五下班前,我们团队上演了一出惊心动魄的“恐怖故事”。两个运维开发在没有充分沟通的情况下,同时在各自的本地终端里执行了 terraform apply。因为当时我们直接把 terraform.tfstate 存放在一个共享的 S3 桶里,却没有配置任何状态锁机制。结果两个进程并发读写同一个状态文件,导致 S3 中的 State 直接混乱。更惨的是,其中一个 apply 判定某台线上关键数据库实例“已不存在”,直接触发了销毁重建!虽然最后通过历史备份抢救回来了,但这绝对是运维生涯里不可承受的低级失误。
痛点分析:为什么只用 S3 存 State 还不够?
许多人刚接触 Terraform 时,只知道把状态文件放到远端 S3 存储桶里,觉得这样大家就能共享最新的配置了。但 S3 本质上只是一个对象存储,它不具备强一致性的分布式锁功能。
如果多个人同时执行写操作:
- A 和 B 同时拉取了当前的 State 缓存;
- A 开始创建资源,B 开始修改资源;
- A 先执行完并写入 S3,随后 B 执行完覆盖了 S3;
- 这时候,A 刚刚创建的资源在最新的 State 里彻底“丢失”了,Terraform 失去了对这些物理资源的追踪,变成无主孤魂。
为了解决这个问题,Terraform 提供了 Backend 锁机制。对于 AWS 环境,标准方案就是 S3(存状态) + DynamoDB(管状态锁)。
第一步:用 Terraform 创建 S3 桶和 DynamoDB 表
我们先用一段基础的 Terraform 代码来初始化这些安全基础设施。注意,DynamoDB 的主键(Hash Key)必须命名为 LockID,类型为 S(String),这是 Terraform 的硬性规定,不能自定义。
# backend-infra.tf
provider "aws" {
region = "us-east-1"
}
# 存放 tfstate 的安全 S3 桶
resource "aws_s3_bucket" "terraform_state" {
bucket = "company-infra-tfstate-prod"
force_destroy = false
}
# 开启 S3 版本控制,万一 State 坏了还能回滚
resource "aws_s3_bucket_versioning" "enabled" {
bucket = aws_s3_bucket.terraform_state.id
versioning_configuration {
status = "Enabled"
}
}
# 开启服务端加密,保护敏感数据
resource "aws_s3_bucket_server_side_encryption_configuration" "default" {
bucket = aws_s3_bucket.terraform_state.id
rule {
apply_server_side_encryption_by_default {
sse_algorithm = "AES256"
}
}
}
# 阻断所有公网访问,防止 State 泄露
resource "aws_s3_bucket_public_access_block" "public_access" {
bucket = aws_s3_bucket.terraform_state.id
block_public_acls = true
block_public_policy = true
ignore_public_acls = true
restrict_public_buckets = true
}
# 创建 DynamoDB 锁表
resource "aws_dynamodb_table" "terraform_locks" {
name = "company-infra-tflocks"
billing_mode = "PAY_PER_REQUEST"
hash_key = "LockID"
attribute {
name = "LockID"
type = "S"
}
}
第二步:配置项目的 Backend 引用
当上面的基础设施创建完毕后,我们就可以在实际的基础设施项目代码中声明这个远程 backend 了。
# backend.tf
terraform {
required_version = ">= 1.5.0"
backend "s3" {
bucket = "company-infra-tfstate-prod"
key = "global/s3/terraform.tfstate"
region = "us-east-1"
dynamodb_table = "company-infra-tflocks"
encrypt = true
}
}
配置好之后,我们在项目根目录下运行 terraform init。Terraform 会检测到 Backend 变更,并提示你将本地的 State 文件搬迁到 S3 桶里。
强锁机制的工作流原理
配置完成之后,我们再次尝试并发操作。
当 A 运行 terraform plan 或 terraform apply 时,Terraform 会自动往 DynamoDB 的 company-infra-tflocks 表里插入一条主键为 global/s3/terraform.tfstate-md5 的锁记录。
如果此时 B 也在运行 apply,Terraform 在修改前会去查表,一旦发现锁已存在,就会直接报错并退出:
Error: Error acquiring the state lock
Error message: ConditionalCheckFailedException: The conditional request failed
Lock Info:
ID: 8eb52b57-a3f1-d007-4228-example
Path: company-infra-tfstate-prod/global/s3/terraform.tfstate
Operation: OperationTypeApply
Who: deploy-user@bastion-01
Version: 1.5.7
Created: 2026-07-13 07:30:00Z
Info:
这样就完美避免了多人并发修改 State 的隐患。
突发情况:锁死了解不开怎么办?
有时候,如果网络突然中断,或者 CI/CD 容器在运行 apply 时被强行杀掉,可能会导致锁没有被正常释放。这时候其他人再执行就会一直报 Error acquiring the state lock。
此时,我们需要获取报错信息里的 ID(例如上面的 8eb52b57-a3f1-d007-4228-example),在确认没有其他人正在部署的情况下,使用下面的命令强制解锁:
terraform force-unlock 8eb52b57-a3f1-d007-4228-example
切记!这个命令非常危险,必须配合团队沟通使用,否则一旦强行解锁时真的有人在做 apply,State 依然会损毁。
你们团队目前在多人协同这块,是用的自建 S3 Backend 锁方案,还是干脆上了 Terraform Cloud / Enterprise 这种 SaaS 平台?