跳至正文

🚀 什么是 CI/CD?

📖 定义

CI/CD 是持续集成(Continuous Integration)和持续部署(Continuous Deployment)的缩写,是一种自动测试和部署代码变更的开发方法论。CI 在每次合并代码时自动构建和测试,CD 则自动将通过测试的代码部署到生产环境。这使得能够快速发现错误并缩短部署周期。

🎯 通过比喻理解

工厂自动化生产线

传统开发 (手动)
开发者 1 → 编写代码
开发者 2 → 编写代码
开发者 3 → 编写代码

一周后

团队负责人手动合并

发生冲突! 😱

手动测试

发现错误! 😱

手动部署

部署失败! 😱

CI/CD (自动化)
开发者 1 → 推送代码 → 自动测试 ✅ → 自动部署 🚀
开发者 2 → 推送代码 → 自动测试 ✅ → 自动部署 🚀
开发者 3 → 推送代码 → 自动测试 ❌ → 立即通知!
(立即发现并修复问题)

食品配送

手动部署 = 直接配送
1. 制作食品 (开发)
2. 检查包装 (测试)
3. 确认地址 (配置)
4. 直接开车配送 (部署)
5. 客户确认 (验证)
→ 耗时长,容易出错

CI/CD = 自动配送系统
1. 制作食品 (开发)
2. 自动包装 (自动构建)
3. 自动质量检查 (自动测试)
4. 机器人自动配送 (自动部署)
5. 实时追踪 (监控)
→ 快速准确

⚙️ 工作原理

1. CI/CD 流水线

代码推送 (Push)

┌─────────────────────────────┐
│ CI: 持续集成 │
│ │
│ 1. 检出代码 │
│ 2. 安装依赖 │
│ 3. 代码检查 │
│ 4. 构建 │
│ 5. 单元测试 │
│ 6. 集成测试 │
│ │
│ ✅ 全部成功 │
│ ❌ 任一失败 → 停止 │
└─────────────────────────────┘

┌─────────────────────────────┐
│ CD: 持续部署 │
│ │
│ 1. 部署到预发环境 │
│ 2. E2E 测试 │
│ 3. 部署到生产环境 │
│ 4. 健康检查 │
│ 5. 发送通知 │
└─────────────────────────────┘

🎉 部署完成!

2. 传统方式 vs CI/CD

传统方式
┌─────────────────────────────────────┐
│ 星期一 │
│ - 开发者 A: 开发功能 1 │
│ - 开发者 B: 开发功能 2 │
│ - 开发者 C: 修复错误 │
├─────────────────────────────────────┤
│ 星期二-星期四 │
│ - 各自继续开发 │
│ - 冲突可能性增加 😰 │
├─────────────────────────────────────┤
│ 星期五 (集成日) │
│ - 合并所有代码 │
│ - 花一整天解决冲突 😱 │
│ - 运行测试 → 发现很多错误 │
│ - 加班到周末 😭 │
└─────────────────────────────────────┘

CI/CD 方式
┌─────────────────────────────────────┐
│ 每天,每次提交 │
│ │
│ 开发者 A: 推送代码 │
│ → 自动测试 ✅ │
│ → 自动部署 🚀 │
│ → 3分钟后反映到生产环境 │
│ │
│ 开发者 B: 推送代码 │
│ → 自动测试 ❌ │
│ → 立即通知 │
│ → 立即修复 (5分钟) │
│ │
│ - 最小化冲突 │
│ - 快速发现错误 │
│ - 按时下班 😊 │
└─────────────────────────────────────┘

3. CI/CD 组件

┌──────────────────────────────────────┐
│ Version Control (版本控制) │
│ Git, GitHub, GitLab │
└───────────────┬──────────────────────┘
↓ Push
┌──────────────────────────────────────┐
│ CI Server (持续集成服务器) │
│ GitHub Actions, Jenkins, CircleCI │
│ - 检出代码 │
│ - 自动构建 │
│ - 自动测试 │
└───────────────┬──────────────────────┘
↓ Pass
┌──────────────────────────────────────┐
│ Artifact Repository (构建仓库) │
│ Docker Hub, npm Registry │
└───────────────┬──────────────────────┘
↓ Deploy
┌──────────────────────────────────────┐
│ Deployment (部署) │
│ AWS, Heroku, Vercel, Netlify │
└───────────────┬──────────────────────┘
↓ Monitor
┌──────────────────────────────────────┐
│ Monitoring (监控) │
│ Sentry, DataDog, CloudWatch │
└──────────────────────────────────────┘

