记一次生产服务变更验收Checklist落地:预检、灰度与回滚三道闸

生产服务变更靠口头发版易漏验,把预检、灰度、回滚做成可勾选三道闸,把可发布变成可证明。

问题背景

上半年接连两起「变更本身逻辑没错、验收却没挡住」的生产事故:一次配置中心键名写错,全量发布后接口 5xx 飙升,值班靠人眼发现;一次数据库索引变更窗口只跑了「SQL 执行成功」,却没做慢查询与连接池回归,早高峰报表被拖死。复盘会上有人说「我们其实都知道该查什么」,只是每次靠记忆与口头催,赶时间就会漏。

与「备份 Job 全绿却还原不出业务」类似:监控绿灯和变更单勾选,往往只证明「做了动作」,不证明「业务仍可用」。服务运维侧缺的不是再多一条发布脚本,而是一份能落地的生产服务变更验收 Checklist——把预检、灰度、回滚做成固定三道闸,结单前必须留下可审计证据。

故障现象

典型「变更后难受」往往不是秒级雪崩,而是分层漏出:

  1. 发布流水线全绿:构建、镜像推送、滚动更新进度 100%,Deployment AVAILABLE,变更单被提前点完成。
  2. 核心路径偶发超时:首页与登录正常,下单、导出、对账等重路径在灰度后几分钟到半小时才开始告警。
  3. 依赖侧「看起来没事」:数据库 CPU 不高、消息队列无积压,但连接池等待、缓存命中率、下游 429 被忽略。
  4. 回滚犹豫:没有预置一键回退版本号/配置快照,值班在「再观察五分钟」与「立刻回滚」之间拉扯,MTTR 被人为拉长。
  5. 复盘只能讲故事:聊天记录里有「发了」「好像好了」,却没有预检截图、灰度比例、冒烟用例结果与回滚演练记录。

这类现象的共同点是:变更被当成部署事件,而不是「可证明的可用性事件」

排查过程

1. 先对齐:变更失败的分型,而不是先骂人

把近一年服务侧变更事故按「闸门失效位置」归三类,比按技术栈归类更有用:

失效闸门 典型漏项 结果
预检闸 配置键、权限、磁盘/fd/连接池水位、依赖版本未核对 带病上线
灰度闸 一次全量、只看进程存活、不做核心路径冒烟 爆炸半径全站
回滚闸 无版本锚点、有状态变更不可逆、无人有权执行 MTTR 失控

结论:清单必须按「闸」组织,而不是按「中间件名词」堆砌命令。

2. 第一道闸:预检(Change Readiness)

发布前 30~60 分钟固定做,全部为可通过/不通过,不通过不得点发布:

A. 变更对象清单(What)

  • 本次变更的服务名、镜像 tag / 包版本、配置项 diff(含配置中心键)已贴到变更单
  • 是否含 DDL / 数据修复 / 缓存键格式变更 / 消息格式变更(有状态变更必须单独标红)
  • 依赖方向:上游谁会先打到新版本、下游哪些 API 契约是否变化
  • 回滚版本号与配置快照 ID 已写死(不是「上一版好像是…」)

B. 容量与资源水位(Capacity)

1
2
3
4
5
6
7
8
# 进程与 fd(按实际 unit/pid 替换)
systemctl show your-app -p MainPID
cat /proc/$(systemctl show your-app -p MainPID --value)/limits | grep 'open files'
ls /proc/$(systemctl show your-app -p MainPID --value)/fd | wc -l

# 磁盘与 inode
df -h / /var /data
df -i / /var /data
  • 磁盘/inode 使用率 < 阈值(建议预留下次日志+镜像空间)
  • 文件描述符、线程数、连接池 active/idle 处于健康带
  • 发布窗口不与全量备份、大促压测、证书轮换撞车

C. 观测与告警(Observability)

  • 本次服务的 RED/USE 看板链接写入变更单(QPS、错误率、P95/P99、饱和度)
  • 关键告警接收人在线;静默规则若开启必须写明结束时间
  • 日志检索关键字(错误码、新版本标识)已验证能搜到

