高 进阶
后端部署发布与回滚实战#
一句话答案#
后端发布核心是「小批量+可观测+秒回滚」:金丝雀放量观察核心指标,异常立即 rollback;回滚要区分代码/配置/DB/依赖四种路径,每种都有对应 SOP。
核心要点
一、发布全流程
代码合并 → CI构建(单测+镜像) → 推送镜像仓库 → 部署Staging验证
→ 金丝雀(5%流量) → 观察15min → 扩到30% → 观察15min → 全量发布plaintext每个阶段的门禁:
- CI 阶段:单测通过率100%、代码扫描无P0漏洞、镜像体积检查
- Staging:核心接口冒烟测试通过、无新增ERROR日志
- 金丝雀:P99延迟无劣化、错误率无上升、业务指标无异常
二、三种发布策略对比
| 策略 | 原理 | 回滚速度 | 风险 | 适用场景 |
|---|---|---|---|---|
| 滚动发布 | 逐个替换Pod | 分钟级 | 新旧版本共存时间长 | 常规无破坏性变更 |
| 蓝绿发布 | 两套环境切流 | 秒级(切回旧环境) | 资源成本翻倍 | 核心服务大版本升级 |
| 金丝雀发布 | 小流量验证再扩量 | 秒级(摘除canary) | 需要流量染色能力 | 高频迭代、需精细验证 |
三、回滚决策 SOP
收到告警/发现异常
│
├─ 判断是否刚发布(15min内有变更)
│ ├─ 是 → 立即回滚,再排查
│ └─ 否 → 先排查根因
│
├─ 确认回滚类型
│ ├─ 代码变更 → kubectl rollout undo deployment/xxx
│ ├─ 配置变更 → Nacos/Apollo 回退版本(秒级生效)
│ ├─ DB 变更 → 执行预备的反向 migration
│ └─ 依赖方变更 → 切换备用地址/启用降级
│
└─ 回滚后验证
├─ 核心指标恢复正常
├─ 无新增异常日志
└─ 写回滚复盘记录plaintext四、发布事故案例集
案例1:金丝雀 OOM 导致批量 Pod 重启
- 现象:金丝雀节点频繁重启,内存持续增长
- 原因:新代码引入内存泄漏(未关闭的Stream),5%流量足以触发
- 排查:
kubectl top pod看内存趋势 →jmap导出堆 → MAT 分析 - 处置:立即摘除金丝雀Pod,修复代码后重新发布
- 教训:金丝雀阶段必须监控内存趋势,不能只看错误率
案例2:DB Migration 导致接口超时
- 现象:全量发布后核心接口P99从50ms飙到3s
- 原因:新增索引的DDL锁表,加上新代码依赖该索引但索引还未建完
- 排查:
SHOW PROCESSLIST发现大量Waiting for table metadata lock - 处置:kill DDL → 代码回滚 → 非高峰期用pt-online-schema-change重建索引
- 教训:DB变更与代码变更要解耦,先加索引再发代码
案例3:配置推送灰度不生效引发全量故障
- 现象:修改超时配置,预期只推5台机器,实际全量生效导致下游被打爆
- 原因:配置中心命名空间混淆,推送了default namespace
- 排查:配置中心操作审计日志
- 处置:立即回退配置版本(秒级)
- 教训:配置变更也需要灰度能力,不同环境严格隔离namespace
案例4:依赖方升级导致序列化不兼容
- 现象:发布后部分接口返回500,日志报序列化异常
- 原因:上游服务升级了protobuf版本,新增字段unknown处理方式变了
- 排查:对比发布前后的RPC调用日志,定位到特定字段解析失败
- 处置:依赖方回滚 + 本方增加兼容性解析逻辑
- 教训:依赖方升级要联调验证,protobuf要开启unknown字段保留
五、发布相关监控指标
| 指标 | 含义 | 告警阈值 |
|---|---|---|
| error_rate_delta | 发布前后错误率变化 | >0.5% |
| latency_p99_delta | 发布前后P99变化 | >50% |
| pod_restart_count | Pod重启次数 | >2次/5min |
| rollback_count | 回滚次数 | >0(通知) |
| deployment_duration | 发布耗时 | >10min |
六、Graceful Shutdown 与流量摘除
# K8s Pod 优雅关闭配置
lifecycle:
preStop:
exec:
command: ["sh", "-c", "sleep 5"] # 等待Service摘除endpoints
terminationGracePeriodSeconds: 30 # 给应用30s清理时间yaml关闭顺序:
- K8s 将 Pod 标记为 Terminating
- Service 从 endpoints 中摘除该 Pod(几秒延迟)
- preStop hook 执行(等待摘除完成)
- 发送 SIGTERM,应用开始优雅关闭
- 拒绝新请求,完成进行中请求
- 关闭连接池、释放资源
- 超过 terminationGracePeriodSeconds 则 SIGKILL
面试回答(2分钟版)
我们的发布流程是金丝雀模式。代码合并后CI自动构建镜像、跑单测和扫描,通过后部署到Staging做冒烟验证。验证通过进入金丝雀阶段,先放5%流量到新版本,观察15分钟看P99延迟、错误率和核心业务指标有无劣化,没问题再扩到30%,最终全量。回滚方面我会区分四种情况:代码问题用kubectl rollout undo秒级回滚;配置问题走配置中心版本回退;数据库变更提前准备反向脚本;依赖方问题启用降级或切备用地址。我们有个原则是”发布后15分钟内出现异常先回滚再排查”,不在故障现场犹豫。发布完成后持续观察发布前后的指标delta,重点看错误率变化和延迟劣化。Pod的优雅关闭也很重要,通过preStop hook等待流量摘除完成再关进程,避免请求打到正在关闭的实例。
追问与易错
追问方向:
- “金丝雀怎么做流量染色?”→ 网关层在请求 Header 中标记版本标签(如 x-canary=true),通过 Istio VirtualService 或 Nginx weight 将带标签的流量路由到金丝雀 Pod
- “回滚后数据不一致怎么办?”→ 新版本期间写入的数据需要评估影响范围,可能需要补偿脚本清理/修正脏数据;关键是发布前制定数据回滚预案
- “数据库变更怎么做到可回滚?”→ 只做加法不做减法(加字段不删旧字段),用 pt-online-schema-change 避免锁表,大表变更走双写过渡期,提前准备反向 migration 脚本
- “多服务同时发布依赖顺序怎么管?”→ 按依赖图拓扑排序发布(被依赖方先发),接口保持向前兼容(新版本兼容旧调用方),必要时通过版本协商确保兼容
- “发布频率和稳定性怎么平衡?”→ 设定固定发布窗口(如每周二/四下午),大促前设变更冻结期,CI/CD 流水线自动化门禁(测试覆盖率/代码扫描)保证质量
易错点:
- ❌ “回滚就是重新部署旧版本”——应该用rollout undo保留历史,而不是手动改镜像tag
- ❌ “DB migration可以随便回滚”——删字段、改类型等破坏性变更通常不可逆
- ❌ “金丝雀没问题就全量”——要观察足够长时间,内存泄漏等问题可能延迟暴露
- ✅ 发布=变更,所有变更(代码/配置/DB/依赖)都要有对应回滚预案