module5
60 分钟

部署与运维

学习进度

当前模块0/6 课
总进度0/36 课
学习时长0 分钟

学习目标

  • 用 Codex 生成多阶段 Dockerfile
  • 配置 GitHub Actions CI/CD 流水线
  • 搭建应用监控和日志系统
  • 实施金丝雀发布策略

405 部署与运维

用 Codex 生成 Dockerfile、CI/CD 配置和监控方案

学习目标

完成本课程后,你将能够:

一、Docker 容器化

用 Codex 生成 Dockerfile

/init 请为 Next.js + Express 项目生成 Docker 配置:
- 前端用多阶段构建,输出静态文件
- 后端用 Node.js 20 Alpine
- 前端静态文件由 Nginx 托管
- 添加健康检查
- 非 root 用户运行

前端 Dockerfile(多阶段构建)

# frontend/Dockerfile
# Stage 1: 构建
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
RUN npm run build

# Stage 2: 运行
FROM nginx:1.25-alpine AS runner
COPY --from=builder /app/out /usr/share/nginx/html
COPY nginx.conf /etc/nginx/nginx.conf
HEALTHCHECK --interval=30s --timeout=3s \
  CMD wget -qO- http://localhost:80/health || exit 1
EXPOSE 80
USER nginx
CMD ["nginx", "-g", "daemon off;"]

后端 Dockerfile

# backend/Dockerfile
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
RUN npm run build

FROM node:20-alpine AS runner
WORKDIR /app
RUN addgroup --system --gid 1001 nodejs && \
    adduser --system --uid 1001 expressjs
COPY --from=builder --chown=expressjs:nodejs /app/dist ./dist
COPY --from=builder --chown=expressjs:nodejs /app/node_modules ./node_modules
COPY --from=builder --chown=expressjs:nodejs /app/prisma ./prisma
COPY --from=builder --chown=expressjs:nodejs /app/package.json ./

HEALTHCHECK --interval=30s --timeout=3s \
  CMD wget -qO- http://localhost:3000/api/health || exit 1
EXPOSE 3000
USER expressjs
CMD ["node", "dist/index.js"]

Docker Compose 编排

# docker-compose.yml
version: '3.8'
services:
  postgres:
    image: postgres:16-alpine
    environment:
      POSTGRES_DB: taskflow
      POSTGRES_USER: app
      POSTGRES_PASSWORD: ${DB_PASSWORD}
    volumes:
      - pgdata:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U app -d taskflow"]
      interval: 10s
      timeout: 5s
      retries: 5

  redis:
    image: redis:7-alpine
    volumes:
      - redisdata:/data
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 10s
      timeout: 5s
      retries: 5

  backend:
    build: ./backend
    depends_on:
      postgres:
        condition: service_healthy
      redis:
        condition: service_healthy
    environment:
      DATABASE_URL: postgresql://app:${DB_PASSWORD}@postgres:5432/taskflow
      REDIS_URL: redis://redis:6379
    ports:
      - "3000:3000"

  frontend:
    build: ./frontend
    depends_on:
      - backend
    ports:
      - "80:80"

volumes:
  pgdata:
  redisdata:

二、CI/CD 流水线

GitHub Actions 配置

# .github/workflows/ci-cd.yml
name: CI/CD Pipeline

on:
  push:
    branches: [main, staging]
  pull_request:
    branches: [main]

jobs:
  lint-and-test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: 'npm'

      - name: Install dependencies
        run: npm ci

      - name: Lint
        run: npm run lint

      - name: Type check
        run: npm run type-check

      - name: Unit tests
        run: npm run test -- --coverage

      - name: Upload coverage
        uses: actions/upload-artifact@v4
        with:
          name: coverage
          path: coverage/

  build-and-push:
    needs: lint-and-test
    if: github.ref == 'refs/heads/main' || github.ref == 'refs/heads/staging'
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3

      - name: Login to Container Registry
        uses: docker/login-action@v3
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Build and push backend
        uses: docker/build-push-action@v5
        with:
          context: ./backend
          push: true
          tags: ghcr.io/${{ github.repository }}/backend:${{ github.sha }}
          cache-from: type=gha
          cache-to: type=gha,mode=max

      - name: Build and push frontend
        uses: docker/build-push-action@v5
        with:
          context: ./frontend
          push: true
          tags: ghcr.io/${{ github.repository }}/frontend:${{ github.sha }}
          cache-from: type=gha
          cache-to: type=gha,mode=max

  deploy-staging:
    needs: build-and-push
    if: github.ref == 'refs/heads/staging'
    runs-on: ubuntu-latest
    environment: staging
    steps:
      - name: Deploy to staging
        run: |
          echo "Deploying to staging server..."
          # 通过 SSH 或 Webhook 触发部署

  deploy-production:
    needs: build-and-push
    if: github.ref == 'refs/heads/main'
    runs-on: ubuntu-latest
    environment: production
    steps:
      - name: Deploy canary
        run: |
          echo "Deploying canary (10% traffic)..."
          # 金丝雀部署 10%

      - name: Health check
        run: |
          sleep 30
          curl -f http://canary.example.com/api/health || exit 1

      - name: Full rollout
        run: |
          echo "Rolling out to 100%..."
          # 全量上线

