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秒,用户完全无感知。
嗯,说到发布策略,还有一点很重要:回滚能力。不管你用哪种策略,都要确保能快速回滚。我建议每次发布前,先演练一遍回滚流程。别等到真出问题了,才发现回滚脚本没写好。
警告:不要依赖「手动回滚」。手动操作太容易出错了。我曾经见过一个团队,回滚时手抖输错了版本号,结果把更旧的版本部署上去了,导致更严重的问题。所有回滚操作都应该自动化。
最后,我想说:持续交付不是一蹴而就的。它需要团队在工具、流程、文化上持续改进。你想想看,如果每次发布都像「走钢丝」一样紧张,那说明持续交付还没做到位。真正好的持续交付,应该是「发布就像喝水一样自然」。