【架构实战】蓝绿部署:零停机发布的工程实践
【架构实战】蓝绿部署:零停机发布的工程实践
一、背景:每一次重启都是一场冒险
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 weight=100; server 10.0.1.2:8080 weight=100; server 10.0.1.3:8080 weight=100; } upstream backend_green { server 10.0.2.1:8080 weight=0; # 初始权重为0 server 10.0.2.2:8080 weight=0; server 10.0.2.3:8080 weight=0; } server { listen 80; location / { # 默认走Blue环境 proxy_pass http://backend_blue; } }切换脚本:
#!/bin/bash# blue-green-switch.shecho"=== 蓝绿部署流量切换 ==="# 1. Green环境预热(先发少量流量验证)echo"1/4 Green环境预热..."sed-i's/weight=0/weight=5/g'/etc/nginx/conf.d/upstream.conf nginx-sreloadsleep30# 2. 检查Green环境健康状态echo"2/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"];thenecho"Green环境节点${i}不健康,终止切换"exit1fidone# 3. 全量切换流量echo"3/4 全量切换到Green环境..."# 将Blue的weight设为0sed-i'/backend_blue/,/}/{s/weight=100/weight=0/g}'/etc/nginx/conf.d/upstream.conf# 将Green的weight设为100sed-i'/backend_green/,/}/{s/weight=5/weight=100/g}'/etc/nginx/conf.d/upstream.conf nginx-sreload# 4. 确认切换完成echo"4/4 确认流量切换..."sleep10BLUE_COUNT=$(tail-1000/var/log/nginx/access.log|grep"backend_blue"|wc-l)GREEN_COUNT=$(tail-1000/var/log/nginx/access.log|grep"backend_green"|wc-l)echo"Blue请求:${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切回Blue)kubectl 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)DEFAULT'NORMAL';-- 错误示例:直接修改字段类型ALTERTABLEordersMODIFYCOLUMNamountDECIMAL(14,4);-- V1代码期望DECIMAL(12,2),V2期望DECIMAL(14,4),同时运行会出错3.3 缓存兼容性处理
蓝绿部署中,新老版本可能操作同一个Redis Key,数据结构不兼容会导致序列化异常。
// 解决方案:版本化缓存KeypublicclassCacheKeyBuilder{privatestaticfinalStringCACHE_VERSION="v2";// 应用版本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}-blue"GREEN_NS="${APP_NAME}-green"echo"========================================="echo" 蓝绿部署:${APP_NAME}->${VERSION}"echo"========================================="# 1. 确定当前活跃环境CURRENT=$(kubectl get svc ${APP_NAME}-ojsonpath='{.spec.selector.version}')if["$CURRENT"=="blue"];thenACTIVE=$BLUE_NSSTANDBY=$GREEN_NSNEW_COLOR="green"elseACTIVE=$GREEN_NSSTANDBY=$BLUE_NSNEW_COLOR="blue"fiecho"当前活跃:$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}--timeout=300s# 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-s"http://prometheus:9090/api/v1/query?query=rate(http_requests_total{app=\"${APP_NAME}\",status=~\"5..\"}[1m])")echo"当前错误率:${ERROR_RATE}"echo"========================================="echo" 蓝绿部署完成:${APP_NAME}${VERSION}"echo" 旧版本保留在${ACTIVE},可用于回滚"echo"========================================="4.2 回滚脚本
#!/bin/bash# 一键回滚APP_NAME=$1echo"=== 紧急回滚${APP_NAME}==="# 获取当前版本CURRENT=$(kubectl get svc ${APP_NAME}-ojsonpath='{.spec.selector.version}')# 切回上一个版本if["$CURRENT"=="blue"];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要版本化隔离
- 需要自动化的部署和切换工具
适用场景:
- 重大版本升级(不兼容变更)
- 需要快速回滚的核心服务
- 有充足资源预算的团队
不适用场景:
- 资源紧张的小团队
- 有状态服务(数据库、消息队列)
- 数据库变更频繁且无法保证兼容性
蓝绿部署不是银弹,但它是保障发布安全的最后一道防线。如果你只能选一种发布策略来保护核心服务,就选蓝绿部署。
个人观点,仅供参考