三、监控与日志

应用监控指标

用 Codex 生成 Prometheus 指标端点:

请为 Express 后端添加 Prometheus 监控:
- HTTP 请求计数、延迟、状态码分布
- 数据库查询耗时
- 活跃连接数
- 内存和 CPU 使用率
// backend/src/middleware/metrics.ts
import { Registry, Counter, Histogram, Gauge } from 'prom-client';

export const registry = new Registry();

export const httpRequestDuration = new Histogram({
  name: 'http_request_duration_seconds',
  help: 'HTTP 请求耗时',
  labelNames: ['method', 'route', 'status'],
  buckets: [0.01, 0.05, 0.1, 0.5, 1, 2, 5],
  registers: [registry],
});

export const httpRequestTotal = new Counter({
  name: 'http_requests_total',
  help: 'HTTP 请求总数',
  labelNames: ['method', 'route', 'status'],
  registers: [registry],
});

export const dbQueryDuration = new Histogram({
  name: 'db_query_duration_seconds',
  help: '数据库查询耗时',
  labelNames: ['operation', 'model'],
  registers: [registry],
});

结构化日志

// backend/src/utils/logger.ts
import pino from 'pino';

export const logger = pino({
  level: process.env.LOG_LEVEL || 'info',
  transport: process.env.NODE_ENV === 'development'
    ? { target: 'pino-pretty', options: { colorize: true } }
    : undefined,
  serializers: {
    req: (req) => ({
      method: req.method,
      url: req.url,
      userId: req.user?.id,
    }),
    err: pino.stdSerializers.err,
  },
  redact: ['req.headers.authorization', 'req.headers.cookie'],
});

四、金丝雀部署

部署策略

全量部署风险:
- Bug 影响所有用户
- 回滚需数分钟
- 无 A/B 验证窗口

金丝雀部署流程:
1. 部署新版本到 10% 实例
2. 观察 15 分钟(错误率、延迟、CPU)
3. 指标正常 → 扩展到 50%
4. 再观察 10 分钟
5. 指标正常 → 全量上线
6. 指标异常 → 自动回滚

用 Codex 生成金丝雀脚本

请生成 Kubernetes 金丝雀部署配置:
- 使用 Istio VirtualService 控制流量权重
- 初始 10%,逐步提升到 100%
- 基于 Prometheus 指标自动判断是否回滚
- 回滚阈值为错误率 > 5% 或 P99 延迟 > 2s

五、实战练习

练习 1:生成完整部署配置

/init 为以下部署场景生成配置:
- 生产环境:Kubernetes + Helm
- 需要配置 Ingress、HPA、PDB
- 环境变量通过 Secret 注入
- 挂载 ConfigMap 作为 Nginx 配置

练习 2:告警规则配置

请为任务管理平台配置 Prometheus 告警规则:
- API 错误率 > 5%(持续 5 分钟)
- P95 延迟 > 1s
- 数据库连接池耗尽
- 磁盘使用率 > 85%
- 证书过期 < 7 天

告警通知到 Slack + 邮件

练习 3:灾备演练

请设计灾备演练方案:
1. 主数据库故障切换流程
2. 多可用区故障转移
3. RTO < 5 分钟,RPO < 1 分钟
4. 演练脚本和检查清单

六、本课总结

**核心要点:**

  • 多阶段 Docker 构建缩小镜像体积,非 root 用户提升安全性
  • CI/CD 流水线分 lint → test → build → deploy 四个阶段
  • 监控三件套:Prometheus 指标 + 结构化日志 + 分布式追踪
  • 金丝雀部署用流量权重逐步切换,配合自动回滚降低风险
  • 备份、监控、告警、灾备是一个整体,缺一不可

**下一步:** 模块 406 — 项目复盘与优化

下一模块