Files
student_manage_system/docs/04-operations-deployment.md
T

473 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 学生管理系统 — 运维部署文档
## 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. 发布报告归档
```