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

18 KiB

Authorization Service 部署文档

目录

  1. 部署架构
  2. 环境配置
  3. Docker 部署
  4. Kubernetes 部署
  5. 数据库迁移
  6. 监控与日志
  7. 故障排除

部署架构

整体架构

                         ┌─────────────────┐
                         │   Load Balancer │
                         │   (Nginx/ALB)   │
                         └────────┬────────┘
                                  │
              ┌───────────────────┼───────────────────┐
              │                   │                   │
    ┌─────────▼─────────┐ ┌──────▼──────┐ ┌─────────▼─────────┐
    │  Authorization    │ │  Identity   │ │    Other         │
    │    Service        │ │  Service    │ │   Services       │
    │   (Port 3002)     │ │ (Port 3001) │ │                  │
    └─────────┬─────────┘ └─────────────┘ └──────────────────┘
              │
    ┌─────────┴─────────────────────────────────┐
    │                                           │
┌───▼───┐  ┌────────┐  ┌────────┐  ┌──────────┐
│  DB   │  │ Redis  │  │ Kafka  │  │ External │
│(PG 15)│  │ (7.x)  │  │(3.7.x) │  │ Services │
└───────┘  └────────┘  └────────┘  └──────────┘

服务依赖

依赖 版本 用途
PostgreSQL 15.x 主数据库
Redis 7.x 缓存、会话
Kafka 3.7.x 事件消息队列
Identity Service - JWT 验证
Referral Service - 推荐关系查询
Statistics Service - 团队统计查询

环境配置

环境变量

# .env.production

# 应用配置
NODE_ENV=production
PORT=3002

# 数据库配置
DATABASE_URL=postgresql://user:password@db-host:5432/authorization_prod

# Redis 配置
REDIS_HOST=redis-host
REDIS_PORT=6379
REDIS_PASSWORD=redis-password

# Kafka 配置
KAFKA_BROKERS=kafka-1:9092,kafka-2:9092,kafka-3:9092
KAFKA_CLIENT_ID=authorization-service
KAFKA_CONSUMER_GROUP=authorization-service-group

# JWT 配置
JWT_SECRET=your-production-jwt-secret-key
JWT_EXPIRES_IN=1h

# 外部服务
IDENTITY_SERVICE_URL=http://identity-service:3001
REFERRAL_SERVICE_URL=http://referral-service:3003
STATISTICS_SERVICE_URL=http://statistics-service:3004

# 日志
LOG_LEVEL=info

配置说明

配置项 说明 默认值
NODE_ENV 运行环境 production
PORT 服务端口 3002
DATABASE_URL PostgreSQL 连接字符串 -
REDIS_HOST Redis 主机地址 localhost
REDIS_PORT Redis 端口 6379
KAFKA_BROKERS Kafka broker 地址列表 -
JWT_SECRET JWT 签名密钥 -
LOG_LEVEL 日志级别 info

密钥管理

生产环境建议使用密钥管理服务:

  • AWS: AWS Secrets Manager / Parameter Store
  • 阿里云: KMS / 密钥管理服务
  • Kubernetes: Secrets

Docker 部署

生产 Dockerfile

# Dockerfile

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

WORKDIR /app

# 安装 OpenSSL
RUN apk add --no-cache openssl openssl-dev libc6-compat

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

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

# 生成 Prisma 客户端
RUN npx prisma generate

# 复制源代码
COPY . .

# 构建
RUN npm run build

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

WORKDIR /app

# 安装运行时依赖
RUN apk add --no-cache openssl libc6-compat

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

# 创建非 root 用户
RUN addgroup -g 1001 -S nodejs && \
    adduser -S nestjs -u 1001 && \
    chown -R nestjs:nodejs /app

USER nestjs

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

EXPOSE 3002

CMD ["node", "dist/main.js"]

Docker Compose (生产)

# docker-compose.prod.yml

version: '3.8'

