「远程状态与团队协作」

讲解 Terraform 远程状态与团队协作:S3 加 DynamoDB 与 Terraform Cloud 的后端选型、状态锁与并发控制、工作区与变量集、状态拆分与数据源共享,以及敏感信息加密与状态迁移恢复。

1. 为什么需要远程状态与协作

一句话总结: 本地 state 只适合单人玩具项目,一旦多人协作就必须把状态放到共享后端,否则两个人的 apply 会互相覆盖,产生无法解释的资源漂移。

terraform.tfstate 是 Terraform 的全部记忆:资源 ID、属性、依赖关系。本地存储有三个致命问题:

# 1. 状态文件在个人电脑上,别人拿不到
ls -la terraform.tfstate*

# 2. 误提交到 git(含明文密钥)
git log --all -- terraform.tfstate

# 3. 两人同时 apply,后者的状态覆盖前者

远程后端解决三件事:共享(所有人读写同一份状态)、加锁(同一时刻只有一个人能改)、审计(状态变更历史可追溯)。

能力本地状态远程后端
多人协作不可能原生支持
状态锁无有
版本历史靠 git(危险)后端原生版本化
加密无KMS / SSE
CI 可用需拷贝文件天然适配

一句话:把 state 从「一个人的文件」变成「团队的服务」,是 Terraform 从个人工具走向团队基础设施的第一步。

2. 后端选型

一句话总结: S3 + DynamoDB 是自托管场景的默认答案,Terraform Cloud 提供托管状态与协作 UI,选择取决于团队是否愿意自己运维存储与权限。

2.1 S3 + DynamoDB

S3 存状态文件,DynamoDB 提供锁。这是最经典的自托管方案,成本极低、可控性强。

terraform {
  backend "s3" {
    bucket         = "company-terraform-state"
    key            = "prod/network/terraform.tfstate"
    region         = "us-east-1"
    dynamodb_table = "terraform-locks"
    encrypt        = true
    kms_key_id     = "arn:aws:kms:us-east-1:123456789012:key/abcd-1234"
  }
}
# 创建锁表(一次性)
aws dynamodb create-table \
  --table-name terraform-locks \
  --attribute-definitions AttributeName=LockID,AttributeType=S \
  --key-schema AttributeName=LockID,KeyType=HASH \
  --billing-mode PAY_PER_REQUEST

2.2 Terraform Cloud / HCP

托管方案把状态、锁、变量、审批、策略门禁都收进一个平台,代价是绑定供应商与按用量计费。

terraform {
  cloud {
    organization = "example-org"

    workspaces {
      name = "prod-network"
    }
  }
}
terraform login
terraform init
# 状态由平台托管,本地不再出现 tfstate 文件
维度S3 + DynamoDBTerraform Cloud
运维成本自建自管零运维
成本模型按存储与请求按用户与并发
状态锁DynamoDB内置
变量与密钥自建(SSM/Vault)内置变量集
策略门禁自行接入 OPA内置 Sentinel
远程执行需自建 runner内置

一句话:选型的核心问题是「你愿意自己运维状态存储吗」——愿意就用 S3,不愿意就用托管,二者没有技术优劣只有组织适配。

3. 状态锁与并发控制

一句话总结: 状态锁保证同一时刻只有一次 apply 在改状态,锁超时与强制解锁是必须掌握的两个操作,但强制解锁前一定要确认没有僵尸进程。

apply 时 Terraform 会先获取锁,失败则报错退出:

terraform apply
# Error: Error acquiring the state lock
# Lock Info:
#   ID:        8f3c2a1e-...
#   Path:      prod/network/terraform.tfstate
#   Operation: OperationTypeApply
#   Who:       ci-runner@build-1234
# 确认没有正在运行的 apply 之后,才强制解锁
terraform force-unlock 8f3c2a1e-...

# 也可以设置锁等待时间,避免 CI 因瞬时冲突失败
terraform apply -lock-timeout=5m

