# 学生管理系统 — 运维部署文档 ## 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. 发布报告归档 ```