2.3 镜像与快照:上线失败就回滚
场景:上线翻车,回滚靠手速
第一次云上发布时我们没有镜像方案,流程是现做的:SSH 上去、git pull、编译、重启服务。某个周五晚上,订单服务上线后十分钟,群里炸了:下单接口错误率从 0 窜到 12%。排查一刻钟定位到是个库存计算的回归 bug,但那时已经没人敢在线上继续 debug。最后怎么恢复的?回滚代码、重新编译、重启,全程四十分钟,期间损失的真金白银让我们集体沉默。
复盘会上最重要的决定不是“下次注意”,而是把回滚变成一个五分钟动作:机器层面的可回滚(镜像与快照)和应用层面的可回滚(版本化发布)。这一节讲前者,后者留给后面的灰度发布展开。
顺带说一句,那晚事故的根因本身并不复杂——一个没写单测的工具函数被改了,本地测试没覆盖到,灰度又没铺开,几件事叠在一起才酿成四十分钟的停摆。所以你看,后面几节要聊的安全组、灰度、监控,本质上都是在给同一件事打补丁:让单点失误不至于演变成整体事故。
方案:先快照垫底,再镜像沉淀
两个概念先分清,不然讨论时老打架:
| 维度 | 快照 | 自定义镜像 |
|---|---|---|
| 粒度 | 单块云硬盘的某一时刻 | 整台实例的完整系统盘 |
| 典型用途 | 数据备份、误删恢复 | 批量开新机器 |
| 恢复速度 | 回滚需关机,分钟级 | 新机即开即用 |
| 是否跨实例 | 仅限原盘 | 可开任意数量实例 |
| 计费 | 按容量 | 按容量 |
我们的节奏是:新机器跑通初始化与基础环境后,立刻做系统盘快照当兜底;等整套部署脚本验证通过、服务能稳定起停后,再制成自定义镜像,作为后续扩容的“黄金镜像”。官方文档入口 cloud.tencent.com/document/product/213 里镜像与快照都有专门章节,操作步骤截图很全,这里只讲我们怎么用,不重复贴控制台路径。
两份东西还要互相补充着用:快照偏“抢救”,出事了拉回来;镜像偏“复制”,好东西摊开用。只做快照的团队扩容时痛苦,只做镜像的团队回滚时痛苦,两个都做才是完整闭环。一开始我们也没分这么清,吃过两回亏才把流程钉成现在这样:快照随时做、镜像稳定做、版本统一管。
具体到节奏,快照是高频小步:发布前、配置变更前各做一次,几张就够;镜像是低频大步:只有整套环境验证通过、跑过一轮压测之后才制作一版。把这个节奏写在团队 wiki 首页,新人第二天就能照着操作,不用再问“什么时候该做快照”这种问题。纪律这东西,写进流程才叫纪律,停在嘴上那叫提醒。
为什么坚持做成镜像而不靠文档?因为人不可靠。wiki 上的部署文档第三个月就没人更新了,但镜像不会撒谎:它就是机器此时此刻的状态。扩容时从镜像开机,环境一致性由平台保证,比靠“按文档操作”靠谱得多。这也是我们后来给所有 新购 CVM 定的规矩:允许从镜像开,不允许人肉配。
镜像还有一个隐性好处:新人入职或机器迁移时,开箱就是一套可复现的环境。以前新人第一天都在配环境,现在第一天就能跑通冒烟用例。对比 2.1 本地 docker-compose 解决的“本地可复现”,镜像解决的是“云上可复现”,两者组合起来,团队才算真正拥有了标准化交付能力。
制作镜像前先清理,不然镜像里带着垃圾和密钥:
# 清理构建产物与缓存
rm -rf /var/cache/apt/archives/*.deb
rm -rf /home/deploy/build
# 清理 SSH 已知主机与操作历史,避免把内部地址带进镜像
rm -f /root/.bash_history /home/deploy/.bash_history
cat /dev/null > /var/log/syslog
# 确认没有残留的测试密钥
find / -name "id_rsa*" -not -path "*/.ssh/authorized_keys" 2>/dev/null
有一次我们漏了清理 git 凭据,镜像开出来的机器上还带着内网仓库的 token,后来翻日志才发现不对劲。从此清单里加了“检查凭据”这条,做完才敢点制作镜像。
真正让大家相信这套东西的不是道理,是第二次发布。那次商品服务上线后,有个接口在高并发下偶发 panic,错误率开始爬。值班同事没有像上次那样手忙脚乱,而是按流程走:安全组切掉入口流量、关机、回滚快照、开机、恢复流量,全程八分钟,群里只发了一句“已回滚,用户无感”。那天晚上大家睡得很踏实,所谓工程化,无非就是让正确动作变成默认动作。
踩坑与排查
坑一:快照不是实时备份。 快照是触发那一刻的状态,之后磁盘的任何变更都不会同步。有同事以为开了快照就高枕无忧,结果误删文件后翻快照,发现是三天前做的。我们的纪律是发布前、大盘变更前各做一次快抄,形成固定动作而不是想起来才做。另外快照本身按容量计费,毫无策略地做一堆快照也是成本黑洞,保留最近三个加发布前标记版就足够覆盖绝大多数场景。
坑二:回滚要关机。 快照回滚云硬盘要求实例处于关机状态。第一次操作时我们直接点回滚被拒绝,还以为是权限问题,其实是机制如此。后来回滚步骤里永远先写一行“关机再操作”,并把它写进发布手册的第一页。
坑三:镜像跨地域不可直接用。 我们曾在上海地域做好镜像,想直接在广州开机器扩容,翻遍控制台找不到。镜像默认只在制作地域可用,跨地域要用复制功能。跨可用区则没问题,同地域随便开。这个区别第一次踩的人特别多,值得提前记下。
坑四:制作镜像很慢。 第一次点制作镜像等了将近二十分钟,还以为卡住了。镜像大小取决于系统盘已用空间,所以前面那条清理清单既是安全问题也是时间问题——清得越干净,做得越快。
坑五:系统盘扩容后快照不自动跟上。 系统盘从 50G 扩到 100G,旧快照仍然是 50G 的视图,恢复数据没问题,但别指望它能还原完整新状态。重大变更后重新做一次基线快照最稳妥。
坑六:回滚方向选错。 快照列表里挂着好几个,关机状态下回滚一旦选错时间点,等于把系统又往回推了一截。我们的做法是给快照打标签时直接写明触发事件,比如 release-20260922-pre,一眼就知道这是哪次发布前的存档,回滚时绝不靠猜。别小看命名,出事时手都在抖,越是那个时刻越需要不动脑子的信息。
发布当天我们最终形成的动作是:先关机做发布前快照,再开机灰度;出问题就关机回滚到该快照,五分钟实例恢复。配合 安全组 的流量控制,回滚期间可以把入口流量切走,用户几乎无感知。这套组合拳在 灰度发布一节 还会升格成完整方案,这里先埋个伏笔。
镜像版本管理
机器多了之后镜像也会泛滥。我们定了三条规则:镜像名带日期和语义(如 order-base-20260920),保留最近三个版本,其余归档删除;每个镜像在 wiki 有一张对应表,记录里面装了什么;开新机必须指定镜像版本,禁止随手选同名旧版本。规则简单,但避免了三个月后对着十几个同名镜像发呆的惨剧。
镜像之外还有一层数据保护:业务数据盘另外挂载并独立做快照,和系统盘分开管理。这样回滚系统盘时业务数据不动,配置回退与数据回退解耦,炸伤范围可控。想象一下如果系统盘和数据盘混在一起,回滚把订单数据也带回半小时前,那才是真正的灾难。
最后给一张我们实际在用的发布检查单,照着勾就能减少八成低级事故:
| 检查项 | 时机 | 负责人 |
|---|---|---|
| 系统盘发布前快照 | 关机前 | 发布人 |
| 业务数据盘快照 | 发布前 | DBA 兼 |
| 镜像版本号记录 | 开新机时 | 运维 |
| 回滚预案写明快照名 | 发布会前 | 发布人 |
| 快照保留三个版本 | 每周 | 运维 |
镜像版本管理之外,还有一条硬规矩:任何回滚动作完成后,必须写一条事件记录,包含回滚时间段、影响的快照版本、触发的根因。这份记录是后面复盘会的输入,也是判断“回滚机制是否够快”的客观数据。靠记忆复盘只会美化历史,靠日志复盘才能看到真相。这一点在我们后来建设 CLS 统一日志 时也反复印证过。
小结
镜像与快照解决的是“机器层面的可逆性”:快照保底,让任何变更都能退回;镜像沉淀,让每次扩容都站在上一次验证过的状态上。回滚能力不是奢侈品,而是上线信心的一部分。第一次四十分钟的手忙脚乱和第二次八分钟的从容,中间差的不是技术,是把这些不起眼的操作提前固化成流程。机器和环境都标准化之后,真正让人睡不着觉的就只剩一件事:这台机器到底向公网开了哪些口子——这正是 下一节安全组与 VPC 的主题。