3.1 锁的常见陷阱

场景表现处理
CI 任务被 kill锁残留确认后 force-unlock
本地 Ctrl+C锁未释放重试或 force-unlock
两人同时 planplan 可以并行plan 不加锁,apply 才加
状态被手工编辑版本不一致立即停止并用备份恢复
# 在 CI 中统一设置锁超时
# terraform plan/apply 都加 -lock-timeout

一句话:锁报错是「保护」不是「故障」——绝大多数情况下正确做法是等待或查明原因,而不是立刻 force-unlock。

4. 工作区与变量集

一句话总结: 工作区解决「同一套配置多环境实例」,变量集解决「多工作区共享同一批变量」,二者配合能把环境差异从代码里剥离到平台配置。

Terraform Cloud 的 workspace 与 CLI workspace 语义不同:前者是独立的状态 + 变量 + 运行历史容器。

# 同一份配置,通过 workspace 区分环境
locals {
  env = terraform.workspace

  instance_count = {
    dev  = 1
    stg  = 2
    prod = 4
  }[local.env]
}
terraform workspace list
terraform workspace new stg
terraform workspace select prod
terraform workspace show

变量集(Variable Set)把「所有工作区都要用的变量」集中管理,避免在每个工作区重复配置:

# 通过 API 创建变量集并关联多个工作区
curl -s -X POST \
  -H "Authorization: Bearer $TFC_TOKEN" \
  -H "Content-Type: application/vnd.api+json" \
  https://app.terraform.io/api/v2/varsets \
  -d '{
    "data": {
      "type": "varsets",
      "attributes": {
        "name": "global-aws-creds",
        "global": false
      }
    }
  }'
机制作用域适用
workspace 变量单个工作区环境特有(实例数、规格)
变量集多工作区共享凭证、公共标签
全局变量集全部工作区组织级默认值

一句话:workspace 是「隔离」,变量集是「复用」——把该隔离的隔离、该共享的共享,配置才不会退化成复制粘贴。

5. 状态拆分与数据源共享

一句话总结: 单一大状态会让每次 plan 都刷新所有资源、爆炸半径过大,按生命周期与变更频率拆分状态,再用 data source 或 remote state 在栈之间传递引用。

拆分原则:变更频率相近、生命周期一致、爆炸半径可接受的资源放同一个状态。

# 拆分示例
# prod/network/   —— VPC、子网、路由(极少变更)
# prod/data/      —— RDS、ElastiCache(季度变更)
# prod/app/       —— ECS、ALB、AutoScaling(频繁变更)

跨栈引用有两种方式。其一是 terraform_remote_state 数据源:

data "terraform_remote_state" "network" {
  backend = "s3"

  config = {
    bucket = "company-terraform-state"
    key    = "prod/network/terraform.tfstate"
    region = "us-east-1"
  }
}

resource "aws_instance" "web" {
  subnet_id = data.terraform_remote_state.network.outputs.private_subnet_ids[0]
}

其二是 Provider 原生数据源,耦合更松、推荐优先:

data "aws_vpc" "main" {
  filter {
    name   = "tag:Name"
    values = ["prod-vpc"]
  }
}

resource "aws_instance" "web" {
  subnet_id = data.aws_subnets.private.ids[0]
}
方式耦合度需要 state 读取权限推荐度
terraform_remote_state高(依赖对方 output)是仅当无原生数据源
Provider 数据源低(按属性查询)否首选

一句话:拆状态的判断标准是「这组资源会不会一起变」——一起变的一起拆,不一起变的坚决分开。

6. 敏感信息与加密

一句话总结: state 里存着数据库密码、私钥、令牌的明文,必须启用静态加密、限制访问权限,并把真正的密钥来源放在 Vault 或 SSM 而非 tfvars。

# 敏感值在 state 中仍是明文,标记 sensitive 只影响输出显示
resource "aws_db_instance" "main" {
  password = var.db_password   # state 里是明文!
}
# 检查 state 中是否有明文密钥
terraform state pull | jq -r '.. | strings' | grep -iE 'password|secret|token' | head

