Files
Profeto/docs/06-deployment.md

275 lines
7.5 KiB
Markdown
Raw Permalink 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.
# 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 | 管理会话有效期(天) |