用 Terraform 管理云资源时状态文件拆分与协作规范
Terraform 这玩意儿,一个人用的时候挺爽,apply 一敲,资源就起来了;一旦变成多人协作,最先出问题的往往不是模块写得烂,而是 state 文件没管好。我见过一个 state 里塞了网络、数据库、K8s、监控、IAM 的,plan 一次要等好几分钟,两个人同时改还得抢锁,最离谱的是有次误删了一个安全组,结果半个环境跟着遭殃。所以 state 拆分和协作规范这事儿,真不是“以后再说”的优化项,而是上生产前就得定下来的底线。
先说为什么别把鸡蛋放一个 state 里
Terraform 的 state 记录的是配置和真实资源的映射关系。state 越大,plan 和 refresh 越慢,锁竞争越明显,爆炸半径也越大。更麻烦的是权限边界:应用团队其实不需要碰网络账号里的 state,但你如果全塞一个文件里,最后只能给所有人一把大钥匙。
但也不能走另一个极端,按每个资源拆一个 state。拆太碎以后,资源之间的依赖、输出引用、执行顺序都会变成灾难,改一个 VPC 得跑五个目录。我的经验是:先按环境拆,再按业务域拆,最后看生命周期和权限边界。比如 prod 和 dev 肯定分开;prod 里面再把网络、数据、应用、监控拆开。数据库这种生命周期长、变更风险高的,单独一个 state;临时实验资源就没必要拆那么细。
backend 和目录结构长什么样
我一般用“模块目录 + 环境目录”的结构,模块放公共逻辑,环境目录是 root module,各自有独立的 backend key。大概长这样:
infra/
modules/
network/
app/
database/
live/
prod/
network/
backend.tf
main.tf
outputs.tf
app/
backend.tf
main.tf
dev/
network/
app/
backend.tf 里把 key 写清楚,别用默认的 terraform.tfstate。比如用 S3 + DynamoDB 锁:
terraform {
backend "s3" {
bucket = "company-terraform-state"
key = "prod/network/terraform.tfstate"
region = "ap-southeast-1"
dynamodb_table = "terraform-locks"
encrypt = true
}
}
key 的命名我习惯按 环境/业务域/组件/terraform.tfstate 来,一眼能看出来这个 state 管什么。还有几个小习惯:本地 state 不要进 Git,.gitignore 里加上 *.tfstate、*.tfstate.*、.terraform/;.terraform.lock.hcl 要提交,Terraform 版本用 required_version 或 .terraform-version 固定住。别小看这些,换个人电脑上跑出 provider 版本不一致,plan 能给你整出一堆莫名其妙的 diff。
workspace 不是不能用,但别拿它硬扛生产隔离
Terraform workspace 适合临时环境、测试环境,或者同一套配置下快速切几个相似环境。但它不太适合做生产隔离:同一个 backend、同一套配置,切 workspace 全靠人记,权限也很难按 workspace 切得很细。要是有人 terraform workspace select prod 完忘了切回来,后面就刺激了。
所以我的做法是:生产、预发、开发用不同目录和不同 backend key,强隔离;workspace 只留给个人临时环境和一次性测试。如果云账号能分,生产最好单独账号,至少也要单独的项目/订阅和权限策略。state 里可能包含不少敏感信息,别指望它“只是个状态文件”。
拆 state 时怎么搬资源
拆分不是新建一个目录就完事,关键是资源不能丢,也不能让旧目录下次 plan 时想重新创建一份。动手前先备份:
terraform state pull > prod-network-backup-$(date +%F).json
跨 backend 搬资源,我一般用比较笨但稳的办法:新目录先把配置写好,用 terraform import 把现有资源接管进来,确认 plan 没有变化;然后在旧目录把对应资源从 state 里移除,最后删掉旧目录里的资源块。顺序别反,反了旧目录可能计划新建重复资源。
# 在新目录里接管已有资源
terraform import aws_vpc.main vpc-xxxxxxxx
# 确认新目录 plan 没有 diff 后,在旧目录里移除 state 记录
terraform state rm aws_vpc.main
如果只是同一 backend 下改资源地址,可以用 terraform state mv,但跨 state 的搬迁别过度依赖花活。moved block 适合改资源地址,不负责跨 state 搬家。不管用哪种方式,搬完一定跑 plan,看到 No changes 再合并。别问我怎么知道的,早年一次拆分没验证,旧目录差点把 RDS 当新资源建出来。
协作规范:把“能跑”变成“别人也能安全地跑”
state 拆分只是第一步,协作规范才是长期不翻车的关键。我们现在的规矩大概这几条:
-
禁止手改 state 文件,也不要把 state 内容贴到 issue 里。需要改地址用
state mv,需要接管用import,需要删记录用state rm,全部走命令并留记录。 -
所有变更走 PR。PR 里至少包含
terraform fmt、validate和plan结果。CI 里可以跑:terraform fmt -check -recursive terraform init -input=false terraform validate terraform plan -out=tfplanapply只从审批后的 plan 执行,别在本地随手apply生产。 -
同一个 state 同时只允许一个变更。Terraform 默认会加锁,除非救火,不要
-lock=false。如果锁住了,先查是谁在跑,别上来就强拆。 -
状态文件权限最小化。backend 加密、访问审计、按环境分权限。state 里可能有密码、连接串、证书,别给所有人读权限。
-
定期 drift 检测。生产 state 最好定时跑只读
plan,发现有人控制台手改资源就及时处理。别等下次发布才发现配置和现实对不上。 -
每个 state 有负责人和文档。写清楚它管哪些资源、依赖哪个 state 的输出、怎么执行、出问题找谁。依赖尽量通过 outputs 或 data source,别互相读整个 state 然后乱引用,很容易绕成循环。
-
模块版本固定。公共模块用 Git tag 或版本号引用,别直接跟主干,不然别人合并个模块改动,可能把你没打算动的环境一起改了。
最后再说一句,state 拆分没有标准答案,粒度合适比“看起来优雅”重要。拆到团队能独立变更、权限能收住、plan 时间能接受,基本就够了。剩下的就是老老实实走流程,别嫌麻烦——真出一次事故,后面补流程更麻烦。
评论
还没有评论。
发表评论
提交后评论将经过自动审核,审核通过后公开展示。