三重防护:

# 1. 后端加密(S3 SSE-KMS)
terraform {
  backend "s3" {
    encrypt    = true
    kms_key_id = "arn:aws:kms:us-east-1:123456789012:key/abcd-1234"
  }
}
# 2. 密钥来自动态数据源,不落 tfvars
data "aws_secretsmanager_secret_version" "db" {
  secret_id = "prod/db/password"
}

resource "aws_db_instance" "main" {
  password = data.aws_secretsmanager_secret_version.db.secret_string
}
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Deny",
      "Principal": "*",
      "Action": "s3:GetObject",
      "Resource": "arn:aws:s3:::company-terraform-state/*",
      "Condition": {
        "StringNotEquals": {
          "aws:PrincipalArn": "arn:aws:iam::123456789012:role/terraform-ci"
        }
      }
    }
  ]
}
措施拦截的风险
SSE-KMS 加密存储介质泄漏
存储桶策略越权读取状态
动态密钥源密钥落入 tfvars 与 git
状态访问审计事后追查谁读了状态

一句话:把 state 当作「密钥库」来对待——它确实就是,只是大多数人忘了这一点。

7. 迁移、备份与故障恢复

一句话总结: 后端迁移用 terraform init -migrate-state,日常靠版本化与定期备份,状态损坏时用备份恢复并用 import 补齐缺口。

# 从本地迁移到 S3
terraform init -migrate-state
# 会提示:Do you want to copy existing state to the new backend?

# 从一个 S3 路径迁到另一个
terraform init -migrate-state -force-copy
# 开启存储桶版本化,保留历史状态
aws s3api put-bucket-versioning \
  --bucket company-terraform-state \
  --versioning-configuration Status=Enabled

# 列出某状态文件的历史版本
aws s3api list-object-versions \
  --bucket company-terraform-state \
  --prefix prod/network/terraform.tfstate \
  --query 'Versions[].{VersionId:VersionId,LastModified:LastModified}'
# 定期备份到独立位置
terraform state pull > backup/network-$(date +%Y%m%d).tfstate

# 恢复:先备份现状,再推送历史版本
terraform state push backup/network-20261001.tfstate
场景恢复手段前置条件
状态被误改state push 历史版本有备份或版本化
状态丢失从备份恢复 + import 补齐备份 + 资源清单
后端迁移失败保留原后端配置回退迁移前备份
锁残留force-unlock确认无运行中任务

一句话:备份不是「以防万一」,而是「一定会用到」——状态损坏只是时间问题,区别在于你有没有备份。

8. 总结

远程状态与协作是把 Terraform 用于团队的门槛,也是收益最大的基础设施投资:

环节关键做法常见坑
后端选型S3+DynamoDB 或 Terraform Cloud用本地状态做团队项目
状态锁保留锁 + 谨慎 force-unlock见锁就强解导致状态损坏
工作区workspace 隔离环境把 CLI workspace 当隔离边界
变量集共享凭证集中管理每个工作区重复配置
状态拆分按变更频率与爆炸半径单一巨型状态
跨栈引用优先 Provider 数据源过度依赖 remote_state
敏感信息SSE-KMS + 动态密钥源state 明文密钥进 git
备份恢复版本化 + 定期 state pull出事时无备份可用

一句话收尾:远程状态是团队协作的地基——它决定了「谁能改、何时能改、改了能不能查」。当后端、锁、工作区、拆分与加密各就各位,Terraform 才真正从「我一个人的脚本」变成「整个团队的基础设施控制面」。

延伸阅读

继续阅读

探索更多技术文章

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

全部文章 返回首页

「terraform」更多文章

  1. Helm Provider 与应用发布:值注入与回滚
  2. 模块注册表与分发:版本、文档与测试
  3. DNS 与证书编排:托管区域与自动验证