- 数据源:全部文档统一为 bzzoiro 唯一来源,删除 understat/api-football 作为现行数据源的表述(仅保留历史注记);.env.example/docs/06 移除 失效的 API_FOOTBALL_KEY - 多 Agent:injuries 阵容完整性 → standings 联赛排名 (README/01/04 的专家表、数据流图、no_data 示例、agent_weights、 prompt 清单全部对齐 form/stats/home_away/standings/h2h) - 数据库:6 张表 → 12 张表,补 standings/app_settings/schedules 与 4 张治理表说明(标注 raw_events/data_quality_checks/data_lineage 为预留未启用,ingest_failures 已启用),predictions 补 agent_weights 列 - API 文档(03):新增鉴权模型三档表;context/standings/leagues 公开端点 补全;predictions/eval/backtest 标注需管理员;ingest/bzzoiro 补 task/limit/season 参数契约,删除 understat/injuries 端点小节 - 目录树(README/01/07):与真实 src/、frontend/src 结构一致 (core 7 文件、data 7 文件、移除不存在的 retry.py) - 采集命令(02/05):understat 回填 → task=stats/standings; 补管理员凭据提示;docs/08 文首标注历史/过时
274 lines
7.4 KiB
Markdown
274 lines
7.4 KiB
Markdown
# 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(唯一数据源) |
|
||
| `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 | 管理会话有效期(天) |
|