1. 什么是 CI/CD?
CI/CD 是现代软件工程中的核心实践,代表:
bash
CI → Continuous Integration (持续集成)
CD → Continuous Delivery (持续交付)
/ Continuous Deployment (持续部署)
2. 核心概念拆解
2.1 CI - 持续集成
核心思想:开发者频繁地将代码合并到主分支,每次合并都自动触发构建和测试
解决的问题:
- 传统开发中,团队各自开发,最后合并时产生大量冲突("集成地狱")
- 缺陷发现太晚,修复成本高
包含的环节:
bash
代码提交 → 自动构建 → 自动测试 → 结果反馈
2. CD - 持续交付(Continuous Delivery)
在 CI 基础上,确保代码随时可以部署到生产环境
- 部署动作需要人工触发(手动点击按钮)
- 保证每个版本都是"可发布状态"
2.3 CD - 持续部署(Continuous Deployment)
在持续交付基础上,通过所有测试后自动部署到生产环境,无需人工干预
bash
持续集成 ⊂ 持续交付 ⊂ 持续部署
3. 完整流水线图示
bash
┌─────────────────────────────────────────────────────────────────┐
│ CI/CD Pipeline │
├──────────────┬──────────────────────────┬───────────────────────┤
│ CI 阶段 │ CD 交付阶段 │ CD 部署阶段 │
│ │ │ │
│ ┌─────────┐ │ ┌──────┐ ┌─────────┐ │ ┌──────────────────┐ │
│ │ 代码提交 │ │ │ 打包 │ │ 预发布 │ │ │ 自动/手动部署 │ │
│ └────┬────┘ │ │ 镜像 │ │环境测试 │ │ │ 到生产环境 │ │
│ │ │ └──┬───┘ └────┬────┘ │ └──────────────────┘ │
│ ┌────▼────┐ │ │ │ │ │
│ │ 自动构建 │ │ └─────┬─────┘ │ │
│ └────┬────┘ │ │ │ │
│ │ │ ┌────▼────┐ │ │
│ ┌────▼────┐ │ │ 制品仓库 │ │ │
│ │单元测试 │ │ └─────────┘ │ │
│ │集成测试 │ │ │ │
│ │代码扫描 │ │ │ │
│ └─────────┘ │ │ │
└──────────────┴──────────────────────────┴───────────────────────┘
4. 实际案例:电商网站的 CI/CD
4.1 场景描述
一个电商团队,10 名开发者,使用 GitHub + GitHub Actions + Docker + AWS
4.2 项目结构
bash
ecommerce-app/
├── src/
│ ├── api/
│ └── frontend/
├── tests/
│ ├── unit/
│ └── integration/
├── Dockerfile
├── docker-compose.yml
└── .github/
└── workflows/
└── ci-cd.yml ← 流水线配置文件
4.3 CI/CD 配置文件示例
bash
# .github/workflows/ci-cd.yml
name: CI/CD Pipeline
# 触发条件:Push 到任意分支,或 PR 到 main 分支
on:
push:
branches: [ "*" ]
pull_request:
branches: [ "main" ]
jobs:
# ============================================
# 阶段一:CI - 持续集成
# ============================================
build-and-test:
name: 🔨 构建与测试
runs-on: ubuntu-latest
steps:
# 1. 拉取代码
- name: 检出代码
uses: actions/checkout@v3
# 2. 安装依赖
- name: 安装 Node.js
uses: actions/setup-node@v3
with:
node-version: '18'
cache: 'npm'
- name: 安装依赖包
run: npm ci
# 3. 代码质量检查
- name: ESLint 代码规范检查
run: npm run lint
# 4. 运行单元测试
- name: 运行单元测试
run: npm run test:unit -- --coverage
# 5. 运行集成测试
- name: 运行集成测试
run: npm run test:integration
env:
DB_URL: ${{ secrets.TEST_DB_URL }}
# 6. 上传测试覆盖率报告
- name: 上传覆盖率到 Codecov
uses: codecov/codecov-action@v3
# ============================================
# 阶段二:安全扫描
# ============================================
security-scan:
name: 🔐 安全扫描
runs-on: ubuntu-latest
needs: build-and-test # 依赖上一阶段成功
steps:
- uses: actions/checkout@v3
# 扫描依赖漏洞
- name: 依赖安全审计
run: npm audit --audit-level=high
# SAST 静态代码安全分析
- name: 代码安全扫描
uses: github/codeql-action/analyze@v2
# ============================================
# 阶段三:构建 Docker 镜像
# ============================================
build-image:
name: 🐳 构建镜像
runs-on: ubuntu-latest
needs: security-scan
steps:
- uses: actions/checkout@v3
- name: 登录镜像仓库
uses: docker/login-action@v2
with:
registry: registry.example.com
username: ${{ secrets.REGISTRY_USER }}
password: ${{ secrets.REGISTRY_PASSWORD }}
- name: 构建并推送 Docker 镜像
uses: docker/build-push-action@v4
with:
push: true
# 用 git commit hash 作为镜像版本标签,确保可追溯
tags: |
registry.example.com/ecommerce:latest
registry.example.com/ecommerce:${{ github.sha }}
# ============================================
# 阶段四:CD - 部署到预发布环境(自动)
# ============================================
deploy-staging:
name: 🚀 部署到预发布环境
runs-on: ubuntu-latest
needs: build-image
environment: staging # 关联 GitHub 环境配置
steps:
- name: 部署到 AWS ECS Staging
run: |
aws ecs update-service \
--cluster staging-cluster \
--service ecommerce-service \
--force-new-deployment \
--region us-east-1
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET }}
# 等待部署完成并做冒烟测试
- name: 冒烟测试
run: |
sleep 30
curl -f https://staging.ecommerce.com/health || exit 1
# ============================================
# 阶段五:CD - 部署到生产环境(需人工审批)
# ============================================
deploy-production:
name: 🎯 部署到生产环境
runs-on: ubuntu-latest
needs: deploy-staging
# 只有 main 分支才部署到生产
if: github.ref == 'refs/heads/main'
environment:
name: production
url: https://www.ecommerce.com
# ⚠️ 这里配置了 required reviewers,需要人工审批后才执行
steps:
- name: 蓝绿部署到 AWS ECS Production
run: |
aws ecs update-service \
--cluster prod-cluster \
--service ecommerce-service \
--force-new-deployment
env:
AWS_ACCESS_KEY_ID: ${{ secrets.PROD_AWS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.PROD_AWS_SECRET }}
# 部署后通知
- name: 发送 Slack 通知
uses: slackapi/slack-github-action@v1
with:
payload: |
{
"text": "✅ 生产部署成功!版本: ${{ github.sha }}"
}
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK }}
4.4 完整工作流演示
bash
【第一步】开发者提交代码
─────────────────────────────
张三:修复了购物车 Bug
$ git add .
$ git commit -m "fix: 修复购物车数量计算错误 #Bug-234"
$ git push origin feature/fix-cart-bug
↓ 自动触发 CI 流水线
【第二步】CI 自动执行(约 8 分钟)
─────────────────────────────
✅ 代码检出
✅ 依赖安装
✅ ESLint 检查通过(0 errors)
✅ 单元测试通过(156/156)
✅ 集成测试通过(32/32)
✅ 测试覆盖率 87%(> 80% 阈值)
✅ 安全扫描无高危漏洞
✅ Docker 镜像构建成功
→ 张三在 GitHub 看到绿色 ✅,可以发起 PR
【第三步】代码审查 + 合并
─────────────────────────────
张三发起 PR → main 分支
李四(Tech Lead)Review 代码 → 批准
合并到 main 分支
↓ 再次触发完整流水线
【第四步】自动部署到预发布环境
─────────────────────────────
✅ 所有测试再次通过
✅ 镜像推送到仓库(标签: abc123f)
✅ 自动部署到 staging.ecommerce.com
✅ 冒烟测试通过(/health 返回 200)
→ 通知 QA 团队:预发布环境已更新
【第五步】人工审批 → 生产部署
─────────────────────────────
QA 在预发布环境验证功能 ✅
产品经理在 GitHub Actions 点击 [Approve]
✅ 自动部署到生产环境(蓝绿部署)
✅ Slack 通知:"购物车 Bug 修复已上线 🎉"
整个过程:代码提交 → 生产上线 ≈ 1-2 小时
5.总结
bash
CI/CD 的本质是:
将软件交付过程中的 "人工、重复、易错" 的环节
转变为 "自动化、标准化、可追溯" 的流水线
核心理念:
• 小步快跑,频繁集成
• 快速失败,快速反馈
• 一切即代码(Pipeline as Code)
• 构建一次,部署多处
5.1 CI/CD 带来的价值对比
| 指标 | 传统开发 | CI/CD |
|---|---|---|
| 发布频率 | 每月/每季度 | 每天多次 |
| 每次发布范围 | 大量功能堆积 | 小而精准 |
| 缺陷发现时间 | 发布前/后 | 提交后几分钟 |
| 修复成本 | 高(问题积累) | 低(即时反馈) |
| 回滚能力 | 困难 | 快速(分钟级) |
| 团队信心 | 发布是噩梦 | 发布是常态 |
5.2 常用 CI/CD 工具全景
bash
┌─────────────────────────────────────────────────────┐
│ CI/CD 工具生态 │
├─────────────────┬───────────────────────────────────┤
│ CI/CD 平台 │ GitHub Actions / GitLab CI │
│ │ Jenkins / CircleCI / Travis CI │
│ │ Azure DevOps / AWS CodePipeline │
├─────────────────┼───────────────────────────────────┤
│ 容器化 │ Docker / Podman │
│ 容器编排 │ Kubernetes / AWS ECS │
├─────────────────┼───────────────────────────────────┤
│ 代码质量 │ SonarQube / ESLint / Checkstyle │
│ 安全扫描 │ Snyk / OWASP / CodeQL │
├─────────────────┼───────────────────────────────────┤
│ 制品管理 │ JFrog Artifactory / Nexus │
│ 镜像仓库 │ Docker Hub / ECR / Harbor │
├─────────────────┼───────────────────────────────────┤
│ 监控告警 │ Prometheus + Grafana │
│ 日志 │ ELK Stack / Datadog │
└─────────────────┴───────────────────────────────────┘
6.附录
6.1 ci-cd.yml 文件关键信息介绍
runs-on 、 uses 、 run 这三个关键字处于不同的层级,作用完全不同。
bash
jobs:
build-and-test:
runs-on: ubuntu-latest # ← Job 级别:指定运行环境(机器)
steps:
- name: 安装 Node.js
uses: actions/setup-node@v3 # ← Step 级别:调用现成的 Action
- name: 安装依赖包
run: npm ci # ← Step 级别:直接执行 Shell 命令
6.1.1 runs-on:指定运行环境
- Job 级别,回答"在哪台机器上跑?"
bash
jobs:
my-job:
runs-on: ubuntu-latest # 使用 GitHub 提供的 Ubuntu 虚拟机
可选的运行环境:
bash
runs-on: ubuntu-latest # Linux(最常用)
runs-on: windows-latest # Windows
runs-on: macos-latest # macOS(如 iOS 应用打包)
runs-on: self-hosted # 你自己公司的服务器
6.1.2 uses:调用现成的 Action 插件
- Step 级别,回答"用哪个现成工具?"
bash
steps:
- uses: actions/checkout@v3 # 官方:拉取代码
- uses: actions/setup-node@v3 # 官方:安装 Node.js 环境
- uses: docker/login-action@v2 # 第三方:登录 Docker
- uses: codecov/codecov-action@v3 # 第三方:上传测试覆盖率
Action 格式解析
bash
actions / setup-node @ v3
│ │ │
│ │ └── 版本号(锁定版本,防止自动升级破坏流水线)
│ └──────────── Action 名称
└──────────────────────── 作者/组织名(actions = GitHub 官方)
uses 配合 with 传参:
bash
- uses: actions/setup-node@v3
with: # ← with 是给这个 Action 传入参数
node-version: '18' # 就像调用函数时传参数
cache: 'npm'
6.1.3 run: 直接执行 Shell 命令
- Step 级别,回答"执行什么命令?"
bash
steps:
- run: npm ci # 单行命令
- run: npm run lint # 另一个命令
- run: | # 多行命令用 | 符号
echo "开始部署..."
aws ecs update-service \
--cluster prod-cluster \
--service my-service
echo "部署完成!"
run 配合 env 注入环境变量
bash
- run: npm run test:integration
env: # ← 注入环境变量给这条命令用
DB_URL: ${{ secrets.TEST_DB_URL }} # 从 GitHub Secrets 读取敏感信息
6.1.4 三者对比总结
| 关键字 | 层级 | 作用 | 类比 |
|---|---|---|---|
runs-on |
Job 级 | 指定运行的虚拟机环境 | 选择在哪台电脑上工作 |
uses |
Step 级 | 调用现成的 Action 插件 | 调用封装好的函数 |
run |
Step 级 | 直接执行 Shell 命令 | 在终端手动输入命令 |
6.1.5 什么时候用 uses,什么时候用 run?
bash
需要做一件事时,问自己:
有没有现成的 Action?
│
├── 有 → 用 uses(更简单、有维护保障)
│ 例:安装 Node.js、登录 Docker、上传到 S3
│
└── 没有 / 逻辑很简单 → 用 run(直接写命令)
例:npm ci、自定义脚本、aws cli 命令
适合使用uses的场景:
bash
# ✅ 用 uses:安装 Node.js(复杂操作,有现成 Action)
- uses: actions/setup-node@v3
with:
node-version: '18'
# ❌ 用 run 自己装(麻烦,容易出错)
- run: |
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.0/install.sh | bash
nvm install 18
nvm use 18
# ... 还有一堆配置
适合使用run场景:
bash
# ✅ 用 run:执行 npm 命令(简单直接)
- run: npm ci
- run: npm run lint
# ❌ 用 uses 反而奇怪(没必要封装这么简单的命令)
- uses: someone/npm-ci-action@v1 # 没有必要