rwadurian/backend/services/wallet-service/docs/DEPLOYMENT.md

17 KiB
Raw Blame History

Wallet Service 部署文档

概述

本文档描述 Wallet Service 的部署架构、配置方式、Docker 容器化以及生产环境部署流程。


部署架构

                         ┌─────────────────┐
                         │   Load Balancer │
                         │    (Nginx/ALB)  │
                         └────────┬────────┘
                                  │
         ┌────────────────────────┼────────────────────────┐
         │                        │                        │
         ▼                        ▼                        ▼
┌─────────────────┐    ┌─────────────────┐    ┌─────────────────┐
│ Wallet Service  │    │ Wallet Service  │    │ Wallet Service  │
│   Instance 1    │    │   Instance 2    │    │   Instance N    │
└────────┬────────┘    └────────┬────────┘    └────────┬────────┘
         │                      │                      │
         └──────────────────────┼──────────────────────┘
                                │
                       ┌────────┴────────┐
                       │   PostgreSQL    │
                       │   (Primary)     │
                       └────────┬────────┘
                                │
                       ┌────────┴────────┐
                       │   PostgreSQL    │
                       │   (Replica)     │
                       └─────────────────┘

环境要求

系统要求

组件 最低要求 推荐配置
CPU 2 核 4+ 核
内存 2 GB 4+ GB
磁盘 20 GB SSD 50+ GB SSD
Node.js 20.x 20.x LTS
PostgreSQL 15.x 15.x

依赖服务

  • PostgreSQL 15.x 数据库
  • Docker (可选,用于容器化部署)
  • Kubernetes (可选,用于编排)

环境变量配置

必需变量

变量 描述 示例
DATABASE_URL PostgreSQL 连接字符串 postgresql://user:pass@host:5432/db
JWT_SECRET JWT 签名密钥 your-secret-key-here
NODE_ENV 环境标识 production
PORT 服务端口 3000

可选变量

变量 描述 默认值
JWT_EXPIRES_IN JWT 过期时间 24h
LOG_LEVEL 日志级别 info
CORS_ORIGIN CORS 允许源 *

环境配置文件

# .env.production
DATABASE_URL="postgresql://wallet:strong_password@db.example.com:5432/wallet_prod?schema=public&connection_limit=10"
JWT_SECRET="your-production-secret-key-min-32-chars"
NODE_ENV=production
PORT=3000
LOG_LEVEL=info

Docker 部署

Dockerfile

# Dockerfile
# 构建阶段
FROM node:20-alpine AS builder

WORKDIR /app

# 复制依赖文件
COPY package*.json ./
COPY prisma ./prisma/

# 安装依赖
RUN npm ci --only=production=false

# 复制源代码
COPY . .

# 生成 Prisma Client
RUN npx prisma generate

# 构建应用
RUN npm run build

# 清理开发依赖
RUN npm prune --production

# 生产阶段
FROM node:20-alpine AS production

WORKDIR /app

# 安装 dumb-init 用于信号处理
RUN apk add --no-cache dumb-init

# 创建非 root 用户
RUN addgroup -g 1001 -S nodejs && \
    adduser -S nestjs -u 1001

# 复制构建产物
COPY --from=builder --chown=nestjs:nodejs /app/dist ./dist
COPY --from=builder --chown=nestjs:nodejs /app/node_modules ./node_modules
COPY --from=builder --chown=nestjs:nodejs /app/prisma ./prisma
COPY --from=builder --chown=nestjs:nodejs /app/package.json ./

# 切换到非 root 用户
USER nestjs

# 暴露端口
EXPOSE 3000

# 健康检查
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
  CMD wget --no-verbose --tries=1 --spider http://localhost:3000/api/v1/health || exit 1

# 启动命令
ENTRYPOINT ["dumb-init", "--"]
CMD ["sh", "-c", "npx prisma migrate deploy && node dist/main"]

.dockerignore

# .dockerignore
node_modules
dist
coverage
.git
.gitignore
*.md
*.log
.env*
!.env.example
test
.vscode
.idea

构建和运行

# 构建镜像
docker build -t wallet-service:latest .

# 运行容器
docker run -d \
  --name wallet-service \
  -p 3000:3000 \
  -e DATABASE_URL="postgresql://wallet:password@host:5432/wallet" \
  -e JWT_SECRET="your-secret-key" \
  -e NODE_ENV=production \
  wallet-service:latest

# 查看日志
docker logs -f wallet-service

Docker Compose 部署

docker-compose.yml

# docker-compose.yml
version: '3.8'

services:
  wallet-service:
    build:
      context: .
      dockerfile: Dockerfile
    ports:
      - "3000:3000"
    environment:
      DATABASE_URL: postgresql://wallet:wallet123@postgres:5432/wallet?schema=public
      JWT_SECRET: ${JWT_SECRET:-development-secret-change-in-production}
      NODE_ENV: ${NODE_ENV:-production}
    depends_on:
      postgres:
        condition: service_healthy
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "wget", "--spider", "-q", "http://localhost:3000/api/v1/health"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 40s

  postgres:
    image: postgres:15-alpine
    environment:
      POSTGRES_USER: wallet
      POSTGRES_PASSWORD: wallet123
      POSTGRES_DB: wallet
    volumes:
      - postgres_data:/var/lib/postgresql/data
    ports:
      - "5432:5432"
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U wallet -d wallet"]
      interval: 10s
      timeout: 5s
      retries: 5