💡 实际示例

GitHub Actions 基本示例

# .github/workflows/ci.yml
# Node.js 项目 CI/CD 流水线

name: CI/CD Pipeline

# 触发器: 推送到 main 分支或创建 PR 时
on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main ]

# 环境变量
env:
NODE_VERSION: '18'

# 任务定义
jobs:
# ========== Job 1: 测试 ==========
test:
name: 测试和代码检查
runs-on: ubuntu-latest # 在 Ubuntu 环境运行

steps:
# 1. 检出代码
- name: 检出代码
uses: actions/checkout@v3

# 2. 设置 Node.js
- name: 设置 Node.js
uses: actions/setup-node@v3
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'npm' # 使用 npm 缓存

# 3. 安装依赖
- name: 安装依赖
run: npm ci # 比 npm install 更快更稳定

# 4. 代码检查
- name: 运行代码检查
run: npm run lint

# 5. 单元测试
- name: 运行测试
run: npm test

# 6. 代码覆盖率
- name: 上传覆盖率
uses: codecov/codecov-action@v3
with:
token: ${{ secrets.CODECOV_TOKEN }}

# ========== Job 2: 构建 ==========
build:
name: 构建
needs: test # test 成功后才运行
runs-on: ubuntu-latest

steps:
- name: 检出代码
uses: actions/checkout@v3

- name: 设置 Node.js
uses: actions/setup-node@v3
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'npm'

- name: 安装依赖
run: npm ci

# 生产构建
- name: 生产构建
run: npm run build
env:
NODE_ENV: production

# 上传构建产物
- name: 上传构建产物
uses: actions/upload-artifact@v3
with:
name: build-files
path: dist/
retention-days: 7

# ========== Job 3: 部署 ==========
deploy:
name: 生产部署
needs: build # build 成功后才运行
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main' # 只部署 main 分支

steps:
- name: 检出代码
uses: actions/checkout@v3

# 下载构建产物
- name: 下载构建产物
uses: actions/download-artifact@v3
with:
name: build-files
path: dist/

# 部署到 Vercel
- name: 部署到 Vercel
uses: amondnet/vercel-action@v25
with:
vercel-token: ${{ secrets.VERCEL_TOKEN }}
vercel-org-id: ${{ secrets.VERCEL_ORG_ID }}
vercel-project-id: ${{ secrets.VERCEL_PROJECT_ID }}
vercel-args: '--prod'

# Slack 通知
- name: Slack 通知
if: always() # 无论成功/失败都运行
uses: 8398a7/action-slack@v3
with:
status: ${{ job.status }}
text: '部署${{ job.status }}!'
webhook_url: ${{ secrets.SLACK_WEBHOOK }}

Docker 构建和部署

# .github/workflows/docker.yml
# Docker 镜像构建和部署

name: Docker CI/CD

on:
push:
branches: [ main ]
tags:
- 'v*' # 推送 v1.0.0 这样的标签时运行

env:
DOCKER_IMAGE: myapp
DOCKER_REGISTRY: docker.io

jobs:
docker:
name: Docker 构建和推送
runs-on: ubuntu-latest

steps:
- name: 检出代码
uses: actions/checkout@v3

# 提取 Docker 元数据
- name: 提取 Docker 元数据
id: meta
uses: docker/metadata-action@v4
with:
images: ${{ env.DOCKER_REGISTRY }}/${{ env.DOCKER_IMAGE }}
tags: |
type=ref,event=branch
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=sha

# 设置 Docker Buildx (多平台构建)
- name: 设置 Docker Buildx
uses: docker/setup-buildx-action@v2

# 登录 Docker Hub
- name: 登录 Docker Hub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}

# 构建和推送 Docker 镜像
- name: 构建和推送 Docker 镜像
uses: docker/build-push-action@v4
with:
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha # 使用 GitHub Actions 缓存
cache-to: type=gha,mode=max
platforms: linux/amd64,linux/arm64 # 多平台

# 部署 (SSH 连接服务器并重启容器)
- name: 部署到服务器
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.SERVER_HOST }}
username: ${{ secrets.SERVER_USERNAME }}
key: ${{ secrets.SERVER_SSH_KEY }}
script: |
docker pull ${{ env.DOCKER_REGISTRY }}/${{ env.DOCKER_IMAGE }}:latest
docker stop myapp || true
docker rm myapp || true
docker run -d \
--name myapp \
-p 3000:3000 \
--restart unless-stopped \
${{ env.DOCKER_REGISTRY }}/${{ env.DOCKER_IMAGE }}:latest
docker system prune -af