D. 有状态变更额外预检(Stateful Extra)

  • DDL 是否在线可做、锁等待预估、是否需要 pt-osc/等价工具
  • 迁移脚本支持 向前兼容一个版本(双写/双读窗口说明)
  • 备份点或快照已打,并记录恢复演练负责人(呼应可恢复性,而不是「备份 Job 绿」)

预检不通过的常见硬拦:配置 diff 未审、回滚锚点缺失、关键告警静默无截止时间、有状态变更无兼容窗口说明。

3. 第二道闸:灰度与冒烟(Progressive Delivery)

预检通过后,禁止「一次切 100%」成为默认路径。最小可执行灰度模型:

  1. 金丝雀 / 小流量:1%~5% 或 1 个实例,观察 ≥ 1 个业务峰值切片(至少 10~15 分钟,不能只看 2 分钟)
  2. 半流量:25%~50%,再跑同一套冒烟
  3. 全量:仅前两阶段门禁全绿才放行

业务冒烟矩阵(必须人工或自动化勾选,禁止只看 HTTP 200):

路径类型 示例 验收点
读多路径 登录、列表、详情 错误率、P95、缓存命中
写多路径 下单、支付回调、审批提交 成功落库、幂等、重复提交
异步路径 发消息、出库任务、报表生成 入队成功、消费延迟、死信
管理路径 导出、批量、对账 超时、连接池、磁盘

灰度期技术观察(勾选即截图或贴链接):

1
2
3
4
5
[ ] 错误率未超过基线 × N(如 1.5 倍或绝对阈值)
[ ] P99 未越基线(对比同时段昨天/上周)
[ ] 依赖错误(DB/Redis/HTTP 下游)无新尖刺
[ ] 无异常重启、OOM、线程池拒绝、连接池耗尽日志
[ ] 新版本实例日志无「配置键缺失 / 权限拒绝 / 迁移未执行」类致命栈

半通现象单独门禁:若出现「读通写不通」「内网通公网不通」「老客户端通新客户端不通」,按网络/契约问题升级,而不是「再观察一下」——这类半通最容易被平均成功率掩盖。

4. 第三道闸:回滚(Rollback Readiness,发布前就要能演)

回滚不是出事时再设计的选项,而是预检就必须可执行的动作:

变更类型 推荐回滚方式 禁止依赖
无状态服务镜像/包 上一 tag 滚动 / 流量切回 手工改代码热修当第一手段
配置中心 回滚到变更前 revision 「再改一版配置试试」拖时间
兼容性 DDL 双版本窗口内停写新列/新表路径 未验证的 drop 反向脚本
不兼容数据变更 变更窗口内禁止上线;或预置可逆脚本+备份点 边写数据边赌

回滚检查清单:

  • 回滚指令(流水线一键 / helm rollback / 上一镜像 tag)已粘贴且权限人在场
  • 回滚后冒烟用例与灰度冒烟同一套(避免「滚回去但核心路径没验」)
  • 有状态回滚边界已写明:哪些数据会丢、如何补偿
  • 决策人:错误率/P99 连续 N 分钟越阈即强制回滚,不靠群聊投票

5. 结单门禁:没有证据就不算完成

变更单从「已部署」改成「已验收」必须附:

  1. 预检勾选表(或流水线预检 job 日志)
  2. 灰度比例时间线与看板截图/链接
  3. 冒烟矩阵结果(通过/失败项)
  4. 若触发回滚:回滚时刻、版本、二次冒烟结果
  5. 遗留项与跟进单(技术债不得只写在聊天里)

没有以上附件,变更单不得关单——这与「备份要以 last_successful_restore 度量」是同一哲学:过程指标不能替代结果指标

解决方案

落地时不要一上来做沉重平台,先用「一页清单 + 流水线三个挡板」跑一个季度:

