CI/CD 流程
一、CI/CD 概述
1.1 基本概念
- CI (持续集成): 频繁将代码合并到主干,自动化测试
- CD (持续交付/部署): 自动化部署到生产环境
1.2 典型流程
代码提交 → 代码检查 → 构建 → 测试 → 部署二、GitHub Actions
2.1 基本结构
yaml
# .github/workflows/ci.yml
name: CI
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '18'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Lint
run: npm run lint
- name: Test
run: npm run test
- name: Build
run: npm run build2.2 常用触发器
yaml
on:
# 推送触发
push:
branches: [main]
paths:
- 'src/**'
- 'package.json'
# PR 触发
pull_request:
types: [opened, synchronize]
# 定时触发
schedule:
- cron: '0 2 * * *' # 每天 2:00
# 手动触发
workflow_dispatch:
inputs:
environment:
description: 'Environment to deploy'
required: true
default: 'staging'
# 其他工作流完成后触发
workflow_run:
workflows: [Build]
types: [completed]2.3 环境变量与 Secrets
yaml
jobs:
deploy:
runs-on: ubuntu-latest
env:
NODE_ENV: production
steps:
- name: Deploy
env:
AWS_ACCESS_KEY: ${{ secrets.AWS_ACCESS_KEY }}
AWS_SECRET_KEY: ${{ secrets.AWS_SECRET_KEY }}
run: |
echo "Deploying to ${{ vars.DEPLOY_URL }}"2.4 缓存依赖
yaml
- name: Cache node modules
uses: actions/cache@v3
with:
path: ~/.npm
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-node-
# 或使用 setup-node 内置缓存
- uses: actions/setup-node@v3
with:
node-version: '18'
cache: 'npm'2.5 矩阵构建
yaml
jobs:
test:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
node: [16, 18, 20]
steps:
- uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node }}2.6 多任务依赖
yaml
jobs:
lint:
runs-on: ubuntu-latest
steps:
- run: npm run lint
test:
runs-on: ubuntu-latest
steps:
- run: npm test
build:
needs: [lint, test] # 依赖前两个任务
runs-on: ubuntu-latest
steps:
- run: npm run build
deploy:
needs: build
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
steps:
- run: npm run deploy三、自动化测试
3.1 测试策略
yaml
jobs:
test:
runs-on: ubuntu-latest
steps:
# 单元测试
- name: Unit Tests
run: npm run test:unit
# 集成测试
- name: Integration Tests
run: npm run test:integration
# E2E 测试
- name: E2E Tests
uses: cypress-io/github-action@v5
with:
start: npm run dev
wait-on: 'http://localhost:3000'
# 上传覆盖率
- name: Upload Coverage
uses: codecov/codecov-action@v33.2 E2E 测试示例
yaml
jobs:
e2e:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Install dependencies
run: npm ci
- name: Install Playwright
run: npx playwright install --with-deps
- name: Run E2E tests
run: npm run test:e2e
- name: Upload test results
if: always()
uses: actions/upload-artifact@v3
with:
name: playwright-report
path: playwright-report/四、自动化部署
4.1 部署到 Vercel
yaml
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Deploy to Vercel
uses: amondnet/vercel-action@v20
with:
vercel-token: ${{ secrets.VERCEL_TOKEN }}
vercel-org-id: ${{ secrets.VERCEL_ORG_ID }}
vercel-project-id: ${{ secrets.VERCEL_PROJECT_ID }}
vercel-args: '--prod'4.2 部署到 AWS S3
yaml
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- run: npm ci && npm run build
- name: Deploy to S3
uses: jakejarvis/s3-sync-action@master
with:
args: --acl public-read --delete
env:
AWS_S3_BUCKET: ${{ secrets.AWS_S3_BUCKET }}
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
SOURCE_DIR: 'dist'
- name: Invalidate CloudFront
uses: chetan/invalidate-cloudfront-action@v2
env:
DISTRIBUTION: ${{ secrets.DISTRIBUTION }}
PATHS: '/*'4.3 部署到服务器
yaml
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- run: npm ci && npm run build
- name: Deploy to Server
uses: appleboy/scp-action@master
with:
host: ${{ secrets.SERVER_HOST }}
username: ${{ secrets.SERVER_USER }}
key: ${{ secrets.SSH_PRIVATE_KEY }}
source: 'dist/*'
target: '/var/www/html'
- name: Restart Nginx
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.SERVER_HOST }}
username: ${{ secrets.SERVER_USER }}
key: ${{ secrets.SSH_PRIVATE_KEY }}
script: sudo nginx -s reload五、Docker 部署
5.1 Dockerfile
dockerfile
# 多阶段构建
FROM node:18-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
# 生产镜像
FROM nginx:alpine
COPY --from=builder /app/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/nginx.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]5.2 GitHub Actions 构建镜像
yaml
jobs:
docker:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Login to Docker Hub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Build and push
uses: docker/build-push-action@v4
with:
context: .
push: true
tags: |
myorg/myapp:latest
myorg/myapp:${{ github.sha }}5.3 部署到 Kubernetes
yaml
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: azure/setup-kubectl@v3
- uses: azure/k8s-set-context@v3
with:
kubeconfig: ${{ secrets.KUBE_CONFIG }}
- run: |
kubectl set image deployment/myapp \
myapp=myorg/myapp:${{ github.sha }}六、最佳实践
6.1 分支策略
main (生产) ← PR ← develop (开发) ← feature/*
↑
release/*6.2 环境管理
yaml
jobs:
deploy-staging:
if: github.ref == 'refs/heads/develop'
environment: staging
deploy-production:
if: github.ref == 'refs/heads/main'
environment: production
needs: deploy-staging6.3 回滚机制
yaml
- name: Rollback on failure
if: failure()
run: |
kubectl rollout undo deployment/myapp七、高频面试题
Q1: CI/CD 的核心价值?
- 自动化减少人工错误
- 快速反馈代码问题
- 保持代码可随时发布
- 提高交付效率
Q2: GitHub Actions 的核心概念?
- Workflow: 自动化流程
- Job: 一组步骤
- Step: 单个任务
- Action: 可复用的任务单元
Q3: 如何优化 CI 速度?
- 依赖缓存
- 并行执行
- 增量构建
- 合理拆分 Job