18 KiB
18 KiB
Authorization Service 部署文档
目录
部署架构
整体架构
┌─────────────────┐
│ 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
数据库迁移
迁移策略
- 新部署: 自动运行所有迁移
- 升级部署: 先迁移数据库,再部署新版本
- 回滚: 支持向下迁移
迁移命令
# 创建新迁移
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 启动正常
- 健康检查通过
- 服务可访问
部署后
- 功能测试通过
- 监控指标正常
- 日志无异常
- 通知相关人员