# 06 · 部署 ## 前置要求 - Docker & Docker Compose - bzzoiro API Key(必填) - LLM API Key(必填,OpenAI / Deepseek / 兼容接口) ## Docker Compose 部署(推荐) ```bash # 1. 配置环境变量 cp .env.example .env # 编辑 .env: 填 LLM_API_KEY / BZZOIRO_KEY # 2. 启动(自动执行数据库迁移) docker compose up -d --build # 3. 验证 curl http://localhost:8000/health ``` `docker-compose.yml` 包含 3 个服务: | 服务 | 端口 | 说明 | |---|---|---| | `postgres` | 5433 | PostgreSQL 16 | | `api` | 8000 | FastAPI 应用(启动时自动执行 `alembic upgrade head`) | | `frontend` | 3000 | React 前端(多阶段构建,nginx 服务静态文件) | 数据卷 `pgdata` 持久化数据库,重启不丢数据。 > **注意**: `api` 服务启动时会先执行 `alembic upgrade head` 迁移数据库,再启动 uvicorn。 > 容器内数据库连接自动使用 `postgres` 服务名(通过 compose `environment` 覆盖 `.env` 中的 `DB_HOST`)。 ## 本地开发部署 ```bash # 1. 安装依赖 pip install -e ".[dev]" # 2. 启动 PostgreSQL(单独) docker run -d --name profeto-pg \ -e POSTGRES_USER=football -e POSTGRES_PASSWORD=football -e POSTGRES_DB=football \ -p 5432:5432 postgres:16-alpine # 3. 配置 .env cp .env.example .env # 4. 建表 alembic upgrade head # 5. 启动 API uvicorn src.api.app:app --reload # 6. 启动前端(另一个终端) cd frontend && npm install && npm run dev ``` 访问: - API 文档: http://localhost:8000/docs - 前端界面: http://localhost:5173 ## 环境变量 | 变量 | 必需 | 默认值 | 说明 | |---|---|---|---| | `APP_ENV` | ❌ | `development` | `production` / `development` | | `LOG_LEVEL` | ❌ | `INFO` | 日志级别 | | `API_PORT` | ❌ | `8000` | API 服务端口映射 | | `FRONTEND_PORT` | ❌ | `3000` | 前端服务端口映射 | | `POSTGRES_USER` | ✅ | — | PostgreSQL 用户名 | | `POSTGRES_PASSWORD` | ✅ | — | PostgreSQL 密码 | | `POSTGRES_DB` | ❌ | `football` | PostgreSQL 数据库名 | | `POSTGRES_PORT` | ❌ | `5433` | PostgreSQL 端口映射 | | `DATABASE_URL` | ✅ | — | PostgreSQL 连接 URL(Docker 内会被覆盖) | | `LLM_PROVIDER` | ❌ | `openai` | 提供商名(仅标记) | | `LLM_API_KEY` | ✅ | — | API Key | | `LLM_BASE_URL` | ❌ | `https://api.openai.com/v1` | 接口地址(Ollama/Deepseek 用) | | `LLM_MODEL` | ❌ | `gpt-4o` | 默认模型 | | `LLM_TIMEOUT` | ❌ | `60` | 单次调用超时(秒) | | `LLM_SPECIALIST_MODEL` | ❌ | — | 专家模型(回落 `LLM_MODEL`) | | `LLM_AGGREGATOR_MODEL` | ❌ | — | 终裁模型(回落 `LLM_MODEL`) | | `BZZOIRO_KEY` | ✅ | — | bzzoiro 数据源 Key | | `API_FOOTBALL_KEY` | ❌ | — | 伤停数据源 Key | | `CORS_ORIGINS` | ❌ | `http://localhost:5173,...` | 允许的跨域来源 | | `SECRET_KEY` | ❌ | — | 加密主密钥(生产环境必填) | | `ADMIN_PASSWORD` | ❌ | — | 管理后台密码(留空=不启用) | | `ADMIN_API_KEY` | ❌ | — | 机器/脚本调用的 API Key | ## LLM 提供商配置示例 ### OpenAI ```bash LLM_API_KEY=sk-xxxx LLM_BASE_URL=https://api.openai.com/v1 LLM_MODEL=gpt-4o ``` ### Deepseek ```bash LLM_API_KEY=sk-xxxx LLM_BASE_URL=https://api.deepseek.com/v1 LLM_MODEL=deepseek-chat ``` ### Ollama(本地) ```bash LLM_API_KEY=ollama LLM_BASE_URL=http://localhost:11434/v1 LLM_MODEL=llama3.1 ``` ### 分档配置(专家用便宜模型) ```bash LLM_MODEL=gpt-4o LLM_SPECIALIST_MODEL=gpt-4o-mini LLM_AGGREGATOR_MODEL=gpt-4o ``` ## 数据库迁移 Alembic 管理 schema 变更: ```bash # 查看当前版本 alembic current # 升级到最新 alembic upgrade head # 回退一级 alembic downgrade -1 # 生成新迁移(改 models.py 后) alembic revision --autogenerate -m "描述" # 空迁移(手动写 SQL) alembic revision -m "描述" ``` **Docker Compose 自动迁移**: `api` 容器启动时会自动执行 `alembic upgrade head`, 无需手动运行。本地开发时需手动执行迁移。 已有迁移: - `0001_initial`: 初始 5 张表 - `0002_agent_outputs`: predictions 加 `mode` + `agent_outputs` - `0003_injuries`: 增加 injuries 表 - `0004_snapshot_and_constraints`: 增加约束 - `0005_prediction_status_and_stats_provenance`: 增加时间语义 - `0006-0012`: 后续 schema 调整、约束命名对齐、partial unique index 等 ## 备份与恢复 ```bash # 备份 docker exec profeto-postgres pg_dump -U football football > backup.sql # 恢复 cat backup.sql | docker exec -i profeto-postgres psql -U football football ``` ## 监控 ### 健康检查 | 端点 | 含义 | HTTP 状态码 | |---|---|---| | `/health` | 存活检查(liveness) | 始终 200(进程在跑即活) | | `/health/ready` | 就绪检查(readiness) | DB 可达 200,不可达 **503** | ### 探针配置 #### Docker Compose `docker-compose.yml` 已为 `api` 服务配置 readiness: ```yaml healthcheck: test: ["CMD-SHELL", "curl -sf http://localhost:8000/health/ready || exit 1"] interval: 10s timeout: 5s retries: 3 start_period: 10s ``` **要点**:必须指向 `/health/ready` 而非 `/health`——后者始终 200,在数据库故障时仍会接收流量,导致请求全部失败。 #### Kubernetes ```yaml livenessProbe: httpGet: path: /health port: 8000 initialDelaySeconds: 5 periodSeconds: 15 readinessProbe: httpGet: path: /health/ready port: 8000 initialDelaySeconds: 10 periodSeconds: 10 failureThreshold: 3 ``` **两探针必须区分**: - `livenessProbe` 用 `/health`:仅在进程死锁/崩溃时重启,避免误杀。 - `readinessProbe` 用 `/health/ready`:DB 不可用时停止转发流量,恢复后自动切回。 #### 验证 ```bash # 宿主机直接运行(经本地 8000 端口) python3 tests/test_health_ready.py ``` ### 评估 ## 安全与限流 ### 内存限流(按进程) `/api/v1/predict` 与登录防爆破均使用**进程内内存**计数: | 机制 | 位置 | 局限 | |------|------|------| | `/predict` 限流 | `_RateLimiter`(内存) | 每 worker 独立计数,不共享 | | 登录防爆破 | `_fail_times`(内存) | 同上 | **多 worker 部署时**(如 `uvicorn --workers 4`),每进程各自计数,实际限额为 `N × 单进程限制`。 ### 公网部署建议 ``` ┌─────────┐ ┌──────────┐ ┌──────────┐ │ Client │────▶│ Nginx │────▶│ API │ │ │ │ 限流层 │ │ 内存限流 │ └─────────┘ └──────────┘ └──────────┘ ``` **推荐配置**: 1. **Nginx 层限流**(第一道防线): ```nginx limit_req_zone $binary_remote_addr zone=predict:10m rate=10r/m; location /api/v1/predict { limit_req zone=predict burst=20 nodelay; proxy_pass http://api:8000; } ``` 2. **TRUST_PROXY_HEADERS=True** 时必须由可信反代设置 `X-Forwarded-For`: ```nginx proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; ``` - 若为 `False`: 仅用 `request.client.host`,忽略 `X-Forwarded-For`,防伪造 - 若为 `True`: 解析 `X-Forwarded-For` 第一个 IP,反代后方可信 3. **生产环境必须配置管理鉴权**: ```bash APP_ENV=production REQUIRE_ADMIN_AUTH=True ADMIN_PASSWORD=your_secure_password ``` 未配置时 `/admin` 等管理接口返回 503。 ### 参数调优 | 参数 | 默认 | 说明 | |------|------|------| | `_predict_limiter.max_requests` | 10 | 每分钟每 IP 最大请求数 | | `_predict_limiter.window_seconds` | 60 | 滑动窗口时长 | | `ADMIN_SESSION_TTL_HOURS` | 168 | 管理会话有效期(天) |