volumes:
  postgres_data:

docker-compose.prod.yml

# docker-compose.prod.yml
version: '3.8'

services:
  wallet-service:
    image: wallet-service:${VERSION:-latest}
    ports:
      - "3000:3000"
    environment:
      DATABASE_URL: ${DATABASE_URL}
      JWT_SECRET: ${JWT_SECRET}
      NODE_ENV: production
      LOG_LEVEL: info
    deploy:
      replicas: 3
      update_config:
        parallelism: 1
        delay: 10s
      restart_policy:
        condition: on-failure
        delay: 5s
        max_attempts: 3
    healthcheck:
      test: ["CMD", "wget", "--spider", "-q", "http://localhost:3000/api/v1/health"]
      interval: 30s
      timeout: 10s
      retries: 3

运行命令

# 开发环境
docker-compose up -d

# 生产环境
docker-compose -f docker-compose.yml -f docker-compose.prod.yml up -d

# 扩展副本
docker-compose -f docker-compose.prod.yml up -d --scale wallet-service=3

Kubernetes 部署

Deployment

# k8s/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: wallet-service
  labels:
    app: wallet-service
spec:
  replicas: 3
  selector:
    matchLabels:
      app: wallet-service
  template:
    metadata:
      labels:
        app: wallet-service
    spec:
      containers:
        - name: wallet-service
          image: wallet-service:latest
          ports:
            - containerPort: 3000
          env:
            - name: NODE_ENV
              value: "production"
            - name: DATABASE_URL
              valueFrom:
                secretKeyRef:
                  name: wallet-secrets
                  key: database-url
            - name: JWT_SECRET
              valueFrom:
                secretKeyRef:
                  name: wallet-secrets
                  key: jwt-secret
          resources:
            requests:
              memory: "256Mi"
              cpu: "250m"
            limits:
              memory: "512Mi"
              cpu: "500m"
          livenessProbe:
            httpGet:
              path: /api/v1/health
              port: 3000
            initialDelaySeconds: 30
            periodSeconds: 10
          readinessProbe:
            httpGet:
              path: /api/v1/health
              port: 3000
            initialDelaySeconds: 5
            periodSeconds: 5

Service

# k8s/service.yaml
apiVersion: v1
kind: Service
metadata:
  name: wallet-service
spec:
  type: ClusterIP
  ports:
    - port: 80
      targetPort: 3000
  selector:
    app: wallet-service

Secret

# k8s/secret.yaml
apiVersion: v1
kind: Secret
metadata:
  name: wallet-secrets
type: Opaque
stringData:
  database-url: "postgresql://wallet:password@postgres:5432/wallet"
  jwt-secret: "your-production-secret-key"

Ingress

# k8s/ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: wallet-ingress
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /
spec:
  rules:
    - host: wallet.example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: wallet-service
                port:
                  number: 80

部署命令

# 创建 Secret
kubectl apply -f k8s/secret.yaml

# 部署服务
kubectl apply -f k8s/deployment.yaml
kubectl apply -f k8s/service.yaml
kubectl apply -f k8s/ingress.yaml

# 查看状态
kubectl get pods -l app=wallet-service
kubectl get svc wallet-service

# 滚动更新
kubectl set image deployment/wallet-service wallet-service=wallet-service:v2.0.0

# 回滚
kubectl rollout undo deployment/wallet-service

数据库迁移

Prisma 迁移

# 开发环境 - 创建迁移
npx prisma migrate dev --name add_new_field

# 生产环境 - 应用迁移
npx prisma migrate deploy

# 查看迁移状态
npx prisma migrate status

迁移脚本

#!/bin/bash
# scripts/migrate.sh

set -e

echo "Running database migrations..."

# 等待数据库就绪
until pg_isready -h $DB_HOST -p $DB_PORT -U $DB_USER; do
  echo "Waiting for database..."
  sleep 2
done

# 运行迁移
npx prisma migrate deploy

echo "Migrations completed successfully!"

CI/CD 流水线

GitHub Actions

# .github/workflows/deploy.yml
name: Deploy

on:
  push:
    branches: [main]
  release:
    types: [published]

env:
  REGISTRY: ghcr.io
  IMAGE_NAME: ${{ github.repository }}

