CI/CD 自动化部署流程
CI/CD(Continuous Integration / Continuous Deployment)是现代软件交付的核心理念,通过自动化流水线将代码从提交到生产环境的过程标准化、可视化、可追溯。
一、核心概念
CI 与 CD 的区别
| 缩写 | 全称 | 含义 | 触发点 | 终点 |
|---|---|---|---|---|
| CI | Continuous Integration | 持续集成 | 代码提交 | 自动化构建 + 测试通过 |
| CD | Continuous Delivery | 持续交付 | CI 通过 | 可随时一键部署到生产 |
| CD | Continuous Deployment | 持续部署 | CI 通过 | 自动部署到生产环境 |
Delivery vs Deployment:Delivery 强调"随时可部署但需人工确认",Deployment 强调"自动部署无需人工"。团队根据合规要求选择。
价值收益
- 频率提升:从周级发布到日级甚至小时级发布
- 缺陷前置:每次提交都跑测试,问题在引入时即被发现
- 回滚迅速:每次部署都是原子操作,失败可快速回退
- 环境一致:通过容器和 IaC 消除"在我机器上能跑"问题
- 审计可追溯:每次发布都有完整的构建日志和审批记录
二、典型流水线阶段
一条完整的 CI/CD 流水线通常包含以下阶段,每个阶段失败都会阻断流水线并通知责任人:
代码提交 → 代码扫描 → 依赖安装 → 构建 → 单元测试 → 集成测试
↓
[CI 结束]
↓
镜像打包 → 推送仓库 → 部署预发 → 冒烟测试 → 人工审批 → 部署生产 → 健康检查
↓
[CD 结束]
↓
监控告警 → 日志聚合各阶段详解
| 阶段 | 任务 | 失败处理 | 典型耗时 |
|---|---|---|---|
| 代码扫描 | Lint、格式检查、安全漏洞扫描 | 阻断提交,通知作者修复 | 10~30s |
| 依赖安装 | npm ci / pip install / mvn install | 缓存失效时重试 | 30s~3min |
| 构建 | 编译、打包、生成制品 | 检查构建配置和依赖版本 | 1~5min |
| 单元测试 | 运行 npm test / pytest | 标记失败用例,阻断流水线 | 30s~3min |
| 集成测试 | 启动依赖服务,跑端到端测试 | 检查测试环境配置 | 2~10min |
| 镜像打包 | docker build 生成镜像 | 检查 Dockerfile 语法 | 1~5min |
| 推送仓库 | 推送到 Harbor / ACR / GHCR | 检查权限和仓库配额 | 30s~2min |
| 部署预发 | 滚动更新预发环境 | 健康检查失败自动回滚 | 1~3min |
| 冒烟测试 | 核心链路 API 验证 | 通知值班人员介入 | 30s~2min |
| 人工审批 | 生产环境合规要求 | 审批人可以在 4 小时内决策 | 数分钟~数小时 |
| 部署生产 | 蓝绿 / 金丝雀 / 滚动发布 | 健康检查失败自动回滚 | 1~5min |
| 健康检查 | 探活接口、关键指标监控 | 触发告警并自动回滚 | 持续 |
三、主流工具链选型
CI/CD 平台对比
| 平台 | 特点 | 适用场景 | 价格模式 |
|---|---|---|---|
| GitHub Actions | 与 GitHub 深度集成,生态丰富 | 开源项目、GitHub 托管项目 | 按分钟计费,公开仓库免费 |
| GitLab CI | 一体化 DevOps 平台,自托管友好 | 企业内网部署、合规要求高 | 自托管免费,云版按用户 |
| Jenkins | 老牌开源,插件生态庞大 | 复杂定制化需求、遗留系统 | 完全免费,需自维护 |
| CircleCI | 速度快,并行能力强 | 重测试场景、需要 macOS | 按 credito 按用量 |
| Drone | 云原生、容器化原生 | K8s 环境、轻量级需求 | 免费开源 |
CI/CD 配套工具
| 类别 | 推荐工具 |
|---|---|
| 容器运行时 | Docker、containerd、Podman |
| 镜像仓库 | Harbor、ACR、ECR、GHCR |
| 编排调度 | Kubernetes、Docker Compose、Nomad |
| 配置管理 | Ansible、Chef、Puppet |
| 基础设施即代码 | Terraform、Pulumi、Crossplane |
| 密钥管理 | Vault、AWS Secrets Manager、Sealed Secrets |
| 监控告警 | Prometheus + Grafana、Datadog、夜莺 |
| 日志聚合 | ELK、Loki、ClickHouse |
| 制品仓库 | Nexus、JFrog Artifactory |
四、GitHub Actions 实战示例
以一个 VitePress 文档站点为例,演示从提交到自动部署到 GitHub Pages 的完整流程。
工作流文件
在仓库根目录创建 .github/workflows/deploy.yml:
yaml
name: Build and Deploy
on:
push:
branches: [main] # 主分支推送触发
workflow_dispatch: # 支持手动触发
permissions:
contents: read
pages: write
id-token: write
# 同一分支只跑最新一次,取消历史构建
concurrency:
group: pages
cancel-in-progress: true
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 20
cache: npm
- name: Install dependencies
run: npm ci
- name: Build
run: npm run build
- name: Upload artifact
uses: actions/upload-pages-artifact@v3
with:
path: docs/.vitepress/dist
deploy:
needs: build
runs-on: ubuntu-latest
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4关键字段说明
| 字段 | 作用 |
|---|---|
on.push.branches | 触发条件:指定分支推送时执行 |
workflow_dispatch | 允许在 Actions 面板手动触发 |
permissions | 授予 GITHUB_TOKEN 的权限(pages 部署必需) |
concurrency | 并发控制,避免重复部署 |
cache: npm | 缓存 npm 依赖,加速后续构建 |
needs | 作业依赖关系,deploy 必须等 build 成功 |
environment | 部署环境,可在 Settings → Environments 配置审批 |
环境变量与密钥
敏感信息(API Key、Token)通过 Secrets 注入:
yaml
- name: Deploy to server
env:
SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }}
DEPLOY_HOST: ${{ vars.DEPLOY_HOST }}
run: |
mkdir -p ~/.ssh
echo "$SSH_PRIVATE_KEY" > ~/.ssh/id_rsa
chmod 600 ~/.ssh/id_rsa
rsync -avz --delete docs/.vitepress/dist/ user@${DEPLOY_HOST}:/var/www/html密钥类型:
secrets.*— 加密变量,日志中自动打码,适合敏感数据vars.*— 明文变量,所有协作者可见,适合配置项
五、部署策略
1. 滚动部署(Rolling Update)
逐步替换旧版本实例,适合无状态服务:
v1 v1 v1 v1 → v2 v1 v1 v1 → v2 v2 v1 v1 → v2 v2 v2 v2- 优点:资源占用平稳,无需额外容量
- 缺点:过渡期 v1 v2 共存,需保证兼容性
- K8s 配置:
strategy: type: RollingUpdate,maxSurge: 25%,maxUnavailable: 25%
2. 蓝绿部署(Blue-Green)
准备两套完全一致的环境,切换流量:
[蓝环境 v1 ← 流量] [绿环境 v2]
↓ 部署新版本到绿环境
[蓝环境 v1] [绿环境 v2 ← 流量,验证通过后切换]- 优点:切换瞬时完成,回滚只需切回
- 缺点:需要双倍资源
- 适用:数据库变更风险高、要求零停机的场景
3. 金丝雀发布(Canary)
小流量先试,逐步扩大:
v1 100% → v1 90% / v2 10% → v1 50% / v2 50% → v2 100%- 优点:风险可控,问题影响范围小
- 缺点:需要流量分发和监控支持
- K8s 工具:Argo Rollouts、Flagger
4. 特性开关(Feature Flag)
代码已部署但功能默认关闭,按需开启:
javascript
if (featureFlags.isEnabled('new-checkout-flow')) {
return newCheckoutFlow();
}
return legacyCheckoutFlow();- 优点:解耦"部署"与"发布",可随时关闭
- 缺点:代码路径复杂,需管理开关生命周期
- 工具:LaunchDarkly、Unleash、Flagsmith
六、回滚机制
回滚决策矩阵
| 场景 | 决策 | 操作 |
|---|---|---|
| 健康检查失败 | 立即自动回滚 | 切换到上一个稳定版本 |
| 错误率 > 1% 持续 5 分钟 | 自动回滚 + 告警 | 触发 PagerDuty 通知 |
| 业务反馈异常 | 人工决策回滚 | 运维人员执行回滚脚本 |
| 数据库已变更 | 谨慎评估 | 优先恢复数据,再回滚代码 |
快速回滚实践
K8s 回滚:
bash
# 查看发布历史
kubectl rollout history deployment/my-app
# 回滚到上一版本
kubectl rollout undo deployment/my-app
# 回滚到指定版本
kubectl rollout undo deployment/my-app --to-revision=3Git 回滚策略:
- 优先使用
git revert(保留历史) - 紧急情况可用
git reset+ 强推(确保无人基于该分支开发) - 数据库迁移必须配套"回滚迁移"脚本
七、最佳实践
1. 流水线设计原则
- 快速失败:将耗时短的检查(lint、单测)放前面,快速阻断
- 并行化:无依赖阶段并行执行,缩短整体耗时
- 缓存优化:依赖、构建产物、Docker layer 全部缓存
- 幂等性:任何阶段重跑都能得到相同结果
- 可观测性:每个阶段都有清晰的日志和状态指标
2. 安全实践
yaml
# 最小权限原则
permissions:
contents: read # 只授予必要权限
# 第三方 Action 锁定版本
- uses: actions/checkout@v4 # ✅ 锁定大版本
# 而非 actions/checkout@main ❌
# SAST 扫描
- uses: github/codeql-action/init@v3
- uses: github/codeql-action/analyze@v3
# 依赖漏洞扫描
- run: npm audit --audit-level=high
- uses: snyk/actions/node@master
env:
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
# 镜像签名(Cosign)
- uses: sigstore/cosign-installer@v3
- run: cosign sign --key env://COSIGN_PRIVATE_KEY my-registry/app:v13. 制品管理
- 版本规范:语义化版本
v1.2.3或 Git SHA 短哈希 - 不可变标签:禁止覆盖已发布的镜像标签
- 保留策略:开发环境保留 7 天,生产环境保留 90 天
- 多架构镜像:
docker buildx构建 amd64/arm64 多架构
4. 数据库变更
yaml
# 数据库迁移独立于应用部署
jobs:
migrate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run migrations
run: |
npm run db:migrate
# 同时生成回滚脚本
npm run db:migrate:rollback > rollback.sql
- name: Save rollback script
uses: actions/upload-artifact@v4
with:
name: rollback-sql
path: rollback.sql变更原则:
- 向后兼容:先部署兼容旧代码的 schema,再部署新代码
- 分步执行:添加列 → 部署代码读写新列 → 数据迁移 → 删除旧列
- 回滚优先:每次变更都要准备回滚脚本
- 窗口期:大规模变更选低峰时段
5. 监控与告警
关键指标:
- SLO:服务等级目标(如 99.9% 可用性)
- 错误预算:SLO 容许的错误率,超支则冻结发布
- DORA 四指标:部署频率、变更前置时间、变更失败率、平均恢复时间
告警分级:
| 级别 | 响应时间 | 通知方式 |
|---|---|---|
| P0 紧急 | 5 分钟 | 电话 + 短信 + IM |
| P1 高 | 15 分钟 | 短信 + IM |
| P2 中 | 1 小时 | IM |
| P3 低 | 工作日 | 邮件 |
八、CI/CD 成熟度模型
| 级别 | 特征 | 典型表现 |
|---|---|---|
| L0 手动 | 全人工操作 | 手动打包、手动上传、手动改配置 |
| L1 脚本化 | 半自动 | 部分步骤脚本化,仍需人工触发 |
| L2 CI | 持续集成 | 提交自动触发构建和测试 |
| L3 CD | 持续交付 | 自动部署到预发,生产需人工确认 |
| L4 全自动 | 持续部署 | 测试通过即自动上线 |
| L5 自治 | GitOps + 渐进式发布 | 声明式配置、自动金丝雀、自适应回滚 |
演进建议:从 L2 起步,建立信心后过渡到 L3,关键业务再考虑 L4+。盲目追求 L4 可能因测试覆盖不足导致事故。
九、常见陷阱
1. "雪花"环境
问题:手动配置的环境随时间漂移,无法复现 解决:基础设施即代码(Terraform / Ansible),所有变更通过 PR
2. 流水线过长
问题:单次构建 30+ 分钟,反馈太慢 解决:
- 拆分流水线,关键路径优先(lint → 单测 → 部署,集成测试并行)
- 缓存依赖和构建产物
- 测试分片(test sharding)
3. 脆弱测试(Flaky Test)
问题:偶发失败的测试阻断流水线 解决:
- 隔离测试数据,避免共享状态
- 时间敏感测试使用 mock 时钟
- 标记 flaky 测试,quarantine 后修复
4. 密钥泄露
问题:Token 写在代码或日志中 解决:
- 一律使用 Secrets Manager
- CI 日志自动打码敏感字段
- 启用 push protection 阻止提交密钥
5. 数据库迁移卡住
问题:长事务迁移锁表导致服务不可用 解决:
- 大表变更使用
pt-online-schema-change或gh-ost - 分批迁移,每批限制行数
- 低峰期执行
十、学习路径
入门阶段(1~2 周)
- 阅读 GitHub Actions 官方文档
- 为现有项目添加一个 lint + test 工作流
- 体验部署到 GitHub Pages
实践阶段(1~2 月)
- 学习 Docker 基础,编写 Dockerfile
- 使用 docker-compose 编排多容器应用
- 在 VPS 上部署一个完整应用(前端 + 后端 + 数据库)
- 配置 Nginx 反向代理和 HTTPS
进阶阶段(3~6 月)
- 学习 Kubernetes 基础,部署应用到 K8s
- 使用 Helm 管理应用部署
- 接入 Prometheus + Grafana 监控
- 实践蓝绿部署和金丝雀发布
高级阶段(持续)
- 学习 GitOps(ArgoCD / Flux)
- 掌握 IaC(Terraform)
- 实践混沌工程(Chaos Mesh)
- 构建平台工程能力(Backstage)
参考资料
- GitHub Actions 官方文档
- GitLab CI/CD 文档
- Kubernetes 部署策略
- DORA 研究报告
- 《持续交付》— Jez Humble
- 《Site Reliability Engineering》— Google SRE 团队
