Jenkins商业发布、灰度与回滚全过程
商业发布不是“新镜像启动成功就结束”。一次发布同时改变代码、镜像、配置、数据库结构、消息格式和流量归属;其中镜像容易回退,数据库和外部副作用可能不可逆。Jenkins的职责是把准入、渐进放量、观测、停止和恢复写成可审计流程,而不是用一段Shell赌生产。
学习目标
- 区分Build、Release、Deploy和Traffic Shift。
- 建立Commit、Build、Jar、镜像Digest、配置版本和发布记录的追踪链。
- 解释一次构建、多环境推广同一制品。
- 使用Expand→Migrate→Contract设计可回滚数据库变更。
- 保证新旧代码、消息生产者和消费者兼容。
- 比较滚动、蓝绿、金丝雀和单机替换策略。
- 设计readiness、冒烟、技术指标和业务指标闸门。
- 区分代码、配置、流量、数据库和数据回滚。
- 判断何时回滚、何时前滚修复。
- 完成发布失败、回滚失败和紧急止损Runbook。
一、四个动作先分清
| 动作 | 含义 | 产出/状态 |
|---|---|---|
| Build | 从固定源码生成制品 | Jar、镜像、SBOM、测试报告 |
| Release | 声明某制品通过准入可发布 | Release记录、Digest、审批 |
| Deploy | 把制品放进目标环境运行 | Deployment/容器/服务版本 |
| Traffic Shift | 把用户流量逐步导向新版本 | 灰度比例、路由、指标 |
部署完成不等于流量已经切换,流量切换也不等于发布成功。只有技术和业务指标在观察期内满足标准,才能把版本标记为稳定。
二、完整商业发布链
flowchart TD
A["固定Git Commit"] --> B["编译、单测、扫描"]
B --> C["构建一次不可变镜像"]
C --> D["推送Registry并记录Digest"]
D --> E["测试环境部署同一Digest"]
E --> F["集成、契约和安全测试"]
F --> G["生产发布审批"]
G --> H["数据库兼容变更"]
H --> I["创建生产新版本实例"]
I --> J["readiness与冒烟"]
J --> K["1%/10%/50%/100%渐进放量"]
K --> L{"技术与业务指标是否合格"}
L -- "否" --> M["停止放量并恢复"]
L -- "是" --> N["标记稳定版本并完成审计"]三、发布追踪链必须留下什么
Git Commit SHA
→ Jenkins Job与Build Number
→ 构建环境和JDK/Maven版本
→ Jar摘要
→ 镜像Tag与Digest
→ SBOM和扫描报告
→ 配置版本
→ 数据库迁移版本
→ 部署环境与时间
→ 审批人
→ 灰度比例与指标
→ 最终稳定/回滚结果Tag可以变化,Digest才是内容身份之一。prod-latest不能作为可审计发布依据。
四、一次构建、多环境推广
正确:
Commit A
→ 构建Image Digest X
→ 测试部署X
→ 预发部署X
→ 生产部署X错误:
测试重新build得到X
生产再次build得到Y即使源码Commit相同,基础镜像Tag、依赖仓库、时间戳和工具环境都可能让X与Y不同。审批的应是已验证Digest,而不是一个将来重新构建的分支名。
五、制品准入门槛
生产Release至少验证:
- Commit来自允许分支并经过评审。
- 单元测试、集成测试和契约测试通过。
- JDK 7/8项目固定具体更新版本和构建工具。
- 依赖和镜像漏洞在允许阈值内。
- SBOM生成并归档。
- 镜像使用非root、固定入口和受信基础镜像。
- 镜像Digest与签名可验证。
- 数据库迁移已评审。
- 配置、Secret和权限已准备。
- 回滚/前滚方案已演练或评估。
任何质量门被 catchError吞成绿色,都会让发布记录失真。
六、环境配置不能构建进镜像
同一镜像在测试和生产运行,环境差异通过受控配置注入:
- ConfigMap/Secret或配置中心。
- 环境变量,仅用于合适的小型配置。
- 只读配置文件。
- Workload Identity和短期凭据。
镜像中不能固化生产数据库密码、Token和私钥。配置也要版本化和审计,否则代码回滚后仍加载新配置,可能继续故障。
七、发布前配置兼容性
假设新版本把:
PAY_TIMEOUT_SECONDS改成:
PAY_TIMEOUT_MS滚动发布期间新旧实例并存。若配置中心只保留新Key,旧实例重启或回滚后读不到配置。
兼容做法:先同时提供旧Key和新Key,新版本优先新Key并兼容旧Key;全部稳定后再删除旧Key。
八、数据库为什么让代码回滚失效
危险发布:
先删除old_column
→ 发布只读取new_column的新代码
→ 新代码故障
→ 回滚旧代码
→ 旧代码仍读取old_column
→ 回滚后直接SQL报错代码版本可快速切换,数据库结构已经改变且承载新写入,通常不能像镜像Tag一样简单反向切换。
九、Expand→Migrate→Contract
9.1 Expand扩展
先做向后兼容的结构变更:新增列、表、索引或可空字段,不删除旧结构。
old_column继续存在
new_column新增
旧代码仍可运行9.2 Migrate迁移
发布兼容新旧结构的代码,执行双读/双写或后台数据回填:
新写入同时维护old/new
历史数据分批回填new
校验数量、摘要和业务结果双写仍可能一边成功一边失败,需要事务、重试、对账和补偿,不能只写两条SQL就声称一致。
9.3 Contract收缩
确认所有代码不再依赖旧结构、数据迁移完成、观察期通过后,再在后续独立发布删除旧列/表。
flowchart TD
A["Expand:新增兼容结构"] --> B["发布兼容新旧代码"]
B --> C["Migrate:双写/回填/校验"]
C --> D["停止旧读写并观察"]
D --> E["Contract:后续版本删除旧结构"]十、DDL在线不等于无风险
即使数据库支持Online DDL,也可能产生:
- 元数据锁等待。
- IO和Redo/Binlog增加。
- 主从延迟。
- 临时磁盘空间。
- 长事务阻塞。
- 应用查询计划变化。
发布流水线应把数据库迁移作为独立、可观察步骤,设置超时和停止条件,不在应用启动时偷偷执行大型DDL。
十一、数据迁移为什么不能简单rollback
结构回退和数据回退不同。新版本运行10分钟后已经写入新格式数据,切回旧代码可能无法理解这些数据;直接恢复10分钟前备份又会丢失10分钟新业务。
需要:
- 向后兼容数据格式。
- 变更日志/CDC。
- 可逆迁移脚本。
- 对账。
- 按业务Key补偿。
- 明确RPO/RTO。
很多事故更适合前滚修复,而不是数据库整体回滚。
十二、消息格式兼容
滚动发布期间,新旧生产者和消费者同时存在:
旧生产者 → 新消费者
新生产者 → 旧消费者
消息重试队列中的旧消息 → 新消费者安全演进:
- 新增可选字段,不随意删除/改义。
- 消费者忽略未知字段。
- 必填字段提供默认或版本分支。
- Schema Registry/契约测试。
- Topic/事件版本只在必要时升级。
- 回滚前确认旧消费者能处理新消息。
镜像回滚不能撤回已经发布到MQ的新格式消息。
十三、API兼容
新旧实例并存时,调用方可能随机命中任一版本。API应:
- 新增字段向后兼容。
- 不随意改变字段类型和枚举语义。
- 客户端忽略未知字段。
- 服务端兼容旧请求。
- 使用契约测试验证多版本组合。
前端与后端独立发布还要避免新前端只调用新接口,而灰度流量命中旧后端。
十四、四种发布策略
| 策略 | 原理 | 优点 | 风险 |
|---|---|---|---|
| 重建 | 停旧再启新 | 简单 | 有停机,不适合高可用生产 |
| 滚动 | 分批替换实例 | 资源成本低 | 新旧并存要求兼容 |
| 蓝绿 | 新旧两套环境切流 | 回切快 | 双倍资源、状态和DB仍共享 |
| 金丝雀 | 小比例流量逐步放量 | 风险暴露范围小 | 路由、指标和自动化复杂 |
旧文档的“docker stop旧容器→rm→run新容器”属于单机重建,会制造停机窗口,只适合明确接受单机中断的环境,不是商业高可用默认方案。
十五、滚动发布过程
flowchart TD
A["现有v1实例"] --> B["创建少量v2实例"]
B --> C["启动探针通过"]
C --> D["readiness通过后接流量"]
D --> E["停止一个v1接新流量"]
E --> F["等待v1在途请求和消费者停机"]
F --> G["继续下一批"]
G --> H["全部v2并观察"]滚动参数要控制maxUnavailable和maxSurge。容量不足时同时下线过多旧实例会引发过载。
十六、蓝绿发布边界
蓝绿拥有两套应用环境,但通常共享数据库、MQ和部分缓存。因此:
- 流量可快速切回蓝环境。
- 数据库结构和新数据不能自动切回。
- 新版本产生的消息仍在MQ。
- 缓存Key和Session需要兼容。
蓝绿解决应用和流量切换,不等于全系统事务快照。
十七、金丝雀发布
渐进比例示意:
内部测试流量
→ 1%
→ 5%
→ 10%
→ 25%
→ 50%
→ 100%每一步都要有最小请求样本、观察时间和通过标准。低QPS业务1%可能几乎没有样本,应按请求数和关键业务覆盖决定,不机械按百分比。
十八、灰度按什么分流
可按:
- 随机比例。
- 用户ID稳定Hash。
- 租户。
- 内部账号。
- 地区/可用区。
- Header/Cookie。
- 特定业务类型。
按用户稳定分流便于对比和复现,但Key必须可信;客户端可伪造Header不能作为高权限灰度入口。
十九、readiness检查什么
readiness表示实例是否准备接流量,可包含:
- Web服务器已监听。
- Spring上下文初始化完成。
- 必要配置已加载。
- 必要连接池初始化。
- 关键本地资源可用。
不要把所有下游短暂抖动都直接变成readiness失败,否则可能所有实例同时摘流。数据库等依赖可用性还需要超时、熔断和降级。
二十、健康接口200仍不够
固定返回200只能证明某个Handler可执行,不能证明:
- 核心订单接口正常。
- 数据库查询正常。
- Redis/MQ可用。
- 配置版本正确。
- 新旧Schema兼容。
- 外部入口和TLS正常。
发布后还需要业务冒烟:创建测试订单、查询状态、撤销或清理测试数据,并保证测试操作幂等和可审计。
二十一、灰度技术指标
- 新旧版本请求数和样本量。
- HTTP 5xx与异常类型。
- P50/P95/P99。
- CPU、Heap、GC、线程数。
- 线程池活跃/队列/拒绝。
- 数据库连接池等待。
- 慢SQL和主从延迟。
- Redis/MQ/ES错误与延迟。
- Pod重启、OOM和探针失败。
必须按版本标签拆分,否则总指标会把少量灰度异常稀释。
二十二、灰度业务指标
订单系统:下单成功率、支付成功率、库存扣减失败、重复订单。
医疗采集:设备在线率、采集成功率、报文解析失败、数据延迟、重复数据。
搜索:零结果率、搜索成功率、ES同步延迟、点击转化。
技术200不代表业务成功。例如接口返回200但订单状态没有推进,只有业务指标能发现。
二十三、自动停止放量
示意规则:
灰度5xx > 基线 + 阈值
或 P99 > SLA
或 核心成功率下降
或 OOM/重启异常
→ 停止增加流量
→ 保留当前小比例或回切
→ 通知负责人并保存证据阈值需考虑样本量、自然波动和告警窗口,避免一个请求就误判,也避免总量平均掩盖灰度异常。
二十四、Jenkins发布闸门
stage('Canary Verify') {
steps {
timeout(time: 10, unit: 'MINUTES') {
sh './verify-canary.sh --version "$IMAGE_DIGEST" --min-samples 1000'
}
}
}脚本必须在指标不合格时返回非0,并输出证据链接。不要只打印“失败”却退出0。
二十五、回滚不是一个动作
flowchart TD
A["发布异常"] --> B["停止继续放量"]
B --> C["流量回切"]
C --> D["应用镜像回退"]
D --> E["配置是否需要回退"]
E --> F["数据库/消息是否兼容旧版本"]
F --> G["验证旧版本健康与业务"]
G --> H["处理新版本副作用和补偿"]二十六、代码/镜像回滚
保存上一稳定Digest:
registry.example.com/order-api@sha256:...回退时部署旧Digest,不使用可变Tag猜测。回退后要验证实例、流量、错误率、业务成功率和数据兼容。
二十七、配置回滚
新代码可能依赖新配置,旧代码可能无法解析新格式。配置必须有:
- 版本号。
- 审批记录。
- 生效范围。
- 前后兼容策略。
- 旧版本快照。
- 回滚命令和验证。
代码回滚不自动回滚Nacos、ConfigMap、Secret和Feature Flag。
二十八、数据库回滚
优先通过Expand/Migrate/Contract避免紧急逆向DDL。必须回退时先评估:
- 是否有新格式写入。
- 逆向脚本是否丢数据。
- 主从延迟。
- 备份时间与RPO。
- 增量如何保留。
- 旧代码能否读当前Schema。
“恢复备份”是灾难恢复动作,不是普通发布失败的第一选择。
二十九、流量回切
金丝雀/蓝绿最先做的通常是停止放量或把流量切回旧版本,减少影响面。回切后新版本实例可以保留用于取证,但要隔离外部流量和副作用任务。
消费者、定时任务和Webhook不一定受HTTP流量比例控制。新版本MQ消费者可能仍在处理,需要单独暂停或切换。
三十、外部副作用补偿
新版本可能已经:
- 发短信/邮件。
- 调用支付。
- 扣库存。
- 写ES。
- 发MQ消息。
- 上传文件。
镜像回滚不会撤销这些动作。必须通过幂等、状态机、对账、本地消息表、补偿任务和人工审核处理。
三十一、什么时候前滚修复
适合前滚:
- 数据库已发生难以逆转变更。
- 新格式消息已大量发布。
- 问题可通过小代码/配置快速修复。
- 回滚会造成更大数据不兼容。
前滚也必须走最小测试、灰度和观测,不能因为紧急就直接跳过审计。
三十二、什么时候立即回滚
- 核心链路明显失败且旧版本兼容。
- 错误影响快速扩大。
- 新版本无必要数据迁移。
- 旧Digest和配置可立即恢复。
- 回滚时间明显短于定位修复。
是否回滚应在发布前定义触发条件和决策人,不在事故现场争论标准。
三十三、完整Pipeline骨架
pipeline {
agent none
options {
timestamps()
disableConcurrentBuilds()
}
parameters {
string(
name: 'IMAGE_DIGEST',
defaultValue: '',
trim: true,
description: '经过测试准入的完整镜像引用,例如registry.example.com/order-api@sha256:...'
)
}
stages {
stage('Release Metadata') {
agent { label 'release-reader' }
steps {
script {
if (!(params.IMAGE_DIGEST ==~ /registry\.example\.com\/order-api@sha256:[a-f0-9]{64}/)) {
error 'IMAGE_DIGEST格式或仓库不在允许范围'
}
env.RELEASE_IMAGE = params.IMAGE_DIGEST
}
sh './validate-release.sh "$RELEASE_IMAGE"'
}
}
stage('Approval') {
steps {
timeout(time: 30, unit: 'MINUTES') {
input message: '批准该Digest进入生产灰度?'
}
}
}
stage('Database Expand') {
agent { label 'prod-migrator' }
steps {
lock(resource: 'prod-order-db-migration') {
sh './migrate.sh expand'
sh './verify-migration.sh expand'
}
}
}
stage('Canary 1%') {
agent { label 'prod-deployer' }
steps {
lock(resource: 'prod-order-release') {
sh './deploy-canary.sh 1 "$RELEASE_IMAGE"'
sh './verify-canary.sh 1'
}
}
}
stage('Progressive Traffic') {
agent { label 'prod-deployer' }
steps {
sh './promote.sh 10 && ./verify-canary.sh 10'
sh './promote.sh 50 && ./verify-canary.sh 50'
sh './promote.sh 100 && ./verify-canary.sh 100'
}
}
}
post {
failure {
echo '停止放量,按Runbook判断回切、回滚或前滚;不要自动执行未知安全性的数据库回滚。'
}
}
}示例依赖lock和自定义脚本,需要目标插件与环境验证。发布Job接收的是经过测试准入的完整Digest并做仓库与格式白名单,不能接收可变Tag;跨Job正式制品来自Registry/Nexus,不依赖只在同一次Pipeline内有效的stash。生产不应把多步Shell用 &&隐藏阶段证据,可按实际拆分Stage/Step和指标报告。
三十四、为什么不默认自动回滚数据库
Pipeline无法仅凭Step失败知道:
- DDL执行到哪。
- 新数据是否写入。
- 消费者是否处理新消息。
- 逆向迁移是否丢数据。
因此failure中可以自动停止放量和回切HTTP流量,但数据库逆向通常需要预先验证的自动化或人工决策,不能写通用 rollback.sql无脑执行。
三十五、发布审计记录
至少保存:
- 谁提交、谁审批、谁执行。
- Commit、PR和变更说明。
- Build和镜像Digest。
- SBOM、测试和扫描报告。
- 配置与数据库迁移版本。
- 灰度每阶段时间、比例、样本和指标。
- 停止/回滚触发原因。
- 最终稳定版本。
- 事故单和补偿任务。
日志不得记录Secret和个人敏感信息。
三十六、发布通知不是只发“成功/失败”
通知应包含:
- 服务、环境和版本。
- Commit/Digest。
- 变更摘要。
- 当前灰度比例。
- 关键指标链接。
- 审批与执行人。
- 失败阶段和Runbook。
- 回滚/前滚状态。
避免把完整环境变量、Token和凭据输出到群通知。
三十七、单机Docker发布边界
旧式脚本:
stop旧容器
→ rm旧容器
→ run新容器会产生停机,并在新容器失败时没有服务。若业务明确接受单机,至少应:
- 保存旧Digest和容器配置。
- 新容器使用不同名称/端口先启动。
- 健康后切换Nginx流量。
- 失败则删除新容器并保留旧容器。
- 数据放Volume并独立备份。
更高可用场景使用Kubernetes等编排平台。
三十八、Jenkins执行Docker的权限风险
把Jenkins用户加入rootful Docker组,或挂载 /var/run/docker.sock,通常近似授予宿主机root级控制能力。不能作为“permission denied”的无风险修复。
替代与治理:
- 隔离专用构建Agent。
- Rootless BuildKit/受控构建服务。
- Kaniko等适用环境方案,按维护状态评估。
- 最小网络和凭据。
- 不可信PR不能访问高权限构建Agent。
- Agent可销毁、审计和资源限制。
三十九、发布失败Runbook
- 停止继续放量和后续Stage。
- 记录当前流量比例、版本和失败时间。
- 保存Jenkins、平台、应用和指标证据。
- 判断问题在制品、配置、基础设施、数据库还是业务。
- 判断旧版本对当前Schema/消息/数据是否兼容。
- 优先回切流量缩小影响。
- 选择镜像回滚或前滚修复。
- 验证技术指标和业务指标。
- 处理新版本副作用和补偿。
- 更新稳定版本记录和事故报告。
四十、回滚失败Runbook
回滚后仍故障可能因为:
- 配置没有回退。
- 数据库已不兼容。
- 新消息仍在消费。
- 缓存被新版本污染。
- DNS/流量未真正切回。
- 旧镜像依赖已失效。
- 故障本来就在共享依赖,不是代码版本。
此时不要反复来回发布。冻结变更,建立当前状态快照,保护数据,隔离副作用,决定前滚修复或灾难恢复。
四十一、商业场景一:新版本导致重复扣库存
灰度版本重试逻辑错误,同一订单重复发送扣库存消息。即使流量回切,已发送消息仍在队列。
处理:停止灰度消费者、按订单幂等Key拦截、暂停相关重试、对账库存流水、补偿错误扣减,再修复并重新灰度。
四十二、商业场景二:医疗采集新解析器污染数据
新版本把单位解析错,HTTP成功率正常,但业务字段异常。业务指标和数据质量规则触发停止放量。
处理:停止新版本采集/清洗消费者,保留原始报文,标记受影响批次,回切旧解析器,按原始数据重新清洗并审计,不直接删除污染记录掩盖现场。
四十三、商业场景三:回滚后旧代码启动失败
新发布删除了数据库旧列,回滚旧镜像后SQL报Unknown column。根因是未采用Expand/Contract。只能前滚兼容代码或恢复兼容列并处理数据,普通镜像回滚已经失效。
四十四、商业场景四:灰度指标被总量稀释
新版本只占1%,错误率50%;总系统错误率约0.5%,低于全局1%告警阈值,发布继续放量。
修复:所有指标按版本/Pod/灰度组拆分,并要求最小样本。总量指标不能替代版本对比。
四十五、面试标准回答
为什么生产要一次构建多环境推广
同一Commit重复构建可能因依赖、基础镜像Tag、时间戳和工具环境产生不同制品。应构建一次、推送仓库并记录Digest,测试、预发和生产推广同一不可变制品,审批的是已验证内容而不是分支名。
数据库变更怎样支持代码回滚
使用Expand→Migrate→Contract:先新增兼容结构,不删除旧字段;发布兼容新旧的代码并双写/回填/校验;确认所有版本不再依赖旧结构后,在后续独立版本删除。直接先删列会让旧代码回滚后无法运行。
滚动、蓝绿和金丝雀区别
滚动分批替换实例,资源省但新旧并存要求兼容;蓝绿保留两套应用并切流,回切快但数据库和消息仍共享;金丝雀按小比例逐步放量,用指标控制风险,但需要稳定分流、版本指标和自动停止能力。
健康检查通过为什么还不能判定发布成功
readiness只证明实例准备接流量,固定200更只证明Handler存活。还要做外部冒烟、核心业务状态验证,并观察按版本拆分的5xx、P99、线程池、数据库以及下单/支付/采集等业务指标。
为什么回滚镜像不能解决所有发布事故
镜像回退不会自动回退配置、数据库Schema、新格式数据、MQ消息、缓存和外部副作用。回滚前必须确认旧代码兼容当前状态,回切流量后还要处理已产生消息、扣款、库存和数据污染,必要时前滚修复。
什么时候应该前滚而不是回滚
数据库或消息格式已发生难以逆转的变化、旧版本不兼容当前数据,且问题可用小改动快速修复时更适合前滚。但前滚仍需最小测试、灰度、指标闸门和审计,不能以紧急为由直接跳过控制。
四十六、学习验收
- 从Commit追踪到镜像Digest、配置和数据库版本。
- 证明测试与生产部署同一Digest。
- 为一次列重命名设计Expand/Migrate/Contract三次发布。
- 设计新旧生产者/消费者消息兼容矩阵。
- 比较滚动、蓝绿和金丝雀的资源与数据边界。
- 为1%、10%、50%、100%定义样本、观察时间和停止阈值。
- 同时设计readiness、冒烟、技术和业务指标。
- 分别演练镜像、配置和流量回滚。
- 解释为什么数据库和外部副作用不能通用自动回滚。
- 制造旧Build反超、回滚失败和总指标稀释三类事故。
- 写出发布失败与回滚失败Runbook。
- 完成订单或医疗采集场景的数据补偿方案。
