Terraform 状态文件被我弄乱后的补救

举报
yd_272744321 发表于 2026/10/18 16:59:57 2026/10/18
【摘要】 我在华为云把 Terraform 当主入口管理 VPC、ECS 和安全组,结果一次手工 CLI 改动叠加两条并发流水线,把 state 搞到前后不一致,apply 连续报 404 还多建了资源。我记录了从 OBS 版本回滚、state 对账、逐条 import 的过程,以及后来补上的并发闸门。

故障发生时的环境

4 月 12 日 01:07,我被值班电话叫醒,CodeArts 的 prod 部署在 apply 阶段连挂 3 次,失败流水线 ID 分别是 #18422、#18423、#18424。我们这套环境规模不大,生产侧是 3 台 ECS s6.large.2(2 vCPU / 4 GB,Ubuntu 22.04.3 LTS),一台 RDS MySQL 8.0.36(2C4G,ESSD 120GB),VPC、子网、路由、安全组全交给 Terraform。版本固定在 Terraform 1.6.6、huaweicloud provider 1.63.2、huaweicloud CLI 3.0.76,CI 里执行命令是 terraform apply -auto-approve -input=false -parallelism=10。

我那晚只睡了 1 小时 40 分。

state 在 OBS 桶 tfstate-prod,SSE-KMS 开着,桶版本控制也开着,但没做锁。我一直靠“同一时间别两个人发版”的口头约定,写这段的时候我都觉得离谱。下面这段 backend 配置当时看起来挺干净,现在看是埋雷。

terraform {
  required_version = "1.6.6"
  backend "s3" {
    bucket                      = "tfstate-prod"
    key                         = "network/prod.tfstate"
    region                      = "cn-north-4"
    endpoint                    = "obs.cn-north-4.myhuaweicloud.com"
    skip_region_validation      = true
    skip_credentials_validation = true
    force_path_style            = true
  }
}

状态文件怎么乱掉的

白天 17:26 有同事为压测临时放通流量,直接跑 CLI 删了一条规则,命令是 huaweicloud vpc security-group-rule delete 7d3f6c2e-2f1e-4db0-9b8a-3a9d9a3a3f11,RequestId 我在审计日志里翻到的是 3f8b4f2d1c8c4f6f9f6f95b3b2e2f4ab。同一天下班前另一位同事合了模块重命名 MR,把 module.net 改成 module.network,代码里地址改了,但 terraform state mv 没补。一个是线上资源被手改,一个是状态地址改了没迁,两个动作叠在一起就开始分叉。

00:58 和 01:02 两条流水线几乎同时起跑,后写入那条把前一条刚更新的 serial 覆盖了。我从 OBS 版本里对比出来 00:58 那次把 serial 从 412 写到 414,01:02 那次又写成 415,但内容不是在 414 基础上增量更新,而是拿了旧快照覆盖。apply 日志里这两段非常刺眼:

Error: error querying Security Group Rule (id=7d3f6c2e-2f1e-4db0-9b8a-3a9d9a3a3f11):
StatusCode=404 RequestId=3f8b4f2d1c8c4f6f9f6f95b3b2e2f4ab
ErrorCode=VPC.0101
ErrorMessage=Resource not found

Error: Provider produced inconsistent result after apply
When applying changes to module.network.huaweicloud_vpc_security_group_rule.web_ingress,
provider "registry.terraform.io/huaweicloud/huaweicloud" produced an unexpected new value.

这个坑是我自己挖的。

我怎么把它拉回一致

我先把 CodeArts 里 prod 分支自动触发关掉,发布页面打了“维护中”,再把两个正在排队的任务手动取消。这个动作花了 6 分钟,时间是 01:13 到 01:19。那几分钟我反复刷新流水线页面,怕又冒出新任务把 state 再写坏一次。

我做过一次失败尝试。01:31 我想偷懒整包回滚,直接把 OBS 的历史版本覆盖回主 key,然后跑 terraform plan,命令是:

obsutil cp "obs://tfstate-prod/network/prod.tfstate?versionId=3L4k...Aq" \
  "obs://tfstate-prod/network/prod.tfstate" -f
terraform init -reconfigure
terraform plan