services:
  authorization-service:
    build:
      context: .
      dockerfile: Dockerfile
    ports:
      - "3002:3002"
    environment:
      - NODE_ENV=production
      - DATABASE_URL=postgresql://postgres:password@db:5432/authorization
      - REDIS_HOST=redis
      - REDIS_PORT=6379
      - KAFKA_BROKERS=kafka:9092
      - JWT_SECRET=${JWT_SECRET}
    depends_on:
      db:
        condition: service_healthy
      redis:
        condition: service_healthy
      kafka:
        condition: service_healthy
    restart: unless-stopped
    deploy:
      resources:
        limits:
          cpus: '1'
          memory: 1G
        reservations:
          cpus: '0.5'
          memory: 512M

  db:
    image: postgres:15-alpine
    environment:
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: password
      POSTGRES_DB: authorization
    volumes:
      - postgres_data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 10s
      timeout: 5s
      retries: 5
    restart: unless-stopped

  redis:
    image: redis:7-alpine
    command: redis-server --requirepass ${REDIS_PASSWORD}
    volumes:
      - redis_data:/data
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 10s
      timeout: 5s
      retries: 5
    restart: unless-stopped

  kafka:
    image: apache/kafka:3.7.0
    environment:
      KAFKA_NODE_ID: 1
      KAFKA_PROCESS_ROLES: broker,controller
      KAFKA_LISTENERS: PLAINTEXT://:9092,CONTROLLER://:9093
      KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://kafka:9092
      KAFKA_CONTROLLER_LISTENER_NAMES: CONTROLLER
      KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: CONTROLLER:PLAINTEXT,PLAINTEXT:PLAINTEXT
      KAFKA_CONTROLLER_QUORUM_VOTERS: 1@kafka:9093
      KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1
      CLUSTER_ID: MkU3OEVBNTcwNTJENDM2Qk
    volumes:
      - kafka_data:/var/lib/kafka/data
    healthcheck:
      test: ["CMD-SHELL", "/opt/kafka/bin/kafka-topics.sh --bootstrap-server localhost:9092 --list"]
      interval: 30s
      timeout: 10s
      retries: 5
      start_period: 60s
    restart: unless-stopped

volumes:
  postgres_data:
  redis_data:
  kafka_data:

networks:
  default:
    driver: bridge

构建和部署

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

# 推送到镜像仓库
docker tag authorization-service:latest your-registry/authorization-service:latest
docker push your-registry/authorization-service:latest

# 使用 Docker Compose 部署
docker compose -f docker-compose.prod.yml up -d

# 查看日志
docker compose -f docker-compose.prod.yml logs -f authorization-service

# 扩容
docker compose -f docker-compose.prod.yml up -d --scale authorization-service=3

Kubernetes 部署

Deployment

# k8s/deployment.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: authorization-service
  namespace: rwadurian
  labels:
    app: authorization-service
spec:
  replicas: 3
  selector:
    matchLabels:
      app: authorization-service
  template:
    metadata:
      labels:
        app: authorization-service
    spec:
      containers:
        - name: authorization-service
          image: your-registry/authorization-service:latest
          ports:
            - containerPort: 3002
          env:
            - name: NODE_ENV
              value: "production"
            - name: PORT
              value: "3002"
            - name: DATABASE_URL
              valueFrom:
                secretKeyRef:
                  name: authorization-secrets
                  key: database-url
            - name: REDIS_HOST
              valueFrom:
                configMapKeyRef:
                  name: authorization-config
                  key: redis-host
            - name: REDIS_PORT
              valueFrom:
                configMapKeyRef:
                  name: authorization-config
                  key: redis-port
            - name: KAFKA_BROKERS
              valueFrom:
                configMapKeyRef:
                  name: authorization-config
                  key: kafka-brokers
            - name: JWT_SECRET
              valueFrom:
                secretKeyRef:
                  name: authorization-secrets
                  key: jwt-secret
          resources:
            requests:
              memory: "512Mi"
              cpu: "250m"
            limits:
              memory: "1Gi"
              cpu: "1000m"
          readinessProbe:
            httpGet:
              path: /health
              port: 3002
            initialDelaySeconds: 10
            periodSeconds: 5
          livenessProbe:
            httpGet:
              path: /health
              port: 3002
            initialDelaySeconds: 30
            periodSeconds: 10
      imagePullSecrets:
        - name: registry-credentials

