4. 持续交付(CD):从代码到生产环境的最后一公里

持续交付,说白了就是让软件随时都能安全地发布到生产环境。我见过太多团队,代码写完了,却卡在「部署」这一步上。测试环境跑得好好的,一上生产就崩。嗯,这其实就是持续交付没做好。

我个人习惯把持续交付看作一条「高速公路」。代码从提交到上线,每个环节都自动化、标准化。你想想看,如果每次发布都要手动操作,那得多容易出错?

4.1 CD概念:不只是自动化部署

持续交付(Continuous Delivery)的核心是:让软件始终处于可发布状态。它和持续部署(Continuous Deployment)不一样。持续部署是每次提交都自动上线,而持续交付是「随时可以上线,但由人来决定什么时候上」。

关键区别:

  • 持续交付:代码通过所有测试后,生成发布包,等待人工审批上线
  • 持续部署:代码通过所有测试后,自动部署到生产环境

我在项目中遇到过一种情况:团队觉得「我们用了Jenkins,就是持续交付了」。其实不是。持续交付不只是工具,更是一种文化。它要求:

  • 每次提交都能触发自动化构建和测试
  • 每次构建都能生成可部署的制品
  • 每次部署都能回滚
  • 每次发布都有清晰的版本记录

我的建议:刚开始做持续交付时,别追求一步到位。先做到「每次提交都能自动化测试」,再逐步加入部署环节。我曾经见过一个团队,上来就想搞全自动部署,结果测试环境都没稳定,反而更乱了。

4.2 部署流水线:代码到生产的传送带

部署流水线(Deployment Pipeline)是持续交付的核心实践。它把代码从提交到上线的过程,拆分成多个阶段。每个阶段都有明确的目标和检查点。

我习惯把流水线分成这几个阶段:

阶段 目标 典型操作
提交阶段 快速反馈代码质量 编译、单元测试、代码检查
验收阶段 验证功能正确性 集成测试、API测试、UI测试
预发布阶段 模拟生产环境验证 性能测试、安全扫描、数据迁移测试
发布阶段 安全上线 灰度发布、蓝绿部署、金丝雀发布

举个例子,一个典型的部署流水线配置(Jenkinsfile):

pipeline {
    agent any
    
    stages {
        stage('构建') {
            steps {
                sh 'mvn clean package'
            }
        }
        stage('单元测试') {
            steps {
                sh 'mvn test'
            }
        }
        stage('集成测试') {
            steps {
                sh 'mvn verify'
            }
        }
        stage('部署到测试环境') {
            steps {
                sh 'deploy.sh test'
            }
        }
        stage('验收测试') {
            steps {
                sh 'run-acceptance-tests.sh'
            }
        }
        stage('部署到预发布环境') {
            steps {
                sh 'deploy.sh staging'
            }
        }
        stage('生产发布审批') {
            input {
                message "确认发布到生产环境?"
                ok "确认发布"
            }
        }
        stage('部署到生产环境') {
            steps {
                sh 'deploy.sh production'
            }
        }
    }
}

注意:流水线不是越长越好。我曾经见过一个团队,流水线有20多个阶段,跑一次要两个小时。结果没人愿意等,大家都绕过流水线直接部署。流水线的核心是「快速反馈」,一般控制在10-15分钟内比较理想。

4.3 环境管理:别让环境差异坑了你

「在我机器上能跑啊!」——这句话是不是很耳熟?环境差异是持续交付最大的敌人之一。我建议用「环境即代码」的思路来管理环境。

环境管理的关键点:

  • 环境一致性:开发、测试、预发布、生产环境尽量保持一致
  • 环境配置化:所有环境配置都放在代码仓库里管理
  • 环境隔离:不同环境之间不能互相影响
  • 环境可重建:任何环境都能从零快速重建

我个人习惯用Docker来保证环境一致性。每个环境都用相同的镜像,只是配置不同。比如:

# 开发环境配置
version: '3'
services:
  app:
    image: myapp:latest
    environment:
      - DB_HOST=dev-db
      - DB_PORT=3306
      - LOG_LEVEL=debug
    ports:
      - "8080:8080"

# 生产环境配置
version: '3'
services:
  app:
    image: myapp:${VERSION}
    environment:
      - DB_HOST=prod-db
      - DB_PORT=3306
      - LOG_LEVEL=info
    ports:
      - "8080:8080"

避坑指南:我曾经遇到过一个问题:测试环境用MySQL 5.7,生产环境用MySQL 8.0。结果测试环境跑得好好的,生产环境却因为字符集问题报错。从那以后,我坚持所有环境用完全相同的数据库版本、中间件版本,甚至操作系统版本。

4.4 发布策略:怎么安全地把代码推上线

发布策略决定了你怎么把新版本部署到生产环境。不同的业务场景,适合不同的策略。我总结了几种常见的发布策略:

策略 原理 适用场景 风险
蓝绿部署 维护两套环境,切换流量 对停机时间敏感的业务 资源消耗大
金丝雀发布 先让少量用户使用新版本 需要验证新功能稳定性的场景 需要流量控制能力
滚动更新 逐个替换旧实例 微服务架构 回滚较慢
功能开关 代码中控制功能是否可见 需要快速开关功能的场景 代码复杂度增加

举个例子,金丝雀发布的实现思路:

# 金丝雀发布脚本示例
# 1. 部署金丝雀版本(5%流量)
kubectl set image deployment/myapp-canary myapp=myapp:v2
kubectl scale deployment/myapp-canary --replicas=1

# 2. 监控金丝雀版本的健康状态
# 如果5分钟内无错误,继续扩大流量
# 如果有错误,立即回滚

# 3. 逐步扩大流量
kubectl scale deployment/myapp-canary --replicas=3  # 15%流量
kubectl scale deployment/myapp-canary --replicas=5  # 25%流量
# ...

# 4. 全部切换
kubectl set image deployment/myapp myapp=myapp:v2
kubectl scale deployment/myapp-canary --replicas=0

我的经验:功能开关是我最推荐的策略之一。它让你可以在不部署的情况下,随时打开或关闭某个功能。我曾经用功能开关处理过一次线上事故:新功能上线后发现有性能问题,我直接在配置中心把开关关掉,整个过程不到30秒,用户完全无感知。

嗯,说到发布策略,还有一点很重要:回滚能力。不管你用哪种策略,都要确保能快速回滚。我建议每次发布前,先演练一遍回滚流程。别等到真出问题了,才发现回滚脚本没写好。

警告:不要依赖「手动回滚」。手动操作太容易出错了。我曾经见过一个团队,回滚时手抖输错了版本号,结果把更旧的版本部署上去了,导致更严重的问题。所有回滚操作都应该自动化。

最后,我想说:持续交付不是一蹴而就的。它需要团队在工具、流程、文化上持续改进。你想想看,如果每次发布都像「走钢丝」一样紧张,那说明持续交付还没做到位。真正好的持续交付,应该是「发布就像喝水一样自然」。