
用 Terraform 管基础设施,状态文件(tfstate)默认躺在执行机的本地盘上。单机跑还好,CI 并发一跑、或者换台机器 apply,状态对不上就开始 drift;为了防止并发破坏状态,很多人还得额外搭一套 DynamoDB 当锁。Terraform 的 S3 backend 能把状态文件和状态锁都以对象形式存进对象存储,RustFS 100% 兼容 S3,直接就能当这个后端,连 DynamoDB 那套都省了。本文用官方验证过的组合把链路跑通:Terraform 1.12.2 对 rustfs/rustfs-x86-musl:v2.3.1。
S3 backend 把状态和锁都交给对象存储
Terraform 的 backend "s3" 会把状态文件写在桶内的 key 前缀之下。开 use_lockfile = true 后,运行期间会写一个 <key>.tflock 对象当锁,这是 S3 原生的锁机制,替代了过去基于 DynamoDB 的锁实现。对 RustFS 来说,状态对象就是普通对象,可以上版本化、可以跨执行机共享,CI 里谁先抢到锁谁写状态。HashiCorp 的工具链对 S3 后端的支持已经很成熟,本文只用它的状态存储能力,不牵扯任何云厂商账号。
写 backend 配置,六个开关缺一不可
关键在 backend 块里的这几个开关。对非 AWS 端点来说,下面六个必须同时设置,缺任何一个,Terraform 就会去连 AWS 默认端点而不是 RustFS:
skip_credentials_validation = true:Terraform 默认会调 AWS 的GetCallerIdentity去验账号合法性,RustFS 没有这个接口,不跳过就会卡在认证阶段。skip_region_validation = true:示例里的us-east-1只是占位,RustFS 不校验 region,但 Terraform 默认会校验,必须显式关掉。skip_metadata_api_check = true:Terraform 默认去169.254.169.254抓 EC2 实例元数据推导配置,非 AWS 环境拿不到,必须跳过。skip_requesting_account_id = true:Terraform 会试图解析账号 ID 写进状态,RustFS 不返回这个,跳过才能让初始化走完。force_path_style = true:对象靠端点/桶名/键的路径定位,而不是把桶名塞进域名。IP 端点上虚拟主机风格会直接失败,RustFS 全程走路径风格。insecure = true:示例在纯 HTTP 上对接,关掉 TLS 校验。公网或跨信任域的链路要换成 HTTPS 并校验证书,不能留insecure。
这六个开关合在一起,才把 backend 从 AWS 默认行为彻底掰到 RustFS 这一侧。少一个,Terraform 就会悄悄回头去连 AWS,表现出来的就是各种 NoSuchBucket 或签名错。
terraform {
backend "s3" {
bucket = "my-bucket"
key = "rustfs-demo/terraform.tfstate"
region = "us-east-1"
endpoint = "http://localhost:9000"
access_key = "<your-access-key>"
secret_key = "<your-secret-key>"
skip_credentials_validation = true
skip_region_validation = true
skip_metadata_api_check = true
skip_requesting_account_id = true
force_path_style = true
insecure = true
use_lockfile = true
}
}
provider "local" {}
resource "local_file" "demo" {
content = "provisioned with terraform, state stored in RustFS"
filename = "${path.module}/demo.txt"
}
endpoint 指向 RustFS 的 9000 端口;force_path_style 加 insecure 表示在纯 HTTP 上用路径风格寻址,正是 RustFS 期望的。文档要求状态对象落地的桶必须已存在,先把它建好,用 rc mb rustfs/my-bucket(rc 是 RustFS 自带的命令行工具)。
init、apply,状态写进桶
terraform init -input=false
terraform apply -auto-approve -input=false
apply 跑完后,状态已经落在 RustFS 桶里,它是一个普通的 JSON 对象,里面记着资源映射、输出值和依赖关系。用 rc CLI 列一下:
docker run --rm --network <rustfs-network> -v "$PWD:/m" \
--entrypoint /bin/sh rustfs/rc:latest \
-c 'rc alias set rustfs http://rustfs:9000 <your-access-key> <your-secret-key> >/dev/null && rc ls rustfs/my-bucket/rustfs-demo --recursive'
会看到 rustfs-demo/terraform.tfstate 这个状态对象。再跑一次 terraform plan,如果输出 No changes,说明状态是从 RustFS 读回的,而不是来自任何本地文件。这就是把状态外置的意义:换机器、换 CI 节点,只要指向同一个桶,状态都对得上。因为状态是普通对象,还可以给桶开版本化,每次 apply 都会留下一个历史版本,误删或误改状态能从旧版本捞回来,不用另外搭备份流程。

