Skip to content

Jenkins商业发布、灰度与回滚全过程

商业发布不是“新镜像启动成功就结束”。一次发布同时改变代码、镜像、配置、数据库结构、消息格式和流量归属;其中镜像容易回退,数据库和外部副作用可能不可逆。Jenkins的职责是把准入、渐进放量、观测、停止和恢复写成可审计流程,而不是用一段Shell赌生产。

学习目标

  1. 区分Build、Release、Deploy和Traffic Shift。
  2. 建立Commit、Build、Jar、镜像Digest、配置版本和发布记录的追踪链。
  3. 解释一次构建、多环境推广同一制品。
  4. 使用Expand→Migrate→Contract设计可回滚数据库变更。
  5. 保证新旧代码、消息生产者和消费者兼容。
  6. 比较滚动、蓝绿、金丝雀和单机替换策略。
  7. 设计readiness、冒烟、技术指标和业务指标闸门。
  8. 区分代码、配置、流量、数据库和数据回滚。
  9. 判断何时回滚、何时前滚修复。
  10. 完成发布失败、回滚失败和紧急止损Runbook。

一、四个动作先分清

动作含义产出/状态
Build从固定源码生成制品Jar、镜像、SBOM、测试报告
Release声明某制品通过准入可发布Release记录、Digest、审批
Deploy把制品放进目标环境运行Deployment/容器/服务版本
Traffic Shift把用户流量逐步导向新版本灰度比例、路由、指标

部署完成不等于流量已经切换,流量切换也不等于发布成功。只有技术和业务指标在观察期内满足标准,才能把版本标记为稳定。

二、完整商业发布链

mermaid
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["标记稳定版本并完成审计"]

三、发布追踪链必须留下什么

text
Git Commit SHA
→ Jenkins Job与Build Number
→ 构建环境和JDK/Maven版本
→ Jar摘要
→ 镜像Tag与Digest
→ SBOM和扫描报告
→ 配置版本
→ 数据库迁移版本
→ 部署环境与时间
→ 审批人
→ 灰度比例与指标
→ 最终稳定/回滚结果

Tag可以变化,Digest才是内容身份之一。prod-latest不能作为可审计发布依据。

四、一次构建、多环境推广

正确:

text
Commit A
→ 构建Image Digest X
→ 测试部署X
→ 预发部署X
→ 生产部署X

错误:

text
测试重新build得到X
生产再次build得到Y

即使源码Commit相同,基础镜像Tag、依赖仓库、时间戳和工具环境都可能让X与Y不同。审批的应是已验证Digest,而不是一个将来重新构建的分支名。

五、制品准入门槛

生产Release至少验证:

  • Commit来自允许分支并经过评审。
  • 单元测试、集成测试和契约测试通过。
  • JDK 7/8项目固定具体更新版本和构建工具。
  • 依赖和镜像漏洞在允许阈值内。
  • SBOM生成并归档。
  • 镜像使用非root、固定入口和受信基础镜像。
  • 镜像Digest与签名可验证。
  • 数据库迁移已评审。
  • 配置、Secret和权限已准备。
  • 回滚/前滚方案已演练或评估。

任何质量门被 catchError吞成绿色,都会让发布记录失真。

六、环境配置不能构建进镜像

同一镜像在测试和生产运行,环境差异通过受控配置注入:

  • ConfigMap/Secret或配置中心。
  • 环境变量,仅用于合适的小型配置。
  • 只读配置文件。
  • Workload Identity和短期凭据。

镜像中不能固化生产数据库密码、Token和私钥。配置也要版本化和审计,否则代码回滚后仍加载新配置,可能继续故障。

七、发布前配置兼容性

假设新版本把:

text
PAY_TIMEOUT_SECONDS

改成:

text
PAY_TIMEOUT_MS

滚动发布期间新旧实例并存。若配置中心只保留新Key,旧实例重启或回滚后读不到配置。

兼容做法:先同时提供旧Key和新Key,新版本优先新Key并兼容旧Key;全部稳定后再删除旧Key。

八、数据库为什么让代码回滚失效

危险发布:

text
先删除old_column
→ 发布只读取new_column的新代码
→ 新代码故障
→ 回滚旧代码
→ 旧代码仍读取old_column
→ 回滚后直接SQL报错

代码版本可快速切换,数据库结构已经改变且承载新写入,通常不能像镜像Tag一样简单反向切换。

九、Expand→Migrate→Contract

9.1 Expand扩展

先做向后兼容的结构变更:新增列、表、索引或可空字段,不删除旧结构。

text
old_column继续存在
new_column新增
旧代码仍可运行

9.2 Migrate迁移

发布兼容新旧结构的代码,执行双读/双写或后台数据回填:

text
新写入同时维护old/new
历史数据分批回填new
校验数量、摘要和业务结果

双写仍可能一边成功一边失败,需要事务、重试、对账和补偿,不能只写两条SQL就声称一致。

9.3 Contract收缩

确认所有代码不再依赖旧结构、数据迁移完成、观察期通过后,再在后续独立发布删除旧列/表。

