用 Terraform 给 AI Agent 上云(二):Provider、认证与 OSS 上的远程 State
钉死 alicloud provider 版本,在 AK/SK、AssumeRole、ECS RAM role 三种认证方式之间正确选择,把 tfstate 放到 OSS 并用 Tablestore 加锁,外加让 dev/staging/prod 不互相踩脚的 workspace 模式。再加上初学者第一天必踩的十几个坑。
这一篇你不再是读,是开始动手。读完之后你会有:
alicloudTerraform provider 装好且版本钉死- 认证接好——用对的方式,不是方便的方式
- 远程 state 放在 OSS,用 Tablestore 加锁
- 三个 workspace(
dev、staging、prod),共用 backend、隔离 state - 一个能跑通的
terraform plan(即使配置是空的)
本篇还不会建出任何 Agent 资源。我们打的是后续每一篇都会假设的地基。
第 0 步:装 Terraform
不展开——官方《Install Terraform》文档覆盖了所有 OS。macOS 上:
| |
钉到一个近期稳定版。阿里云文档以 >= 0.12 为基线测试,但新项目应该用 >= 1.9。新版本有真实的人体工程学改进(for_each、optional()、改进过的 moved block)。
第 1 步:钉死 provider 版本
建一个项目目录、一个 versions.tf:
| |
~> 1.230 约束允许 1.230.0 到 1.230.x,挡住 1.231.0。这是合理默认。一旦你把 terraform init 自动生成的 .terraform.lock.hcl 也提交到 git,连 provider 的精确版本和 checksum 都被锁了。同事后面跑 terraform init 拿到的 provider 字节级一致。
早钉版本是便宜的保险。alicloud provider 在 minor 版本之间发布过 break change(最近一次大的是 1.220 附近的 OSS bucket schema 重构)。你早晚要升级——故意升、在 PR 里、看 plan diff 升,而不是某个同事电脑上意外升。
第 2 步:认证——三种选择按优劣排序
provider 需要阿里云凭证。真实选择有三种,按职业认可度递增排序:

方案 A:静态 AK/SK(仅限个人电脑)
| |
provider 自动发现这些环境变量。绝对不要把 key 写进 .tf 文件。state file 不存 secret,但 provider {} block 存,而那个 block 是要提交到 git 的。
如果 AK/SK 是只能��� Terraform 管的资源的子账号,单人项目里这是可以接受的。任何共享场景,跳到方案 B。
方案 B:AssumeRole(CI runner)
CI runner 不该带长期有效的 AK。给 CI runner 一个只有一种权限的 AK——对目标 role 的 sts:AssumeRole——让 Terraform 在 apply 时去 assume:
| |
真正的写权限在 role 上;AK 只有 assume 的权利。STS session 短期有效(默认一小时),ActionTrail 全审计,trust policy 一摘就立刻失效。GitLab CI、GitHub Actions、Jenkins runner 都该用这个模式。
方案 C:ECS RAM Role(堡垒机 / IaC 服务的 runner)
如果 terraform apply 跑在阿里云 ECS 实例上——比如团队的运维堡垒机,或阿里云托管的 IaC 服务 runner——给实例挂一个 RAM role,provider 自动从实例元数据拿凭证:
| |
任何配置、任何环境变量、任何文件里都没有 secret。轮转自动。这是金标准。
实操提示: 不管选哪种,都要显式设
ALICLOUD_REGION(或者provider { region = ... })。不设的话 provider 不取默认——你会在terraform plan时拿到一个让人莫名其妙的 “Region must be specified” 错误。这事我栽过不止一次。
第 3 步:State——为什么本地 tfstate 是个坑
跑 terraform apply 时,默认 Terraform 会把 terraform.tfstate 写在当前目录。这个文件是你基础设施存在与否的真相源。三件事会出问题:
- 丢失。 删掉目录,Terraform 就以为什么都不存在。下次
apply试图重建一切(或者因为重复而失败)。 - 冲突。 两个工程师同时跑
apply能把 state 文件搞坏。 - 明文密钥。 某些 resource 属性(数据库密码、密钥材料)会落到 tfstate 里。放在笔记本上不好,提交到 git 更糟——而且真有人这么干。
修法是 远程 state + state 锁。阿里云上典型的搭配是 OSS + Tablestore:

OSS 持有真正的 terraform.tfstate 文件(开版本控制——出问题时一条 CLI 就能恢复)。Tablestore 持有一个很小的"锁"行,Terraform 在每次 apply 之前写、apply 之后删。如果第二个 apply 在第一个还持锁的时候启动,它会等或失败——绝不会同时跑两个。
第 4 步:bootstrap backend(先有鸡还是先有蛋)
承载 backend 的 OSS bucket 和 Tablestore……得先于 backend 存在。诚实的做法是用一个单独的 bootstrap/ 目录、用 本地 state file 把它们建出来,然后再也不动它。
| |
进 bootstrap/,terraform init && terraform apply。约 30 秒。然后把 local tfstate 归档到某处(我放 1Password 里做最后一道备份),从此不再从这个目录跑 Terraform。
第 5 步:配置 backend
回到真实项目,加上:
| |
prefix 让你在一个 bucket 里塞多个 state 文件——后面把基础设施拆成多个 Terraform 项目时很方便。encrypt = true 开 OSS 端加密(bucket 级别我们已经开了 KMS 规则,但纵深防御从不嫌多)。
跑:
| |
如果挂 “AccessDenied”,是你的认证 role 没有 bucket 上的 oss:GetObject/PutObject。最小 role policy:
| |
附给你认证用的 role。不要直接给 oss:*——backend role 也要最小权限,因为它在你的 CI runner 里。
第 6 步:用 workspace 隔离环境
workspace 就是同一个 backend 里的另一份 state 文件。默认 workspace——很贴心地——叫 default。把你需要的其他几个建出来:
| |
HCL 里 terraform.workspace 解析成当前 workspace 名,你可以用它参数化资源规模:
| |
干净的替代是每个环境一份 *.tfvars:
| |
我把 tfvars 用于"显然不同的配置"(CIDR、region、实例数),把 terraform.workspace 只用于条件式 is_prod 开关。混用没问题——一个项目里挑一种作为主机制就行。
第 7 步:五条命令的循环
日常 Terraform 就五条命令:

| |
三条规则:
- apply 之前一定要看 plan 输出。 它会告诉你接下来要发生什么——哪些资源会创建(
+)、原地更新(~)、强制重建(-/+)、销毁(-)。原地→重建那种箭头尤其会偷偷带停机。 - CI 里把 plan 和 apply 拆成两步。 先
terraform plan -out=tfplan,把 plan 输出贴到 PR,等人工 approve,merge 时terraform apply tfplan。绝不要 push 自动 apply。 - 别跳过
state命令。terraform state list列出所有当前管理的资源;terraform state show <addr>看单个资源的全部属性。debug 奇怪 drift 时这就是起点。
第一天必踩的八个坑
按它们坑到我的顺序:
terraform init时Error: Failed to query available provider packages。 GFW。设HTTPS_PROXY,或者按官方《Configure an acceleration solution for Terraform initialization》文档——镜像在https://mirrors.aliyun.com/terraform/。Error: state lock。 上一次 apply Ctrl-C 了,锁残留。terraform force-unlock <LOCK_ID>(错误信息里有 ID)。先确认确实没在跑别的 apply。Error: Region must be specified。 设ALICLOUD_REGION环境变量或providerblock 里的region。- backend init 时
AccessDenied。 OSS bucket prefix 上的 RAM 权限不对。再核第 5 步的 policy。 - Tablestore 上
InvalidParameter.NotFound。 bootstrap 错了 region。Tablestore endpoint 和 OSS bucket 必须在同一 region。 Provider produced inconsistent result after apply。 几乎总是 provider 升级后.terraform/缓存没清。rm -rf .terraform .terraform.lock.hcl && terraform init。Resource already exists。 你在控制台手建过这个资源。要么删掉,要么 import:terraform import alicloud_vpc.main vpc-uf6xxxxxx。- 刚 apply 完一个资源,立刻
terraform plan又出 diff。 “Drift”。要么有人去控制台动了,要么 provider 的读逻辑和创建逻辑不一致。看 diff 里具体哪个属性;通常修法是把那个属性显式写出来,让 Terraform 不再"注意到"差异。
实操提示: 每次
apply之后立刻再跑一次terraform plan,哪怕你以为没改。plan 应该是空的。如果不是,你已经有 drift——drift 留得越久越难和。
下一篇
第三篇造第一块真实基础设施:可复用的 vpc-baseline module。VPC、跨三个 zone 的三个 vSwitch、NAT 网关、EIP、安全组基线、KMS key。后面每一篇都会用它,它也是我所有 Agent stack 里被复制粘贴最多的 module。
如果这篇你跑通了,你现在应该能 terraform init、terraform workspace select dev、terraform plan 看到 “No changes."。这就是地基。后面所有东西都摞在它上面。