Files
model-platform/README.md
T
2026-08-14 19:30:25 +08:00

207 lines
9.5 KiB
Markdown
Raw 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.
# Model Platform
自托管的 **Jupyter 模型开发平台**:交互式 workspace、DAG 调度、对象存储工件、
按 workspace 隔离的运行时 — 全部经一个 Nginx 网关对外。
> 技术栈:React Router SPA · FastAPI · APScheduler · MySQL · S3 兼容存储
> (或本地文件系统,`STORAGE_BACKEND=local`)· 共享 Jupyter · rclone FUSE 挂载(仅 s3 模式)
> 单一入口(Nginx :80);其他服务只在 Docker 内网互通。
## 它做什么
| 能力 | 位置 |
|---|---|
| workspace 内 notebook 编辑,行级锁 | `backend/jupyter.py` + `scripts.is_locked` |
| Jupyter 鉴权路由(浏览器永远拿不到 runtime token | `nginx/default.conf` + `auth_request` + `backend/jupyter.py` |
| notebook / script / version / run_log 的对象存储(s3 / local 二选一) | `common/storage/` + `backend/scripts.py` |
| DAG 调度:节点、边、cron、手动触发、重试、快照 | `backend/schedules.py` + `backend/schedule_runs.py` + `schedule/`5 个模块) |
| DAG 执行走 MySQL Outbox(无 Redis,无进程内队列) | `schedule/orchestrator.py` + `schedule/worker.py` |
| 每个 workspace 一个 Jupyter 子进程池,配 asyncio 锁 | `runtime/process.py` |
| runtime 内 rclone FUSE 把 workspace 桶挂上来(s3 模式) | `runtime/mount.py` |
| 仅 MySQL 持久化(26 张表,软删除,无外键) | `common/db/models/` |
## 架构一览
```
┌────────────────────┐
│ Browser (SPA) │
└─────────┬──────────┘
│ HTTPS / WS
┌─────────▼──────────┐
│ Nginx (only :80) │ ← templates/default.conf
│ /api/ /jupyter/ /storage/
└────┬───────┬──────┘
│ │
┌──────────────┘ └─────────────┐
▼ ▼
┌──────────────────┐ ┌──────────────────────┐
│ FastAPI Backend │ │ Runtime (Jupyter) │
│ + /internal/v1 │ control │ - rclone FUSE mount │
│ (storage) ├──────────────►│ - subprocess pool │
│ - DAG CRUD │ │ (per workspace) │
│ - script CRUD │ └──────────┬───────────┘
│ - auth_request │ │ FUSE / shared vol
│ - /api/v1/... │ ▼
└────┬──────┬──────┘ ┌──────────────────────┐
│ │ │ Object storage │
│ └──────── HTTP ───────►│ (s3: S3 service / │
▼ │ local: shared vol) │
┌────────────┐ │ 4 buckets per usage │
│ MySQL │◄───────── poll ─────│ │
│ - 26 tbls │ └──────────────────────┘
│ - outbox │
│ - jobstore │
└────┬───────┘
│ outbox poll
┌────┴──────────────────────────┐
│ Schedule Executor │
│ - CronScheduler (APScheduler) │
│ - DispatchOrchestrator │
│ - NodeExecutor (worker) │
│ - SchedulerService (facade) │
└───────────────────────────────┘
```
对象存储通过 `STORAGE_BACKEND`s3 | local)二选一。s3 模式下 4 个 purpose 命名桶
`workspace` / `version` / `run-log` / `trash`)是独立的 S3 bucketlocal 模式下
`LOCAL_STORAGE_BASE_DIR` 的子目录,通过 Docker volume `local-storage` 共享。
详见 `DEVELOP.md` §存储。
详细设计见 `ARCHITECTURE.md`。实现的偏离和近期重构记录在 `HANDOVER.md`
## 目录结构
```text
frontend/ React Router SPA
backend/ FastAPI:公开 API + 内部存储 API
runtime/ Jupyter 子进程管理 + rclone FUSE
schedule/ DAG 调度器(5 模块:context/scheduler/
orchestrator/worker/service
common/ 配置、SQLAlchemy 模型、存储 SDK、
outbox 事件、jobstore
migrations/ Alembic 基线 + 各特性 migration
nginx/ (仅概念 — 见下方「容器」一节)
scripts/ nginx-entrypoint.sh(模板渲染)
docker-compose.yml 4 服务 — web / backend / runtime / schedule
default.conf Nginx 模板(挂载,启动时渲染)
.env.example common.config.Settings 消费的所有环境变量
```
## 容器
| 服务 | 镜像 | 暴露 | 用途 |
|---|---|---|---|
| `web` | `nginx:alpine` | 宿主机 `:8888``:80` | SPA、`/api/` 反向代理、`/jupyter/{ws}/` auth_request 代理、`/storage/` S3 直通(仅 s3 模式) |
| `backend` | `Dockerfile` | 仅内网 | DAG CRUD、script CRUD、schedule 触发、`/api/v1/auth/jupyter``/internal/v1/*` 存储控制面 |
| `runtime` | `Dockerfile` | 仅内网 | 每个 workspace 一个 Jupyter 子进程池、rclone FUSE 挂载 `workspace` 桶(s3 模式) |
| `schedule` | `Dockerfile` | 仅内网 | cron tick + DAG 执行(轮询 MySQL Outbox |
架构**故意只暴露一个宿主机端口**(网关);其他服务都在 Docker 内网。
这一点在 `docker-compose.yml` 里强制执行 — backend / runtime / schedule 都没有 `ports:`
## 快速启动
```bash
cp .env.example .env
# 编辑 .env — 至少改 MYSQL 密码,以及(s3 模式下)S3 凭据。
# 静态检查
uv sync --all-packages
uv run --frozen --package backend python -m compileall -q backend/src common/src
uv run --frozen --package schedule python -m compileall -q schedule/src
uv run --frozen --package runtime python -m compileall -q runtime/src
# 应用 schema
uv run --frozen --package backend alembic upgrade head
# 启动整套服务
docker compose config # 校验
docker compose up -d --build
docker compose ps
```
访问 `http://localhost:8888`
### 日志
```bash
docker compose logs -f backend
docker compose logs -f schedule
docker compose logs -f runtime
```
### 停服(保留 MySQL + S3 / local-storage 数据卷)
```bash
docker compose down
```
### 抹数据
```bash
docker compose down -v
```
## 配置
所有环境变量在 `common/src/common/config.py` 里用 pydantic-settings 的 `Settings`
类一次性声明,外面套一层 `@lru_cache` 单例。新增环境变量:
1.`common/src/common/config.py``Settings` 里加字段(带合理 default,使 dev 启动不需要设)
2.`.env.example` 加一行带注释
3. 调用点用 `settings.<name>`,永远不要用 `os.environ["..."]`
完整环境变量列表和含义见 `DEVELOP.md`
## 存储布局
4 个 purpose 命名桶。从 `StorageObjects.usage_type` 到桶的映射由
**单一入口**`backend/storage_api.py:resolve_bucket`)决定:
| `usage_type` | 桶(环境变量) | 默认名 |
|---|---|---|
| `working_copy``public_script``data_resource``snapshot` | `S3_WORKSPACE_BUCKET` | `workspace` |
| `version_artifact` | `S3_VERSION_BUCKET` | `version` |
| `run_log``run_result` | `S3_RUN_LOG_BUCKET` | `run-log` |
| (软删除目标) | `S3_TRASH_BUCKET` | `trash` |
`STORAGE_BACKEND=s3` 模式下是 4 个独立 S3 桶。`STORAGE_BACKEND=local` 模式下
`LOCAL_STORAGE_BASE_DIR`(默认 `/data`)下的 4 个子目录:
```
/data/
├── workspace/ # S3_WORKSPACE_BUCKET
├── version/ # S3_VERSION_BUCKET
├── run_log/ # S3_RUN_LOG_BUCKET
└── trash/ # S3_TRASH_BUCKET
```
某个 workspace 的 `artifact_bucket` 列(非 NULL 时)覆盖该 workspace 的默认桶,
无视 `usage_type` — 适合把付费客户隔离到专属桶。
对象 key 是两层扁平路径 — `workspace_id` 加服务端签发的 `ulid`
```
<workspace_bucket>/<workspace_id>/<ulid>{.<ext>}
```
文件名、扩展名、MIME、逻辑路径都放在 `StorageObjects``Scripts` 行里,不进
object key — 重新组织存储不需要重写数据库。
Backend 代码从不写容器本地文件系统(`STORAGE_BACKEND=local` 模式除外,那里共享
`local-storage` volume 就是规范存储)。Schedule Executor 在 `tempfile.TemporaryDirectory()`
里暂存节点工件(自动清理)。只有 `runtime` 容器保留宿主 volume — s3 模式下 rclone FUSE
挂载需要;local 模式下是 no-op 透传。
## 文档
- `README.md`(本文)— 快速导读
- `ARCHITECTURE.md` — 设计图 + 简化历史
- `HANDOVER.md` — 实现偏离、近期重构、待办事项
- `DEVELOP.md` — 开发指南(环境变量、代码规约、常用操作)
- `CLAUDE.md` — agent 面向的本仓库规约
## 许可
内部。