mermaid
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。

很多事故更适合前滚修复,而不是数据库整体回滚。

十二、消息格式兼容

滚动发布期间,新旧生产者和消费者同时存在:

text
旧生产者 → 新消费者
新生产者 → 旧消费者
消息重试队列中的旧消息 → 新消费者

安全演进:

  • 新增可选字段,不随意删除/改义。
  • 消费者忽略未知字段。
  • 必填字段提供默认或版本分支。
  • Schema Registry/契约测试。
  • Topic/事件版本只在必要时升级。
  • 回滚前确认旧消费者能处理新消息。

镜像回滚不能撤回已经发布到MQ的新格式消息。

十三、API兼容

新旧实例并存时,调用方可能随机命中任一版本。API应:

  • 新增字段向后兼容。
  • 不随意改变字段类型和枚举语义。
  • 客户端忽略未知字段。
  • 服务端兼容旧请求。
  • 使用契约测试验证多版本组合。

前端与后端独立发布还要避免新前端只调用新接口,而灰度流量命中旧后端。

十四、四种发布策略

策略原理优点风险
重建停旧再启新简单有停机,不适合高可用生产
滚动分批替换实例资源成本低新旧并存要求兼容
蓝绿新旧两套环境切流回切快双倍资源、状态和DB仍共享
金丝雀小比例流量逐步放量风险暴露范围小路由、指标和自动化复杂

旧文档的“docker stop旧容器→rm→run新容器”属于单机重建,会制造停机窗口,只适合明确接受单机中断的环境,不是商业高可用默认方案。

十五、滚动发布过程

mermaid
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需要兼容。

蓝绿解决应用和流量切换,不等于全系统事务快照。

十七、金丝雀发布

渐进比例示意:

text
内部测试流量
→ 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但订单状态没有推进,只有业务指标能发现。

二十三、自动停止放量

示意规则:

text
灰度5xx > 基线 + 阈值
或 P99 > SLA
或 核心成功率下降
或 OOM/重启异常
→ 停止增加流量
→ 保留当前小比例或回切
→ 通知负责人并保存证据

阈值需考虑样本量、自然波动和告警窗口,避免一个请求就误判,也避免总量平均掩盖灰度异常。

二十四、Jenkins发布闸门

groovy
stage('Canary Verify') {
    steps {
        timeout(time: 10, unit: 'MINUTES') {
            sh './verify-canary.sh --version "$IMAGE_DIGEST" --min-samples 1000'
        }
    }
}

脚本必须在指标不合格时返回非0,并输出证据链接。不要只打印“失败”却退出0。

二十五、回滚不是一个动作

mermaid
flowchart TD
    A["发布异常"] --> B["停止继续放量"]
    B --> C["流量回切"]
    C --> D["应用镜像回退"]
    D --> E["配置是否需要回退"]
    E --> F["数据库/消息是否兼容旧版本"]
    F --> G["验证旧版本健康与业务"]
    G --> H["处理新版本副作用和补偿"]

二十六、代码/镜像回滚

保存上一稳定Digest:

text
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骨架

groovy
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发布边界

旧式脚本:

text
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

  1. 停止继续放量和后续Stage。
  2. 记录当前流量比例、版本和失败时间。
  3. 保存Jenkins、平台、应用和指标证据。
  4. 判断问题在制品、配置、基础设施、数据库还是业务。
  5. 判断旧版本对当前Schema/消息/数据是否兼容。
  6. 优先回切流量缩小影响。
  7. 选择镜像回滚或前滚修复。
  8. 验证技术指标和业务指标。
  9. 处理新版本副作用和补偿。
  10. 更新稳定版本记录和事故报告。

四十、回滚失败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消息、缓存和外部副作用。回滚前必须确认旧代码兼容当前状态,回切流量后还要处理已产生消息、扣款、库存和数据污染,必要时前滚修复。

什么时候应该前滚而不是回滚

数据库或消息格式已发生难以逆转的变化、旧版本不兼容当前数据,且问题可用小改动快速修复时更适合前滚。但前滚仍需最小测试、灰度、指标闸门和审计,不能以紧急为由直接跳过控制。

四十六、学习验收

  1. 从Commit追踪到镜像Digest、配置和数据库版本。
  2. 证明测试与生产部署同一Digest。
  3. 为一次列重命名设计Expand/Migrate/Contract三次发布。
  4. 设计新旧生产者/消费者消息兼容矩阵。
  5. 比较滚动、蓝绿和金丝雀的资源与数据边界。
  6. 为1%、10%、50%、100%定义样本、观察时间和停止阈值。
  7. 同时设计readiness、冒烟、技术和业务指标。
  8. 分别演练镜像、配置和流量回滚。
  9. 解释为什么数据库和外部副作用不能通用自动回滚。
  10. 制造旧Build反超、回滚失败和总指标稀释三类事故。
  11. 写出发布失败与回滚失败Runbook。
  12. 完成订单或医疗采集场景的数据补偿方案。

关联知识点