部署与CI/CD技能 deployment-cicd

这个技能涉及自动化部署应用的实践,使用持续集成/持续交付(CI/CD)管道、容器化(如Docker)和基础设施即代码(IaC)。涵盖GitHub Actions、Docker、Vercel等工具,以及云部署模式、环境管理、健康检查和回滚策略。适用于软件开发团队,旨在提升发布效率、可靠性和运维自动化。关键词包括:部署、CI/CD、自动化、GitHub Actions、Docker、Vercel、DevOps、云原生、容器化、基础设施即代码。

DevOps 0 次安装 0 次浏览 更新于 3/7/2026

name: deployment-cicd description: 使用CI/CD管道、容器化和基础设施即代码自信地部署应用。涵盖GitHub Actions、Docker、Vercel和云部署模式。在部署、CI/CD、Docker、GitHub Actions或DevOps请求时触发。 license: MIT

部署与持续集成/持续交付

可靠且自动地发布代码。

部署哲学

部署管道

代码 → 构建 → 测试 → 暂存 → 生产

 │       │       │       │        │
 └───────┴───────┴───────┴────────┘
         自动化、可重复

原则

  1. 自动化一切 - 无手动步骤
  2. 快速失败 - 尽早发现问题
  3. 回滚就绪 - 始终有逃生方案
  4. 环境一致性 - 开发 ≈ 暂存 ≈ 生产
  5. 可观测性 - 了解正在发生的事情

GitHub Actions

基础工作流

# .github/workflows/ci.yml
name: CI

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

jobs:
  build-and-test:
    runs-on: ubuntu-latest
    
    steps:
      - uses: actions/checkout@v4
      
      - name: 设置 Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'
      
      - name: 安装依赖
        run: npm ci
      
      - name: 运行代码检查
        run: npm run lint
      
      - name: 运行测试
        run: npm test
      
      - name: 构建
        run: npm run build

矩阵构建

jobs:
  test:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        node-version: [18, 20, 22]
        os: [ubuntu-latest, macos-latest]
    
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: ${{ matrix.node-version }}
      - run: npm ci
      - run: npm test

推送至主分支时部署

name: 部署

on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    
    steps:
      - uses: actions/checkout@v4
      
      - name: 设置 Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'
      
      - run: npm ci
      - run: npm run build
      
      - 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'

密钥管理

env:
  DATABASE_URL: ${{ secrets.DATABASE_URL }}
  API_KEY: ${{ secrets.API_KEY }}

# 在GitHub中:设置 → 密钥和变量 → Actions

缓存

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

Docker

Node.js Dockerfile

# 构建阶段
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

# 生产阶段
FROM node:20-alpine AS runner
WORKDIR /app
ENV NODE_ENV=production

COPY --from=builder /app/package*.json ./
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/.next ./.next
COPY --from=builder /app/public ./public

EXPOSE 3000
CMD ["npm", "start"]

Docker Compose

# docker-compose.yml
version: '3.8'

services:
  app:
    build: .
    ports:
      - "3000:3000"
    environment:
      - DATABASE_URL=postgresql://postgres:password@db:5432/mydb
    depends_on:
      - db
  
  db:
    image: postgres:15-alpine
    environment:
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: password
      POSTGRES_DB: mydb
    volumes:
      - postgres_data:/var/lib/postgresql/data
    ports:
      - "5432:5432"

volumes:
  postgres_data:

.dockerignore

node_modules
.git
.gitignore
README.md
.env
.env.*
Dockerfile
docker-compose.yml
.next
coverage

平台部署

Vercel (Next.js)

// vercel.json
{
  "framework": "nextjs",
  "buildCommand": "npm run build",
  "outputDirectory": ".next",
  "env": {
    "DATABASE_URL": "@database-url"
  },
  "headers": [
    {
      "source": "/api/(.*)",
      "headers": [
        { "key": "Cache-Control", "value": "no-store" }
      ]
    }
  ]
}

Railway