Service

# k8s/service.yaml

apiVersion: v1
kind: Service
metadata:
  name: authorization-service
  namespace: rwadurian
spec:
  selector:
    app: authorization-service
  ports:
    - port: 3002
      targetPort: 3002
  type: ClusterIP

ConfigMap

# k8s/configmap.yaml

apiVersion: v1
kind: ConfigMap
metadata:
  name: authorization-config
  namespace: rwadurian
data:
  redis-host: "redis-master.redis.svc.cluster.local"
  redis-port: "6379"
  kafka-brokers: "kafka-0.kafka.svc.cluster.local:9092,kafka-1.kafka.svc.cluster.local:9092"

Secret

# k8s/secret.yaml

apiVersion: v1
kind: Secret
metadata:
  name: authorization-secrets
  namespace: rwadurian
type: Opaque
stringData:
  database-url: "postgresql://user:password@postgres:5432/authorization"
  jwt-secret: "your-production-jwt-secret"

Ingress

# k8s/ingress.yaml

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: authorization-ingress
  namespace: rwadurian
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /
spec:
  ingressClassName: nginx
  rules:
    - host: api.rwadurian.com
      http:
        paths:
          - path: /authorization
            pathType: Prefix
            backend:
              service:
                name: authorization-service
                port:
                  number: 3002

HPA (自动扩缩容)

# k8s/hpa.yaml

apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: authorization-service-hpa
  namespace: rwadurian
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: authorization-service
  minReplicas: 2
  maxReplicas: 10
  metrics:
    - type: Resource
      resource:
        name: cpu
        target:
          type: Utilization
          averageUtilization: 70
    - type: Resource
      resource:
        name: memory
        target:
          type: Utilization
          averageUtilization: 80

部署命令

# 创建命名空间
kubectl create namespace rwadurian

# 应用配置
kubectl apply -f k8s/

# 查看部署状态
kubectl get pods -n rwadurian -l app=authorization-service

# 查看日志
kubectl logs -f -n rwadurian -l app=authorization-service

# 扩缩容
kubectl scale deployment authorization-service -n rwadurian --replicas=5

数据库迁移

迁移策略

  1. 新部署: 自动运行所有迁移
  2. 升级部署: 先迁移数据库,再部署新版本
  3. 回滚: 支持向下迁移

迁移命令

# 创建新迁移
npx prisma migrate dev --name add_new_field

# 应用迁移(生产)
npx prisma migrate deploy

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

# 查看迁移状态
npx prisma migrate status

迁移脚本

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

set -e

echo "Running database migrations..."

# 等待数据库就绪
until npx prisma migrate status > /dev/null 2>&1; do
  echo "Waiting for database..."
  sleep 2
done

# 运行迁移
npx prisma migrate deploy

echo "Migrations completed successfully!"

Kubernetes Job (迁移)

# k8s/migration-job.yaml

apiVersion: batch/v1
kind: Job
metadata:
  name: authorization-migration
  namespace: rwadurian
spec:
  template:
    spec:
      containers:
        - name: migration
          image: your-registry/authorization-service:latest
          command: ["npx", "prisma", "migrate", "deploy"]
          env:
            - name: DATABASE_URL
              valueFrom:
                secretKeyRef:
                  name: authorization-secrets
                  key: database-url
      restartPolicy: Never
  backoffLimit: 3

监控与日志

健康检查端点

// src/health/health.controller.ts

