名称: ci-cd-pipeline 描述: 使用GitHub Actions、GitLab CI、Jenkins或类似工具设置和维护持续集成和持续部署管道,以自动化测试、构建和部署。在配置自动化构建、设置测试自动化、实现部署自动化、创建发布工作流、管理环境部署、配置构建缓存、实现蓝绿部署、设置回滚策略或自动化整个软件交付管道时使用。
CI/CD 管道 - 自动化构建、测试和部署
包管理器检测
重要:在创建CI/CD配置之前,通过检查锁文件检测项目的包管理器,并相应地调整所有命令。
检测顺序(第一个匹配项为准):
bun.lockb→ 使用 bunpnpm-lock.yaml→ 使用 pnpmyarn.lock→ 使用 yarnpackage-lock.json→ 使用 npm
命令映射 - 将本技能中的示例调整为使用检测到的包管理器:
| 操作 | npm | yarn | pnpm | bun |
|---|---|---|---|---|
| 安装(CI) | npm ci |
yarn install --frozen-lockfile |
pnpm install --frozen-lockfile |
bun install --frozen-lockfile |
| 运行脚本 | npm run <script> |
yarn <script> |
pnpm <script> |
bun run <script> |
| 执行二进制 | npx <cmd> |
yarn dlx <cmd> |
pnpm dlx <cmd> |
bunx <cmd> |
| 缓存键 | package-lock.json |
yarn.lock |
pnpm-lock.yaml |
bun.lockb |
| 缓存路径 | ~/.npm |
~/.yarn/cache |
~/.pnpm-store |
~/.bun/install/cache |
注意:以下示例默认使用npm。实施时,请替换为适用于您检测到的包管理器的相应命令。
何时使用此技能
- 设置GitHub Actions或GitLab CI工作流
- 每次提交时自动执行测试
- 实现自动化构建和部署
- 创建发布和版本控制工作流
- 管理暂存和生产部署
- 配置构建缓存以加速管道
- 实现蓝绿或金丝雀部署
- 设置失败时自动回滚
- 在CI中运行代码检查和类型检查
- 在管道中自动化数据库迁移
- 在CI中实现安全扫描
- 创建部署审批工作流
何时使用此技能
- 设置持续集成、自动化部署、配置构建管道或实现DevOps工作流。
- 处理相关任务或功能时
- 在需要此专业知识的开发期间
使用时机:设置持续集成、自动化部署、配置构建管道或实现DevOps工作流。
核心原则
- 自动化一切 - 手动步骤容易出错
- 快速失败 - 在管道早期发现问题
- 部署前测试 - 切勿部署未测试的代码
- 可重复构建 - 相同输入 = 相同输出
- 零停机部署 - 用户不受影响
GitHub Actions
1. 基本CI工作流
# .github/workflows/ci.yml
名称: CI
触发条件:
push:
分支: [main, develop]
pull_request:
分支: [main]
任务:
测试:
运行环境: ubuntu-latest
步骤:
- 使用: actions/checkout@v4
- 名称: 设置Node.js
使用: actions/setup-node@v4
参数:
node-version: '20'
cache: 'npm'
- 名称: 安装依赖
运行: npm ci
- 名称: 运行代码检查
运行: npm run lint
- 名称: 运行类型检查
运行: npm run typecheck
- 名称: 运行测试
运行: npm test -- --coverage
- 名称: 上传覆盖率
使用: codecov/codecov-action@v3
参数:
文件: ./coverage/lcov.info
2. 矩阵测试
# 跨多个版本测试
任务:
测试:
运行环境: ${{ matrix.os }}
策略:
矩阵:
os: [ubuntu-latest, windows-latest, macos-latest]
node-version: [18, 20, 21]
步骤:
- 使用: actions/checkout@v4
- 名称: 设置Node.js ${{ matrix.node-version }}
使用: actions/setup-node@v4
参数:
node-version: ${{ matrix.node-version }}
- 运行: npm ci
- 运行: npm test
3. 部署工作流
# .github/workflows/deploy.yml
名称: 部署
触发条件:
push:
分支: [main]
任务:
部署:
运行环境: ubuntu-latest
步骤:
- 使用: actions/checkout@v4
- 名称: 设置Node.js
使用: actions/setup-node@v4
参数:
node-version: '20'
- 运行: npm ci
- 运行: npm run build
- 名称: 部署到Vercel
使用: amondnet/vercel-action@v25
参数:
vercel-token: ${{ secrets.VERCEL_TOKEN }}
vercel-org-id: ${{ secrets.VERCEL_ORG_ID }}
vercel-project-id: ${{ secrets.VERCEL_PROJECT_ID }}
vercel-args: '--prod'
4. Docker构建与推送
名称: Docker
触发条件:
push:
标签: ['v*']
任务:
构建:
运行环境: ubuntu-latest
步骤:
- 使用: actions/checkout@v4
- 名称: 设置Docker Buildx
使用: docker/setup-buildx-action@v3
- 名称: 登录到Docker Hub
使用: docker/login-action@v3
参数:
用户名: ${{ secrets.DOCKER_USERNAME }}
密码: ${{ secrets.DOCKER_PASSWORD }}
- 名称: 提取元数据
id: meta
使用: docker/metadata-action@v5
参数:
镜像: myorg/myapp
标签: |
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
- 名称: 构建和推送
使用: docker/build-push-action@v5
参数:
context: .
push: true
标签: ${{ steps.meta.outputs.tags }}
cache-from: type=registry,ref=myorg/myapp:buildcache
cache-to: type=registry,ref=myorg/myapp:buildcache,mode=max
5. 端到端测试
名称: 端到端测试
触发条件:
pull_request:
任务:
端到端测试:
运行环境: ubuntu-latest
服务:
postgres:
镜像: postgres:15
环境变量:
POSTGRES_PASSWORD: postgres
选项: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
步骤:
- 使用: actions/checkout@v4
- 名称: 设置Node.js
使用: actions/setup-node@v4
参数:
node-version: '20'
- 运行: npm ci
- 名称: 安装Playwright
运行: npx playwright install --with-deps
- 名称: 运行端到端测试
运行: npm run test:e2e
环境变量:
DATABASE_URL: postgresql://postgres:postgres@localhost:5432/test
- 名称: 上传测试结果
if: always()
使用: actions/upload-artifact@v3
参数:
名称: playwright-report
路径: playwright-report/
GitLab CI
# .gitlab-ci.yml
阶段:
- 代码检查
- 测试
- 构建
- 部署
变量:
NODE_VERSION: "20"
代码检查:
阶段: 代码检查
镜像: node:${NODE_VERSION}
脚本:
- npm ci
- npm run lint
- npm run typecheck
测试:
阶段: 测试
镜像: node:${NODE_VERSION}
服务:
- postgres:15
变量:
POSTGRES_DB: test
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
脚本:
- npm ci
- npm test -- --coverage
覆盖率: '/All files[^|]*\|[^|]*\s+([\d\.]+)/'
工件:
报告:
覆盖率报告:
coverage_format: cobertura
路径: coverage/cobertura-coverage.xml
构建:
阶段: 构建
镜像: node:${NODE_VERSION}
脚本:
- npm ci
- npm run build
工件:
路径:
- dist/
部署生产:
阶段: 部署
镜像: node:${NODE_VERSION}
脚本:
- npm install -g vercel
- vercel --prod --token=$VERCEL_TOKEN
only:
- main
部署策略
1. 蓝绿部署
# 先部署到暂存槽
- 名称: 部署到暂存
运行: |
az webapp deployment slot create \
--name myapp \
--resource-group mygroup \
--slot staging
az webapp deployment source config-zip \
--name myapp \
--resource-group mygroup \
--slot staging \
--src ./dist.zip
# 对暂存环境运行冒烟测试
- 名称: 冒烟测试
运行: npm run test:smoke -- --url=https://myapp-staging.azurewebsites.net
# 将暂存切换到生产(即时切换)
- 名称: 切换到生产
运行: |
az webapp deployment slot swap \
--name myapp \
--resource-group mygroup \
--slot staging \
--target-slot production
2. 金丝雀部署
# 先部署到10%的服务器
- 名称: 部署金丝雀
运行: |
kubectl set image deployment/myapp \
myapp=myapp:${{ github.sha }} \
--record
kubectl patch deployment myapp \
-p '{"spec":{"replicas":1}}'
# 监控10分钟
- 名称: 监控金丝雀
运行: |
sleep 600
ERROR_RATE=$(kubectl logs -l app=myapp --tail=1000 | grep ERROR | wc -l)
if [ $ERROR_RATE -gt 10 ]; then
echo "高错误率,正在回滚"
kubectl rollout undo deployment/myapp
exit 1
fi
# 如果健康,扩展到100%
- 名称: 完整部署
运行: kubectl scale deployment myapp --replicas=5
3. 滚动部署
# Kubernetes滚动更新(零停机)
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp
spec:
replicas: 5
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1 # 更新期间最多1个额外pod
maxUnavailable: 1 # 任何时候最多1个pod不可用
template:
spec:
containers:
- name: myapp
image: myapp:latest
readinessProbe:
httpGet:
path: /health
port: 3000
initialDelaySeconds: 5
periodSeconds: 5
管道最佳实践
1. 缓存
# 缓存依赖
- 名称: 缓存node模块
使用: actions/cache@v3
参数:
路径: ~/.npm
键: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-node-
# 缓存构建输出
- 名称: 缓存构建
使用: actions/cache@v3
参数:
路径: .next/cache
键: ${{ runner.os }}-nextjs-${{ hashFiles('**/package-lock.json') }}
2. 密钥管理
# ✅ 将密钥存储在CI/CD平台中
任务:
部署:
步骤:
- 名称: 部署
环境变量:
API_KEY: ${{ secrets.API_KEY }}
DATABASE_URL: ${{ secrets.DATABASE_URL }}
运行: |
echo "API_KEY is set: ${API_KEY:+yes}"
npm run deploy
# ✅ 使用保险库存储敏感数据
- 名称: 导入密钥
使用: hashicorp/vault-action@v2
参数:
url: https://vault.example.com
token: ${{ secrets.VAULT_TOKEN }}
secrets: |
secret/data/prod api_key | API_KEY ;
secret/data/prod db_url | DATABASE_URL
3. 质量门
任务:
质量检查:
运行环境: ubuntu-latest
步骤:
- 使用: actions/checkout@v4
- 名称: 运行测试并计算覆盖率
运行: npm test -- --coverage
- 名称: 检查覆盖率阈值
运行: |
COVERAGE=$(cat coverage/coverage-summary.json | jq '.total.lines.pct')
if (( $(echo "$COVERAGE < 80" | bc -l) )); then
echo "覆盖率 $COVERAGE% 低于80%"
exit 1
fi
- 名称: SonarQube扫描
使用: sonarsource/sonarcloud-github-action@master
环境变量:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
- 名称: 检查质量门
运行: |
STATUS=$(curl -u $SONAR_TOKEN: \
"https://sonarcloud.io/api/qualitygates/project_status?projectKey=myproject" \
| jq -r '.projectStatus.status')
if [ "$STATUS" != "OK" ]; then
echo "质量门失败"
exit 1
fi
4. 通知
任务:
通知:
运行环境: ubuntu-latest
if: always()
needs: [测试, 构建, 部署]
步骤:
- 名称: Slack通知
使用: 8398a7/action-slack@v3
参数:
status: ${{ job.status }}
text: '部署 ${{ job.status }}'
webhook_url: ${{ secrets.SLACK_WEBHOOK }}
- 名称: 邮件通知
if: failure()
使用: dawidd6/action-send-mail@v3
参数:
server_address: smtp.gmail.com
server_port: 465
username: ${{ secrets.MAIL_USERNAME }}
password: ${{ secrets.MAIL_PASSWORD }}
subject: 'CI/CD 失败: ${{ github.workflow }}'
body: '${{ github.ref }} 上的构建失败'
to: team@example.com
CI/CD 检查清单
构建管道:
□ 每次提交自动运行
□ 运行代码检查和类型检查
□ 执行所有测试
□ 生成代码覆盖率报告
□ 构建生产工件
□ 缓存依赖以加速
质量门:
□ 最低代码覆盖率阈值(例如80%)
□ 无关键漏洞(npm audit, Snyk)
□ 代码质量检查(SonarQube)
□ 无代码检查错误
□ 所有测试通过
部署:
□ 自动化部署到暂存环境
□ 生产部署需要手动批准
□ 流量切换前进行健康检查
□ 支持回滚能力
□ 零停机策略(蓝绿、金丝雀、滚动)
安全:
□ 密钥安全存储(不在代码中)
□ 依赖扫描(Dependabot)
□ 容器漏洞扫描
□ SAST/DAST安全扫描
□ 要求签名提交
监控:
□ 构建状态通知(Slack, 邮件)
□ 部署通知
□ 部署后错误率监控
□ 错误时自动回滚
□ 维护审计日志
资源
记住:优秀的CI/CD管道速度快、可靠,让开发人员有信心频繁部署。