# railway.toml
[build]
builder = "nixpacks"
buildCommand = "npm run build"

[deploy]
startCommand = "npm start"
healthcheckPath = "/api/health"
healthcheckTimeout = 100
restartPolicyType = "on_failure"
restartPolicyMaxRetries = 3

Fly.io

# fly.toml
app = "my-app"
primary_region = "ord"

[build]
  dockerfile = "Dockerfile"

[http_service]
  internal_port = 3000
  force_https = true
  auto_stop_machines = true
  auto_start_machines = true
  min_machines_running = 0

[[services]]
  internal_port = 3000
  protocol = "tcp"

  [[services.ports]]
    port = 80
    handlers = ["http"]

  [[services.ports]]
    port = 443
    handlers = ["tls", "http"]

环境管理

环境文件

.env                # 共享默认值(提交到版本控制)
.env.local          # 本地覆盖(git忽略)
.env.development    # 开发特定
.env.production     # 生产特定
.env.test           # 测试特定

环境变量模式

// lib/env.ts
import { z } from 'zod';

const envSchema = z.object({
  DATABASE_URL: z.string().url(),
  API_KEY: z.string().min(1), <!-- 允许密钥 -->
  NODE_ENV: z.enum(['development', 'production', 'test']),
});

export const env = envSchema.parse(process.env);

数据库迁移

Prisma 迁移

# 创建迁移
npx prisma migrate dev --name init

# 应用迁移(CI/CD)
npx prisma migrate deploy

# 生成客户端
npx prisma generate

CI/CD 中的迁移

- name: 运行数据库迁移
  run: npx prisma migrate deploy
  env:
    DATABASE_URL: ${{ secrets.DATABASE_URL }}

回滚策略

蓝绿部署

          ┌─────────┐
流量 → │  蓝版   │ ← 当前版本
          └─────────┘
          
          ┌─────────┐
          │  绿版   │ ← 新版本(测试中)
          └─────────┘

验证后:切换流量到绿版

金丝雀部署

          ┌─────────┐
90% ────→ │ 当前版本 │
          └─────────┘
          
          ┌─────────┐
10% ────→ │   新版本   │ ← 监控问题
          └─────────┘

逐渐增加新版本流量

即时回滚(Vercel)

# 回滚到前一个部署
vercel rollback

# 或通过仪表板:部署 → ... → 提升到生产

健康检查

健康端点

// app/api/health/route.ts
import { db } from '@/lib/db';

export async function GET() {
  try {
    // 检查数据库
    await db.$queryRaw`SELECT 1`;
    
    return Response.json({
      status: '健康',
      timestamp: new Date().toISOString(),
      version: process.env.APP_VERSION || '未知',
    });
  } catch (error) {
    return Response.json(
      { status: '不健康', error: '数据库连接失败' },
      { status: 503 }
    );
  }
}

完整CI/CD管道

name: CI/CD

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

env:
  NODE_VERSION: '20'

jobs:
  lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: ${{ env.NODE_VERSION }}
          cache: 'npm'
      - run: npm ci
      - run: npm run lint

  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: ${{ env.NODE_VERSION }}
          cache: 'npm'
      - run: npm ci
      - run: npm test -- --coverage
      - uses: codecov/codecov-action@v3

  build:
    needs: [lint, test]
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: ${{ env.NODE_VERSION }}
          cache: 'npm'
      - run: npm ci
      - run: npm run build
      - uses: actions/upload-artifact@v3
        with:
          name: build
          path: .next

  deploy-preview:
    if: github.event_name == 'pull_request'
    needs: build
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - 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 }}

  deploy-production:
    if: github.ref == 'refs/heads/main'
    needs: build
    runs-on: ubuntu-latest
    environment: production
    steps:
      - uses: actions/checkout@v4
      - 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'

参考

  • references/github-actions-recipes.md - 常见工作流模式
  • references/docker-patterns.md - Docker最佳实践
  • references/monitoring-setup.md - 可观测性配置