1080 lines
26 KiB
Markdown
1080 lines
26 KiB
Markdown
|
|
# 解班客项目部署和运维文档
|
|||
|
|
|
|||
|
|
## 📋 文档概述
|
|||
|
|
|
|||
|
|
本文档详细说明解班客项目的部署流程、运维管理和监控体系,涵盖开发、测试、生产环境的完整部署方案。
|
|||
|
|
|
|||
|
|
### 文档目标
|
|||
|
|
- 建立标准化的部署流程
|
|||
|
|
- 提供完整的运维管理方案
|
|||
|
|
- 建立监控和告警体系
|
|||
|
|
- 确保系统高可用性和稳定性
|
|||
|
|
|
|||
|
|
## 🏗️ 系统架构
|
|||
|
|
|
|||
|
|
### 整体架构图
|
|||
|
|
```
|
|||
|
|
┌─────────────────────────────────────────────────────────────┐
|
|||
|
|
│ 负载均衡层 │
|
|||
|
|
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
|
|||
|
|
│ │ Nginx │ │ Nginx │ │ Nginx │ │
|
|||
|
|
│ │ (主节点) │ │ (备节点1) │ │ (备节点2) │ │
|
|||
|
|
│ └─────────────┘ └─────────────┘ └─────────────┘ │
|
|||
|
|
└─────────────────────────────────────────────────────────────┘
|
|||
|
|
│
|
|||
|
|
┌─────────────────────────────────────────────────────────────┐
|
|||
|
|
│ 应用服务层 │
|
|||
|
|
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
|
|||
|
|
│ │ Node.js │ │ Node.js │ │ Node.js │ │
|
|||
|
|
│ │ API-1 │ │ API-2 │ │ API-3 │ │
|
|||
|
|
│ └─────────────┘ └─────────────┘ └─────────────┘ │
|
|||
|
|
└─────────────────────────────────────────────────────────────┘
|
|||
|
|
│
|
|||
|
|
┌─────────────────────────────────────────────────────────────┐
|
|||
|
|
│ 数据存储层 │
|
|||
|
|
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
|
|||
|
|
│ │ MySQL │ │ Redis │ │ MinIO │ │
|
|||
|
|
│ │ (主从复制) │ │ (集群) │ │ (对象存储) │ │
|
|||
|
|
│ └─────────────┘ └─────────────┘ └─────────────┘ │
|
|||
|
|
└─────────────────────────────────────────────────────────────┘
|
|||
|
|
│
|
|||
|
|
┌─────────────────────────────────────────────────────────────┐
|
|||
|
|
│ 监控运维层 │
|
|||
|
|
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
|
|||
|
|
│ │ Prometheus │ │ Grafana │ │ ELK Stack │ │
|
|||
|
|
│ │ (监控) │ │ (可视化) │ │ (日志) │ │
|
|||
|
|
│ └─────────────┘ └─────────────┘ └─────────────┘ │
|
|||
|
|
└─────────────────────────────────────────────────────────────┘
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 技术栈选择
|
|||
|
|
```yaml
|
|||
|
|
# 技术栈配置
|
|||
|
|
tech_stack:
|
|||
|
|
frontend:
|
|||
|
|
framework: "Vue 3"
|
|||
|
|
build_tool: "Vite"
|
|||
|
|
ui_library: "Element Plus"
|
|||
|
|
state_management: "Pinia"
|
|||
|
|
|
|||
|
|
backend:
|
|||
|
|
runtime: "Node.js 18+"
|
|||
|
|
framework: "Express.js"
|
|||
|
|
orm: "Sequelize"
|
|||
|
|
authentication: "JWT"
|
|||
|
|
|
|||
|
|
database:
|
|||
|
|
primary: "MySQL 8.0"
|
|||
|
|
cache: "Redis 7.0"
|
|||
|
|
search: "Elasticsearch 8.0"
|
|||
|
|
|
|||
|
|
storage:
|
|||
|
|
object_storage: "MinIO"
|
|||
|
|
cdn: "阿里云CDN"
|
|||
|
|
|
|||
|
|
infrastructure:
|
|||
|
|
container: "Docker"
|
|||
|
|
orchestration: "Docker Compose / Kubernetes"
|
|||
|
|
reverse_proxy: "Nginx"
|
|||
|
|
|
|||
|
|
monitoring:
|
|||
|
|
metrics: "Prometheus"
|
|||
|
|
visualization: "Grafana"
|
|||
|
|
logging: "ELK Stack"
|
|||
|
|
alerting: "AlertManager"
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 🐳 容器化部署
|
|||
|
|
|
|||
|
|
### Docker配置
|
|||
|
|
|
|||
|
|
#### 前端Dockerfile
|
|||
|
|
```dockerfile
|
|||
|
|
# frontend/Dockerfile
|
|||
|
|
FROM node:18-alpine AS builder
|
|||
|
|
|
|||
|
|
# 设置工作目录
|
|||
|
|
WORKDIR /app
|
|||
|
|
|
|||
|
|
# 复制package文件
|
|||
|
|
COPY package*.json ./
|
|||
|
|
|
|||
|
|
# 安装依赖
|
|||
|
|
RUN npm ci --only=production
|
|||
|
|
|
|||
|
|
# 复制源代码
|
|||
|
|
COPY . .
|
|||
|
|
|
|||
|
|
# 构建应用
|
|||
|
|
RUN npm run build
|
|||
|
|
|
|||
|
|
# 生产环境镜像
|
|||
|
|
FROM nginx:alpine
|
|||
|
|
|
|||
|
|
# 复制构建产物
|
|||
|
|
COPY --from=builder /app/dist /usr/share/nginx/html
|
|||
|
|
|
|||
|
|
# 复制nginx配置
|
|||
|
|
COPY nginx.conf /etc/nginx/nginx.conf
|
|||
|
|
|
|||
|
|
# 暴露端口
|
|||
|
|
EXPOSE 80
|
|||
|
|
|
|||
|
|
# 启动命令
|
|||
|
|
CMD ["nginx", "-g", "daemon off;"]
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 后端Dockerfile
|
|||
|
|
```dockerfile
|
|||
|
|
# backend/Dockerfile
|
|||
|
|
FROM node:18-alpine
|
|||
|
|
|
|||
|
|
# 设置工作目录
|
|||
|
|
WORKDIR /app
|
|||
|
|
|
|||
|
|
# 创建非root用户
|
|||
|
|
RUN addgroup -g 1001 -S nodejs
|
|||
|
|
RUN adduser -S nodejs -u 1001
|
|||
|
|
|
|||
|
|
# 复制package文件
|
|||
|
|
COPY package*.json ./
|
|||
|
|
|
|||
|
|
# 安装依赖
|
|||
|
|
RUN npm ci --only=production && npm cache clean --force
|
|||
|
|
|
|||
|
|
# 复制源代码
|
|||
|
|
COPY . .
|
|||
|
|
|
|||
|
|
# 更改文件所有者
|
|||
|
|
RUN chown -R nodejs:nodejs /app
|
|||
|
|
USER nodejs
|
|||
|
|
|
|||
|
|
# 暴露端口
|
|||
|
|
EXPOSE 3000
|
|||
|
|
|
|||
|
|
# 健康检查
|
|||
|
|
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
|
|||
|
|
CMD curl -f http://localhost:3000/health || exit 1
|
|||
|
|
|
|||
|
|
# 启动命令
|
|||
|
|
CMD ["npm", "start"]
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### Docker Compose配置
|
|||
|
|
```yaml
|
|||
|
|
# docker-compose.yml
|
|||
|
|
version: '3.8'
|
|||
|
|
|
|||
|
|
services:
|
|||
|
|
# 前端服务
|
|||
|
|
frontend:
|
|||
|
|
build:
|
|||
|
|
context: ./frontend
|
|||
|
|
dockerfile: Dockerfile
|
|||
|
|
container_name: jiebanke-frontend
|
|||
|
|
ports:
|
|||
|
|
- "80:80"
|
|||
|
|
- "443:443"
|
|||
|
|
volumes:
|
|||
|
|
- ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
|
|||
|
|
- ./nginx/ssl:/etc/nginx/ssl:ro
|
|||
|
|
depends_on:
|
|||
|
|
- backend
|
|||
|
|
networks:
|
|||
|
|
- jiebanke-network
|
|||
|
|
restart: unless-stopped
|
|||
|
|
|
|||
|
|
# 后端服务
|
|||
|
|
backend:
|
|||
|
|
build:
|
|||
|
|
context: ./backend
|
|||
|
|
dockerfile: Dockerfile
|
|||
|
|
container_name: jiebanke-backend
|
|||
|
|
ports:
|
|||
|
|
- "3000:3000"
|
|||
|
|
environment:
|
|||
|
|
- NODE_ENV=production
|
|||
|
|
- DB_HOST=mysql
|
|||
|
|
- DB_PORT=3306
|
|||
|
|
- DB_NAME=jiebanke
|
|||
|
|
- DB_USER=jiebanke_user
|
|||
|
|
- DB_PASSWORD=${DB_PASSWORD}
|
|||
|
|
- REDIS_HOST=redis
|
|||
|
|
- REDIS_PORT=6379
|
|||
|
|
- JWT_SECRET=${JWT_SECRET}
|
|||
|
|
- MINIO_ENDPOINT=minio
|
|||
|
|
- MINIO_PORT=9000
|
|||
|
|
- MINIO_ACCESS_KEY=${MINIO_ACCESS_KEY}
|
|||
|
|
- MINIO_SECRET_KEY=${MINIO_SECRET_KEY}
|
|||
|
|
volumes:
|
|||
|
|
- ./logs:/app/logs
|
|||
|
|
- ./uploads:/app/uploads
|
|||
|
|
depends_on:
|
|||
|
|
- mysql
|
|||
|
|
- redis
|
|||
|
|
- minio
|
|||
|
|
networks:
|
|||
|
|
- jiebanke-network
|
|||
|
|
restart: unless-stopped
|
|||
|
|
deploy:
|
|||
|
|
resources:
|
|||
|
|
limits:
|
|||
|
|
memory: 512M
|
|||
|
|
cpus: '0.5'
|
|||
|
|
reservations:
|
|||
|
|
memory: 256M
|
|||
|
|
cpus: '0.25'
|
|||
|
|
|
|||
|
|
# MySQL数据库
|
|||
|
|
mysql:
|
|||
|
|
image: mysql:8.0
|
|||
|
|
container_name: jiebanke-mysql
|
|||
|
|
ports:
|
|||
|
|
- "3306:3306"
|
|||
|
|
environment:
|
|||
|
|
- MYSQL_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD}
|
|||
|
|
- MYSQL_DATABASE=jiebanke
|
|||
|
|
- MYSQL_USER=jiebanke_user
|
|||
|
|
- MYSQL_PASSWORD=${DB_PASSWORD}
|
|||
|
|
volumes:
|
|||
|
|
- mysql_data:/var/lib/mysql
|
|||
|
|
- ./mysql/conf.d:/etc/mysql/conf.d:ro
|
|||
|
|
- ./mysql/init:/docker-entrypoint-initdb.d:ro
|
|||
|
|
networks:
|
|||
|
|
- jiebanke-network
|
|||
|
|
restart: unless-stopped
|
|||
|
|
command: --default-authentication-plugin=mysql_native_password
|
|||
|
|
|
|||
|
|
# Redis缓存
|
|||
|
|
redis:
|
|||
|
|
image: redis:7-alpine
|
|||
|
|
container_name: jiebanke-redis
|
|||
|
|
ports:
|
|||
|
|
- "6379:6379"
|
|||
|
|
volumes:
|
|||
|
|
- redis_data:/data
|
|||
|
|
- ./redis/redis.conf:/usr/local/etc/redis/redis.conf:ro
|
|||
|
|
networks:
|
|||
|
|
- jiebanke-network
|
|||
|
|
restart: unless-stopped
|
|||
|
|
command: redis-server /usr/local/etc/redis/redis.conf
|
|||
|
|
|
|||
|
|
# MinIO对象存储
|
|||
|
|
minio:
|
|||
|
|
image: minio/minio:latest
|
|||
|
|
container_name: jiebanke-minio
|
|||
|
|
ports:
|
|||
|
|
- "9000:9000"
|
|||
|
|
- "9001:9001"
|
|||
|
|
environment:
|
|||
|
|
- MINIO_ROOT_USER=${MINIO_ACCESS_KEY}
|
|||
|
|
- MINIO_ROOT_PASSWORD=${MINIO_SECRET_KEY}
|
|||
|
|
volumes:
|
|||
|
|
- minio_data:/data
|
|||
|
|
networks:
|
|||
|
|
- jiebanke-network
|
|||
|
|
restart: unless-stopped
|
|||
|
|
command: server /data --console-address ":9001"
|
|||
|
|
|
|||
|
|
# Nginx负载均衡
|
|||
|
|
nginx:
|
|||
|
|
image: nginx:alpine
|
|||
|
|
container_name: jiebanke-nginx
|
|||
|
|
ports:
|
|||
|
|
- "80:80"
|
|||
|
|
- "443:443"
|
|||
|
|
volumes:
|
|||
|
|
- ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
|
|||
|
|
- ./nginx/ssl:/etc/nginx/ssl:ro
|
|||
|
|
- ./nginx/logs:/var/log/nginx
|
|||
|
|
depends_on:
|
|||
|
|
- backend
|
|||
|
|
networks:
|
|||
|
|
- jiebanke-network
|
|||
|
|
restart: unless-stopped
|
|||
|
|
|
|||
|
|
# Prometheus监控
|
|||
|
|
prometheus:
|
|||
|
|
image: prom/prometheus:latest
|
|||
|
|
container_name: jiebanke-prometheus
|
|||
|
|
ports:
|
|||
|
|
- "9090:9090"
|
|||
|
|
volumes:
|
|||
|
|
- ./monitoring/prometheus.yml:/etc/prometheus/prometheus.yml:ro
|
|||
|
|
- prometheus_data:/prometheus
|
|||
|
|
networks:
|
|||
|
|
- jiebanke-network
|
|||
|
|
restart: unless-stopped
|
|||
|
|
command:
|
|||
|
|
- '--config.file=/etc/prometheus/prometheus.yml'
|
|||
|
|
- '--storage.tsdb.path=/prometheus'
|
|||
|
|
- '--web.console.libraries=/etc/prometheus/console_libraries'
|
|||
|
|
- '--web.console.templates=/etc/prometheus/consoles'
|
|||
|
|
|
|||
|
|
# Grafana可视化
|
|||
|
|
grafana:
|
|||
|
|
image: grafana/grafana:latest
|
|||
|
|
container_name: jiebanke-grafana
|
|||
|
|
ports:
|
|||
|
|
- "3001:3000"
|
|||
|
|
environment:
|
|||
|
|
- GF_SECURITY_ADMIN_PASSWORD=${GRAFANA_PASSWORD}
|
|||
|
|
volumes:
|
|||
|
|
- grafana_data:/var/lib/grafana
|
|||
|
|
- ./monitoring/grafana/dashboards:/etc/grafana/provisioning/dashboards:ro
|
|||
|
|
- ./monitoring/grafana/datasources:/etc/grafana/provisioning/datasources:ro
|
|||
|
|
depends_on:
|
|||
|
|
- prometheus
|
|||
|
|
networks:
|
|||
|
|
- jiebanke-network
|
|||
|
|
restart: unless-stopped
|
|||
|
|
|
|||
|
|
volumes:
|
|||
|
|
mysql_data:
|
|||
|
|
redis_data:
|
|||
|
|
minio_data:
|
|||
|
|
prometheus_data:
|
|||
|
|
grafana_data:
|
|||
|
|
|
|||
|
|
networks:
|
|||
|
|
jiebanke-network:
|
|||
|
|
driver: bridge
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 环境配置
|
|||
|
|
|
|||
|
|
#### 环境变量配置
|
|||
|
|
```bash
|
|||
|
|
# .env.production
|
|||
|
|
# 数据库配置
|
|||
|
|
DB_PASSWORD=your_secure_db_password
|
|||
|
|
MYSQL_ROOT_PASSWORD=your_secure_root_password
|
|||
|
|
|
|||
|
|
# JWT配置
|
|||
|
|
JWT_SECRET=your_jwt_secret_key_here
|
|||
|
|
|
|||
|
|
# MinIO配置
|
|||
|
|
MINIO_ACCESS_KEY=your_minio_access_key
|
|||
|
|
MINIO_SECRET_KEY=your_minio_secret_key
|
|||
|
|
|
|||
|
|
# Grafana配置
|
|||
|
|
GRAFANA_PASSWORD=your_grafana_password
|
|||
|
|
|
|||
|
|
# 邮件配置
|
|||
|
|
SMTP_HOST=smtp.example.com
|
|||
|
|
SMTP_PORT=587
|
|||
|
|
SMTP_USER=your_email@example.com
|
|||
|
|
SMTP_PASSWORD=your_email_password
|
|||
|
|
|
|||
|
|
# 微信小程序配置
|
|||
|
|
WECHAT_APP_ID=your_wechat_app_id
|
|||
|
|
WECHAT_APP_SECRET=your_wechat_app_secret
|
|||
|
|
|
|||
|
|
# 阿里云配置
|
|||
|
|
ALIYUN_ACCESS_KEY_ID=your_aliyun_access_key
|
|||
|
|
ALIYUN_ACCESS_KEY_SECRET=your_aliyun_secret_key
|
|||
|
|
ALIYUN_OSS_BUCKET=your_oss_bucket_name
|
|||
|
|
ALIYUN_OSS_REGION=your_oss_region
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 🚀 部署流程
|
|||
|
|
|
|||
|
|
### 自动化部署脚本
|
|||
|
|
|
|||
|
|
#### 部署脚本
|
|||
|
|
```bash
|
|||
|
|
#!/bin/bash
|
|||
|
|
# deploy.sh - 自动化部署脚本
|
|||
|
|
|
|||
|
|
set -e
|
|||
|
|
|
|||
|
|
# 配置变量
|
|||
|
|
PROJECT_NAME="jiebanke"
|
|||
|
|
DEPLOY_ENV=${1:-production}
|
|||
|
|
BACKUP_DIR="/backup"
|
|||
|
|
LOG_FILE="/var/log/deploy.log"
|
|||
|
|
|
|||
|
|
# 日志函数
|
|||
|
|
log() {
|
|||
|
|
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a $LOG_FILE
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# 错误处理
|
|||
|
|
error_exit() {
|
|||
|
|
log "ERROR: $1"
|
|||
|
|
exit 1
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# 检查环境
|
|||
|
|
check_environment() {
|
|||
|
|
log "检查部署环境..."
|
|||
|
|
|
|||
|
|
# 检查Docker
|
|||
|
|
if ! command -v docker &> /dev/null; then
|
|||
|
|
error_exit "Docker未安装"
|
|||
|
|
fi
|
|||
|
|
|
|||
|
|
# 检查Docker Compose
|
|||
|
|
if ! command -v docker-compose &> /dev/null; then
|
|||
|
|
error_exit "Docker Compose未安装"
|
|||
|
|
fi
|
|||
|
|
|
|||
|
|
# 检查环境变量文件
|
|||
|
|
if [ ! -f ".env.${DEPLOY_ENV}" ]; then
|
|||
|
|
error_exit "环境变量文件 .env.${DEPLOY_ENV} 不存在"
|
|||
|
|
fi
|
|||
|
|
|
|||
|
|
log "环境检查完成"
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# 备份数据
|
|||
|
|
backup_data() {
|
|||
|
|
log "开始数据备份..."
|
|||
|
|
|
|||
|
|
# 创建备份目录
|
|||
|
|
BACKUP_PATH="${BACKUP_DIR}/$(date +%Y%m%d_%H%M%S)"
|
|||
|
|
mkdir -p $BACKUP_PATH
|
|||
|
|
|
|||
|
|
# 备份数据库
|
|||
|
|
docker exec jiebanke-mysql mysqldump -u root -p${MYSQL_ROOT_PASSWORD} jiebanke > "${BACKUP_PATH}/database.sql"
|
|||
|
|
|
|||
|
|
# 备份Redis数据
|
|||
|
|
docker exec jiebanke-redis redis-cli BGSAVE
|
|||
|
|
docker cp jiebanke-redis:/data/dump.rdb "${BACKUP_PATH}/redis.rdb"
|
|||
|
|
|
|||
|
|
# 备份上传文件
|
|||
|
|
if [ -d "./uploads" ]; then
|
|||
|
|
tar -czf "${BACKUP_PATH}/uploads.tar.gz" ./uploads
|
|||
|
|
fi
|
|||
|
|
|
|||
|
|
log "数据备份完成: $BACKUP_PATH"
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# 拉取最新代码
|
|||
|
|
pull_code() {
|
|||
|
|
log "拉取最新代码..."
|
|||
|
|
|
|||
|
|
# 检查Git状态
|
|||
|
|
if [ -d ".git" ]; then
|
|||
|
|
git fetch origin
|
|||
|
|
git reset --hard origin/main
|
|||
|
|
log "代码更新完成"
|
|||
|
|
else
|
|||
|
|
error_exit "不是Git仓库"
|
|||
|
|
fi
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# 构建镜像
|
|||
|
|
build_images() {
|
|||
|
|
log "构建Docker镜像..."
|
|||
|
|
|
|||
|
|
# 构建前端镜像
|
|||
|
|
docker build -t ${PROJECT_NAME}-frontend:latest ./frontend
|
|||
|
|
|
|||
|
|
# 构建后端镜像
|
|||
|
|
docker build -t ${PROJECT_NAME}-backend:latest ./backend
|
|||
|
|
|
|||
|
|
log "镜像构建完成"
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# 部署服务
|
|||
|
|
deploy_services() {
|
|||
|
|
log "部署服务..."
|
|||
|
|
|
|||
|
|
# 复制环境变量文件
|
|||
|
|
cp .env.${DEPLOY_ENV} .env
|
|||
|
|
|
|||
|
|
# 停止旧服务
|
|||
|
|
docker-compose down
|
|||
|
|
|
|||
|
|
# 启动新服务
|
|||
|
|
docker-compose up -d
|
|||
|
|
|
|||
|
|
# 等待服务启动
|
|||
|
|
sleep 30
|
|||
|
|
|
|||
|
|
log "服务部署完成"
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# 健康检查
|
|||
|
|
health_check() {
|
|||
|
|
log "执行健康检查..."
|
|||
|
|
|
|||
|
|
# 检查后端服务
|
|||
|
|
if ! curl -f http://localhost:3000/health > /dev/null 2>&1; then
|
|||
|
|
error_exit "后端服务健康检查失败"
|
|||
|
|
fi
|
|||
|
|
|
|||
|
|
# 检查前端服务
|
|||
|
|
if ! curl -f http://localhost > /dev/null 2>&1; then
|
|||
|
|
error_exit "前端服务健康检查失败"
|
|||
|
|
fi
|
|||
|
|
|
|||
|
|
# 检查数据库连接
|
|||
|
|
if ! docker exec jiebanke-mysql mysqladmin ping -h localhost > /dev/null 2>&1; then
|
|||
|
|
error_exit "数据库连接检查失败"
|
|||
|
|
fi
|
|||
|
|
|
|||
|
|
log "健康检查通过"
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# 清理旧镜像
|
|||
|
|
cleanup() {
|
|||
|
|
log "清理旧镜像..."
|
|||
|
|
|
|||
|
|
# 删除未使用的镜像
|
|||
|
|
docker image prune -f
|
|||
|
|
|
|||
|
|
# 删除未使用的容器
|
|||
|
|
docker container prune -f
|
|||
|
|
|
|||
|
|
# 删除未使用的网络
|
|||
|
|
docker network prune -f
|
|||
|
|
|
|||
|
|
log "清理完成"
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# 发送通知
|
|||
|
|
send_notification() {
|
|||
|
|
local status=$1
|
|||
|
|
local message="解班客项目部署${status}: ${DEPLOY_ENV}环境"
|
|||
|
|
|
|||
|
|
log $message
|
|||
|
|
|
|||
|
|
# 这里可以集成钉钉、企业微信等通知
|
|||
|
|
# curl -X POST "https://oapi.dingtalk.com/robot/send?access_token=YOUR_TOKEN" \
|
|||
|
|
# -H 'Content-Type: application/json' \
|
|||
|
|
# -d "{\"msgtype\": \"text\", \"text\": {\"content\": \"$message\"}}"
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# 主函数
|
|||
|
|
main() {
|
|||
|
|
log "开始部署 ${PROJECT_NAME} 到 ${DEPLOY_ENV} 环境"
|
|||
|
|
|
|||
|
|
# 检查环境
|
|||
|
|
check_environment
|
|||
|
|
|
|||
|
|
# 备份数据
|
|||
|
|
backup_data
|
|||
|
|
|
|||
|
|
# 拉取代码
|
|||
|
|
pull_code
|
|||
|
|
|
|||
|
|
# 构建镜像
|
|||
|
|
build_images
|
|||
|
|
|
|||
|
|
# 部署服务
|
|||
|
|
deploy_services
|
|||
|
|
|
|||
|
|
# 健康检查
|
|||
|
|
health_check
|
|||
|
|
|
|||
|
|
# 清理资源
|
|||
|
|
cleanup
|
|||
|
|
|
|||
|
|
# 发送成功通知
|
|||
|
|
send_notification "成功"
|
|||
|
|
|
|||
|
|
log "部署完成"
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# 错误处理
|
|||
|
|
trap 'send_notification "失败"; exit 1' ERR
|
|||
|
|
|
|||
|
|
# 执行主函数
|
|||
|
|
main "$@"
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 回滚脚本
|
|||
|
|
```bash
|
|||
|
|
#!/bin/bash
|
|||
|
|
# rollback.sh - 回滚脚本
|
|||
|
|
|
|||
|
|
set -e
|
|||
|
|
|
|||
|
|
PROJECT_NAME="jiebanke"
|
|||
|
|
BACKUP_DIR="/backup"
|
|||
|
|
LOG_FILE="/var/log/rollback.log"
|
|||
|
|
|
|||
|
|
# 日志函数
|
|||
|
|
log() {
|
|||
|
|
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a $LOG_FILE
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# 列出可用备份
|
|||
|
|
list_backups() {
|
|||
|
|
log "可用备份列表:"
|
|||
|
|
ls -la $BACKUP_DIR | grep "^d" | awk '{print $9}' | grep -E "^[0-9]{8}_[0-9]{6}$" | sort -r
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# 回滚到指定备份
|
|||
|
|
rollback_to_backup() {
|
|||
|
|
local backup_name=$1
|
|||
|
|
local backup_path="${BACKUP_DIR}/${backup_name}"
|
|||
|
|
|
|||
|
|
if [ ! -d "$backup_path" ]; then
|
|||
|
|
log "ERROR: 备份目录不存在: $backup_path"
|
|||
|
|
exit 1
|
|||
|
|
fi
|
|||
|
|
|
|||
|
|
log "开始回滚到备份: $backup_name"
|
|||
|
|
|
|||
|
|
# 停止当前服务
|
|||
|
|
docker-compose down
|
|||
|
|
|
|||
|
|
# 恢复数据库
|
|||
|
|
if [ -f "${backup_path}/database.sql" ]; then
|
|||
|
|
log "恢复数据库..."
|
|||
|
|
docker-compose up -d mysql
|
|||
|
|
sleep 10
|
|||
|
|
docker exec -i jiebanke-mysql mysql -u root -p${MYSQL_ROOT_PASSWORD} jiebanke < "${backup_path}/database.sql"
|
|||
|
|
fi
|
|||
|
|
|
|||
|
|
# 恢复Redis数据
|
|||
|
|
if [ -f "${backup_path}/redis.rdb" ]; then
|
|||
|
|
log "恢复Redis数据..."
|
|||
|
|
docker cp "${backup_path}/redis.rdb" jiebanke-redis:/data/dump.rdb
|
|||
|
|
docker-compose restart redis
|
|||
|
|
fi
|
|||
|
|
|
|||
|
|
# 恢复上传文件
|
|||
|
|
if [ -f "${backup_path}/uploads.tar.gz" ]; then
|
|||
|
|
log "恢复上传文件..."
|
|||
|
|
rm -rf ./uploads
|
|||
|
|
tar -xzf "${backup_path}/uploads.tar.gz"
|
|||
|
|
fi
|
|||
|
|
|
|||
|
|
# 启动所有服务
|
|||
|
|
docker-compose up -d
|
|||
|
|
|
|||
|
|
log "回滚完成"
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# 主函数
|
|||
|
|
main() {
|
|||
|
|
if [ $# -eq 0 ]; then
|
|||
|
|
list_backups
|
|||
|
|
echo "使用方法: $0 <backup_name>"
|
|||
|
|
exit 1
|
|||
|
|
fi
|
|||
|
|
|
|||
|
|
rollback_to_backup $1
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
main "$@"
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 📊 监控配置
|
|||
|
|
|
|||
|
|
### Prometheus配置
|
|||
|
|
```yaml
|
|||
|
|
# monitoring/prometheus.yml
|
|||
|
|
global:
|
|||
|
|
scrape_interval: 15s
|
|||
|
|
evaluation_interval: 15s
|
|||
|
|
|
|||
|
|
rule_files:
|
|||
|
|
- "rules/*.yml"
|
|||
|
|
|
|||
|
|
alerting:
|
|||
|
|
alertmanagers:
|
|||
|
|
- static_configs:
|
|||
|
|
- targets:
|
|||
|
|
- alertmanager:9093
|
|||
|
|
|
|||
|
|
scrape_configs:
|
|||
|
|
# Prometheus自身监控
|
|||
|
|
- job_name: 'prometheus'
|
|||
|
|
static_configs:
|
|||
|
|
- targets: ['localhost:9090']
|
|||
|
|
|
|||
|
|
# Node.js应用监控
|
|||
|
|
- job_name: 'jiebanke-backend'
|
|||
|
|
static_configs:
|
|||
|
|
- targets: ['backend:3000']
|
|||
|
|
metrics_path: '/metrics'
|
|||
|
|
scrape_interval: 10s
|
|||
|
|
|
|||
|
|
# MySQL监控
|
|||
|
|
- job_name: 'mysql'
|
|||
|
|
static_configs:
|
|||
|
|
- targets: ['mysql-exporter:9104']
|
|||
|
|
|
|||
|
|
# Redis监控
|
|||
|
|
- job_name: 'redis'
|
|||
|
|
static_configs:
|
|||
|
|
- targets: ['redis-exporter:9121']
|
|||
|
|
|
|||
|
|
# Nginx监控
|
|||
|
|
- job_name: 'nginx'
|
|||
|
|
static_configs:
|
|||
|
|
- targets: ['nginx-exporter:9113']
|
|||
|
|
|
|||
|
|
# 系统监控
|
|||
|
|
- job_name: 'node'
|
|||
|
|
static_configs:
|
|||
|
|
- targets: ['node-exporter:9100']
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 告警规则
|
|||
|
|
```yaml
|
|||
|
|
# monitoring/rules/alerts.yml
|
|||
|
|
groups:
|
|||
|
|
- name: jiebanke-alerts
|
|||
|
|
rules:
|
|||
|
|
# 服务可用性告警
|
|||
|
|
- alert: ServiceDown
|
|||
|
|
expr: up == 0
|
|||
|
|
for: 1m
|
|||
|
|
labels:
|
|||
|
|
severity: critical
|
|||
|
|
annotations:
|
|||
|
|
summary: "服务 {{ $labels.job }} 不可用"
|
|||
|
|
description: "服务 {{ $labels.job }} 已经停止响应超过1分钟"
|
|||
|
|
|
|||
|
|
# 高响应时间告警
|
|||
|
|
- alert: HighResponseTime
|
|||
|
|
expr: http_request_duration_seconds{quantile="0.95"} > 1
|
|||
|
|
for: 2m
|
|||
|
|
labels:
|
|||
|
|
severity: warning
|
|||
|
|
annotations:
|
|||
|
|
summary: "响应时间过高"
|
|||
|
|
description: "95%的请求响应时间超过1秒,当前值: {{ $value }}s"
|
|||
|
|
|
|||
|
|
# 高错误率告警
|
|||
|
|
- alert: HighErrorRate
|
|||
|
|
expr: rate(http_requests_total{status=~"5.."}[5m]) / rate(http_requests_total[5m]) > 0.05
|
|||
|
|
for: 2m
|
|||
|
|
labels:
|
|||
|
|
severity: critical
|
|||
|
|
annotations:
|
|||
|
|
summary: "错误率过高"
|
|||
|
|
description: "5分钟内错误率超过5%,当前值: {{ $value | humanizePercentage }}"
|
|||
|
|
|
|||
|
|
# 内存使用率告警
|
|||
|
|
- alert: HighMemoryUsage
|
|||
|
|
expr: (node_memory_MemTotal_bytes - node_memory_MemAvailable_bytes) / node_memory_MemTotal_bytes > 0.8
|
|||
|
|
for: 5m
|
|||
|
|
labels:
|
|||
|
|
severity: warning
|
|||
|
|
annotations:
|
|||
|
|
summary: "内存使用率过高"
|
|||
|
|
description: "内存使用率超过80%,当前值: {{ $value | humanizePercentage }}"
|
|||
|
|
|
|||
|
|
# CPU使用率告警
|
|||
|
|
- alert: HighCPUUsage
|
|||
|
|
expr: 100 - (avg by(instance) (irate(node_cpu_seconds_total{mode="idle"}[5m])) * 100) > 80
|
|||
|
|
for: 5m
|
|||
|
|
labels:
|
|||
|
|
severity: warning
|
|||
|
|
annotations:
|
|||
|
|
summary: "CPU使用率过高"
|
|||
|
|
description: "CPU使用率超过80%,当前值: {{ $value }}%"
|
|||
|
|
|
|||
|
|
# 磁盘空间告警
|
|||
|
|
- alert: DiskSpaceLow
|
|||
|
|
expr: (node_filesystem_avail_bytes / node_filesystem_size_bytes) < 0.1
|
|||
|
|
for: 1m
|
|||
|
|
labels:
|
|||
|
|
severity: critical
|
|||
|
|
annotations:
|
|||
|
|
summary: "磁盘空间不足"
|
|||
|
|
description: "磁盘 {{ $labels.mountpoint }} 可用空间少于10%"
|
|||
|
|
|
|||
|
|
# 数据库连接数告警
|
|||
|
|
- alert: MySQLHighConnections
|
|||
|
|
expr: mysql_global_status_threads_connected / mysql_global_variables_max_connections > 0.8
|
|||
|
|
for: 2m
|
|||
|
|
labels:
|
|||
|
|
severity: warning
|
|||
|
|
annotations:
|
|||
|
|
summary: "MySQL连接数过高"
|
|||
|
|
description: "MySQL连接数超过最大连接数的80%"
|
|||
|
|
|
|||
|
|
# Redis内存使用告警
|
|||
|
|
- alert: RedisHighMemoryUsage
|
|||
|
|
expr: redis_memory_used_bytes / redis_memory_max_bytes > 0.8
|
|||
|
|
for: 2m
|
|||
|
|
labels:
|
|||
|
|
severity: warning
|
|||
|
|
annotations:
|
|||
|
|
summary: "Redis内存使用率过高"
|
|||
|
|
description: "Redis内存使用率超过80%"
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 🔧 运维管理
|
|||
|
|
|
|||
|
|
### 日志管理
|
|||
|
|
|
|||
|
|
#### 日志收集配置
|
|||
|
|
```yaml
|
|||
|
|
# logging/filebeat.yml
|
|||
|
|
filebeat.inputs:
|
|||
|
|
# 应用日志
|
|||
|
|
- type: log
|
|||
|
|
enabled: true
|
|||
|
|
paths:
|
|||
|
|
- /app/logs/*.log
|
|||
|
|
fields:
|
|||
|
|
service: jiebanke-backend
|
|||
|
|
environment: production
|
|||
|
|
multiline.pattern: '^\d{4}-\d{2}-\d{2}'
|
|||
|
|
multiline.negate: true
|
|||
|
|
multiline.match: after
|
|||
|
|
|
|||
|
|
# Nginx访问日志
|
|||
|
|
- type: log
|
|||
|
|
enabled: true
|
|||
|
|
paths:
|
|||
|
|
- /var/log/nginx/access.log
|
|||
|
|
fields:
|
|||
|
|
service: nginx
|
|||
|
|
log_type: access
|
|||
|
|
|
|||
|
|
# Nginx错误日志
|
|||
|
|
- type: log
|
|||
|
|
enabled: true
|
|||
|
|
paths:
|
|||
|
|
- /var/log/nginx/error.log
|
|||
|
|
fields:
|
|||
|
|
service: nginx
|
|||
|
|
log_type: error
|
|||
|
|
|
|||
|
|
output.elasticsearch:
|
|||
|
|
hosts: ["elasticsearch:9200"]
|
|||
|
|
index: "jiebanke-logs-%{+yyyy.MM.dd}"
|
|||
|
|
|
|||
|
|
processors:
|
|||
|
|
- add_host_metadata:
|
|||
|
|
when.not.contains.tags: forwarded
|
|||
|
|
- add_docker_metadata: ~
|
|||
|
|
- add_kubernetes_metadata: ~
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 日志轮转配置
|
|||
|
|
```bash
|
|||
|
|
# /etc/logrotate.d/jiebanke
|
|||
|
|
/app/logs/*.log {
|
|||
|
|
daily
|
|||
|
|
missingok
|
|||
|
|
rotate 30
|
|||
|
|
compress
|
|||
|
|
delaycompress
|
|||
|
|
notifempty
|
|||
|
|
create 0644 nodejs nodejs
|
|||
|
|
postrotate
|
|||
|
|
docker kill -s USR1 jiebanke-backend
|
|||
|
|
endscript
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/var/log/nginx/*.log {
|
|||
|
|
daily
|
|||
|
|
missingok
|
|||
|
|
rotate 52
|
|||
|
|
compress
|
|||
|
|
delaycompress
|
|||
|
|
notifempty
|
|||
|
|
create 0644 nginx nginx
|
|||
|
|
postrotate
|
|||
|
|
docker kill -s USR1 jiebanke-nginx
|
|||
|
|
endscript
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 备份策略
|
|||
|
|
|
|||
|
|
#### 自动备份脚本
|
|||
|
|
```bash
|
|||
|
|
#!/bin/bash
|
|||
|
|
# backup.sh - 自动备份脚本
|
|||
|
|
|
|||
|
|
BACKUP_DIR="/backup"
|
|||
|
|
RETENTION_DAYS=30
|
|||
|
|
LOG_FILE="/var/log/backup.log"
|
|||
|
|
|
|||
|
|
# 日志函数
|
|||
|
|
log() {
|
|||
|
|
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a $LOG_FILE
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# 创建备份
|
|||
|
|
create_backup() {
|
|||
|
|
local backup_name=$(date +%Y%m%d_%H%M%S)
|
|||
|
|
local backup_path="${BACKUP_DIR}/${backup_name}"
|
|||
|
|
|
|||
|
|
mkdir -p $backup_path
|
|||
|
|
|
|||
|
|
log "开始创建备份: $backup_name"
|
|||
|
|
|
|||
|
|
# 备份数据库
|
|||
|
|
log "备份MySQL数据库..."
|
|||
|
|
docker exec jiebanke-mysql mysqldump \
|
|||
|
|
-u root -p${MYSQL_ROOT_PASSWORD} \
|
|||
|
|
--single-transaction \
|
|||
|
|
--routines \
|
|||
|
|
--triggers \
|
|||
|
|
jiebanke > "${backup_path}/database.sql"
|
|||
|
|
|
|||
|
|
# 备份Redis
|
|||
|
|
log "备份Redis数据..."
|
|||
|
|
docker exec jiebanke-redis redis-cli BGSAVE
|
|||
|
|
docker cp jiebanke-redis:/data/dump.rdb "${backup_path}/redis.rdb"
|
|||
|
|
|
|||
|
|
# 备份上传文件
|
|||
|
|
log "备份上传文件..."
|
|||
|
|
if [ -d "./uploads" ]; then
|
|||
|
|
tar -czf "${backup_path}/uploads.tar.gz" ./uploads
|
|||
|
|
fi
|
|||
|
|
|
|||
|
|
# 备份配置文件
|
|||
|
|
log "备份配置文件..."
|
|||
|
|
tar -czf "${backup_path}/config.tar.gz" \
|
|||
|
|
docker-compose.yml \
|
|||
|
|
.env.production \
|
|||
|
|
nginx/ \
|
|||
|
|
monitoring/
|
|||
|
|
|
|||
|
|
# 压缩备份
|
|||
|
|
log "压缩备份文件..."
|
|||
|
|
cd $BACKUP_DIR
|
|||
|
|
tar -czf "${backup_name}.tar.gz" $backup_name
|
|||
|
|
rm -rf $backup_name
|
|||
|
|
|
|||
|
|
log "备份完成: ${backup_name}.tar.gz"
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# 清理旧备份
|
|||
|
|
cleanup_old_backups() {
|
|||
|
|
log "清理${RETENTION_DAYS}天前的备份..."
|
|||
|
|
|
|||
|
|
find $BACKUP_DIR -name "*.tar.gz" -mtime +$RETENTION_DAYS -delete
|
|||
|
|
|
|||
|
|
log "旧备份清理完成"
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# 上传到云存储
|
|||
|
|
upload_to_cloud() {
|
|||
|
|
local backup_file=$1
|
|||
|
|
|
|||
|
|
# 这里可以集成阿里云OSS、AWS S3等
|
|||
|
|
log "上传备份到云存储: $backup_file"
|
|||
|
|
|
|||
|
|
# 示例:上传到阿里云OSS
|
|||
|
|
# ossutil cp $backup_file oss://your-bucket/backups/
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# 主函数
|
|||
|
|
main() {
|
|||
|
|
create_backup
|
|||
|
|
cleanup_old_backups
|
|||
|
|
|
|||
|
|
# 可选:上传最新备份到云存储
|
|||
|
|
# latest_backup=$(ls -t ${BACKUP_DIR}/*.tar.gz | head -1)
|
|||
|
|
# upload_to_cloud $latest_backup
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
main "$@"
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 性能调优
|
|||
|
|
|
|||
|
|
#### MySQL优化配置
|
|||
|
|
```ini
|
|||
|
|
# mysql/conf.d/my.cnf
|
|||
|
|
[mysqld]
|
|||
|
|
# 基本设置
|
|||
|
|
default-storage-engine = InnoDB
|
|||
|
|
character-set-server = utf8mb4
|
|||
|
|
collation-server = utf8mb4_unicode_ci
|
|||
|
|
|
|||
|
|
# 连接设置
|
|||
|
|
max_connections = 200
|
|||
|
|
max_connect_errors = 1000
|
|||
|
|
wait_timeout = 28800
|
|||
|
|
interactive_timeout = 28800
|
|||
|
|
|
|||
|
|
# 缓冲区设置
|
|||
|
|
innodb_buffer_pool_size = 1G
|
|||
|
|
innodb_buffer_pool_instances = 4
|
|||
|
|
innodb_log_file_size = 256M
|
|||
|
|
innodb_log_buffer_size = 16M
|
|||
|
|
|
|||
|
|
# 查询缓存
|
|||
|
|
query_cache_type = 1
|
|||
|
|
query_cache_size = 128M
|
|||
|
|
query_cache_limit = 2M
|
|||
|
|
|
|||
|
|
# 临时表设置
|
|||
|
|
tmp_table_size = 64M
|
|||
|
|
max_heap_table_size = 64M
|
|||
|
|
|
|||
|
|
# 慢查询日志
|
|||
|
|
slow_query_log = 1
|
|||
|
|
slow_query_log_file = /var/log/mysql/slow.log
|
|||
|
|
long_query_time = 2
|
|||
|
|
|
|||
|
|
# 二进制日志
|
|||
|
|
log-bin = mysql-bin
|
|||
|
|
binlog_format = ROW
|
|||
|
|
expire_logs_days = 7
|
|||
|
|
|
|||
|
|
# InnoDB设置
|
|||
|
|
innodb_file_per_table = 1
|
|||
|
|
innodb_flush_log_at_trx_commit = 2
|
|||
|
|
innodb_flush_method = O_DIRECT
|
|||
|
|
innodb_io_capacity = 200
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### Redis优化配置
|
|||
|
|
```conf
|
|||
|
|
# redis/redis.conf
|
|||
|
|
# 内存设置
|
|||
|
|
maxmemory 512mb
|
|||
|
|
maxmemory-policy allkeys-lru
|
|||
|
|
|
|||
|
|
# 持久化设置
|
|||
|
|
save 900 1
|
|||
|
|
save 300 10
|
|||
|
|
save 60 10000
|
|||
|
|
|
|||
|
|
# AOF设置
|
|||
|
|
appendonly yes
|
|||
|
|
appendfsync everysec
|
|||
|
|
no-appendfsync-on-rewrite no
|
|||
|
|
auto-aof-rewrite-percentage 100
|
|||
|
|
auto-aof-rewrite-min-size 64mb
|
|||
|
|
|
|||
|
|
# 网络设置
|
|||
|
|
tcp-keepalive 300
|
|||
|
|
timeout 0
|
|||
|
|
|
|||
|
|
# 安全设置
|
|||
|
|
requirepass your_redis_password
|
|||
|
|
|
|||
|
|
# 日志设置
|
|||
|
|
loglevel notice
|
|||
|
|
logfile /var/log/redis/redis-server.log
|
|||
|
|
|
|||
|
|
# 客户端连接
|
|||
|
|
maxclients 10000
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 🎯 总结
|
|||
|
|
|
|||
|
|
本部署和运维文档提供了解班客项目的完整部署和运维方案,包括:
|
|||
|
|
|
|||
|
|
### 核心特性
|
|||
|
|
1. **容器化部署**:使用Docker和Docker Compose实现标准化部署
|
|||
|
|
2. **自动化运维**:提供自动化部署、备份、回滚脚本
|
|||
|
|
3. **监控告警**:基于Prometheus和Grafana的完整监控体系
|
|||
|
|
4. **日志管理**:集中化日志收集和分析
|
|||
|
|
5. **高可用架构**:负载均衡、数据备份、故障恢复
|
|||
|
|
|
|||
|
|
### 运维流程
|
|||
|
|
1. **部署流程**:代码拉取 → 镜像构建 → 服务部署 → 健康检查
|
|||
|
|
2. **监控体系**:指标收集 → 可视化展示 → 告警通知
|
|||
|
|
3. **备份策略**:定期备份 → 云端存储 → 快速恢复
|
|||
|
|
4. **性能优化**:数据库调优 → 缓存优化 → 系统监控
|
|||
|
|
|
|||
|
|
通过标准化的部署和运维流程,确保解班客项目能够稳定、高效地为用户提供服务。
|