拉取请求自动验证

# .github/workflows/pr.yml
# PR 创建时自动验证

name: Pull Request Check

on:
pull_request:
types: [ opened, synchronize, reopened ]

jobs:
# ========== 代码质量检查 ==========
lint:
name: 代码检查和格式化检查
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v3

- name: 设置 Node.js
uses: actions/setup-node@v3
with:
node-version: '18'
cache: 'npm'

- name: 安装依赖
run: npm ci

# ESLint
- name: 运行 ESLint
run: npm run lint

# Prettier
- name: Prettier 检查
run: npm run format:check

# TypeScript 类型检查
- name: TypeScript 类型检查
run: npm run type-check

# ========== 测试 ==========
test:
name: 测试
runs-on: ubuntu-latest

strategy:
matrix:
node-version: [16, 18, 20] # 在多个 Node 版本上测试

steps:
- uses: actions/checkout@v3

- name: 设置 Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
cache: 'npm'

- name: 安装依赖
run: npm ci

- name: 运行测试
run: npm test -- --coverage

- name: 上传测试结果
uses: actions/upload-artifact@v3
if: always()
with:
name: test-results-${{ matrix.node-version }}
path: coverage/

# ========== 安全检查 ==========
security:
name: 安全检查
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v3

- name: 设置 Node.js
uses: actions/setup-node@v3
with:
node-version: '18'

# npm audit
- name: npm 安全检查
run: npm audit --audit-level=moderate

# Snyk 安全扫描
- name: Snyk 安全扫描
uses: snyk/actions/node@master
env:
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}

# ========== 性能测试 ==========
performance:
name: 性能测试
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v3

- name: 设置 Node.js
uses: actions/setup-node@v3
with:
node-version: '18'
cache: 'npm'

- name: 安装依赖
run: npm ci

- name: 构建
run: npm run build

# Lighthouse CI
- name: Lighthouse CI
uses: treosh/lighthouse-ci-action@v9
with:
urls: |
http://localhost:3000
uploadArtifacts: true

# ========== PR 评论 ==========
comment:
name: PR 评论
needs: [lint, test, security]
runs-on: ubuntu-latest
if: always()

steps:
- name: 编写 PR 评论
uses: actions/github-script@v6
with:
script: |
const statuses = {
lint: '${{ needs.lint.result }}',
test: '${{ needs.test.result }}',
security: '${{ needs.security.result }}'
};

let message = '## 🤖 CI 检查结果\n\n';

for (const [job, status] of Object.entries(statuses)) {
const emoji = status === 'success' ? '✅' : '❌';
message += `${emoji} **${job}**: ${status}\n`;
}

github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: message
});

定时任务

# .github/workflows/scheduled.yml
# 定期执行的任务

name: Scheduled Tasks

on:
schedule:
# 每天上午 9 点 (UTC 0点)
- cron: '0 0 * * *'
workflow_dispatch: # 也可手动执行

jobs:
# ========== 依赖更新检查 ==========
dependency-check:
name: 检查依赖更新
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v3

- name: 设置 Node.js
uses: actions/setup-node@v3
with:
node-version: '18'

# 检查 npm outdated
- name: 检查过时的包
run: npm outdated || true

# Dependabot 替代方案
- name: 检查可更新的包
run: |
npx npm-check-updates

# ========== 备份 ==========
backup:
name: 数据库备份
runs-on: ubuntu-latest

steps:
- name: 备份数据库
run: |
# 实际运行数据库备份脚本
echo "正在备份数据库..."

# 上传到 S3
- name: 上传备份到 S3
uses: aws-actions/configure-aws-credentials@v2
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: ap-northeast-2

- name: 上传到 S3
run: |
aws s3 cp backup.sql s3://my-backups/$(date +%Y%m%d).sql

# ========== 性能监控 ==========
performance-monitor:
name: 性能监控
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v3

- name: 运行 Lighthouse
uses: treosh/lighthouse-ci-action@v9
with:
urls: |
https://myapp.com
uploadArtifacts: true

# 性能下降时的 Slack 通知
- name: 性能下降警报
if: failure()
uses: 8398a7/action-slack@v3
with:
status: failure
text: '⚠️ 网站性能下降!'
webhook_url: ${{ secrets.SLACK_WEBHOOK }}

