4. 远程后端选型实战

远程后端解决的是三个问题:状态文件放在哪里、谁有权读写、多人同时操作时如何避免互相覆盖。不同后端在这三点上的实现差异很大,选型时不能只看“支持远程存储”,还要看锁机制是否可靠、认证是否与团队现有身份体系打通、以及状态版本与审计能力是否满足要求。本节以 S3+DynamoDB、Azure Storage、GCS、Terraform Cloud 四类主流后端为例,说明它们的配置方式、认证路径与锁机制差异。

一、S3 + DynamoDB:最经典的组合后端

S3 负责存放 terraform.tfstate,DynamoDB 负责提供状态锁。S3 本身具备版本控制与对象锁能力,但 Terraform 的并发保护依赖 DynamoDB 表中的条件写入:执行 terraform apply 时,Terraform 会尝试向锁表写入一条以状态文件路径为键的记录,写入成功才继续,失败则提示状态已被锁定。

terraform {
  backend "s3" {
    bucket         = "tfstate-prod-apne1"
    key            = "network/terraform.tfstate"
    region         = "ap-northeast-1"
    dynamodb_table = "tfstate-lock"
    encrypt        = true
    kms_key_id     = "alias/tfstate-key"
  }
}

锁表只需一个主键为 LockID(字符串)的 DynamoDB 表,容量模式选按需即可。认证方面,本地开发通常使用 AWS CLI 配置的 profile 或 SSO 临时凭证;CI/CD 中更推荐 OIDC 联合身份,让流水线直接换取短期凭证,避免在仓库中存放长期 Access Key。需要注意:encrypt = true 只启用 SSE,若要求使用自有 KMS 密钥,应显式指定 kms_key_id。另外,S3 后端默认不会自动开启版本控制,建议在创建桶时即启用,以便状态被误写后回滚。

S3 后端还支持 workspace_key_prefix 区分多工作区状态路径;若团队使用 Terraform Workspace 隔离环境,应统一命名规范,避免不同环境状态互相覆盖。

二、Azure Storage:Blob 容器 + 原生租约锁

Azure 后端把状态存放在 Storage Account 的 Blob 容器中,锁机制直接使用 Blob 租约(Lease)。执行计划或应用时,Terraform 会获取该 Blob 的租约,租约未释放前其他进程无法写入。相比 DynamoDB 需要额外维护一张锁表,Azure 的锁是存储服务原生能力,配置更简洁。

terraform {
  backend "azurerm" {
    resource_group_name  = "rg-tfstate"
    storage_account_name = "sttfstateprod"
    container_name       = "tfstate"
    key                  = "network.terraform.tfstate"
    use_azuread_auth     = true
  }
}

认证上,use_azuread_auth = true 表示使用 Entra ID(原 Azure AD)身份而非存储账户密钥,配合 az login 或服务主体即可。CI 中推荐使用联邦身份凭证(Workload Identity Federation),避免存储账户密钥泄露。若必须使用 Access Key,应将其放入密钥管理服务并定期轮换。Azure 后端同样建议开启 Blob 版本控制与软删除,防止状态被覆盖后无法恢复。

三、GCS:对象版本 + 一致性锁

Google Cloud Storage 后端将状态写入指定 Bucket 的对象中,锁通过对象元数据与 GCS 的强一致性实现。Terraform 在操作期间会写入锁信息,其他进程检测到锁存在即拒绝执行。GCS 后端配置项较少,核心是 bucket 与 prefix:

terraform {
  backend "gcs" {
    bucket = "tfstate-prod-apne1"
    prefix = "network"
  }
}

认证通常依赖 gcloud auth application-default login 生成的 ADC 凭证,CI 中可使用服务账号密钥或 Workload Identity Federation。GCS 的对象版本控制(Object Versioning)是状态回滚的关键,建议在 Bucket 上开启并配置生命周期规则,避免历史版本无限增长。与 S3 类似,GCS 后端也支持通过 prefix 区分不同环境或模块的状态路径。

四、Terraform Cloud:托管状态与内置锁

Terraform Cloud(或 Terraform Enterprise)把状态、锁、运行历史、变量管理、审批流整合在一个平台内。使用 remote 后端时,本地不再直接读写状态文件,而是通过 API 与 TFC 交互:

terraform {
  backend "remote" {
    hostname     = "app.terraform.io"
    organization = "acme-infra"

    workspaces {
      name = "network-prod"
    }
  }
}

认证使用 terraform login 生成的 API Token,CI 中通过 TF_TOKEN_app_terraform_io 环境变量注入。锁由 TFC 在运行队列层面保证:同一 Workspace 同时只允许一个 Run 处于 apply 阶段,天然避免并发写状态。TFC 还提供状态版本历史、Run 日志、变量集与 Sentinel/OPA 策略,适合希望减少自建后端运维成本的团队。代价是状态与运行数据托管在第三方平台,需评估合规与网络出口要求。

四类远程后端:状态存储 · 认证 · 锁机制 后端类型 状态存储位置 认证方式 锁机制 S3 + DynamoDB AWS 经典组合 S3 对象 key 区分模块/环境 IAM / SSO / OIDC CI 用短期凭证 DynamoDB 条件写 LockID 主键 Azure Storage azurerm 后端 Blob 容器 key 指定状态文件 Entra ID / 联邦身份 use_azuread_auth Blob 租约 存储原生锁 GCS gcs 后端 Bucket 对象 prefix 划分路径 ADC / 服务账号 Workload Identity 对象元数据锁 强一致性保障 Terraform Cloud remote 后端 托管状态 Workspace 隔离 API Token TF_TOKEN_* 注入 运行队列串行 同 Workspace 单 Run

五、选型判断与常见坑

如果团队已经在 AWS 上运行,S3+DynamoDB 是成熟度最高、资料最全的选择;若希望减少一张锁表的运维,Azure Storage 的原生租约更省心;GCS 适合以 GCP 为主、且已统一使用 ADC 的团队;Terraform Cloud 则适合希望把状态、锁、审批、策略集中托管、减少自建后端维护成本的场景。选型时至少确认四点:状态文件是否加密、是否开启版本控制、锁是否在异常中断后能自动释放、认证是否支持短期凭证。

常见问题中,最典型的是“锁残留”:某次 apply 被强制中断后,DynamoDB 中的锁记录未删除,后续操作报 Error acquiring the state lock。此时应先确认没有其他进程正在运行,再用 terraform force-unlock <LOCK_ID> 释放,而不是直接删除锁表记录。另一个坑是状态路径冲突:多个模块共用同一个 key 或 prefix,导致状态互相覆盖。建议按“环境/模块/组件”三层命名,例如 prod/network/vpc.tfstate,并在后端配置中固定下来。

无论选择哪种后端,都不要把状态文件提交到 Git。状态中可能包含数据库密码、私钥、Token 等敏感值,即使使用加密后端,也应通过 IAM 最小权限控制访问,并开启访问日志审计。

最后,后端配置一旦确定,迁移成本较高。切换后端时使用 terraform init -migrate-state,Terraform 会把本地或旧后端的状态复制到新后端;迁移前务必备份当前状态,并在低峰期执行,确认新后端可正常读取后再清理旧存储。