已经有本地状态,怎么迁过来
如果之前状态在本地 terraform.tfstate,改完 backend 配置后跑 terraform init -migrate-state,Terraform 会把本地状态推到 RustFS 桶里,并就地删掉本地文件。迁移是一次性的,之后所有 init、apply、plan 都从桶里读写。状态里往往含敏感值,比如资源里的密钥和连接串,RustFS 可以开服务端加密(SSE)给桶兜底,但状态本身还是建议配合 Terraform 的 sensitive 标记和最小权限的访问密钥一起用。
三个最常见的坑
接 RustFS 这类非 AWS 端点时,报错基本都集中在三点。一是 dial tcp: lookup rustfs ... server misbehaving,根因是 endpoint 必须能被跑 Terraform 的机器解析到,Compose 网络内用 http://rustfs:9000,宿主机上用 http://localhost:9000,别混。二是 Bucket cannot have ACLs set 或签名错误,几乎都是那六个 skip_*、force_path_style、insecure 开关没配齐,Terraform 又默默去连 AWS 了。三是进程中途崩溃留下 .tflock 对象,下次 init 或 apply 会报锁被占用,先用 terraform force-unlock <lock-id> 释放,急的话也可以直接用 rc rm 删掉桶里那个 <key>.tflock 对象,但删之前要确认确实没有别的进程在跑。

多环境共用一个桶:workspace 的前缀隔离
Terraform 的 workspace 机制会在状态 key 前面加 env:/<workspace>/ 前缀。开 terraform workspace select staging 之后,状态落到 env:/staging/rustfs-demo/terraform.tfstate,dev 和 prod 各自一套,但底层全在同一个 RustFS 桶 my-bucket 里。这就不用给每个环境建一个桶,一个桶按前缀把多套环境隔开,备份、生命周期、跨区域复制这些桶级能力对所有环境统一生效。注意 workspace 只是 key 前缀隔离,不是权限隔离,不同环境之间仍可能互相看到对象,真正的访问收窄还是要靠 IAM 把密钥权限压到桶级或前缀级。
为什么能省掉 DynamoDB
老的 S3 backend 用 DynamoDB 表存锁,靠条件写入实现互斥,得单独建表、配 IAM、管容量。RustFS 这一侧用的是 use_lockfile 的原生对象锁:运行期间写一个 <key>.tflock 对象,Terraform 用条件写(写前检查对象是否存在)保证同一时刻只有一个进程能拿到锁,运行结束删掉对象即释放。少了一个要长期维护的数据库组件,状态、锁、历史都在同一个桶里,排障时也只盯一个地方。代价是这套锁依赖对象存储的最终一致窗口,不是强一致的分布式锁,但对绝大多数 CI 场景够用。
上线前确认边界
这套部署官方标注为本地集成测试,不是生产配置。落到生产要补的是:给 Terraform 单独建一组访问密钥并通过 RustFS 内置的 IAM(身份与访问管理)收窄到状态桶;use_lockfile 启用后锁就是桶里一个 .tflock 对象,要确认没人手动删它;另外 insecure = true 是纯 HTTP,公网或跨信任域的链路要换成 HTTPS 并校验证书。当前 RustFS 稳定版是 1.0.5(2026-11-10 发布),对接命令以官方集成指南为准(docs.rustfs.com/zh/developer/integration/devops/terraform)。把状态外置进 RustFS,状态漂移和并发撞锁这两件事一起解决了。