多环境部署

# .github/workflows/multi-env.yml
# 按环境部署: 开发、预发、生产

name: Multi-Environment Deployment

on:
push:
branches:
- develop # 开发环境
- staging # 预发环境
- main # 生产环境

jobs:
deploy:
name: 部署
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v3

- name: 设置 Node.js
uses: actions/setup-node@v3
with:
node-version: '18'
cache: 'npm'

- name: 安装依赖
run: npm ci

# 按环境构建
- name: 构建
run: npm run build
env:
NODE_ENV: ${{
github.ref == 'refs/heads/main' && 'production' ||
github.ref == 'refs/heads/staging' && 'staging' ||
'development'
}}

# 部署到开发环境
- name: 部署到开发环境
if: github.ref == 'refs/heads/develop'
run: |
echo "正在部署到开发环境..."
# 开发服务器部署命令
env:
API_URL: https://api-dev.myapp.com

# 部署到预发环境
- name: 部署到预发环境
if: github.ref == 'refs/heads/staging'
run: |
echo "正在部署到预发环境..."
# 预发服务器部署命令
env:
API_URL: https://api-staging.myapp.com

# 部署到生产环境
- name: 部署到生产环境
if: github.ref == 'refs/heads/main'
uses: aws-actions/amazon-ecs-deploy-task-definition@v1
with:
task-definition: task-definition.json
service: myapp-service
cluster: production-cluster
wait-for-service-stability: true
env:
API_URL: https://api.myapp.com

# 部署后健康检查
- name: 健康检查
run: |
sleep 30 # 等待 30 秒

URL=${{
github.ref == 'refs/heads/main' && 'https://myapp.com' ||
github.ref == 'refs/heads/staging' && 'https://staging.myapp.com' ||
'https://dev.myapp.com'
}}

STATUS=$(curl -s -o /dev/null -w "%{http_code}" $URL/health)

if [ $STATUS -eq 200 ]; then
echo "✅ 健康检查通过"
else
echo "❌ 健康检查失败: $STATUS"
exit 1
fi

# 部署成功通知
- name: 部署通知
if: success()
uses: 8398a7/action-slack@v3
with:
status: success
text: |
🚀 部署完成!
环境: ${{ github.ref_name }}
提交: ${{ github.sha }}
webhook_url: ${{ secrets.SLACK_WEBHOOK }}

🤔 常见问题

Q1. 采用 CI/CD 有什么好处?

A:

✅ 主要好处:

1. 快速发现错误
传统: 一周后集成时发现
CI/CD: 提交后 5 分钟内发现
→ 修复成本降低 90%

2. 缩短部署时间
传统: 手动部署需要 2 小时
CI/CD: 自动部署需要 5 分钟
→ 每天可以多次部署

3. 提高代码质量
- 自动代码检查、测试
- 自动化代码审查
- 减少错误

4. 提高开发者生产力
- 消除手动任务
- 自动化重复工作
- 只专注于开发

5. 提高稳定性
- 自动测试
- 自动回滚
- 一致的部署流程

6. 改善协作
- 最小化代码冲突
- 快速反馈
- 透明的流程

实际案例:
A 公司: CI/CD 之前
- 部署: 每月 1 次
- 部署时间: 4 小时
- 部署失败率: 30%
- 错误发现时间: 平均 3 天

A 公司: CI/CD 之后
- 部署: 每天 10 次
- 部署时间: 5 分钟
- 部署失败率: 5%
- 错误发现时间: 平均 10 分钟

Q2. GitHub Actions vs Jenkins vs CircleCI?

A:

// ========== GitHub Actions ==========
优点:
-GitHub 完美集成
- 免费 (公共仓库,每月 2000 分钟免费)
- 设置简单 (YAML 文件)
- Marketplace 中有各种 Actions

缺点:
- 依赖 GitHub
- 复杂工作流受限

适用场景:
- 使用 GitHub
- 简单的 CI/CD
- 需要快速开始

示例:
name: CI
on: [push]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- run: npm test

// ========== Jenkins ==========
优点:
- 最古老且最成熟
- 庞大的插件生态系统
- 完全自定义
- 自托管 (无成本)

缺点:
- 设置复杂
- UI 老旧
- 需要维护
- 学习曲线陡峭

适用场景:
- 本地部署环境
- 复杂流水线
- 遗留系统集成

