leeguoo

# 你没法单独 import 一个 GSI

一张 prod DynamoDB 表被人绕过 CloudFormation 直接改了计费和索引,结果整个 stack 卡在回滚里五天。记一次漂移(drift)的成因和解法,顺便说清一个反直觉的点:CFN 的 import 是整资源级的。

2026年6月23日 · 文章 · 公开

事故是这么开始的:有人嫌 prod 那张 DynamoDB 表的预置容量不够,直接敲了两条 aws dynamodb update-table —— 把计费从 PROVISIONED 切成按需,顺手又加了个 GSI。改完测了,能用,收工。

没走 CloudFormation。

一周后,一次完全无关的发版,CloudFormation 直接卡死回滚,整个 stack 进了 UPDATE_ROLLBACK_COMPLETE,谁也不敢再点部署。错误信息就一句:Attempting to create an index which already exists

CFN 只信自己的账本,不看现实

CloudFormation 本质是个账本:你交一份模板,它记下"这套资源应该长什么样",然后照着创建和更新。问题是,它做 diff 的时候,比的是新模板 vs 它自己的账本 —— 它从不去看云上资源实际是什么样。

所以当有人绕过它直接改了资源,账本就和现实对不上了。这就是漂移(drift)

CFN 账本(以为的) 计费:PROVISIONED GSI:4 个 云上现实(被带外改过) 计费:PAY_PER_REQUEST GSI:5 个 你发一份"正确"的新模板(5 GSI)—— CFN 拿过时账本(4 GSI)一比:"少一个,我去 create" → 那个 GSI 现实里早就有 → already exists → 回滚 💥
漂移的致命之处:CFN 按账本算差异,去 create 一个现实里已存在的资源

更阴的是回滚本身:CFN 回滚一个"加 GSI"失败的操作时,会去删掉它以为自己刚建的那个 GSI —— 而那其实是你线上正在用的索引。回滚误删。这也是为什么没人敢动:每次部署都在赌这一把。

反直觉的点:你没法 import 一个 GSI

自然的想法是:那让 CFN 重新"认领"这个带外建的 GSI 不就行了?CloudFormation 有 resource import 啊。

但这里有个坑:import 是整资源级的。你能 import 一整张表、一整个桶,但 GSI 不是一个独立资源,它是 AWS::DynamoDB::Table 的一个 property。你没法把"一张已经被 stack 管着的表里的某个子属性"单独 import 进去。

想让 CFN 接纳一个子属性的漂移,只能把整张表先踢出 stack,再整张重新 import 回来。

解法:remove → retain → reimport

核心是利用 DeletionPolicy: Retain —— 让 CFN "放手"一个资源时,不真的删掉它。

1. 加 Retain 删它=保留真表 2. 从模板删 CFN 账本剥离 3. 对齐现实 5 GSI / 按需 4. IMPORT 重新认领 账本 =现实 全程真表在线服务:第 2 步的 detach 不调任何 DynamoDB API,只动 CFN 账本
整张表踢出去再 import 回来,中间真实的表一直在线

第 4 步真正写 import changeset 的时候,还有两个不写在显眼处的坑,踩过一次才知道:

aws cloudformation create-change-set \
  --change-set-type IMPORT \
  --template-body file://original.yaml \   # 要 Original YAML(transform 前),不是 processed JSON
  --parameters "$PARAMS" \                 # UsePreviousValue 把 stack 全部参数传齐
  --resources-to-import '[{"ResourceType":"AWS::DynamoDB::Table",...}]'
  • 参数要用 UsePreviousValue 一次性传齐全部(不只是必填那几个)。少传的参数会回退成模板默认值,导致 CFN 把一堆无关资源判成"被改了",直接报 "N resources modified" 拒绝。
  • 模板要用 Original YAML(transform 之前的),不是 get-template 默认给的 processed JSON。processed 版本提交回去,CFN 对那些由 SAM 展开生成的资源还是会判出差异。

留给自己的几条

真正吓人的不是这套操作 —— detach 那步压根不碰 DynamoDB,表一直在服务,做坏了也能退回来。吓人的是它埋了一周才炸,而且炸在一次跟它毫无关系的发版上

  • 绕过 IaC 直接 update-table / 改控制台,是慢性毒药。当下能用,代价记在了账本里,等下一个倒霉蛋发版时一次结清。
  • CFN 的 diff 是"模板 vs 账本",永远不含"现实"。所以 detect-stack-drift 值得定期跑,别等它在回滚里告诉你。
  • import 是资源粒度的。子属性(GSI、参数组某项……)的漂移,认命整资源 reimport。

那张表现在回到了 CFN 名下,账本和现实对上了,detect-stack-drift 干净。下次发版,终于不用赌了。

下一篇 →
让 Claude Code 自动用你的 ChatGPT 订阅生图:不要 API key,背后怎么绕过 Turnstile

评论

评论发布后会立即公开,如触发规则可能被审核下架。

最多 1000 字。