【架构实战】蓝绿部署零停机发布的工程实践一、背景每一次重启都是一场冒险2019年我们支付系统有次发版运维凌晨3点手动操作通知Nginx摘除一台机器等5分钟确认没有未完成的请求停服务 → 替换Jar包 → 重启 → 健康检查 → 挂回Nginx重复以上操作4台机器总共花了40分钟期间系统处理能力下降到25%。那晚差点超时触发银行的对账异常。更离谱的是有一次运维在第三步替换Jar包时复制错了文件把测试环境的包放到了线上。用户支付成功后余额没扣——白送了一天。每一次发布都是一次赌博赌服务重启不超时、赌新版本没Bug、赌操作不出错。蓝绿部署的核心思想很简单准备两套完整环境切换流量而非更新服务。二、蓝绿部署的核心原理2.1 什么是蓝绿部署┌─────────────────────────────────────────────────────────┐ │ 蓝绿部署 │ │ │ │ 当前线上环境Blue 备用环境Green │ │ ┌──────────────┐ ┌──────────────┐ │ │ │ 订单服务 V1 │ │ 订单服务 V2 │ │ │ │ 库存服务 V1 │ │ 库存服务 V2 │ │ │ │ 支付服务 V1 │ │ 支付服务 V2 │ │ │ └──────┬───────┘ └──────┬───────┘ │ │ │ │ │ │ ┌──────▼───────────────────────▼───────┐ │ │ │ 负载均衡器 │ │ │ │ (Nginx/Haproxy/Kong) │ │ │ └──────┬───────────────────────────────┘ │ │ │ │ │ 用户流量 │ └─────────────────────────────────────────────────────────┘ 发布流程 1. 当前 Blue 环境承载 100% 流量 2. 在 Green 环境部署新版本充分验证 3. 负载均衡器将流量从 Blue 切到 Green 4. Green 成为新 Blue旧 Blue 保留作为回滚环境2.2 蓝绿部署 vs 其他发布策略维度蓝绿部署金丝雀发布滚动更新切换速度秒级分钟-小时分钟-小时回滚速度秒级秒级分钟级资源成本2倍1.1-1.2倍正常适用场景重大版本升级日常发布无状态服务环境隔离完全隔离部分隔离无隔离数据库兼容需要注意需要注意需要注意三、蓝绿部署的关键技术实现3.1 负载均衡层切换方案一Nginx upstream 切换# nginx.conf upstream backend_blue { server 10.0.1.1:8080 weight100; server 10.0.1.2:8080 weight100; server 10.0.1.3:8080 weight100; } upstream backend_green { server 10.0.2.1:8080 weight0; # 初始权重为0 server 10.0.2.2:8080 weight0; server 10.0.2.3:8080 weight0; } server { listen 80; location / { # 默认走Blue环境 proxy_pass http://backend_blue; } }切换脚本#!/bin/bash# blue-green-switch.shecho 蓝绿部署流量切换 # 1. Green环境预热先发少量流量验证echo1/4 Green环境预热...sed-is/weight0/weight5/g/etc/nginx/conf.d/upstream.conf nginx-sreloadsleep30# 2. 检查Green环境健康状态echo2/4 检查Green环境...foriin{1..3};doSTATUS$(curl-s-o/dev/null-w%{http_code}http://10.0.2.${i}:8080/actuator/health)if[$STATUS!200];thenechoGreen环境节点${i}不健康终止切换exit1fidone# 3. 全量切换流量echo3/4 全量切换到Green环境...# 将Blue的weight设为0sed-i/backend_blue/,/}/{s/weight100/weight0/g}/etc/nginx/conf.d/upstream.conf# 将Green的weight设为100sed-i/backend_green/,/}/{s/weight5/weight100/g}/etc/nginx/conf.d/upstream.conf nginx-sreload# 4. 确认切换完成echo4/4 确认流量切换...sleep10BLUE_COUNT$(tail-1000/var/log/nginx/access.log|grepbackend_blue|wc-l)GREEN_COUNT$(tail-1000/var/log/nginx/access.log|grepbackend_green|wc-l)echoBlue请求:${BLUE_COUNT}, Green请求:${GREEN_COUNT}echo 切换完成 方案二Kubernetes Service切换# Blue环境 ServiceapiVersion:v1kind:Servicemetadata:name:order-service-bluelabels:app:orderversion:bluespec:selector:app:orderversion:v1# 老版本ports:-port:8080---# Green环境 ServiceapiVersion:v1kind:Servicemetadata:name:order-service-greenlabels:app:orderversion:greenspec:selector:app:orderversion:v2# 新版本ports:-port:8080---# 主 Service通过修改selector切换apiVersion:v1kind:Servicemetadata:name:order-service# 对外暴露的Service名不变spec:selector:app:orderversion:v1# 切换时仅改这个字段ports:-port:8080切换命令# 从Blue切换到Greenkubectl patchserviceorder-service-p{spec:{selector:{version:v2}}}# 回滚从Green切回Bluekubectl patchserviceorder-service-p{spec:{selector:{version:v1}}}3.2 数据库兼容性处理蓝绿部署最大的坑不是服务是数据库。新老版本可能同时操作同一张表Schema不兼容就会出问题。错误示例 V1: ALTER TABLE orders ADD COLUMN status VARCHAR(20); -- 新增字段 V2: ALTER TABLE orders DROP COLUMN status; -- 删除字段 结果V1和V2同时在跑V1依赖status字段 → 报错 正确做法数据库变更独立于应用发布分两步走 第一步兼容阶段 V1 V2 同时支持 status和new_status 两个字段 第二步清理阶段 确认不需要回滚到V1后再删除 status 字段数据库变更黄金法则只新增不修改不删除新增字段必须有默认值字段重命名 新增 双写 老字段延迟删除数据库变更和应用发布分开执行-- 正确示例新增字段ALTERTABLEordersADDCOLUMNcoupon_typeVARCHAR(20)DEFAULTNORMAL;-- 错误示例直接修改字段类型ALTERTABLEordersMODIFYCOLUMNamountDECIMAL(14,4);-- V1代码期望DECIMAL(12,2)V2期望DECIMAL(14,4)同时运行会出错3.3 缓存兼容性处理蓝绿部署中新老版本可能操作同一个Redis Key数据结构不兼容会导致序列化异常。// 解决方案版本化缓存KeypublicclassCacheKeyBuilder{privatestaticfinalStringCACHE_VERSIONv2;// 应用版本publicstaticStringbuildKey(Stringentity,Stringid){returnString.format(%s:%s:%s,entity,CACHE_VERSION,id);}}// V1缓存Key: user:v1:123// V2缓存Key: user:v2:123// 新老版本互不影响避免序列化冲突四、蓝绿部署实战脚本4.1 完整的蓝绿部署流程#!/bin/bash# 完整的蓝绿部署脚本set-eAPP_NAME$1VERSION$2ENV${3:-prod}BLUE_NS${APP_NAME}-blueGREEN_NS${APP_NAME}-greenechoecho 蓝绿部署:${APP_NAME}-${VERSION}echo# 1. 确定当前活跃环境CURRENT$(kubectl get svc ${APP_NAME}-ojsonpath{.spec.selector.version})if[$CURRENTblue];thenACTIVE$BLUE_NSSTANDBY$GREEN_NSNEW_COLORgreenelseACTIVE$GREEN_NSSTANDBY$BLUE_NSNEW_COLORbluefiecho当前活跃:$ACTIVE, 目标环境:$STANDBY# 2. 部署新版本到备用环境echo部署${VERSION}到${STANDBY}...kubectlsetimage deployment/${APP_NAME}\${APP_NAME}registry.company.com/${APP_NAME}:${VERSION}\-n${STANDBY}# 3. 等待就绪echo等待新版本就绪...kubectl rollout status deployment/${APP_NAME}-n${STANDBY}--timeout300s# 4. 健康检查echo健康检查...PODS$(kubectl get pods-n${STANDBY}-lapp${APP_NAME}-ojsonpath{.items[*].status.podIP})forPODin$PODS;doHEALTH$(curl-s-o/dev/null-w%{http_code}http://${POD}:8080/actuator/health)if[$HEALTH!200];thenecho节点${POD}不健康exit1fidoneecho所有节点健康 ✓# 5. 预热可选先发送少量流量echo预热备用环境...# 通过Istio注入1%测试流量...# 6. 切换流量echo切换流量到${STANDBY}...kubectl patchservice${APP_NAME}-p{\spec\:{\selector\:{\version\:\${NEW_COLOR}\}}}# 7. 验证echo验证切换结果...sleep10ERROR_RATE$(curl-shttp://prometheus:9090/api/v1/query?queryrate(http_requests_total{app\${APP_NAME}\,status~\5..\}[1m]))echo当前错误率:${ERROR_RATE}echoecho 蓝绿部署完成:${APP_NAME}${VERSION}echo 旧版本保留在${ACTIVE}可用于回滚echo4.2 回滚脚本#!/bin/bash# 一键回滚APP_NAME$1echo 紧急回滚${APP_NAME}# 获取当前版本CURRENT$(kubectl get svc ${APP_NAME}-ojsonpath{.spec.selector.version})# 切回上一个版本if[$CURRENTblue];thenkubectl patchservice${APP_NAME}-p{spec:{selector:{version:green}}}elsekubectl patchservice${APP_NAME}-p{spec:{selector:{version:blue}}}fiecho已切回上一个版本请检查监控五、蓝绿部署的进阶技巧5.1 数据库迁移独立于发布时间线 T0: 执行数据库变更脚本只新增字段不删除 T1: 部署Green环境V2版本 T2: 切换流量到Green T3: 观察30分钟确认无问题 T4: 执行数据库清理脚本删除旧字段← 此时已确认不回滚5.2 蓝绿 金丝雀组合蓝绿部署也可以结合金丝雀策略先切10%流量到Green → 观察 → 再全量切换 实现 Step 1: Nginx将10%流量路由到Green upstream Step 2: 观察15分钟 Step 3: 将100%流量切到Green5.3 基础设施即代码# Terraform 管理蓝绿环境resource kubernetes_namespace blue{metadata{name ${var.app_name}-blue}}resource kubernetes_namespace green{metadata{name ${var.app_name}-green}}# 每次部署在备用环境创建完整副本resource kubernetes_deployment standby{metadata{name var.app_name namespace var.target_namespace# blue 或 green}# ... 部署配置}六、总结蓝绿部署的核心价值零停机流量切换而非服务重启用户无感知秒级回滚出问题切回旧环境比重新部署快100倍充分验证新环境可以充分测试后再切流量操作简单不需要复杂的灰度规则切换一个路由即可蓝绿部署的代价需要两倍硬件资源或至少1.5倍数据库Schema要兼容新老版本缓存Key要版本化隔离需要自动化的部署和切换工具适用场景重大版本升级不兼容变更需要快速回滚的核心服务有充足资源预算的团队不适用场景资源紧张的小团队有状态服务数据库、消息队列数据库变更频繁且无法保证兼容性蓝绿部署不是银弹但它是保障发布安全的最后一道防线。如果你只能选一种发布策略来保护核心服务就选蓝绿部署。个人观点仅供参考