473 lines
11 KiB
Markdown
473 lines
11 KiB
Markdown
# 学生管理系统 — 运维部署文档
|
||||
|
|
|
|||
|
|
## 1. 环境说明
|
|||
|
|
|
|||
|
|
| 环境 | 用途 | 访问地址 | 数据库 |
|
|||
|
|
|------|------|----------|--------|
|
|||
|
|
| 本地开发 | 功能开发、调试 | http://localhost:8000 | SQLite(文件) |
|
|||
|
|
| 测试环境 | 集成测试、QA 验证 | http://sms-test.internal:8000 | MySQL 5.7+ |
|
|||
|
|
| 生产环境 | 线上服务 | https://sms.example.com | MySQL 8.0 主从集群 |
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 2. 本地开发部署
|
|||
|
|
|
|||
|
|
### 2.1 前置依赖
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
# Python 3.13+
|
|||
|
|
# uv 包管理器
|
|||
|
|
pip install uv
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 2.2 一键启动
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
# 进入项目目录
|
|||
|
|
cd student_manage_system
|
|||
|
|
|
|||
|
|
# 同步依赖(首次运行)
|
|||
|
|
uv sync
|
|||
|
|
|
|||
|
|
# 启动开发服务器(热重载)
|
|||
|
|
uv run uvicorn main:app --reload --host 0.0.0.0 --port 8000
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 2.3 验证
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
# 访问 API 文档
|
|||
|
|
curl http://localhost:8000/docs
|
|||
|
|
|
|||
|
|
# 健康检查
|
|||
|
|
curl http://localhost:8000/api/teachers/
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 2.4 初始化数据
|
|||
|
|
|
|||
|
|
应用启动时 `lifespan` 自动调用 `seed.init_db()` 建表,`seed.seed_data()` 写入测试数据。
|
|||
|
|
如需重新初始化:
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
# 删除本地数据库文件后重启即可
|
|||
|
|
rm -f sms.db
|
|||
|
|
uv run uvicorn main:app --reload
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 3. Docker 部署
|
|||
|
|
|
|||
|
|
### 3.1 Dockerfile
|
|||
|
|
|
|||
|
|
```dockerfile
|
|||
|
|
# 多阶段构建,减小镜像体积
|
|||
|
|
FROM python:3.13-slim AS base
|
|||
|
|
WORKDIR /app
|
|||
|
|
|
|||
|
|
# 安装 uv
|
|||
|
|
RUN pip install --no-cache-dir uv
|
|||
|
|
|
|||
|
|
COPY pyproject.toml uv.lock ./
|
|||
|
|
RUN uv sync --frozen --no-dev
|
|||
|
|
|
|||
|
|
COPY . .
|
|||
|
|
|
|||
|
|
# 使用 gunicorn + uvicorn worker 生产部署
|
|||
|
|
RUN uv pip install "gunicorn[asyncio]>=23.0.0"
|
|||
|
|
|
|||
|
|
EXPOSE 8000
|
|||
|
|
|
|||
|
|
# 生产启动命令:4 个 worker 进程
|
|||
|
|
CMD ["gunicorn", "main:app", \
|
|||
|
|
"--workers", "4", \
|
|||
|
|
"--worker-class", "uvicorn.workers.UvicornWorker", \
|
|||
|
|
"--bind", "0.0.0.0:8000", \
|
|||
|
|
"--timeout", "120"]
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 3.2 docker-compose.yml
|
|||
|
|
|
|||
|
|
```yaml
|
|||
|
|
version: "3.9"
|
|||
|
|
|
|||
|
|
services:
|
|||
|
|
app:
|
|||
|
|
build: .
|
|||
|
|
ports:
|
|||
|
|
- "8000:8000"
|
|||
|
|
environment:
|
|||
|
|
- DATABASE_URL=mysql+pymysql://sms_user:SMS_pass@db:3306/sms
|
|||
|
|
depends_on:
|
|||
|
|
db:
|
|||
|
|
condition: service_healthy
|
|||
|
|
restart: unless-stopped
|
|||
|
|
healthcheck:
|
|||
|
|
test: ["CMD", "curl", "-f", "http://localhost:8000/api/teachers/"]
|
|||
|
|
interval: 30s
|
|||
|
|
timeout: 10s
|
|||
|
|
retries: 3
|
|||
|
|
|
|||
|
|
db:
|
|||
|
|
image: mysql:8.0
|
|||
|
|
environment:
|
|||
|
|
MYSQL_ROOT_PASSWORD: root_pass
|
|||
|
|
MYSQL_DATABASE: sms
|
|||
|
|
MYSQL_USER: sms_user
|
|||
|
|
MYSQL_PASSWORD: SMS_pass
|
|||
|
|
volumes:
|
|||
|
|
- sms_mysql_data:/var/lib/mysql
|
|||
|
|
- ./init.sql:/docker-entrypoint-initdb.d/init.sql # 可选:初始化 SQL
|
|||
|
|
ports:
|
|||
|
|
- "3306:3306"
|
|||
|
|
restart: unless-stopped
|
|||
|
|
healthcheck:
|
|||
|
|
test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "sms_user", "-pSMS_pass"]
|
|||
|
|
interval: 10s
|
|||
|
|
timeout: 5s
|
|||
|
|
retries: 5
|
|||
|
|
|
|||
|
|
volumes:
|
|||
|
|
sms_mysql_data:
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 3.3 启动命令
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
# 构建并启动
|
|||
|
|
docker-compose up -d --build
|
|||
|
|
|
|||
|
|
# 查看日志
|
|||
|
|
docker-compose logs -f app
|
|||
|
|
|
|||
|
|
# 停止
|
|||
|
|
docker-compose down
|
|||
|
|
|
|||
|
|
# 清理数据卷(慎重)
|
|||
|
|
docker-compose down -v
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 4. Nginx 反向代理配置
|
|||
|
|
|
|||
|
|
```nginx
|
|||
|
|
server {
|
|||
|
|
listen 80;
|
|||
|
|
server_name sms.example.com;
|
|||
|
|
return 301 https://$server_name$request_uri;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
server {
|
|||
|
|
listen 443 ssl http2;
|
|||
|
|
server_name sms.example.com;
|
|||
|
|
|
|||
|
|
# SSL 证书(Let's Encrypt 示例)
|
|||
|
|
ssl_certificate /etc/nginx/ssl/sms.example.com/fullchain.pem;
|
|||
|
|
ssl_certificate_key /etc/nginx/ssl/sms.example.com/privkey.pem;
|
|||
|
|
|
|||
|
|
# 安全头
|
|||
|
|
add_header X-Frame-Options DENY always;
|
|||
|
|
add_header X-Content-Type-Options nosniff always;
|
|||
|
|
add_header Strict-Transport-Security "max-age=31536000" always;
|
|||
|
|
|
|||
|
|
# 静态文件
|
|||
|
|
location /static/ {
|
|||
|
|
proxy_pass http://127.0.0.1:8000;
|
|||
|
|
expires 7d;
|
|||
|
|
add_header Cache-Control "public, immutable";
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# API 请求
|
|||
|
|
location /api/ {
|
|||
|
|
proxy_pass http://127.0.0.1:8000;
|
|||
|
|
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_connect_timeout 30s;
|
|||
|
|
proxy_read_timeout 60s;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# API 文档(仅内网访问)
|
|||
|
|
location /docs {
|
|||
|
|
allow 10.0.0.0/8;
|
|||
|
|
allow 172.16.0.0/12;
|
|||
|
|
allow 192.168.0.0/16;
|
|||
|
|
deny all;
|
|||
|
|
proxy_pass http://127.0.0.1:8000;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
location / {
|
|||
|
|
proxy_pass http://127.0.0.1:8000;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 5. 生产环境部署
|
|||
|
|
|
|||
|
|
### 5.1 部署架构
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
┌─────────────────┐
|
|||
|
|
│ Cloudflare / │
|
|||
|
|
│ CDN + WAF │
|
|||
|
|
└────────┬────────┘
|
|||
|
|
│
|
|||
|
|
┌────────▼────────┐
|
|||
|
|
│ Nginx │ ← 反向代理 + HTTPS
|
|||
|
|
│ (port 443) │
|
|||
|
|
└────────┬────────┘
|
|||
|
|
│
|
|||
|
|
┌──────────────┼──────────────┐
|
|||
|
|
▼ ▼ ▼
|
|||
|
|
┌──────────┐ ┌──────────┐ ┌──────────┐
|
|||
|
|
│ FastAPI │ │ FastAPI │ │ FastAPI │ ← 多实例(水平扩展)
|
|||
|
|
│ :8000 │ │ :8001 │ │ :8002 │
|
|||
|
|
│ (Worker1)│ │ (Worker2)│ │ (Worker3)│
|
|||
|
|
└────┬─────┘ └────┬─────┘ └────┬─────┘
|
|||
|
|
│ │ │
|
|||
|
|
└──────────────┼──────────────┘
|
|||
|
|
▼
|
|||
|
|
┌────────────────┐
|
|||
|
|
│ MySQL 主从 │ ← 一主两从,读写分离
|
|||
|
|
│ 主库:3306 │
|
|||
|
|
│ 从库:3307/3308│
|
|||
|
|
└────────────────┘
|
|||
|
|
│
|
|||
|
|
┌────────────────┐
|
|||
|
|
│ Redis │ ← 缓存层(V2.0 规划)
|
|||
|
|
│ :6379 │
|
|||
|
|
└────────────────┘
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 5.2 systemd 服务配置(Linux 直接部署)
|
|||
|
|
|
|||
|
|
```ini
|
|||
|
|
# /etc/systemd/system/sms.service
|
|||
|
|
[Unit]
|
|||
|
|
Description=Student Manage System API
|
|||
|
|
After=network.target mysql.service
|
|||
|
|
|
|||
|
|
[Service]
|
|||
|
|
Type=notify
|
|||
|
|
User=sms
|
|||
|
|
Group=sms
|
|||
|
|
WorkingDirectory=/opt/sms
|
|||
|
|
ExecStart=/opt/sms/.venv/bin/gunicorn main:app \
|
|||
|
|
--workers 4 \
|
|||
|
|
--worker-class uvicorn.workers.UvicornWorker \
|
|||
|
|
--bind 127.0.0.1:8000 \
|
|||
|
|
--timeout 120 \
|
|||
|
|
--access-logfile - \
|
|||
|
|
--error-logfile -
|
|||
|
|
Restart=always
|
|||
|
|
RestartSec=10
|
|||
|
|
|
|||
|
|
# 环境变量
|
|||
|
|
Environment="DATABASE_URL=mysql+pymysql://sms_user:SMS_pass@localhost:3306/sms"
|
|||
|
|
Environment="LOG_LEVEL=info"
|
|||
|
|
|
|||
|
|
# 安全加固
|
|||
|
|
NoNewPrivileges=true
|
|||
|
|
PrivateTmp=true
|
|||
|
|
ProtectSystem=strict
|
|||
|
|
ProtectHome=true
|
|||
|
|
ReadWritePaths=/opt/sms/logs
|
|||
|
|
|
|||
|
|
[Install]
|
|||
|
|
WantedBy=multi-user.target
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
# 启用服务
|
|||
|
|
sudo systemctl daemon-reload
|
|||
|
|
sudo systemctl enable sms
|
|||
|
|
sudo systemctl start sms
|
|||
|
|
sudo systemctl status sms
|
|||
|
|
|
|||
|
|
# 查看日志
|
|||
|
|
sudo journalctl -u sms -f
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 6. 监控与告警
|
|||
|
|
|
|||
|
|
### 6.1 健康检查端点(待 V2.0 实现)
|
|||
|
|
|
|||
|
|
```python
|
|||
|
|
@app.get("/health")
|
|||
|
|
async def health_check(db: Session = Depends(get_db)):
|
|||
|
|
"""综合健康检查"""
|
|||
|
|
try:
|
|||
|
|
db.execute(text("SELECT 1"))
|
|||
|
|
db_status = "ok"
|
|||
|
|
except Exception as e:
|
|||
|
|
db_status = f"error: {e}"
|
|||
|
|
|
|||
|
|
return {
|
|||
|
|
"status": "healthy" if db_status == "ok" else "degraded",
|
|||
|
|
"database": db_status,
|
|||
|
|
"timestamp": datetime.utcnow().isoformat(),
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 6.2 关键监控指标
|
|||
|
|
|
|||
|
|
| 指标 | 工具 | 阈值建议 | 告警方式 |
|
|||
|
|
|------|------|----------|----------|
|
|||
|
|
| API 响应时间 P99 | Prometheus + Grafana | > 500ms | 邮件/钉钉 |
|
|||
|
|
| 错误率(5xx) | Prometheus | > 1% | 即时告警 |
|
|||
|
|
| 数据库连接数 | MySQL SHOW STATUS | > 80% max_connections | 邮件 |
|
|||
|
|
| 磁盘使用率 | node_exporter | > 85% | 即时告警 |
|
|||
|
|
| 服务可用性 | Uptime Robot / Prometheus | < 99.9% | 即时告警 |
|
|||
|
|
|
|||
|
|
### 6.3 Prometheus 配置示例
|
|||
|
|
|
|||
|
|
```yaml
|
|||
|
|
# prometheus.yml
|
|||
|
|
scrape_configs:
|
|||
|
|
- job_name: 'sms-api'
|
|||
|
|
metrics_path: '/metrics' # FastAPI 内置指标(需安装 prometheus-fastapi-instrumentor)
|
|||
|
|
static_configs:
|
|||
|
|
- targets: ['sms-app:8000']
|
|||
|
|
|
|||
|
|
- job_name: 'mysql'
|
|||
|
|
static_configs:
|
|||
|
|
- targets: ['exporter-mysql:9104']
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 7. 日志管理
|
|||
|
|
|
|||
|
|
### 7.1 日志级别
|
|||
|
|
|
|||
|
|
| 环境 | 级别 | 说明 |
|
|||
|
|
|------|------|------|
|
|||
|
|
| 开发 | DEBUG | 详细调试信息 |
|
|||
|
|
| 测试 | INFO | 常规业务日志 |
|
|||
|
|
| 生产 | WARNING | 仅警告及以上 |
|
|||
|
|
|
|||
|
|
### 7.2 日志收集方案
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
应用日志 → /var/log/sms/
|
|||
|
|
├── access.log # HTTP 请求日志(Nginx upstream_log)
|
|||
|
|
├── error.log # 错误日志
|
|||
|
|
└── app.log # 业务日志(loguru)
|
|||
|
|
|
|||
|
|
日志收集:
|
|||
|
|
方案A(轻量):logrotate 轮转 + 手动归档
|
|||
|
|
方案B(推荐):Filebeat → Logstash → Elasticsearch(ELK)
|
|||
|
|
方案C(云原生):Fluent Bit → Loki
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 7.3 logrotate 配置
|
|||
|
|
|
|||
|
|
```conf
|
|||
|
|
# /etc/logrotate.d/sms
|
|||
|
|
/opt/sms/logs/*.log {
|
|||
|
|
weekly
|
|||
|
|
rotate 12
|
|||
|
|
compress
|
|||
|
|
missingok
|
|||
|
|
notifempty
|
|||
|
|
copytruncate
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 8. 备份与恢复
|
|||
|
|
|
|||
|
|
### 8.1 数据库备份策略
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
#!/bin/bash
|
|||
|
|
# /opt/sms/scripts/backup.sh
|
|||
|
|
BACKUP_DIR="/opt/sms/backups"
|
|||
|
|
DATE=$(date +%Y%m%d_%H%M%S)
|
|||
|
|
DB_NAME="sms"
|
|||
|
|
DB_USER="sms_user"
|
|||
|
|
DB_PASS="SMS_pass"
|
|||
|
|
|
|||
|
|
mkdir -p "$BACKUP_DIR"
|
|||
|
|
|
|||
|
|
# 全量备份
|
|||
|
|
mysqldump -u"$DB_USER" -p"$DB_PASS" \
|
|||
|
|
--single-transaction \
|
|||
|
|
--routines \
|
|||
|
|
--triggers \
|
|||
|
|
"$DB_NAME" > "$BACKUP_DIR/sms_$DATE.sql"
|
|||
|
|
|
|||
|
|
# 压缩
|
|||
|
|
gzip "$BACKUP_DIR/sms_$DATE.sql"
|
|||
|
|
|
|||
|
|
# 保留最近 30 天
|
|||
|
|
find "$BACKUP_DIR" -name "sms_*.sql.gz" -mtime +30 -delete
|
|||
|
|
|
|||
|
|
echo "Backup completed: sms_$DATE.sql.gz"
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 8.2 定时备份(crontab)
|
|||
|
|
|
|||
|
|
```cron
|
|||
|
|
# 每天凌晨 2 点备份
|
|||
|
|
0 2 * * * /opt/sms/scripts/backup.sh >> /var/log/sms/backup.log 2>&1
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 8.3 恢复流程
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
# 查看备份文件
|
|||
|
|
ls -lh /opt/sms/backups/
|
|||
|
|
|
|||
|
|
# 恢复(先停止服务写入)
|
|||
|
|
gunzip < /opt/sms/backups/sms_20250101_020000.sql.gz | mysql -u sms_user -p sms
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 9. 故障排查手册
|
|||
|
|
|
|||
|
|
### 9.1 常见问题
|
|||
|
|
|
|||
|
|
| 现象 | 可能原因 | 排查步骤 |
|
|||
|
|
|------|----------|----------|
|
|||
|
|
| 服务无法启动 | 端口被占用 | `lsof -i :8000` 查看并 kill |
|
|||
|
|
| 数据库连接失败 | URL 错误/网络不通 | 检查 DATABASE_URL,`telnet db_host 3306` |
|
|||
|
|
| API 返回 500 | SQL 错误/模型字段缺失 | 查看 `journalctl -u sms -n 100` |
|
|||
|
|
| 响应慢 | 缺少索引/慢查询 | `SHOW PROCESSLIST`,分析 EXPLAIN |
|
|||
|
|
| 磁盘满 | 日志未轮转/备份堆积 | `du -sh /var/log/sms/`,清理旧日志 |
|
|||
|
|
|
|||
|
|
### 9.2 快速重启命令
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
sudo systemctl restart sms
|
|||
|
|
sudo systemctl status sms
|
|||
|
|
sudo journalctl -u sms --since "10 minutes ago" -n 50
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 10. 版本发布流程
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
1. 代码审查(PR merge to main)
|
|||
|
|
2. 自动化测试(CI pipeline)
|
|||
|
|
3. 构建镜像(docker build + push)
|
|||
|
|
4. 部署测试环境(docker-compose up -d)
|
|||
|
|
5. 集成测试通过
|
|||
|
|
6. 生产灰度发布(先上 1 个实例)
|
|||
|
|
7. 观察 30 分钟无异常
|
|||
|
|
8. 全量发布(剩余实例滚动更新)
|
|||
|
|
9. 发布报告归档
|
|||
|
|
```
|