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

11 KiB
Raw Blame History

学生管理系统 — 运维部署文档

1. 环境说明

环境 用途 访问地址 数据库
本地开发 功能开发、调试 http://localhost:8000 SQLite(文件)
测试环境 集成测试、QA 验证 http://sms-test.internal:8000 MySQL 5.7+
生产环境 线上服务 https://sms.example.com MySQL 8.0 主从集群

2. 本地开发部署

2.1 前置依赖

# Python 3.13+
# uv 包管理器
pip install uv

2.2 一键启动

# 进入项目目录
cd student_manage_system

# 同步依赖(首次运行)
uv sync

# 启动开发服务器(热重载)
uv run uvicorn main:app --reload --host 0.0.0.0 --port 8000

2.3 验证

# 访问 API 文档
curl http://localhost:8000/docs

# 健康检查
curl http://localhost:8000/api/teachers/

2.4 初始化数据

应用启动时 lifespan 自动调用 seed.init_db() 建表,seed.seed_data() 写入测试数据。 如需重新初始化:

# 删除本地数据库文件后重启即可
rm -f sms.db
uv run uvicorn main:app --reload

3. Docker 部署

3.1 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

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 启动命令

# 构建并启动
docker-compose up -d --build

# 查看日志
docker-compose logs -f app

# 停止
docker-compose down

# 清理数据卷(慎重)
docker-compose down -v

4. 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 直接部署)

# /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
# 启用服务
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 实现)

@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 配置示例

# 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 配置

# /etc/logrotate.d/sms
/opt/sms/logs/*.log {
    weekly
    rotate 12
    compress
    missingok
    notifempty
    copytruncate
}

8. 备份与恢复

8.1 数据库备份策略

#!/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)

# 每天凌晨 2 点备份
0 2 * * * /opt/sms/scripts/backup.sh >> /var/log/sms/backup.log 2>&1

8.3 恢复流程

# 查看备份文件
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 快速重启命令

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. 发布报告归档