持续交付与持续部署

持续交付和持续部署,这俩概念经常被混着用。但实际上它们之间有一条很清晰的线——说白了,就是「是否由人来决定上线那一刻」。我自己刚接触时也搞混过,直到在一次线上事故中彻底想明白了。

持续交付 vs 持续部署:就差一个按钮

持续交付的意思是:代码每次合并到主干后,自动完成构建、测试、环境部署,最终生成一个可随时上线的制品。但部署到生产环境这一步,仍然需要人工点一下「确认」。

持续部署则更进一步:直接把这个确认动作也自动化了。只要通过全部质量门禁,代码自动推送到生产环境。

维度持续交付持续部署
人工干预点部署到生产前需要审批完全自动化
适用场景金融、医疗等强监管行业SaaS、内部工具等低风险场景
回滚策略人工执行回滚自动回滚 + 灰度
质量门禁必须,可能更严格必须,且自动阻断

我在项目中遇到过一个案例:团队强推持续部署,但QA部门没有准备好环境隔离,结果一次数据库迁移脚本自动上线,直接导致生产数据丢失。嗯,从那以后我建议:刚开始做持续部署,先保留一个「手动确认」的开关,等所有自动化检测足够成熟再切过去。

部署流水线的核心要素

不管是持续交付还是持续部署,底层都是同一套流水线逻辑。我把它拆成五个阶段:

  1. 代码提交:触发流水线,通常用 webhook 或 git 事件。
  2. 构建与单元测试:编译、打包、运行单元测试,生成制品。
  3. 集成测试与质量检查:接口测试、安全扫描、代码覆盖率、性能基准。
  4. 环境部署与验收测试:自动部署到 staging 环境,运行端到端测试、冒烟测试。
  5. 发布(手动或自动):灰度放量、生产部署、健康检查、自动回滚触发。

这里有一条铁律:流水线中任何一个阶段失败,都必须自动阻断并通知。我见过最离谱的事是测试覆盖率下降了 20% 都没人管,因为流水线没设失败条件。

质量门禁 — 你不可逾越的红线

质量门禁是持续部署的保险丝。我习惯在流水线中设置以下几个强制检查点:

  • 单元测试覆盖率 >= 80%:低于这个值直接 fail。
  • 新增代码无报警级别 lint 错误:这些错误往往在代码评审中漏掉。
  • 安全漏洞扫描(SAST/SCA):高危漏洞必须修,中危可以放但要有 ticket。
  • 性能基线对比:接口响应时间不能超过上次基线 20%。
  • 变更数据库兼容性:这个后面单独说。

为什么把「新增代码无报警级别 lint 错误」单独拎出来?因为代码评审时,reviewer 往往只关注逻辑,不会仔细检查每个 linter 提示。流水线自动卡住,反而倒逼团队养成好习惯。

数据库变更 — 最容易被忽视的暗坑

我记得有一次做持续部署改造,所有代码都顺利通过了,唯独数据库变更出了大问题。一个表结构变更需要先锁表再迁移,而旧版本的代码还在运行,导致线上请求全部超时。后来我总结了一套策略:

  • 向后兼容的变更:只增加列或表,不删除旧字段。
  • 分步骤执行:先改代码支持新列,再跑迁移脚本,最后清理旧字段。
  • 使用工具管理迁移:Flyway、Liquibase 之类的,版本号和代码挂钩。
  • 自动回滚测试:在 staging 环境先验证迁移脚本的回滚是否原子性。

千万不要在生产环境直接执行 DDL 而不做验证。我曾经见过开发同事手抖执行了「DROP TABLE users」,还好有自动备份。从那以后,任何 DDL 变更都要走流水线 + 人工确认。

灰度发布与自动回滚

持续部署不是一股脑全量推给所有人。我建议用「灰度 + 金丝雀」的方式:

  1. 先部署 1% 的节点,运行健康检查(CPU、错误率、响应时间)。
  2. 检查通过后,逐步扩大到 5%、25%、100%。
  3. 任何一步失败,自动回滚到上一个稳定版本。

这个机制的实现,我在 Jenkinsfile 中大概是这样写的:

stage('Canary Deploy') {
    steps {
        script {
            def canaryPercent = 1
            while (canaryPercent <= 100) {
                deploy(percent: canaryPercent)
                sleep(30) // 等待指标收集
                if (healthCheckFailed()) {
                    rollback()
                    error "Canary failed at ${canaryPercent}%, rolled back"
                }
                canaryPercent *= 5
            }
        }
    }
}

实际生产中,你可能需要结合监控系统(Prometheus、Datadog)来做健康判断,而不是简单的 sleep。

一张图看清持续交付与持续部署的整个流程

下面这张图可以帮你理解从代码提交到生产部署的完整链路,以及两者的分界线在哪。

持续交付与持续部署流程图 持续交付 vs 持续部署 流程对比 代码提交 构建 + 单元测试 集成测试 & 质量检查 环境部署 + 验收测试 人工确认 (持续交付) 自动部署到生产 (持续部署) 生产环境 质量门禁:覆盖率≥80%

图里可以看到,持续交付和持续部署只有最后一步不同——一个需要人点确认,一个不需要。但前面所有质量门禁和环境验证都是共通的,甚至持续部署的要求更严。

灰度部署的实战建议

我再分享一个亲身踩过的坑:灰度部署时,健康检查只检查了服务器是否 UP,没检查业务指标。结果灰度到 10% 时,订单创建成功率已经从 99.9% 掉到 80%,但 CPU 和内存都正常。后来我增加了业务指标监控(订单错误率、支付成功率等)作为回滚条件。

# 灰度健康检查脚本片段
def healthCheck() {
    def cpu = getMetric("cpu_usage")
    def errRate = getMetric("http_error_rate")
    def orderFailRate = getMetric("order_failure_rate")
    if (cpu > 80 || errRate > 1 || orderFailRate > 0.5) {
        return false
    }
    return true
}

其实说白了,持续部署的最大价值不是省掉一次点击,而是倒逼你把所有质量门禁、异常处理、回滚机制都做到自动化。很多团队先上持续部署才发现测试覆盖不全,这反而是好事。

环境一致性与配置管理

持续部署的另一个前提是环境一致性。我见过一次事故:开发环境用的 MySQL 8.0,生产环境是 MySQL 5.7,结果一个 JSON 函数在线上直接报错。解决办法就是基础设施即代码(IaC),把版本、配置、依赖都写进代码仓库。

我建议在每个项目中,至少把下面这几项纳入版本管理:

  • Docker 镜像的精确 tag(不要用 latest)
  • 数据库迁移脚本(Flyway 或 Liquibase)
  • 环境变量模板(比如 .env.example)
  • Kubernetes 部署清单(通过 GitOps 工具同步)

配置管理也有陷阱。我遇到过团队把数据库密码写在代码仓库的 env 文件中,然后不小心推到了公开仓库…… 从那以后,我强制所有敏感变量走密钥管理服务(Vault、AWS Secrets Manager),流水线中只传递引用。

环境一致性和配置管理,是持续部署的两条腿。缺任何一条,自动部署就是定时炸弹。我建议在切换到持续部署之前,先用持续交付跑一个月,把环境漂移问题全部暴露出来。

本章小结

持续交付和持续部署并非技术上的巨大差异,而是一个组织成熟度的分界线。我个人习惯先跑通持续交付,把人工部署的流程用自动化流水线代替,再逐步把「确认按钮」变成「自动放行」,同时补上灰度、回滚、质量门禁这些护城河。记住:自动化不是目标,可靠地交付价值才是。