前言
很多人把 CI/CD 当成一个词来用,但实际上它是三个递进的层次。理解这三个层次的边界,决定了你团队自动化的成熟度。本篇用三个实际的流水线配置,让你直观感受从 CI 到 CD 到 Continuous Deployment 的演进。
一、三个层次的关系:一个递进模型
Level 1: Continuous Integration(持续集成)
代码合并 → 自动构建 → 自动测试 → 反馈结果
产出:可运行的构建产物
Level 2: Continuous Delivery(持续交付)
CI + 自动部署到类生产环境 + 随时可发布状态
产出:随时可以点击发布的系统
Level 3: Continuous Deployment(持续部署)
CD + 自动发布到生产环境(无人工干预)
产出:每次通过测试的代码自动上线
三者是包含关系:
┌─────────────────────────────────────────┐
│ Continuous Deployment │
│ ┌───────────────────────────────────┐ │
│ │ Continuous Delivery │ │
│ │ ┌─────────────────────────────┐ │ │
│ │ │ Continuous Integration │ │ │
│ │ │ 代码 → 构建 → 测试 → 反馈 │ │ │
│ │ └─────────────────────────────┘ │ │
│ │ + 部署到预发/测试环境 │ │
│ │ + 保持随时可发布状态 │ │
│ └───────────────────────────────────┘ │
│ + 自动部署到生产环境 │
└─────────────────────────────────────────┘
二、Level 1:持续集成(CI)长什么样
目标
每次代码提交后自动构建和测试,快速发现问题。这个阶段不涉及任何部署。
流水线配置(GitLab CI 示例)
# .gitlab-ci.yml --- 仅 CI 阶段
stages:
- build
- test
- scan
variables:
MAVEN_OPTS: "-Dmaven.repo.local=.m2/repository"
# 构建阶段
build:
stage: build
image: maven:3.9-eclipse-temurin-17
cache:
key: maven-${CI_COMMIT_REF_SLUG}
paths:
- .m2/repository
script:
- mvn clean compile -DskipTests
artifacts:
paths:
- target/
expire_in: 1 hour
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
- if: $CI_COMMIT_BRANCH == "main"
# 单元测试阶段
unit-test:
stage: test
image: maven:3.9-eclipse-temurin-17
needs: [build]
script:
- mvn test
artifacts:
reports:
junit: target/surefire-reports/TEST-*.xml
paths:
- target/site/jacoco/
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
- if: $CI_COMMIT_BRANCH == "main"
# 代码质量扫描
code-scan:
stage: scan
image: sonarsource/sonar-scanner-cli:latest
needs: [unit-test]
script:
- sonar-scanner
-Dsonar.projectKey=myapp
-Dsonar.sources=src
-Dsonar.host.url=$SONAR_HOST
-Dsonar.login=$SONAR_TOKEN
rules:
- if: $CI_COMMIT_BRANCH == "main"
这条流水线做到了什么
-
PR 或 main 分支提交时自动触发
-
编译代码------有编译错误立即失败
-
跑单元测试------测试不过立即失败
-
代码质量扫描------输出质量报告
-
没有部署任何东西到任何环境
**培训要点**:很多团队以为用了 Jenkins 就是"做 CI/CD"了,实际上如果流水线只做到编译+测试,连产物都没有固化,那只是 CI 的第一步。
三、Level 2:持续交付(CD)多了什么
目标
在 CI 基础上,把构建产物打包成可部署的格式(Docker 镜像),自动部署到测试和预发环境,保持系统处于"随时可发布"状态。但生产环境的发布需要人工触发。
流水线配置(增加部署阶段)
# .gitlab-ci.yml --- CI + CD(持续交付)
stages:
- build
- test
- scan
- package # 新增:打包镜像
- deploy-test # 新增:部署测试环境
- deploy-staging # 新增:部署预发环境
- release # 新增:准备发布(人工审批后触发)
# ... build / test / scan 同上 ...
# 打包 Docker 镜像
package:
stage: package
image: docker:24
needs: [unit-test]
services:
- docker:24-dind
script:
- docker build -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA .
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
- docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA
rules:
- if: $CI_COMMIT_BRANCH == "main"
# 部署到测试环境
deploy-test:
stage: deploy-test
image: bitnami/kubectl:latest
needs: [package]
environment:
name: test
url: https://test.myapp.com
script:
- kubectl set image deployment/myapp app=$CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA -n test
- kubectl rollout status deployment/myapp -n test --timeout=180s
rules:
- if: $CI_COMMIT_BRANCH == "main"
# 部署到预发环境(需要手动触发)
deploy-staging:
stage: deploy-staging
image: bitnami/kubectl:latest
needs: [deploy-test]
environment:
name: staging
url: https://staging.myapp.com
when: manual # ← 关键:手动触发
script:
- kubectl set image deployment/myapp app=$CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA -n staging
- kubectl rollout status deployment/myapp -n staging --timeout=180s
rules:
- if: $CI_COMMIT_BRANCH == "main"
# 准备生产发布(需要人工审批)
release:
stage: release
when: manual # ← 人工审批后才执行
script:
- echo "Release $CI_COMMIT_SHORT_SHA is ready for production"
- kubectl label deployment/myapp -n staging release-candidate=$CI_COMMIT_SHORT_SHA
rules:
- if: $CI_COMMIT_BRANCH == "main"
关键区别
| 特征 | CI | CD(持续交付) |
|------|-----|---------------|
| 自动构建 | 是 | 是 |
| 自动测试 | 是 | 是 |
| 构建产物固化 | 否 | 是(Docker镜像) |
| 自动部署测试环境 | 否 | 是 |
| 自动部署预发环境 | 否 | 是(可设手动触发) |
| 生产部署 | 否 | 否,需人工触发 |
| 系统状态 | 知道代码能编译 | 随时可发布 |
**踩坑提示**:`when: manual` 是持续交付的关键------它保证生产部署永远需要人确认。如果漏了这个配置,你的"持续交付"就变成了"持续部署"------测试没通过就上线了。
四、Level 3:持续部署(Continuous Deployment)的终极形态
目标
去掉所有人工审批环节,通过测试的代码自动部署到生产环境。这需要极高的测试覆盖率和完善的监控告警支撑。
流水线配置(移除手动触发)
# .gitlab-ci.yml --- CI + CD + Continuous Deployment
stages:
- build
- test
- scan
- package
- deploy-test
- deploy-staging
- deploy-prod # 新增:自动部署生产环境
# ... 前面阶段同上 ...
# 部署到预发环境(自动)
deploy-staging:
stage: deploy-staging
image: bitnami/kubectl:latest
needs: [deploy-test]
environment:
name: staging
script:
- kubectl set image deployment/myapp app=$CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA -n staging
- kubectl rollout status deployment/myapp -n staging --timeout=180s
# 在预发环境跑冒烟测试
- |
for i in $(seq 1 30); do
STATUS=$(curl -s -o /dev/null -w "%{http_code}" https://staging.myapp.com/health)
if [ "$STATUS" = "200" ]; then break; fi
sleep 5
done
if [ "$STATUS" != "200" ]; then exit 1; fi
rules:
- if: $CI_COMMIT_BRANCH == "main"
# 自动部署到生产环境(金丝雀模式)
deploy-prod-canary:
stage: deploy-prod
image: bitnami/kubectl:latest
needs: [deploy-staging]
environment:
name: production
script:
# 第一步:金丝雀部署------只更新1个Pod
- kubectl set image deployment/myapp app=$CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA -n production
- |
# 等待金丝雀Pod健康
kubectl rollout status deployment/myapp -n production --timeout=300s
# 观察5分钟,检查错误率
sleep 300
ERROR_RATE=$(curl -s http://monitoring:9090/api/v1/query \
-d "query=rate(http_requests_total{status=~\"5..\",namespace=\"production\"}[5m])" \
| jq -r '.data.result[0].value[1]')
if [ $(awk "BEGIN {print ($ERROR_RATE > 0.01)}") -eq 1 ]; then
echo "Error rate too high, rolling back"
kubectl rollout undo deployment/myapp -n production
exit 1
fi
# 第二步:全量更新
- kubectl scale deployment/myapp --replicas=10 -n production
rules:
- if: $CI_COMMIT_BRANCH == "main"
持续部署的前提条件
-
测试覆盖率 > 80%:没有充分的自动化测试,自动部署等于自杀
-
完善的监控告警:部署后能自动检测异常并自动回滚
-
金丝雀/蓝绿部署能力:降低自动部署的风险
-
数据库变更零停机:使用扩展-收缩模式
-
功能开关(Feature Flags):新功能可以先上线再开启
自动回滚配置示例
# 监控驱动的自动回滚(Prometheus + ArgoCD)
apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
name: myapp
spec:
strategy:
canary:
steps:
- setWeight: 10 # 10%流量到新版本
- pause: { duration: 5m } # 观察5分钟
analysis: # 自动分析指标
templates:
- templateName: success-rate
args:
- name: service-name
value: myapp
- setWeight: 50 # 通过后增加到50%
- pause: { duration: 5m }
- setWeight: 100 # 全量上线
五、你的团队在哪个层次
| 信号 | CI | CD | Continuous Deployment |
|------|-----|------|----------------------|
| 部署频率 | 每周 | 每天 | 每小时 |
| 部署方式 | 手动 | 手动点击 | 全自动 |
| 测试覆盖率 | 50%+ | 70%+ | 80%+ |
| 回滚方式 | 手动找包 | 一键回滚 | 自动回滚 |
| 监控告警 | 基础 | 完善 | 驱动回滚 |
| 团队心态 | 紧张 | 从容 | 习惯 |
**实践建议**:不要急于追求 Continuous Deployment。先把 CI 做扎实,再迈向持续交付,最后在测试和监控都成熟后再尝试自动部署。跳级发展只会让线上事故频发。
六、本篇要点回顾
-
CI = 构建+测试,CD = CI+自动部署到预发,Continuous Deployment = CD+自动上线生产
-
持续交付的标志是
when: manual,持续部署的标志是自动触发+自动回滚 -
金丝雀部署 + 自动指标分析是持续部署的安全网
-
功能开关(Feature Flags)让新功能可以先部署再开启,降低风险
-
团队应按 CI → CD → Continuous Deployment 逐步演进,不可跳级
下一篇预告:《工具链全景:Jenkins、GitLab CI、GitHub Actions、Drone 对比选型》------了解了三个层次,我们来看看实现这些层次需要哪些工具,以及如何选型。