@Controller('health')
export class HealthController {
  constructor(
    private health: HealthCheckService,
    private db: PrismaHealthIndicator,
    private redis: RedisHealthIndicator,
  ) {}

  @Get()
  @HealthCheck()
  check() {
    return this.health.check([
      () => this.db.pingCheck('database'),
      () => this.redis.pingCheck('redis'),
    ])
  }

  @Get('live')
  live() {
    return { status: 'ok' }
  }

  @Get('ready')
  @HealthCheck()
  ready() {
    return this.health.check([
      () => this.db.pingCheck('database'),
    ])
  }
}

Prometheus 指标

// src/metrics/metrics.module.ts

import { PrometheusModule } from '@willsoto/nestjs-prometheus'

@Module({
  imports: [
    PrometheusModule.register({
      defaultMetrics: {
        enabled: true,
      },
      path: '/metrics',
    }),
  ],
})
export class MetricsModule {}

日志配置

// src/main.ts

import { WinstonModule } from 'nest-winston'
import * as winston from 'winston'

const app = await NestFactory.create(AppModule, {
  logger: WinstonModule.createLogger({
    transports: [
      new winston.transports.Console({
        format: winston.format.combine(
          winston.format.timestamp(),
          winston.format.json(),
        ),
      }),
    ],
  }),
})

结构化日志格式

{
  "timestamp": "2024-01-20T10:30:00.000Z",
  "level": "info",
  "message": "Authorization created",
  "service": "authorization-service",
  "traceId": "abc-123",
  "userId": "user-001",
  "authorizationId": "auth-456",
  "roleType": "AUTH_PROVINCE_COMPANY"
}

故障排除

常见问题

1. 数据库连接失败

# 检查数据库连接
npx prisma db pull

# 查看连接字符串
echo $DATABASE_URL

# 测试网络连通性
nc -zv db-host 5432

2. Redis 连接失败

# 测试 Redis 连接
redis-cli -h redis-host -p 6379 -a password ping

# 查看 Redis 状态
redis-cli -h redis-host info

3. Kafka 连接失败

# 列出 topics
kafka-topics.sh --bootstrap-server kafka:9092 --list

# 查看 consumer groups
kafka-consumer-groups.sh --bootstrap-server kafka:9092 --list

4. Pod 启动失败

# 查看 Pod 状态
kubectl describe pod <pod-name> -n rwadurian

# 查看容器日志
kubectl logs <pod-name> -n rwadurian

# 进入容器调试
kubectl exec -it <pod-name> -n rwadurian -- sh

性能调优

数据库连接池

// prisma/schema.prisma

datasource db {
  provider = "postgresql"
  url      = env("DATABASE_URL")
  // 连接池配置
  // ?connection_limit=20&pool_timeout=30
}

Redis 连接池

// src/infrastructure/cache/redis.service.ts

const redis = new Redis({
  host: process.env.REDIS_HOST,
  port: parseInt(process.env.REDIS_PORT),
  password: process.env.REDIS_PASSWORD,
  maxRetriesPerRequest: 3,
  enableReadyCheck: true,
  // 连接池大小
  lazyConnect: true,
})

运维命令

# 查看服务状态
kubectl get all -n rwadurian

# 查看资源使用
kubectl top pods -n rwadurian

# 滚动更新
kubectl rollout restart deployment/authorization-service -n rwadurian

# 回滚
kubectl rollout undo deployment/authorization-service -n rwadurian

# 查看回滚历史
kubectl rollout history deployment/authorization-service -n rwadurian

部署检查清单

部署前

  • 环境变量配置完成
  • 数据库迁移已准备
  • 镜像已构建并推送
  • 配置文件已验证
  • 密钥已配置

部署中

  • 数据库迁移成功
  • Pod 启动正常
  • 健康检查通过
  • 服务可访问

部署后

  • 功能测试通过
  • 监控指标正常
  • 日志无异常
  • 通知相关人员

参考资源