// ========== CircleCI ==========
优点:
- 构建速度快
- Docker 支持优秀
- 强大的并行处理
- UI 直观

缺点:
- 付费 (免费层受限)
- 仅支持 GitHub/GitLab

适用场景:
- 性能至关重要
- 大量使用 Docker
- 需要并行测试

// ========== 对比表 ==========
特性 | GitHub Actions | Jenkins | CircleCI
--------------|----------------|---------|----------
价格 | 免费(受限) | 免费 | 付费
设置难度 | 简单 | 困难 | 中等
性能 | 中等 ||
集成性 | GitHub 完美 | 通用 | 通用
维护 | 不需要 | 需要 | 不需要
Docker 支持 | 中等 || 非常好

推荐:
- 小型团队,使用 GitHubGitHub Actions
- 大型团队,复杂需求 → Jenkins
- 性能至关重要,有预算 → CircleCI

Q3. 如何管理密钥(Secrets)?

A:

# ========== GitHub Secrets 设置 ==========
# Repository → Settings → Secrets and variables → Actions

# 使用方法
- name: 部署
env:
API_KEY: ${{ secrets.API_KEY }}
DATABASE_URL: ${{ secrets.DATABASE_URL }}
run: npm run deploy

# ========== 按环境设置 Secrets ==========
# Repository → Settings → Environments

# 生产环境
environment: production
env:
API_URL: ${{ secrets.PROD_API_URL }}

# 预发环境
environment: staging
env:
API_URL: ${{ secrets.STAGING_API_URL }}

# ========== 安全最佳实践 ==========

# ✅ 好的做法
- name: 部署
env:
# 从 Secrets 获取
AWS_ACCESS_KEY: ${{ secrets.AWS_ACCESS_KEY }}
AWS_SECRET_KEY: ${{ secrets.AWS_SECRET_KEY }}
run: |
# 使用环境变量
aws s3 sync dist/ s3://my-bucket

# ❌ 不好的做法
- name: 部署
run: |
# 硬编码在代码中 (绝对禁止!)
export AWS_ACCESS_KEY="AKIAIOSFODNN7EXAMPLE"
export AWS_SECRET_KEY="wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"

# ========== 创建 .env 文件 ==========
- name: 创建 .env 文件
run: |
cat << EOF > .env
API_URL=${{ secrets.API_URL }}
DATABASE_URL=${{ secrets.DATABASE_URL }}
JWT_SECRET=${{ secrets.JWT_SECRET }}
EOF

# ========== Docker Secrets ==========
- name: Docker 构建
env:
DOCKER_BUILDKIT: 1
run: |
docker build \
--secret id=api_key,env=API_KEY \
--secret id=db_url,env=DATABASE_URL \
-t myapp .

# Dockerfile
# syntax=docker/dockerfile:1
FROM node:18
RUN --mount=type=secret,id=api_key \
API_KEY=$(cat /run/secrets/api_key) npm install

# ========== 使用 Vault (高级) ==========
- name: 从 HashiCorp Vault 获取 Secrets
uses: hashicorp/vault-action@v2
with:
url: https://vault.mycompany.com
token: ${{ secrets.VAULT_TOKEN }}
secrets: |
secret/data/production api_key | API_KEY ;
secret/data/production db_url | DATABASE_URL

Q4. 测试失败会怎样?

A:

# ========== 默认行为: 流水线停止 ==========
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: 运行测试
run: npm test
# 测试失败 → 在此停止
# 后续步骤不执行

deploy:
needs: test # test 成功后才运行
runs-on: ubuntu-latest
steps:
- name: 部署
run: npm run deploy
# test 失败时此步骤不运行 ✅

# ========== 失败后继续 ==========
- name: 运行测试
run: npm test
continue-on-error: true # 失败后继续
# ⚠️ 不推荐

# ========== 条件执行 ==========
- name: 失败时通知
if: failure() # 仅在上一步失败时
uses: 8398a7/action-slack@v3
with:
status: failure
text: '❌ 测试失败!'
webhook_url: ${{ secrets.SLACK_WEBHOOK }}

- name: 成功时部署
if: success() # 仅在所有步骤成功时
run: npm run deploy

- name: 总是运行
if: always() # 无论成功/失败
run: npm run cleanup

# ========== 重试 ==========
- name: 不稳定的测试 (重试)
uses: nick-invision/retry@v2
with:
timeout_minutes: 10
max_attempts: 3
command: npm run test:e2e

