一、一次全量发布差点搞垮公司
2020年,我们上线了一个新的支付模块,直接全量发布。结果10分钟后开始出现"双重扣款"的bug。
回滚花了7分钟,那7分钟又产生了几十笔问题订单。
从那以后,"灰度发布"成了铁律。
二、灰度发布策略
2.1 策略类型
┌─────────────────────────────────────────────────────────────────┐ │ 灰度发布策略 │ │ │ │ 1. 按比例灰度 │ │ - 1% → 5% → 10% → 50% → 100% │ │ - 适合一般业务发布 │ │ │ │ 2. 按用户灰度 │ │ - 内部用户 → 白名单用户 → 全量 │ │ - 适合有明确用户分组的业务 │ │ │ │ 3. 按地域灰度 │ │ - 杭州 → 上海 → 北京 → 全国 │ │ - 适合地域性强的业务 │ │ │ │ 4. 按功能开关 │ │ - 新功能默认关闭,逐步开启 │ │ - 适合新功能上线 │ │ │ │ 5. A/B测试 │ │ - 两套方案同时运行,对比效果 │ │ - 适合产品实验 │ │ │ └──────────────────────────────────────────────────────────────────┘三、Kubernetes灰度发布
3.1 基于Weight的灰度
# 稳定版本apiVersion:v1kind:Servicemetadata:name:order-servicespec:selector:app:order-serviceversion:stableports:-port:8080---# 灰度版本apiVersion:v1kind:Servicemetadata:name:order-service-canaryspec:selector:app:order-serviceversion:canaryports:-port:8080---# Istio VirtualService - 10%流量到灰度apiVersion:networking.istio.io/v1alpha3kind:VirtualServicemetadata:name:order-servicespec:hosts:-order-servicehttp:-route:-destination:host:order-servicesubset:stableweight:90-destination:host:order-servicesubset:canaryweight:103.2 灰度发布脚本
/** * 灰度发布服务 */@Service@Slf4jpublicclassCanaryDeployService{@AutowiredprivateKubernetesClientk8sClient;@AutowiredprivateMonitoringServicemonitoringService;/** * 执行灰度发布 */publicvoidcanaryDeploy(Stringservice,StringnewImage,CanaryStrategystrategy){log.info("开始灰度发布: service={}, newImage={}",service,newImage);// 1. 部署灰度版本deployCanary(service,newImage,strategy.getInitialWeight());// 2. 等待灰度版本就绪waitForReady(service,"canary");// 3. 观察指标observeMetrics(service,strategy);log.info("灰度发布完成: service={}",service);}/** * 观察灰度指标 */privatevoidobserveMetrics(Stringservice,CanaryStrategystrategy){intcurrentWeight=strategy.getInitialWeight();while(currentWeight<100){// 等待观察期sleep(strategy.getObserveDuration());// 检查灰度版本指标Metricsmetrics=monitoringService.getServiceMetrics(service,"canary");if(metrics.getErrorRate()>strategy.getMaxErrorRate()){log.error("灰度版本错误率过高: {}%",metrics.getErrorRate());rollback(service);thrownewRuntimeException("灰度发布失败,已回滚");}if(metrics.getP99Latency()>strategy.getMaxP99Ms()){log.error("灰度版本延迟过高: {}ms",metrics.getP99Latency());rollback(service);thrownewRuntimeException("灰度发布失败,已回滚");}// 指标正常,增加灰度比例currentWeight=Math.min(100,currentWeight*2);adjustWeight(service,currentWeight);log.info("灰度比例调整: {}%",currentWeight);}}/** * 回滚 */publicvoidrollback(Stringservice){// 将所有流量切回稳定版本adjustWeight(service,0);// 删除灰度版本deleteCanary(service);log.warn("灰度发布已回滚: service={}",service);}}四、踩坑实录
坑1:灰度比例跳跃太大
直接从5%跳到50%,出了问题影响面太大。
解决:灰度比例倍增(5→10→20→40→80→100),每步观察。
坑2:灰度期间没有监控
灰度版本出了问题但没发现,全量后才知道。
解决:灰度期间对比新旧版本的关键指标。
坑3:灰度版本和数据库不兼容
新版本的数据库Schema和旧版本不兼容。
解决:数据库变更要向前兼容,先加字段后删字段。
坑4:灰度版本影响缓存
新旧版本写入的缓存格式不同,互相覆盖。
解决:缓存Key加版本号,或保证向后兼容。
坑5:回滚不彻底
回滚了应用但没回滚配置,导致功能异常。
解决:回滚检查清单,应用+配置+数据一起回滚。
五、总结
灰度发布最佳实践:
| 原则 | 说明 |
|---|---|
| 小步快跑 | 1%→5%→10%→50%→100% |
| 对比监控 | 新旧版本指标对比 |
| 自动回滚 | 错误率超阈值自动回滚 |
| 向前兼容 | 数据库变更要兼容 |
| 完整回滚 | 应用+配置+数据一起回滚 |
血的教训:
每一次全量发布都是在赌命。灰度发布不是浪费时间,是买保险。
思考题:你的团队是怎么做灰度发布的?
个人观点,仅供参考