788 lines
17 KiB
Markdown
788 lines
17 KiB
Markdown
# 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 允许源 | `*` |
|
||
|
||
### 环境配置文件
|
||
|
||
```bash
|
||
# .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
|
||
# 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
|
||
```
|
||
|
||
### 构建和运行
|
||
|
||
```bash
|
||
# 构建镜像
|
||
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
|
||
|
||
```yaml
|
||
# 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
|
||
|
||
```yaml
|
||
# 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
|
||
```
|
||
|
||
### 运行命令
|
||
|
||
```bash
|
||
# 开发环境
|
||
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
|
||
|
||
```yaml
|
||
# 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
|
||
|
||
```yaml
|
||
# k8s/service.yaml
|
||
apiVersion: v1
|
||
kind: Service
|
||
metadata:
|
||
name: wallet-service
|
||
spec:
|
||
type: ClusterIP
|
||
ports:
|
||
- port: 80
|
||
targetPort: 3000
|
||
selector:
|
||
app: wallet-service
|
||
```
|
||
|
||
### Secret
|
||
|
||
```yaml
|
||
# 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
|
||
|
||
```yaml
|
||
# 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
|
||
```
|
||
|
||
### 部署命令
|
||
|
||
```bash
|
||
# 创建 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 迁移
|
||
|
||
```bash
|
||
# 开发环境 - 创建迁移
|
||
npx prisma migrate dev --name add_new_field
|
||
|
||
# 生产环境 - 应用迁移
|
||
npx prisma migrate deploy
|
||
|
||
# 查看迁移状态
|
||
npx prisma migrate status
|
||
```
|
||
|
||
### 迁移脚本
|
||
|
||
```bash
|
||
#!/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
|
||
|
||
```yaml
|
||
# .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..."
|
||
```
|
||
|
||
---
|
||
|
||
## 监控和日志
|
||
|
||
### 健康检查端点
|
||
|
||
```bash
|
||
# 健康检查
|
||
curl http://localhost:3000/api/v1/health
|
||
|
||
# 响应示例
|
||
{
|
||
"success": true,
|
||
"data": {
|
||
"status": "ok",
|
||
"service": "wallet-service",
|
||
"timestamp": "2024-01-01T00:00:00.000Z"
|
||
}
|
||
}
|
||
```
|
||
|
||
### 日志配置
|
||
|
||
```typescript
|
||
// 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'],
|
||
});
|
||
// ...
|
||
}
|
||
```
|
||
|
||
### 日志格式 (生产环境)
|
||
|
||
```json
|
||
{
|
||
"timestamp": "2024-01-01T00:00:00.000Z",
|
||
"level": "info",
|
||
"context": "WalletController",
|
||
"message": "Deposit processed",
|
||
"userId": "12345",
|
||
"amount": 100,
|
||
"requestId": "abc-123"
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 安全配置
|
||
|
||
### Nginx 反向代理
|
||
|
||
```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;
|
||
}
|
||
}
|
||
```
|
||
|
||
### 环境变量安全
|
||
|
||
```bash
|
||
# 不要在版本控制中提交敏感信息
|
||
# 使用环境变量或密钥管理服务
|
||
|
||
# AWS Secrets Manager
|
||
aws secretsmanager get-secret-value --secret-id wallet/production
|
||
|
||
# HashiCorp Vault
|
||
vault kv get secret/wallet/production
|
||
```
|
||
|
||
---
|
||
|
||
## 故障排除
|
||
|
||
### 常见问题
|
||
|
||
#### 1. 数据库连接失败
|
||
|
||
```bash
|
||
# 检查连接
|
||
psql $DATABASE_URL -c "SELECT 1"
|
||
|
||
# 检查网络
|
||
nc -zv db.example.com 5432
|
||
```
|
||
|
||
#### 2. 容器无法启动
|
||
|
||
```bash
|
||
# 查看日志
|
||
docker logs wallet-service
|
||
|
||
# 进入容器调试
|
||
docker exec -it wallet-service sh
|
||
```
|
||
|
||
#### 3. 迁移失败
|
||
|
||
```bash
|
||
# 查看迁移状态
|
||
npx prisma migrate status
|
||
|
||
# 重置数据库 (开发环境)
|
||
npx prisma migrate reset
|
||
```
|
||
|
||
### 回滚流程
|
||
|
||
```bash
|
||
# 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 响应正常
|
||
|
||
### 部署后
|
||
|
||
- [ ] 功能验证通过
|
||
- [ ] 性能测试通过
|
||
- [ ] 安全扫描通过
|
||
- [ ] 文档更新
|
||
|
||
---
|
||
|
||
## 联系方式
|
||
|
||
如遇部署问题,请联系:
|
||
|
||
- 运维团队: devops@example.com
|
||
- 开发团队: dev@example.com
|
||
- 紧急联系: oncall@example.com
|
||
|