jobs:
  test:
    runs-on: ubuntu-latest
    services:
      postgres:
        image: postgres:15-alpine
        env:
          POSTGRES_USER: wallet
          POSTGRES_PASSWORD: wallet123
          POSTGRES_DB: wallet_test
        ports:
          - 5432:5432
        options: >-
          --health-cmd pg_isready
          --health-interval 10s
          --health-timeout 5s
          --health-retries 5          

    steps:
      - uses: actions/checkout@v3

      - name: Setup Node.js
        uses: actions/setup-node@v3
        with:
          node-version: '20'
          cache: 'npm'

      - name: Install dependencies
        run: npm ci

      - name: Generate Prisma Client
        run: npx prisma generate

      - name: Run migrations
        run: npx prisma db push
        env:
          DATABASE_URL: postgresql://wallet:wallet123@localhost:5432/wallet_test

      - name: Run tests
        run: npm test
        env:
          DATABASE_URL: postgresql://wallet:wallet123@localhost:5432/wallet_test
          JWT_SECRET: test-secret

      - name: Run E2E tests
        run: npm run test:e2e
        env:
          DATABASE_URL: postgresql://wallet:wallet123@localhost:5432/wallet_test
          JWT_SECRET: test-secret

  build:
    needs: test
    runs-on: ubuntu-latest
    permissions:
      contents: read
      packages: write

    steps:
      - uses: actions/checkout@v3

      - name: Log in to Container Registry
        uses: docker/login-action@v2
        with:
          registry: ${{ env.REGISTRY }}
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Extract metadata
        id: meta
        uses: docker/metadata-action@v4
        with:
          images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
          tags: |
            type=ref,event=branch
            type=semver,pattern={{version}}
            type=sha            

      - name: Build and push
        uses: docker/build-push-action@v4
        with:
          context: .
          push: true
          tags: ${{ steps.meta.outputs.tags }}
          labels: ${{ steps.meta.outputs.labels }}

  deploy:
    needs: build
    runs-on: ubuntu-latest
    if: github.ref == 'refs/heads/main'

    steps:
      - name: Deploy to production
        run: |
          # 触发部署 (例如 ArgoCD, Kubernetes, 或 SSH)
          echo "Deploying to production..."          

监控和日志

健康检查端点

# 健康检查
curl http://localhost:3000/api/v1/health

# 响应示例
{
  "success": true,
  "data": {
    "status": "ok",
    "service": "wallet-service",
    "timestamp": "2024-01-01T00:00:00.000Z"
  }
}

日志配置

// src/main.ts
import { Logger } from '@nestjs/common';

async function bootstrap() {
  const app = await NestFactory.create(AppModule, {
    logger: process.env.NODE_ENV === 'production'
      ? ['error', 'warn', 'log']
      : ['error', 'warn', 'log', 'debug', 'verbose'],
  });
  // ...
}

日志格式 (生产环境)

{
  "timestamp": "2024-01-01T00:00:00.000Z",
  "level": "info",
  "context": "WalletController",
  "message": "Deposit processed",
  "userId": "12345",
  "amount": 100,
  "requestId": "abc-123"
}

安全配置

Nginx 反向代理

# nginx.conf
upstream wallet_service {
    server wallet-service-1:3000;
    server wallet-service-2:3000;
    server wallet-service-3:3000;
}

server {
    listen 80;
    server_name wallet.example.com;

    # 重定向到 HTTPS
    return 301 https://$server_name$request_uri;
}

server {
    listen 443 ssl http2;
    server_name wallet.example.com;

    ssl_certificate /etc/nginx/ssl/cert.pem;
    ssl_certificate_key /etc/nginx/ssl/key.pem;

    # 安全头
    add_header X-Frame-Options "SAMEORIGIN" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header X-XSS-Protection "1; mode=block" always;
    add_header Strict-Transport-Security "max-age=31536000" always;

    # 速率限制
    limit_req zone=api burst=20 nodelay;

    location /api/ {
        proxy_pass http://wallet_service;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_cache_bypass $http_upgrade;
    }
}

环境变量安全

# 不要在版本控制中提交敏感信息
# 使用环境变量或密钥管理服务

# AWS Secrets Manager
aws secretsmanager get-secret-value --secret-id wallet/production

# HashiCorp Vault
vault kv get secret/wallet/production

故障排除

常见问题

1. 数据库连接失败

# 检查连接
psql $DATABASE_URL -c "SELECT 1"

# 检查网络
nc -zv db.example.com 5432

2. 容器无法启动

# 查看日志
docker logs wallet-service

# 进入容器调试
docker exec -it wallet-service sh

3. 迁移失败

# 查看迁移状态
npx prisma migrate status

# 重置数据库 (开发环境)
npx prisma migrate reset

回滚流程

# Docker 回滚
docker stop wallet-service
docker run -d --name wallet-service wallet-service:previous-tag

# Kubernetes 回滚
kubectl rollout undo deployment/wallet-service

# 数据库回滚 (手动)
# 需要提前备份Prisma 不支持自动回滚
pg_restore -d wallet wallet_backup.dump

部署检查清单

部署前

  • 所有测试通过
  • 代码审查完成
  • 环境变量配置正确
  • 数据库备份完成
  • 迁移脚本测试通过

部署中

  • 监控指标正常
  • 健康检查通过
  • 日志无错误
  • API 响应正常

部署后

  • 功能验证通过
  • 性能测试通过
  • 安全扫描通过
  • 文档更新

联系方式

如遇部署问题,请联系: