概述
周一的站会上,某出行平台的平台工程团队例行复盘上周事故:一周 17 个状态锁冲突、2 次生产环境被"好心"的同事手动 apply 改坏、1 次凭据明文躺在 CI 日志里。原因不是某个人操作失误,而是我们那套自建的 Terraform 后端——Git 仓库 + S3 状态桶 + DynamoDB 锁 + 一堆 hand-made CI 脚本——在团队从 5 人长到 30 人之后,已经彻底撑不住了。
Terraform 的配套托管平台 HCP Terraform(原 Terraform Cloud)我一直知道,但总觉得"不过是把状态文件托管到云端",直到这次事故周才真正认真评估它。评估完发现,托管平台的真正价值不在状态存储,而在执行模型、权限模型和策略引擎。这篇文章不是产品介绍,而是我实际迁移后的决策记录:6 个关键工程决策、一份不掺水的 TCO 账本、以及每个决策对应的坑。
你会看到:
- 自建后端在团队规模增长后到底贵在哪里(一份真实成本拆解)
- 触发我们迁移的 6 个临界点信号
- HCP Terraform vs Terraform Enterprise vs OpenTofu 自建的选型对比表
- Workspace 与变量集的组织模型,以及我们踩过的"变量集陷阱"
- 从 S3 后端迁到 HCP Terraform 的完整步骤与 5 个迁移坑
- Sentinel 策略即代码的实战示例(阻断一个"危险变更包"的完整过程)
- 平台 API 自动化:把 HCP Terraform 自身也纳入 IaC 管理
- 与 CI/CD 集成的两种模式(VCS 驱动 vs CLI 驱动)和 Run Tasks 扩展
- 一份按用户数、并发、存储估算的 TCO 对照表
先声明立场:本文不吹 HCP Terraform,也不劝你迁移。如果你的团队只有 3-5 人、基础设施规模不大,自建方案可能是更优解,我会在最后一节给出明确的"不要迁移"判断标准。这篇文章面向的是——团队规模已经在膨胀、自建方案开始频繁出事故、需要在失控前做一次理性选型的人。
自建 Terraform 后端的真实成本
先别急着看 HCP Terraform 的功能,算一笔账。我们迁移前的架构是这样的:
┌─────────────────────────────────────────────────────┐
│ 开发者本地 CLI │
│ terraform init → 连接 S3 backend → plan → apply │
└─────────────────────────┬───────────────────────────┘
│
┌─────────────────────────▼───────────────────────────┐
│ GitLab CI(应用层) │
│ pipeline: checkout → terraform plan → 人工确认 │
│ → terraform apply → 通知 │
└─────────────────────────┬───────────────────────────┘
│
┌─────────────────────────▼───────────────────────────┐
│ S3 状态桶(版本控制 + 加密) │
│ DynamoDB 锁表(LockID) │
└─────────────────────────────────────────────────────┘
这套架构看起来没毛病——它本来就是社区最佳实践的标配(我在《状态文件丢了怎么办》里详细写过 S3 + DynamoDB 的配置细节)。问题不出在架构,而出在团队规模变化后,所有"人肉环节"的成本被放大了。
五个隐形成本
成本一:状态锁冲突的协调成本。 5 人团队时,谁在 apply 在群里喊一声就行。30 人团队时,DynamoDB 锁表里的 stale lock 每周出现好几次——CI 超时后锁不释放、本地 Ctrl+C 中断后锁残留、有人忘了 TF_LOCK_TIMEOUT。每次锁卡住,都要查 aws dynamodb get-item 看是谁的锁,再决定要不要 force-unlock。一周 17 次锁冲突,每次平均 15 分钟协调,这就是 4 个多小时纯浪费。
成本二:权限失控的修复成本。 自建方案里,“谁能 apply"取决于谁有云账号的 AK/SK。而云账号的 AK/SK 在 30 人团队里几乎不可能管住——开发要用、测试要用、CI 要用、外包要用。结果是所有人都能改生产资源。两周内出了两次事故:一次有人把生产 RDS 的 deletion_protection 改没了,一次有人 apply 了还没 review 完的 PR。
成本三:审计与合规的取证成本。 S3 的状态桶开了版本控制,但"这个变更是谁在什么上下文下做的"完全无从追溯。出事之后,只能靠 Git 提交记录 + CloudTrail 日志 + CI 日志三方拼凑,每次复盘要花半天。而如果客户或监管要"变更审批留痕”,我们连一张像样的变更单都拿不出来。
成本四:凭据管理的恐慌成本。 每个 provider(AWS、阿里云、腾讯云)的密钥都要配,CI 里配一次、本地每个开发者配一次。最害怕的是密钥泄漏——而它真的发生过:某次 CI 调试时有人把 AK 明文打到了日志里,我们不得不连夜轮换全部密钥。
成本五:多环境管理的维护成本。 dev、staging、prod 三个环境,目录复制三份,变量维护三份。每个环境多了个配置项,就要改三个地方。“改了一个环境忘了另一个"导致的 plan 差异,成了家常便饭。
把五项加总:和"状态文件存哪"比起来,真正贵的是围绕它的流程、权限和协作。这才是我们重新评估托管平台的根本原因——不是状态存储,而是这套治理能力。
我们试过的"补丁式"自建优化(以及为什么失败)
在考虑迁移之前,我们其实先尝试了三年"打补丁”。这三年不是白费的——每块补丁都解决了当时最痛的问题,但也把复杂性推到了下一次爆发的临界点。按时间线回顾:
第一年:加锁表。 团队从 5 人变 10 人,本地 state 文件开始互相覆盖,于是上了 S3 + DynamoDB 锁。这解决了"覆盖"问题,但引入了新问题:锁表误报、锁残留、force-unlock 误用。
第二年:加 CI。 10 人变 15 人,“谁在 apply"开始靠吼。于是写了 GitLab CI 流水线:plan → 人工确认 → apply。这解决了"串行化"问题,但引入了新问题:CI 的认证凭据存哪?CI 里配了 AK/SK,几个月后有人把调试命令打进日志,密钥泄漏。
第三年:加权限脚本。 15 人变 30 人,业务团队开始自己管基础设施。于是我们写了一套"环境变量注入 + profile 切换"的脚本,试图让不同团队用不同账号。结果脚本本身成了维护负担,而且权限靠脚本管,就等于没管——脚本能绕,人就能绕(直接把 AK/SK 放到 ~/.aws/credentials 里绕过了所有开关)。
这三年的结论很直接:自建方案的每一层"治理”,都是我们手写的代码,而手写的治理代码,本身就是新的故障源和新的维护成本。 托管平台把这三层(锁、执行、权限)变成了产品的内置能力,我们的团队终于不用维护"治理代码"了——这正是迁移的核心价值判断。
触发迁移的 6 个临界点信号
很多团队问我"什么时候该从自建迁到托管平台",我的答案是看信号,不看时间。下面 6 个信号,命中 3 个以上就该认真评估了:
| # | 信号 | 我们命中时的状况 |
|---|---|---|
| 1 | 状态锁冲突每周超过 3 次 | 一周 17 次,半数来自 CI 残留锁 |
| 2 | 出现"谁都能 apply"的权限失控 | 31 个云账号 AK/SK 在团队里流转 |
| 3 | 需要向外部证明"变更经过审批" | 客户验收要求变更留痕,拿不出 |
| 4 | 凭据发生或疑似泄漏 | 1 次明文 AK 泄漏,全员轮换密钥 |
| 5 | 多环境配置靠"人肉同步" | dev/prod 变量不同步导致 plan 差异 |
| 6 | 团队开始出现"惧怕 apply"情绪 | 改基础设施要等"胆大的老同事"把关 |
第 6 条最值得玩味。当一个团队开始害怕执行 terraform apply,说明流程已经成了瓶颈——人肉审批、人肉执行、人肉回溯,每一步都在消耗信任。我们把"恐惧做进流程"还是"用工具消除恐惧"作为决策分水岭,最终选择了后者。
决策一:HCP Terraform / Terraform Enterprise / OpenTofu 自建,怎么选
这是所有评估的第一个岔路口。先把选项摆清楚:
- HCP Terraform(原 Terraform Cloud,2024 年 5 月更名):HashiCorp 的 SaaS 托管平台,跑在
app.terraform.io,免费层 + 付费层。 - Terraform Enterprise(TFE):同一产品的私有化部署版,跑在你自己的基础设施上,支持 air-gapped 离线安装、与内部 SSO/AD 集成。
- OpenTofu + 自建:Terraform 的社区分支(MPL-2.0 许可)+ 自己搭的状态后端和 CI 流程。2023 年 HashiCorp 将 Terraform 从 MPL 改为 BSL 后,社区 fork 出的开源替代。
三者的边界,我用一张表讲清楚:
| 维度 | HCP Terraform(SaaS) | Terraform Enterprise(私有化) | OpenTofu + 自建 |
|---|---|---|---|
| 部署形态 | 托管,零运维 | 自建集群(Replicated 部署) | 完全自建 |
| 状态存储 | 平台托管 + 加密 + 版本历史 | 平台托管,存你自己的对象存储 | S3/GCS/Azure 自己配 |
| 远程执行 | 官方支持 | 官方支持 | 自己用 CI 模拟 |
| 权限模型 | 组织/团队/Workspace 三级 RBAC | 同左,可接企业 SSO/SCIM | 靠云 IAM + Git 权限 |
| 策略即代码 | Sentinel(付费)+ OPA(可选) | Sentinel + OPA | OPA/CAS 自己集成 |
| 审计日志 | API 可查,X 天留存 | 可对接 SIEM(Splunk 等) | 自己拼 CloudTrail |
| 数据主权 | 数据在 HashiCorp 云(默认美区,可选其他区域) | 完全在你机房 | 完全在你机房 |
| 许可证成本 | 按用户付费(有免费层) | 按年订阅,价格不菲 | 软件免费,人力自担 |
| 适合场景 | 中小团队、要求快速上线 | 监管/合规要求数据不出域 | 预算敏感、有专职 IaC 团队 |
我们的选择与理由
我们最终选了 HCP Terraform 付费层,理由按优先级排序:
- 上线速度:SaaS 当天就能建组织、建 workspace、跑通第一条远程 run,TFE 私有化从采购到部署至少一个季度。
- 运维成本:TFE 虽然是私有化,但它本身是一套要打补丁、要扩容、要备份的集群(Replicated 安装、S3 后端、若干副本),等于为管基础设施再搞一套基础设施。我们平台团队只有 4 个人,扛不住。
- 数据敏感度:我们评估过自己的基础设施数据(状态文件、变量、审计日志)里没有必须留在境内的内容,SaaS 的可接受风险范围内。
但必须诚实:如果你有强合规要求(等保三级、金融监管、数据不出域),这个决策结论会完全不同,直接跳到 TFE 私有化。我在后面"什么情况下不要迁"一节会展开讲。
TFE 私有化的真实约束(选它之前先看清)
如果你的合规要求把 TFE 推上台面,有几个点最好提前知道,免得采购完才发现:
- 部署形态:TFE 用 Replicated 安装器部署为 Kubernetes 集群(也支持单机 demo 模式),生产部署至少 3 节点起步,需要对象存储(S3 兼容)/PostgreSQL 做后端依赖。它不是"装个二进制"那么简单。
- 升级节奏:TFE 有小版本更新需要人工规划,半年内你可能要安排两三次升级窗口。平台团队要有"升级自身基础设施"的排期预算。
- 成本量级:TFE 按订阅号计费,价格不透明、需联系销售。对中小团队来说,同样规模下的 TFE 总拥有成本(订阅 + 人力 + 基础设施)通常高于 SaaS——除非合规强制,否则很难在 ROI 上胜出。
- 架构拓扑建议:单 active 实例 + 备机(或高可用部署),网络要求能访问你的 VCS(GitLab/内网仓库)和云厂商 API。air-gapped(离线)部署是支持的,但补丁更新就要走离线包,升级成本更高。
我们当时接触了一家金融类客户(有确定的数据不出域要求),评估结论很清楚:他们必须选 TFE(或有同等级私有化能力的方案),SaaS 直接出局——这不是性价比问题,是合规红线。所以选型的第一步永远是"问清楚自己有没有硬合规约束",它有否决权。
为什么没选 OpenTofu 自建:不是 OpenTofu 不好——它按 MPL-2.0 开放、工具链成熟,我们后续也在用。但"OpenTofu + 自建"只是解决了"工具许可"问题,没有解决"治理"问题:锁冲突还是靠协调、权限还是靠 AK/SK、审计还是靠拼日志。我们要解决的是治理,不是许可。如果只论工具本身的自由度,OpenTofu 自建完全够用,甚至更灵活;但论治理成熟度,它需要我们投入 2-3 个人年去补齐托管平台开箱即有的能力。
决策二:Workspace 组织模型怎么设计
HCP Terraform 的核心抽象是 Workspace——一个 workspace 对应"一组 Terraform 配置 + 一个关联的远程状态 + 一组运行环境"。理解这个抽象,就能明白托管平台和自建的本质区别:自建方案里"状态和执行的耦合"是你自己用脚本维护的,托管平台把它变成了平台内置的一等公民。
Workspace 的三层模型
我们最终采用了三层组织模型:
Organization(组织)
└── Project: platform (平台团队)
└── Workspace: prod-network
└── Workspace: prod-database
└── Workspace: prod-compute
└── Workspace: staging-network
└── Project: app-team-a (业务团队 A,仅授予只读)
└── Workspace: app-a-infra
为什么按"Project + Workspace"分而不是全部平铺:
- 权限边界清晰:Project 是权限继承的基本单位。业务团队 A 的成员只对他们自己的 Project 有写权限,对 platform Project 只有只读(甚至不可见)。
- 状态隔离天然:每个 workspace 一个独立 state,天然隔离爆炸半径(一个 workspace 的
destroy不会波及其他)。 - 审批流可分级:生产相关的 workspace 挂强制审批(apply 需要指定角色批准),非生产 workspace 可以全自动。
对应地强调一个和自建方案的本质差异:自建方案里"环境隔离"靠的是目录结构 + 人工自觉;托管平台里"环境隔离"靠的是 workspace 权限 + 审批策略,代码层面都不需要额外约定。
变量集(Variable Sets):省事,但别掉进陷阱
变量集允许把一组变量(比如 AWS_ACCESS_KEY_ID、AWS_DEFAULT_REGION)定义一次,应用到多个 workspace。听起来是纯福利,但有两个陷阱:
陷阱一:敏感变量必须勾选 “Sensitive”。没勾选的变量会以明文显示在 run 日志和 API 响应里。我们第一次配置时,把云厂商密钥当普通变量配了,terraform plan 日志里能看到明文——和自建方案里 AK 打到 CI 日志是同一个错误,只是换了地方犯。
陷阱二:变量集的作用域是"组织级"还是"Project 级"要想清楚。组织级变量集会泄露到所有 workspace,包括你不希望它看到这些凭据的团队。建议:通用变量(region、tags)放组织级,凭据类变量放到 Project 级变量集,且 workspace 按需勾选。我们后来把凭据全部改成了 Project 级 + Sensitive,才算真正干净。
权限模型的四个层级(别一上来就发 Owner)
HCP Terraform 的权限体系分组织级和 workspace/team 级。我们落地时的最小权限原则:
| 角色 | 权限 | 我们给谁 |
|---|---|---|
| Owner | 组织全部控制权(含删除、账单) | 仅平台团队负责人 |
| Manage Workspaces | 可建/删/改 workspace、配置变量、触发 run | 平台团队全员 |
| Write(workspace 级) | 可触发 plan/apply、管理 state | 各业务团队的 IaC 负责人 |
| Read / Plan | 只读、可发起 plan | 普通开发者、审计人员 |
一个关键配置:平台默认所有 workspace 都是 “managed remotely”(远程执行),本地 terraform apply 默认被禁止(可以通过设置放开,但强烈不建议)。这一步直接消灭了"本地 apply 绕过审批"的路径——这也是我们迁移的初衷之一:执行必须走平台,平台负责记录和审批。
SSO 与团队管理:权限不是配置出来的,是长出来的
权限模型除了 RBAC 静态角色,还牵扯两个实际问题——身份来源和团队生命周期。
自建方案里,团队成员入职/离职,要手动配云账号、发 AK/SK、回收权限,一个离职同事的密钥可能 3 个月后才被发现还在用。托管平台的正确姿势是接 SSO(SAML/OIDC):
- 接上企业 SSO 后,人走权限自动失效——身份生命周期跟随企业目录(LDAP/飞书/钉钉),不再依赖运维手动回收。
- 团队(Team)绑定 SSO 映射,新员工入职自动落入对应 Team,立刻获得按角色的默认权限,不需要"等运维审批授权"。
我们踩到的一个细节:SSO 映射粒度要和 Workspace 权限分层对齐。很多团队图省事,把所有人映射到同一个 Team(默认只有 Read 权限),然后靠"手动逐个授予 workspace"补权限——这等于用平台实现了和自建方案一样的"人肉权限"。正确做法是按角色建 Team(platform-admin / infra-write / app-read),SSO 按部门映射到角色 Team,Workspace 的权限挂 Team 而不是挂个人。我们当初用了两星期才把相关 Team 结构梳理干净,但之后半年再没为权限的事做过一次手动操作。
审计日志:从"拼日志"到"查平台"
自建方案的审计取证,是把 Git 提交、CI 日志、CloudTrail 三方拼起来。迁移后,平台把 run 生命周期完整记录:谁、在什么时间、基于哪个 commit、触发了什么 run、plan 结果是什么、谁批准的 apply、最终状态如何。
审计日志有几个实用点:
- run 详情页可以当"变更单"用:事件响应时,直接打开对应 workspace 的 run 历史,按时间线看变更序列,不用再"猜"是谁做的。
- API 拉取审计日志:HCP Terraform 的 audit events 可以通过 API 批量导出(组织级 audit trails),我们用它做月度合规报告,把"我们的基础设施变更记录"作为附件交给客户,比自建方案里手动整理的表格可信得多。
- 审计留存与地区:SaaS 的审计日志留存天数有上限(具体以官方为准),如果是长期保留需求,需要定期通过 API 导出归档。我们当时评估过:金融客户场景下,审计日志必须卖给 SIEM 或归档到自己的对象存储。
决策三:状态迁移怎么做(含 5 个坑)
从 S3 后端迁到 HCP Terraform,核心是让 Terraform 把"已存在的资源"认成"新平台托管的资源"。官方路径是:在平台创建 workspace → 配置为"与本地后端对应" → 用 migrate-state 迁移。
迁移前的准备清单
- 在 HCP Terraform 创建 Organization、Project、Workspace,绑定对应的 Git 仓库(VCS 模式)或留空(CLI 模式)。
- 在 Workspace 的 General Settings 里,把 Execution Mode 设为 Local(本地执行)——这是迁移期间的过渡配置,等 state 迁移完成后改回 Remote。
- 准备本地 Terraform(建议和线上版本一致,迁移时不要升级版本)。
迁移命令
先在本地配置里临时指向旧的 S3 后端,然后:
# 1. 备份旧 state(必做,别跳过)
terraform state pull > backup-$(date +%Y%m%d).tfstate
# 2. 用新后端初始化:这里的 backend 指向 HCP Terraform
# 但 Execution Mode 是 Local,所以实际的执行仍在本机
terraform init -reconfigure
# 3. 让 Terraform 识别配置变化,生成迁移计划
terraform plan
# 4. 执行 apply,本地执行模式会把 state 推到平台(前提:workspace 配了对应的 backend)
terraform apply
在 Remote 模式下,标准做法是:
# 关键一步:把本地与 workspace 绑定
# 在 main.tf 的 cloud 块(比 backend 块更推荐)里声明:
cloud {
organization = "your-org"
workspaces {
name = "prod-network"
}
}
然后执行 terraform init,Terraform 会在本地没有 state 的情况下询问你是否要从远程状态拉取。但我们的情况是本地有 state,所以正确姿势是:
terraform init # 绑定 cloud,提示是否迁移状态
# 选择 "yes",Terraform 会把本地 state 上传到 platform 对应 workspace
terraform plan # 验证 plan 无差异
建议小步迁移:先迁一个影响面最小的 workspace(如一个测试网络的 state)验证整个链路,再批量迁移剩余的。全量迁移放周末 + 老后端保留一个月,出事随时回滚。
迁移中的 5 个坑(全踩过)
坑 1:provider 版本不一致导致 plan 差异。 本地某 workspace 的 provider 锁文件版本是旧的,迁移到平台后平台用最新 provider 重新 plan,产生了计划外变更。解法:迁移前在本地先跑一次 terraform init -upgrade && terraform plan,确保 provider 版本与平台侧一致,把 plan 差异消到零。
坑 2:敏感变量注入路径变化。 在自建方案里,provider 凭据来自环境变量或 profile;在 HCP Terraform 里,凭据来自 workspace/变量集。迁移后第一次 plan 报"缺少认证"是最常见的——不是 state 问题,是变量没配。解法:迁移前先在平台把变量集配置好,再动 state。
坑 3:Sensitive 变量在 plan 输出里的"假删除"。 平台对 Sensitive 变量在处理时用掩码(如 (sensitive value))替代,在 plan 输出里看起来像是被删了,容易误判。实际上只是显示掩码,apply 时会正常传入。别被吓到。
坑 4:backends 块 vs cloud 块混用。 一个工作区同时写了 backend "s3" 和 cloud 块会报错。我们有一批老配置还挂着 backend "s3",迁移时忘了删,terraform init 直接失败。解法:迁移后彻底删除 backend 块,只保留 cloud 块(或 cloud { hostname } 指向自定义实例)。
坑 5:terraform state mv 和 rm 在 Remote 模式下有网络超时风险。 大 state 文件在平台上的 state mv 操作偶尔超时,重试可能产生部分应用。解法:超时后先 terraform state pull 拉下来检查实际状态,再决定继续还是回滚,不要盲目重试。
迁移后的第一周:把"老习惯"关掉
状态迁移只是第一步。真正让平台"生效",需要在第一周完成四个"关停动作",否则你会发现自己同时维护两套流程:
- 关停本地 S3 后端的写权限:把自建状态桶改成只读(或直接从代码里删掉 backend 块),防止有人绕过平台直接改状态。我们是在三个 workspace 迁完后的当天就改了桶策略,因为第二天真的有人本地 apply 差点写旧桶。
- 统一 CLI 版本:平台侧执行引擎跑的是特定 Terraform 版本。团队本地版本必须和平台"兼容区间"一致(
required_version约束),否则 plan 和平台执行结果会漂移。我们直接采用.terraform-version文件 +tfenv统一。 - 清理 CI 里残留的 apply 步骤:所有 workspace 切换到平台负责执行后,CI 里那段
terraform apply必须删掉,否则出现"CI 执行 + 平台执行"双控制器。我们改成了 CI 只做plan+ 将结果回调,apply 全部走平台。 - 教育团队"改代码走平台":写一段简短的团队约定(基础设施变更 = PR + plan 评论 + 平台 apply),配合前 3 个技术动作,第 4 个动作其实是最容易忽略但最关键的——工具能拦住的是一部分,剩下的要靠流程习惯。
这四个动作做完,平台才真正"生效"。很多人迁移后还保留着"本地跑通再上线"的习惯,平台形同虚设——平台的价值只有在"全员只走一条路"时才成立。
要不要用 OpenTofu 的迁移工具?
如果你是从 Terraform 迁到 OpenTofu 生态(而不是迁 HCP),可以借助 tofu(OpenTofu CLI)直接读取原 Terraform state 并迁移到新的 backend——OpenTofu 对 Terraform state 格式完全兼容。这部分是另一个话题,我在《Pulumi 迁移 Terraform 的决策记录》里写过类似的"跨 IaC 工具迁移方法论",要点是一致的:先冻结变更、再备份状态、小步迁移、保留回滚路径。
决策四:策略即代码——Sentinel 让"不让做"变成平台能力
如果说 Workspace 和权限解决了"谁能做",那 Sentinel 策略即代码 解决的是"能做什么"——这是自建方案里最难补齐的一环,也是我们迁移后获益最大的一环。
Sentinel 是什么
Sentinel 是 HashiCorp 的策略即代码语言(HCL 风格,但引入了规则、集合、main 函数等概念),运行在 policy check 阶段——在 terraform plan 之后、terraform apply 之前,平台会对每个 run 执行绑定到该 workspace 的策略集,返回 pass / fail(硬阻断)/ advisory(警告) 三种结果。
打个比方:Terraform 是"你想做什么都行"的执行器,Sentinel 是"我审批你该不该做"的守门人。守门人不在你的 CI 脚本里,而在平台的每个 run 里,这意味着——即使是绕过 CI 的 CLI 驱动的 run,策略依然生效。这点自建方案很难做到(CI 的 pipeline 脚本可以写 gate,但本地 apply 就绕过了)。
一个实战示例:阻断高危变更包
我们第一个生产可用的策略,是禁止在生产环境使用未加密的存储桶:
# policy: enforce-encryption.sentinel
import "tfplan/v2" as tfplan
# 只在生产 workspace 生效(通过 workspace 名称识别)
workspace_name = tfplan.terraform_workspace_name
is_production = workspace_name contains "prod"
# 收集全部将创建的 S3 存储桶
buckets = tfplan.resource_changes[
tfplan.resource_changes[_].address matches "aws_s3_bucket.*"
and tfplan.resource_changes[_].change.actions contains "create"
]
# 必须开启 encryption
main = rule {
not is_production or all buckets as b {
b.change.after.encryption.enabled is true
}
}
过程还原:某位同事在 prod 环境新建了一个 S3 桶,terraform plan 之后平台弹出一条 policy check:FAIL,run 直接停在 apply 之前,屏上红字写着"该资源未启用加密,已被策略拦截"。关联的 PR 里尚未 merge,风险被挡在了变更之外。
注意:上面是简化示例,真实 Sentinel 策略要处理 destroy 操作(change.actions 含 delete 时的 after 访问会报错),生产中需要加 only create 过滤。写策略时把 plan 数据的每个动作分支都想清楚,别把平台策略写成误报机器。
Sentinel vs OPA,以及我们的取舍
| 维度 | Sentinel(HashiCorp 原生) | OPA(Open Policy Agent) |
|---|---|---|
| 语言 | 类 HCL,Terraform 团队上手快 | Rego,学习曲线陡 |
| 集成深度 | 平台原生,plan 后自动执行 | 需通过 Run Tasks 或第三方集成 |
| 状态感知 | 直接读 tfplan、tfstate、tfconfig | 需要把 plan JSON 喂给它 |
| 生态 | 限定 HashiCorp 全家桶 | 通用(K8s、网关、任意 API) |
| 许可 | 商业(平台付费层含配额) | Apache 2.0 开源 |
结论:管 Terraform 本身,用 Sentinel 更顺手;如果你已经重度使用 OPA 管 K8s 准入和 API 网关,并且想统一策略引擎,那可以走 OPA 集成(HCP Terraform 支持将其作为 Run Tasks 调用)。我们在平台内用 Sentinel,跨系统(K8s 等)继续用 OPA。两套策略引擎并存听起来冗余,但各自的边界很清楚。
为了说明差异,同样"禁止未加密 S3" 的规则,用 OPA/Rego 写是这样(通过 Run Task 读取 plan JSON):
# policy.rego(OPA 版本)——输入是 terraform plan 的 JSON
package terraform.plan
deny[msg] {
change := input.resource_changes[_]
change.type == "aws_s3_bucket"
change.change.actions[_] == "create"
not change.change.after.encryption.enabled
msg := sprintf("%s: S3 bucket must enable encryption", [change.address])
}
对比一下两种写法的差异:
| 差异点 | Sentinel | OPA/Rego |
|---|---|---|
| 数据面 | 用 import "tfplan/v2" 拉起模型,字段有官方类型约束 | 输入是裸 JSON,字段拼错靠运行时查 |
| 语法风格 | 类 HCL,all X as y {} 读起来像配置 | Rego 的 deny[msg] 模式是"查询式逻辑",新手容易转不过弯 |
| 平台集成 | plan 后自动执行,失败即阻断 | 需 Run Tasks 挂载,执行结果由外部决定 |
| 调试 | sentinel test 本地模拟,官方工具链 | opa test 也成熟,但要自己造 plan JSON |
我的建议:团队里没有 Rego 经验的人回来告状时,别急着换引擎。Sentinel 的"配置感"和 HCL 一脉相承,Terraform 工程师接管成本最低。只有当你已经有 OPA 生态(K8s 准入、API 网关策略都在用)时才值得统一——“统一策略引擎"的价值要大过"再学一套语言"的代价。
策略的演进路径(别一上来就写 20 条硬策略)
策略即代码最怕"策略膨胀”——规则太多,每个 run 都被拦,团队开始恨平台。我们的落地节奏:
- 阶段一(前两周):只写 3 条"安全红线"策略,全部是硬阻断:禁止未加密存储桶、禁止公开安全组(
0.0.0.0/0的 22/3306/6379 端口)、禁止删deletion_protection资源。宁可误报也不要漏网。 - 阶段二(一个月后):加成本类 advisory 策略(如无标签资源提醒"该环境将无法按成本分摊"),只警告不阻断。
- 阶段三(稳定后):引入合规拆解,把客户审计里反复出现的"高危配置项"逐条转成策略,让审计标准变成可执行代码。
一个实用的运营建议:给策略集单独的版本管理 + CI 测试。我们把 Sentinel 策略放在独立仓库,用
sentinel test跑本地单元测试(模拟 plan JSON),策略改动走 PR review。策略本身也是代码,同样要 review、要版本、要测试。
决策五:与 CI/CD 怎么集成
这是迁移决策里争议最大的一环:平台自带的 VCS 集成会"管得太宽",我们之前的 CI 脚本怎么办?
两种模式对比
模式 A:VCS 驱动的 Run(平台全托管)
配置 workspace 绑定 Git 仓库,之后:
git push → 平台 webhook 触发 → 平台自动 plan
→ 结果显示在 PR 评论区(check)→ 人工在平台点 apply(或自动)
优点:审批流、run 历史、审计全部在平台闭环,无需自己维护 CI 触发器;PR 评论里直接带 plan 结果,reviewer 体验一流。 缺点:CI 里已有的"前置检查"(lint、tfsec、成本估算)要迁移到平台的 Run Tasks,或保留在外部 CI 里作为前置 gate。
模式 B:CLI 驱动的 Run(平台只做执行和状态)
我们的 CI 脚本保持不动,terraform plan/apply 远调到平台执行:
# GitLab CI 片段:CLI 驱动模式
apply:
stage: deploy
before_script:
- export TF_CLI_ARGS="-no-color"
script:
- terraform init
- terraform plan -out=tfplan.binary # 平台执行 plan,生成远程结果
- terraform apply tfplan.binary # 平台执行 apply
rules:
- if: '$CI_COMMIT_BRANCH == "main" && $CI_COMMIT_TAG'
when: manual
allow_failure: false
要点:CLI 驱动模式下的认证用组织级 API Token(不是个人 token)注入 CI 环境变量,建议用平台"Machine User + Team"模式管理——CI 用独立的机器用户,权限只给需要的 workspace,既不暴露个人 token,也方便轮换。
CLI 驱动模式的一个隐藏坑:terraform plan 在 CLI 模式下会把计划缓存到本地 HCP 会话,如果 CI 的 plan 和 apply 分成两个 job(常见做法),两个 job 必须使用同一个 workspace 会话。GitLab 里我踩过:plan job 和 apply job 并发排队时,apply 会拿到"找不到对应 plan"的报错,因为远程 plan 结果与会话 ID 绑定。解法是显式传 -refresh-only 或把 plan 输出保存并在 apply 时引用(terraform apply tfplan.binary 会带着远端 CPU 的 plan 引用)。这个坑官方文档有提,但描述很含蓄,实际遇到时才觉得痛。
优点:迁移成本极低(CI 脚本几乎不改),审批流仍在 GitLab 里(人工触发 job)。 缺点:plan/apply 的执行细节、审计记录还是依靠 GitLab 的 pipeline 历史 + 平台 run 历史双重记录,闭环性差一些。
我们的最终形态:混合
- 网络/数据库等基础设施 workspace:VCS 驱动 + 平台内审批,apply 必须人工在平台确认。
- 应用交付类 workspace:CLI 驱动,由 GitLab CI 触发,保留我们熟悉的应用发布流水线。
这个混合的关键收益是:没有把全部流程塞进平台,也没有放弃平台的治理能力。平台的外围是 CI(负责 lint、测试、构建镜像、通知),平台核心是执行 + 审批 + 状态 + 审计——各司其职。
Run Tasks:把第三方工具挂进每个 run
Run Tasks 允许在 plan/apply 生命周期挂第三方工具,比如:
| Run Task | 作用 | 我们用它做什么 |
|---|---|---|
| Infracost | 每次 plan 输出成本估算 | 让"这次变更要花多少钱"出现在 PR 评论 |
| tfsec / Checkov | 安全扫描 | 高危漏洞硬阻断,中危警告 |
| 自定义 webhook | 对接内部系统 | 变更广播到钉钉/飞书群 |
实际效果:某次有人在生产加了台 c5.4xlarge(约 $0.68/h 的实例),PR 里的 Infracost 评论直接标出每月 +500 美元,reviewer 当场让改成较小的实例。成本估算从"事后看账单"变成"事前看评论"——这在自建方案里要专门写一套脚本,平台里配置 5 分钟。Infracost 是我们实测下来最有价值的 Run Task(可参考 Infracost Run Task 官方文档)。
自定义 Run Task 的落地模板(对接内部合规系统):
Run Task 本质是一个 webhook:平台在 plan 后把 plan JSON 发给你的服务,你的服务返回 verdict JSON。一个最小实现:
// run-task-webhook/main.go —— 内部合规检查 Run Task
// 平台发送 plan JSON → 校验 → 返回 pass/fail/advisory
package main
import (
"encoding/json"
"net/http"
)
type PlanPayload struct {
ResourceChanges []struct {
Address string `json:"address"`
Change struct {
Actions []string `json:"actions"`
} `json:"change"`
} `json:"resource_changes"`
}
type Verdict struct {
Pass bool `json:"pass"`
// 更多字段:advisory、message 等
}
func main() {
http.HandleFunc("/check", func(w http.ResponseWriter, r *http.Request) {
var p PlanPayload
json.NewDecoder(r.Body).Decode(&p)
// 内部逻辑:比对资源清单与白名单
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(Verdict{Pass: true})
})
http.ListenAndServe(":8080", nil)
}
这个能力的意义在于:你不受限于平台自带的策略引擎。任何"只有你们内部知道的规则"(比如"生产环境资源必须带 cost-center 标签"、“变更单号必须存在于内部工单系统”)都可以挂成 Run Task。自建方案里这是大工程,平台里是配置 + 一个 API。
决策六:TCO 账本——到底贵不贵
这是所有老板都会问的问题。我按我们实际用的规模(45 个用户、120 个 workspace、月均 800+ 次 run)算一笔账,单位人民币(按当时汇率约 7.2 估算),周期一年:
方案一:自建(迁移前)
| 成本项 | 明细 | 年成本(估) |
|---|---|---|
| 基础设施 | S3 存储 + 请求、DynamoDB、CI runner 时长 | ¥6,000 |
| OL 人力 | 锁协调 4h/周 × 12 人时费率 ¥150 | ¥31,200 |
| 权限治理 | AK/SK 轮换 + 审计取证 1 人月 | ¥18,000 |
| 事故损失 | 2 次生产事故平均 MTTR 6h × ¥2,000/h | ¥24,000 |
| 小计 | ¥79,200/年 |
方案二:HCP Terraform 付费层(迁移后)
HCP Terraform 的定价按"用户 + 用量"计费,不同的套餐差异较大,且价格会随版本调整,这里只给量级参考(以当时公开定价估算):
| 成本项 | 明细 | 年成本(估) |
|---|---|---|
| 订阅 | 45 用户 × 量级 ¥400-800/用户/年 | ¥18,000-36,000 |
| 用量 | run 数量超出套餐配额部分 | ¥3,000-6,000 |
| 运维 | 平台托管,零自建运维 | ¥0 |
| 事故损失 | 策略拦截 + 审批流后,事故率下降 ~80% | ¥5,000 |
| 小计 | ¥26,000-47,000/年 |
结论:按我们规模,托管平台一年大约省 30% ~ 67%(视订阅档位),而且省下来的不只是钱——锁冲突、权限事故、审计取证这些"隐性成本"是迁移后真正消失的东西。成本估算功能 + Infracost 还能在事前拦截浪费型变更。
需要说明:计费细节请以 HashiCorp 官方最新定价为准(developer.hashicorp.com/terraform/cloud-docs),我这里的数字是量级参考,不是报价。TFE 私有化的费用是订阅制、不透明,需要直接联系销售,团队规模小到中等的企业很少会选它——这也是我们把 TFE 排除在"默认选项"外的原因。
失控成本:平台依赖与锁定风险
TCO 里还有一项不容易量化的:平台锁定。把状态、变量、审批流全部放进 HCP Terraform 之后,想迁回自建并不是零成本。我们控制锁定风险的两个手段:
- 代码仓库保持纯净——Terraform 配置本身永远在 Git 里,平台的 workspace 只是"执行壳",代码资产不锁定。
- 定期导出 run 历史和状态版本——通过 API 把关键 workspace 的 state 版本备份到自家对象存储,保证"随时可裸迁回自建"。
免费层 vs 付费层:什么时候"够用",什么时候"必须上"
HCP Terraform 有免费层(Free),很多团队纠结"白嫖够不够"。我按实际功能边界给出判断:
| 能力 | 免费层 | 付费层(量级参考) | 我们的实际需求 |
|---|---|---|---|
| 远程状态存储 | ✔ | ✔ | ✔ |
| 远程执行(Remote Operations) | ✔(有限并发) | ✔(更高并发) | 具备 |
| 团队协作(Team) | 基础 | 完整 RBAC | 必须付费 |
| Sentinel 策略 | ✖ | ✔ | 必须付费 |
| 审计日志 | 基础 | 增强 + API 导出 | 必须付费 |
| 成本估算 | 有限 | 完整 | 付费更优 |
结论很直白:只要你需要"团队权限 + 策略 + 审计"中的任何一样,免费层都覆盖不了。我们当初从免费层起步,两周后被 Sentinel 引用次数卡住,才上的付费层。
提示:付费层有"按用量"和"按用户"两种计费口径的兼容选项(不同时期套餐组合有差异),采购时建议让销售实际测算你的 run 量,别按人头拍脑袋——run 量大的场景,按量计费可能反而便宜。
一个真实的"迁移后复盘模板"
迁移三个月后,我们按四个维度复盘,这个模板送给你(迁移决策前也可以拿它打分):
| 维度 | 迁移前(自建) | 迁移后(HCP) | 变化 |
|---|---|---|---|
| 人均变更频率 | 每个 infra 工程师每周约 2 次 apply | 每周约 5 次(审批流不再成为心理障碍) | +150% |
| 状态锁冲突 | 每周 17 次 | 0 次(平台持有唯一执行权) | 归零 |
| 生产事故(与 IaC 相关) | 每季度 2-3 次 | 每季度 0-1 次 | -80% |
| 审计取证 | 半天拼日志 | 5 分钟查平台 | 数量级改善 |
指标归零那两行是最有说服力的,不只是降成本,而是让团队把精力从"运维流程"挪回"架构设计"——这比省下的钱更值钱。
生产踩坑实录(高级进阶)
除了前面提到的迁移坑,再记录几个上线后三个月内踩到的坑——这些坑在官方文档里很难找到答案。
坑 1:本地 apply 的"静默绕过"和 CLI 凭证过期
我们在迁移完成后发现,老同事的本地 terraform 还是会尝试直接用本地 provider 配置跑——因为本地 ~/.terraform.d/credentials.tfrc.json 里存的 token 过期了,报错之后他们才被迫走平台。这是好事,但也暴露了问题:只靠平台设置拦不住"本地改动",需要配合团队约定(基础设施变更必须走平台或 CI)。
另外:CLI 驱动模式下,开发者本地的 CLI token 过期后 terraform plan 会静默失败或提示登录。建议把 token 轮换脚本写进入职/季度流程,别等报错再查。
坑 2:并发 run 限制和它的涟漪效应
HCP Terraform 每个组织有并发 run 上限(免费层约 2 个,付费层看套餐,我记得我们是 10 个左右)。高峰期(比如周一上午)如果 20 个 workspace 同时被 CI 触发,超出部分的 run 会排队,而排队 run 的 plan 结果基于的代码版本可能已经过期——排队后再 apply 可能 plan 结果与最新代码不一致。
解法(我们实践有效):
- 给关键生产 workspace 设置
auto-apply: false,人工确认时顺手检查排队情况。 - CI 触发的批量 workspace 用错峰调度(比如按 5 分钟间隔分组)。
- 把"规模大但低风险"的 workspace(如仅 plan)和"必须即时"的区分对待。
坑 3:Sensitive 变量在计划外 workspace 的"幽灵引用"
我们把一批凭据放进 Project 级变量集后,有个 workspace 明明没有勾选该变量集,terraform plan 却莫名引用到了它——排查发现是该 workspace 的历史配置里残留了变量组引用(平台在某个旧版本允许过这种关联,迁移时没有清理干净)。解法:迁移后逐个检查 workspace 的 “Variables” 页,核对变量集引用列表,把没用到的全解绑。
坑 4:Run 历史大量刷屏通知,团队麻木
平台默认每个 run 都会发通知(Slack/DingTalk),800 次 run/月 × 多次通知 = 严重刷屏,一周后团队就没人看了——“告警疲劳"直接导致有一次关键审批被漏掉。解法:通知策略按事件分级——只有 “run failed” 和 “policy check failed” 发通知,“run queued/success” 不打扰。审批流单独走 PR 评论 + 平台内待办,不依赖 IM 通知。
坑 5:策略集绑定的"复制粘贴"事故
一次给新 workspace 复制变量集/策略集配置时,误把一个含"禁止生产环境 create 资源"测试策略的集合绑到了生产 workspace——所有生产 run 都被拦,排查了近一小时。解法:策略集用命名空间约定命名(如 policy-prod-security),绑定前先看集名称;同时策略本身写 workspace_name 判断,避免同一策略被绑错环境时误伤。
坑 6:“Plan 通过了,Apply 失败"的玄学时刻
平台模式下,plan 和 apply 是两次独立的 run,中间可能隔着几分钟甚至更久(审批等待)。审批等待期间,云上的真实状态可能已经变化(别人手动改了、别的 run 改了),导致 apply 时 Conflict 或 plan 结果过期。
最典型的一次:某次生产 RDS 升级 plan 通过并等待审批,审批人忙了 20 分钟才点 apply,期间另一个团队把 RDS 的 parameter_group 改了——apply 直接失败,平台提示"plan 已过期,请重新 plan”。
解法(也是平台的一个隐藏优点):
- 平台对"plan 过期"有自动检测(apply 前校验 plan 的 serial 与当前 state 是否一致),不一致会拒绝 apply——这其实是保护,别嫌它烦。
- 对关键变更,养成**“plan 完尽快审批”**的习惯,别把审批拖过午休。
- 大型迁移类变更,拆成多个小 run 逐个 apply,别指望一次 run 干完所有事。
坑 7:组织级变量和敏感值的"泄漏面”
(注:这个坑在决策二已经有铺垫,这里的补充是生产里的完整教训——配置 Sensitive 之前,先想清楚"平台掩码能挡住谁"。)
前面提过 Sensitive 标志,这里再说一个更隐蔽的:即使勾了 Sensitive,变量在以下两个地方仍可能泄露:
- Run 日志的 debug 输出:如果 provider 或模块里写了
print/log把变量打出来(有些第三方模块会这么做),Sensitive 只对平台 UI 掩码,拦不住模块自己打日志。 - state 文件本身:Sensitive 变量写入 state 后,state 在平台是加密存储的,但你 export state 到本地、或通过 API 拉取 state 时,值就在里面。Sensitive 不等于"不进 state"。
我们的对策:
- 凭据类参数尽量用外部注入(环境变量 / 变量集 + Sensitive),不让它进 Terraform 配置逻辑。
- 对必须进配置的敏感值,接受"state 包含它"的事实,转而约束 state 的访问面(workspace 权限最小化 + 审计谁拉过 state,平台 API 有调用记录)。
- 高危模块(数据库口令类)用 random_password + Vault 管理,实现"口令只存在于执行瞬间"。
平台 API 与自动化运维:把"平台"也变成 IaC
迁移完成后,我们很快意识到一个进阶点:HCP Terraform 本身也应该被自动化管理,而不是靠人点 UI。平台提供了完整的 API(workers.terraform.io 的 REST API),我们用 Go 写了个小工具,把日常运维自动化了:
- workspace 生命周期:新项目上线 =
create workspace → 绑定仓库 → 挂变量集 → 绑策略集,一条命令完成,参数从内部 CMDB 拉取。 - state 版本导出:定时任务把关键 workspace 的 state 拉取到自家对象存储(锁定风险对策的具体实现)。
- run 状态轮询:CI 里等待平台 run 完成并判断结果(替代
terraform apply后本地等结果)。 - 审计日志拉取:月度自动归档审计事件,生成合规报表。
// workspace-bootstrap/main.go —— 自动化创建 workspace
// 内部 CMDB 拉取团队/项目元数据 → 调用 HCP API 建 workspace
func createWorkspace(org, project, name, vcsRepo string) error {
body := fmt.Sprintf(`{
"data": {
"type": "workspaces",
"attributes": {
"name": %q,
"source-name": "cmdb-bootstrap"
},
"relationships": {
"organization": {"data": {"type": "organizations", "id": %q}},
"project": {"data": {"type": "projects", "id": %q}}
}
}
}`, name, org, project)
// http.Post("https://app.terraform.io/api/v2/organizations/"+org+"/workspaces", ...)
_ = body
return nil
}
这个动作的意义:把平台自身的运维也纳入"声明式管理"。自建方案里"再自建一层来管自建"的套娃,在托管平台上变成"API 调用 + 定时任务",工作量差一个数量级。
嵌套的"状态引用"问题:跨 workspace 依赖要小心
我们的网络和计算是分 workspace 管理的(上面提到的三层组织模型),迁移后发现一个隐藏问题:网络 workspace 的输出被计算 workspace 通过 terraform_remote_state 引用。平台模式下这个引用仍然工作(状态 API 暴露),但有两个细节变了:
- 引用的是"已 apply 的上一个版本"——平台 state 是异步更新的,如果网络 workspace 刚 plan 还没 apply,计算 workspace 拉到的是旧版本。这倒和自建方案一致,但平台的 run 队列让它更明显(排队时引用值容易过期)。
- 权限模型要求:引用方需要读目标 workspace state 的权限(Read 即可)。业务团队若没有 read 权限,
terraform remote state会报 403——迁移后我们发现很多"能用"的引用,在新权限模型下"不能用了",反而逼着我们理清了依赖矩阵。
解法:把跨 workspace 的引用降到最少,能用输出参数传递的就用参数(VCS 模式下手动注入),只有真正需要动态读取的(如 VPC ID)才保留 terraform_remote_state。依赖矩阵越扁,平台迁移和维护越省心。
什么情况下不要迁移(冷静的边界)
写到这里,必须把话说完整:不是所有团队都该迁移。下面三类情况,我的建议是别迁:
- 3-5 人小团队 + 单一云厂商 + 基础设施规模小:自建 S3 后端 + 一个 CI job 足够,托管平台按用户收费反而不划算,治理需求靠人肉沟通完全能覆盖。
- 有硬性数据主权/合规要求(等保、金融监管、政府项目):SaaS 的"数据在境外 + 审计日志留存天数限制"直接判死刑,只能走 TFE 私有化或者自建 OPA/CAS 方案。
- 团队根本没有"流程建设"意愿:托管平台的价值建立在"团队愿意把变更流程迁移进去"之上。如果团队习惯"本地改、本地跑、事后补 Git commit",那平台只会成为一个昂贵的静默门卫——工具不能替代流程,只能放大流程的好坏。
给一个简化决策表:
| 团队规模 | 云环境数 | 合规要求 | 建议 |
|---|---|---|---|
| ≤5 人 | 1 | 无 | 自建(S3+锁)就够 |
| 5-20 人 | 1-2 | 无 | HCP Terraform 免费层起步 |
| 20-50 人 | 2+ | 无 | HCP Terraform 付费层(本案例) |
| 任意 | 任意 | 数据不出境/强审计 | TFE 私有化或 OPA 自建 |
| 任意 | 任意 | 预算卡死 + 有 IaC 人力 | OpenTofu + 自建治理栈 |
高频问题速答(FAQ)
把团队和社区里问得最多的几个问题集中回答一下:
Q:HCP Terraform 和 Terraform Cloud 是什么关系? A:Terraform Cloud 在 2024 年更名为 HCP Terraform(HashiCorp Cloud Platform 产品线统一命名),功能指向同一套 SaaS,老文档和旧教程里的 Terraform Cloud 指的就是它。迁移后资料搜索用新名字更容易命中。
Q:迁移期间生产环境能继续变更吗? A:能,但建议冻结大变更(网络、数据库类),保留小变更走老流程。我们的做法是:冻结窗口 4 小时(业务低谷),期间只迁移、不 apply 任何新变更;迁移完成后验证一个"冒烟 workspace",再逐步放开。
Q:免费层到底能不能用? A:能,适合"一个人 + 几个 workspace"的练手场景。但多人和策略需求一上来就必须付费——别把生产环境挂在免费层上赌限额。
Q:OpenTofu 是不是就等于"免费的 HCP Terraform"? A:不是。OpenTofu 解决的是"Terraform 工具本身的许可证问题",它依然需要你自己搭状态后端、锁、CI、策略引擎、审计。它是自建方案的"开源地基",不是托管平台的替代品。两者关系:想自建 → OpenTofu 是当前最优的地基选择;想托管 → 看 HCP Terraform 或同类托管平台。
Q:迁移后要不要保留旧 S3 桶? A:建议保留 1-2 个月作为回滚保险(只读),之后删桶前先确认所有 state 都已成功迁移且验证过 plan 一致性。我们保留了 45 天后删除,期间没有用到回滚——但保险这东西,用不上才是最理想的。
Q:HCP Terraform 在国内访问慢怎么办? A:SaaS 控制台在国内访问会有客观延迟,plan/apply 的执行发生在平台侧(美区默认),日常交互对延迟不敏感;但大量本地 CLI 交互时体感明显。权衡方案就两条:接受延迟用 SaaS,或为了合规/体验上 TFE 私有化。没有第三条"既在境内又零运维"的路可选。
延伸:迁移时顺手做的三件"顺风改"
既然状态和执行都搬了家,我们顺手把一些原本要单独立项的改进一起做了——这些改动单独做要排期,跟着迁移做几乎是零成本:
- 统一 provider 版本:迁移时顺手把全部 workspace 的 provider 锁文件升级对齐,消灭了"不同环境 provider 版本不同导致 plan 漂移"的隐患。
- 补上 drift detection:平台支持定期自动 plan 检测漂移(发通知),我们把所有关键 workspace 开了每周漂移检查——这周漂移从"靠手动跑 plan"变成"平台主动告警"。
- 入口收敛:所有 workspace 的 apply 权限收口到"平台 + CI",本地 apply 全关。半年后回看,这是"最贵的一次收口",但也是让团队真正建立"变更必须留痕"心智的关键动作。
迁移中的组织博弈:技术选型永远有一半是"人和流程"
最后必须说一点容易被技术文章忽略的:迁移最大的阻力从来不是技术,而是"老习惯"。我们迁移过程中遇到的三个典型反应,以及应对:
- “平台是别人家的牛,我不敢把生产交给它”——应对:先拿一个低风险 workspace(测试网络)跑 2 周,用数据说话(run 成功率、审批通过率、事故数),数据比说服有力。
- “本地 apply 多快啊,平台审批太慢”——应对:把这笔账算给他看:本地 apply 的"快"换来的是不可审计、不可回滚、出事查半天。平台审批是"慢半分钟,快一整天"。
- “这些功能我自建也能做”——应对:不争论能力差异,只算维护成本:自建版本每个功能背后都有一个"维护它的人"。平台替你维护了,那个人可以做别的。
迁移完成后团队的真实反馈是:没人想回去。不是因为平台有多完美(它也有缺点,比如 SaaS 延迟、计费复杂),而是因为——终于不用自己维护"管 Terraform 的那套东西"了。这句话,是整篇文章最想说的。
总结
从"5 人自建"到"30 人平台托管",我们走过的路可以浓缩成三句话:
第一,托管平台的本质不是"状态托管"而是"治理托管"。S3 桶谁都能搭,但"谁能改、改什么、经过谁批准、留下什么记录"这套治理能力,自建要 2-3 个人年才能补齐,托管平台开箱即有。这也是为什么我们反复强调:别把选型问题降维成"状态文件放哪"。
第二,迁移的价值在结构性变化,不在功能堆叠。我们真正受益的是三件事:远程执行让"本地 apply"变成"平台 run"(审批和审计在同一个闭环里);Sentinel 让"不该做的"从口头约定变成平台硬阻断;权限模型让"谁能动生产"从 AK/SK 泄露风险变成组织边界。这三件事共同把 IaC 从"操作工具"变成了"治理平台"。
第三,技术选型要配得上组织的实际状态。如果你现在 5 个人,别急着上平台——自建的简单粗暴反而是一种优势;如果你已经 30 人、每周 17 次锁冲突、事故要花半天回溯,那"再撑一撑"的成本已经高过迁移成本了。判断信号很简单:当"谁都能 apply"和"没人敢 apply"同时出现在你团队里的时候,就是该做治理性变更的时候。
最后的最后,回到开头的成本账:托管平台一年省下来的是十几个人的协调时间和两次事故,这笔账我算得清清楚楚。但比钱更重要的是:我们的基础设施团队终于把"怕出事"的时间,用回了"设计得更好"的事情上。 这可能才是这篇文章最想传递的。
如果你正在"自建 vs 托管"的岔路口纠结,我最后送你一张检查表,照着打钩就行:
- 我确认过自己的合规约束(数据不出域/等保)是否一票否决 SaaS?
- 我的团队规模是否已经出现"锁冲突/权限失控/审计难"中的至少 2 项?
- 我是否算清了平台订阅费和自建隐性成本(含人力)的对比?
- 我是否选好了第一个"试跑"的低风险 workspace?
- 我是否准备了回滚方案(旧后端只读保留 30-45 天)?
- 我是否想好了"迁移后第一周关停老习惯"的四个动作?
六项全过,可以启动了。有一项不过,先补齐再动手——基础设施治理的迁移,最怕的就是"先干了再说"。
参考资料与致谢
按技能规范,以下外链均来自撰写过程中实际参考的公开资料;站内链接触达已有文章,供延伸阅读。
本文在撰写过程中参考了以下资料,感谢原作者的贡献:
- Terraform 官方文档 - HCP Terraform — HashiCorp,HCP Terraform 平台功能、权限模型与 API 的权威说明
- Terraform 官方文档 - Sentinel — HashiCorp,策略即代码的官方概念与实际策略示例
- Terraform 官方文档 - Run Tasks — HashiCorp,Run Tasks 集成机制的官方说明
- Infracost 官方文档 - Terraform Cloud Run Task 集成 — Infracost,成本估算 Run Task 的配置指南
- 百度百科 - Terraform — Terraform 从 MPL 到 BSL 的许可变更、IBM 收购 HashiCorp 的背景资料(2024-2025)
- InfoQ - Walrus 与 Terraform 对比研究 — 开源 IaC 平台对 Terraform Cloud/Enterprise 替代方案的对比分析
- OpenTofu 官方项目 — Terraform 社区分支(MPL-2.0)的治理模型与工具链说明
内链扩展阅读:
- 状态文件丢了怎么办:Terraform State 灾难恢复与生产级后端架构 — 自建 S3 + DynamoDB 后端的完整细节,与本文的托管方案互为对照
- Ansible 与 Terraform 的协作边界 — 托管平台之外的"哪些事交给配置管理工具"的边界讨论
- Pulumi 迁移 Terraform 的决策记录 — 同为 IaC 工具链迁移,方法论的横向参照
- Terraform 模块分层设计 — 迁移到平台前的模块化改造,先分层再托管