1. 文档与模板

  • 在知识库固定《生产服务变更验收 Checklist》页面,变更单强制链接该版本号(防止私下删减条目)。
  • 按风险分级:低风险(文案/配置无状态)、中风险(无状态版本)、高风险(数据/契约/权限)套用不同必选项;高风险三道闸全开且拉双人复核。

2. 流水线挡板(示例语义)

1
2
3
4
5
6
7
# 示意:真实工具链替换为你们的 CI/CD
stages:
  - precheck      # 配置 diff、镜像签名、水位脚本、回滚锚点存在性
  - canary        # 1%~5% + 自动冒烟
  - soak          # 观察窗口,失败自动阻断
  - full          # 人工或自动放量
  - postcheck     # 结单证据打包(看板链接、冒烟报告)
  • precheck 失败:禁止进入 canary。
  • canary 冒烟失败:自动回滚 canary,不得手搓「再发一次全量碰运气」。
  • postcheck 未生成证据包:变更单 API 拒绝关单。

3. 组织上的最小制度

  • 变更窗口:生产默认窗口外仅允许 P0 热修,热修仍走精简三道闸。
  • 角色:执行人、验收人分离;验收人负责勾选冒烟,不得由纯部署机器人代勾。
  • 回顾:每月抽 5 张关单变更,核对证据完整率;完整率 < 95% 回炉培训,而不是再写一份更长的规范。

4. 与已有能力对齐

  • 可恢复性:涉及数据的变更,预检必须指向备份/快照与还原负责人(参见备份演练教训)。
  • 暴露面:若变更打开端口、放宽安全组、新公网入口,必须附带暴露面条目(临时规则要有 TTL)。
  • 健康检查:新版本探针/依赖就绪与发布策略一致,避免「进程在、业务未就绪」被灰度当成成功。

根因分析

表面根因是某次配错键、某次索引变更;结构根因是三句话:

  1. 把部署成功等同于变更成功——流水线绿、进程 Ready,只是必要非充分条件。
  2. 验收知识在人脑里,不在闸门上——忙时被压缩的永远是冒烟与回滚准备。
  3. 缺少强制证据与自动阻断——没有结单附件与失败即停,清单会迅速退化成形式主义。

方法论价值在于:把「老运维知道该看什么」编译成可复制的三道闸,让新人按表执行也不至于漏掉爆炸半径控制。

预防措施

  1. 默认灰度:无状态服务禁止「一刀切全量」为默认按钮;特例需总监级备注原因。
  2. 冒烟资产化:每个核心服务维护 ≥ 5 条可自动跑的业务冒烟(含一条写路径);变更只引用,不即兴口测。
  3. 回滚演练:每季度在预发做一次「故意失败触发回滚」,确认指令、权限、耗时。
  4. 有状态变更双周评审:DDL/契约变更单独例会,强制兼容窗口与回滚边界。
  5. 指标双看:发布看板同时展示版本错误率对比与依赖错误率,避免只看本服务 CPU。
  6. 清单版本化:Checklist 变更走评审;禁止个人维基私改生产门禁。
  7. 与值班手册挂钩:夜班热修包同样附带精简三道闸卡片(预检 5 项 + 冒烟 3 项 + 回滚 1 键)。

总结

生产服务变更最贵的不是多写几条检查命令,而是在压力下仍然会被执行的门禁。预检解决「带病上线」,灰度冒烟解决「爆炸半径与真业务可用性」,回滚闸解决「错了能不能快点回去」;结单证据则让这一切可审计,而不是事后靠印象复盘。

把《生产服务变更验收 Checklist》嵌进变更单与流水线之后,团队讨论焦点会从「谁昨晚手滑」转向「哪一闸数据不足、哪条冒烟该自动化」——这才是服务运维从救火走向工程化的标志。下一次大版本,先问三句:预检谁签?灰度怎么切?回滚键在谁手里?

使用 Hugo 构建
主题 StackJimmy 设计