# ========== 失败处理示例 ==========
- name: 测试
id: test
run: npm test

- name: 测试失败时创建 issue
if: failure() && steps.test.outcome == 'failure'
uses: actions/github-script@v6
with:
script: |
github.rest.issues.create({
owner: context.repo.owner,
repo: context.repo.repo,
title: '❌ 测试失败',
body: '测试失败。需要检查。',
labels: ['bug', 'ci']
});

# ========== 回滚 ==========
- name: 部署
id: deploy
run: npm run deploy

- name: 部署失败时回滚
if: failure() && steps.deploy.outcome == 'failure'
run: |
echo "部署失败! 正在回滚到之前版本..."
npm run rollback

Q5. 如何降低成本?

A:

# ========== 使用缓存 ==========
- name: 设置 Node.js
uses: actions/setup-node@v3
with:
node-version: '18'
cache: 'npm' # npm 缓存 (构建时间减少 50%)

- name: 缓存依赖
uses: actions/cache@v3
with:
path: |
~/.npm
node_modules
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-node-

# ========== 条件执行 ==========
on:
push:
branches: [ main ]
paths: # 仅在特定文件更改时运行
- 'src/**'
- 'package.json'
# README.md 更改时不运行 → 节省成本

# ========== 限制并发运行 ==========
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true # 取消之前的运行
# → 防止不必要的重复运行

# ========== Self-hosted Runner ==========
# 使用自己的服务器 (免费)
jobs:
build:
runs-on: self-hosted # 不使用 GitHub Actions 免费分钟
steps:
- run: npm run build

# ========== 只运行短任务 ==========
- name: 检查更改的文件
id: changes
uses: dorny/paths-filter@v2
with:
filters: |
frontend:
- 'frontend/**'
backend:
- 'backend/**'

- name: 前端测试
if: steps.changes.outputs.frontend == 'true'
run: npm run test:frontend
# 仅在前端更改时运行 → 节省时间

- name: 后端测试
if: steps.changes.outputs.backend == 'true'
run: npm run test:backend

# ========== 成本示例 ==========
# GitHub Actions 免费层:
# - 公共仓库: 无限制
# - 私有仓库: 每月 2000 分钟

# 优化前:
# - 每次提交完整测试 (20 分钟)
# - 每天 10 次推送
# - 月使用量: 10 × 20 × 30 = 6000 分钟
# - 超额费用: (6000 - 2000) × $0.008 = $32

# 优化后:
# - 缓存节省 10 分钟
# - 仅测试更改部分
# - 平均构建时间 5 分钟
# - 月使用量: 10 × 5 × 30 = 1500 分钟
# - 费用: $0 (在免费层内)
# → 每月节省 $32!

🎓 下一步

了解 CI/CD 后,学习以下主题:

  1. 什么是 Git? (待编写) - 版本控制基础
  2. 什么是 Docker? (待编写) - 容器化部署
  3. 什么是 TDD? (待编写) - 测试驱动开发

实践

# ========== 1. 开始使用 GitHub Actions ==========

# 1) 在仓库中创建工作流文件
mkdir -p .github/workflows
cat > .github/workflows/ci.yml << 'EOF'
name: CI
on: [push]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: 设置 Node.js
uses: actions/setup-node@v3
with:
node-version: '18'
- run: npm ci
- run: npm test
EOF

# 2) 提交并推送
git add .github/workflows/ci.yml
git commit -m "Add CI workflow"
git push

# 3) 在 GitHub 上查看 Actions 标签
# → 查看自动运行的工作流!

# ========== 2. 本地测试 (act) ==========
# 使用 act 可以在本地测试 GitHub Actions

# 安装 act (macOS)
brew install act

# 运行工作流
act push

# 仅运行特定 job
act -j test

# ========== 3. 部署自动化示例 ==========

# Vercel 部署
npm install -g vercel
vercel login
vercel --prod

# Netlify 部署
npm install -g netlify-cli
netlify login
netlify deploy --prod

# Heroku 部署
heroku login
git push heroku main

🎬 总结

CI/CD 是现代开发的必备工具:

  • CI: 自动测试和集成代码变更
  • CD: 自动部署通过测试的代码
  • 好处: 快速反馈、提高稳定性、增加生产力
  • 工具: GitHub Actions、Jenkins、CircleCI 等

通过自动化专注于开发并更快地部署! 🚀