结果 plan 直接给出 Plan: 9 to add, 3 to change, 7 to destroy,里面包含一条当晚 00:46 刚合法生效的子网路由变更,差点被我一把抹掉。我当时手是抖的。这个方案我一开始以为能省时间,现在回头看风险太高,我在 01:38 停掉了这条路,重新从当前损坏 state 做增量修复。

我从 OBS 拉了最近 10 个版本做对账,先看 serial 和 lineage,再比 resources 地址列表。对账命令如下:

obsutil ls obs://tfstate-prod/network/prod.tfstate -v
obsutil cp "obs://tfstate-prod/network/prod.tfstate?versionId=3L4k...Aq" ./prod-prev.tfstate
terraform state pull > prod-broken.tfstate
jq '.serial,.lineage' prod-prev.tfstate prod-broken.tfstate
jq -r '.resources[].module + "." + .type + "." + .name' prod-prev.tfstate | sort > prev.list
jq -r '.resources[].module + "." + .type + "." + .name' prod-broken.tfstate | sort > broken.list
diff -u prev.list broken.list

结论是 prev serial=412,broken serial=415,lineage 一样,但 415 少了 4 条 huaweicloud_vpc_security_group_rule,多了一个 module.network.huaweicloud_nat_gateway.main。这个 NAT 是并发 apply 时误建的,计费中心第二天显示 NAT 使用 43 分钟 0.88 元,附带 EIP 0.31 元,总计 1.19 元,钱不多,脸很疼。

修复时我没有直接回退整包,而是删掉误建资源,再把缺失项逐条 import,最后跑 refresh-only 收敛状态。命令和顺序是这样:

terraform state rm module.network.huaweicloud_nat_gateway.main
terraform import module.network.huaweicloud_vpc_security_group_rule.web_ingress[0] 5a4e7e4d-6b4b-4d8a-9ad8-2dd7f8d6aa21
terraform import module.network.huaweicloud_vpc_security_group_rule.web_ingress[1] 8f3ab6c3-b5c7-4a51-92bc-c61cc7f8e9aa
terraform import module.network.huaweicloud_vpc_security_group_rule.web_ingress[2] 11e6f0d9-0a0d-4eb9-8a19-3eb3f1f80f64
terraform import module.network.huaweicloud_vpc_security_group_rule.web_ingress[3] b7ac55dc-67e1-4fcb-a566-94e9f3c1092a
terraform state mv module.net.huaweicloud_vpc.main module.network.huaweicloud_vpc.main
terraform state mv module.net.huaweicloud_vpc_subnet.app[0] module.network.huaweicloud_vpc_subnet.app[0]
terraform plan -refresh-only -out refresh.tfplan
terraform apply refresh.tfplan

这一步从 01:46 跑到 02:15,29 分钟,期间我还处理了一次 provider 读超时,报错是 context deadline exceeded while reading VPC subnet,重试第 2 次才过。01:07 到 03:24 业务可用性没掉,发布窗口丢了 2 小时 17 分,这个损失是实打实的。

我后来加的约束

我没上复杂平台改造,只做了能当天落地的约束。CodeArts 里 prod 环境改成单并发;任何人用 CLI 改 Terraform 托管资源,MR 必须贴命令和 RequestId;每次 apply 后自动跑 terraform plan -refresh-only,输出非空就阻断后续发布。这个规则上线后 11 天里拦住了 2 次漂移,一次是手工改了安全组描述,一次是控制台删了测试子网。

我还塞了个很土的锁脚本,用 OBS 对象当互斥锁,30 秒拿不到锁就退出,退出码 2,流水线会标红:

LOCK_KEY="obs://tfstate-prod/lock/prod.lock"
if obsutil stat ${LOCK_KEY}; then
  echo "lock exists"; exit 2
fi
obsutil cp ./build_id.txt ${LOCK_KEY}
trap 'obsutil rm ${LOCK_KEY}' EXIT

这个锁不是强一致,任务异常退出会残留锁文件,我现在靠 15 分钟超时清理,体验一般。

下周我会把 state 拆成 network、compute、app 三个后端,减少单文件冲突面;另外准备把 CLI 变更收敛到一个审计脚本里,至少别再出现“先改了再补记录”。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。