Files
gpu-rent/docs/architecture.md
T
Leonid PershinandCursor b24c1b5d4b Sync remaining docs with CLI surface, Assistent, and Civitai dataset.
Align architecture/cli/decisions with modules and Debug API; cross-link seed overlays and scrape→FTS so user docs match 0.2.0.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-23 08:18:08 +03:00

189 lines
12 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.
# Архитектура
## Обзор
```
┌─ локальная машина (Windows / Linux) ─────────────────────────┐
│ gpu-rent CLI │
│ up / stop / status / doctor / hold / debug │
│ tunnel / open — SSH localhost:17801 → VM :7801 │
│ Debug API (read-only) — http://127.0.0.1:17821 │
│ state <repo>/.gpu-rent/state.json │
│ │
│ браузер / MCP / curl API → http://127.0.0.1:17801 │
│ агент / curl debug → http://127.0.0.1:17821/snapshot │
│ локальный SwarmUI → http://127.0.0.1:7801 (не трогаем)
└───────────────────────────────┬──────────────────────────────┘
│ SSH :22 и OpenStack API
┌─ Selectel, сегмент пула (например ru-7a) ────────────────────┐
│ GPU VM (tag preemptible + gpu-rent) │
│ SwarmUI : 127.0.0.1:7801 │
│ idle-killer: очередь / hold / LLM busy → delete this server │
│ app cred: DELETE/GET только этот server_id (fail closed) │
│ optional: Ollama :11434 (туннель 17811) │
│ │
│ boot volume (network) ОС + NVIDIA + SwarmUI нативно + snapshot │
│ data volume (network) Models, Output, Data, workflows │
│ floating IP только SSH, удаляется на stop │
└──────────────────────────────────────────────────────────────┘
```
Два сетевых диска. После удаления VM boot-диск снова указывают как `--volume` при create.
Разделение **жизни GPU** и **локального туннеля** — следствие [decisions.md](decisions.md): ноут можно закрыть.
## Слои CLI
| Модуль | Ответственность |
| --- | --- |
| `cli` / `term` / `prompts` | Typer + цветной лог + нумерованные меню |
| `config` / `varsfile` | `.env` + `gpu-rent.vars`, пути |
| `state` | JSON сессии: ids, фаза, timestamps |
| `os_client` / `cloud` / `pools` | openstacksdk, ресурсы, скан пулов |
| `inventory` / `ux` / `placement` / `flavor_presets` | Flavors, квоты, AZ; preview / ServerPlan |
| `session` | `cmd_up` / `cmd_stop` / adopt |
| `bootstrap` + `remote/bootstrap.sh` | Идемпотентный first-boot; light без apt |
| `provision` | extensions, autocomplete, civitai seed, LLM, push, idle-killer arm, start SwarmUI; seed `search.jsonl``Assistent/civitai-examples.jsonl` |
| `civitai` / `civitai_dataset` | Civitai HTTP API; локальный scrape Krea2 → train/search jsonl (отдельный CLI) |
| `huggingface` | HF probe / метадата / URL для seed и capture |
| `capture` | Инвентарь VM → merge ссылок в локальные манифесты |
| `doctor` | Preflight без mutating compute |
| `sync_files` / `payload` | SFTP Models / Wildcards / workflows / Output; локальные деревья приложения |
| `notify` / `balance` | Toast/звук при ready; баланс ₽ через X-Token + watchdog |
| `tunnel` | sshtunnel + Nova EXPIRED watchdog |
| `debug_api` / `debug_checks` / `debug_assistent` / `debug_assistent_session` | localhost read-only HTTP sidecar (`:17821`); deep Assistent + multi-turn sessions |
| `vm_logs` | journalctl helpers; алиасы `swarm`/`ollama`/`killer`/`cloud-init` |
| `local_watchdog` | Опциональный локальный тик → stop при unclean exit |
| `llm_runtime` / `setup_wizard` / `perf_tiers` | Opt-in Ollama + `ollama-models.yaml`; GPU tier для Swarm/Ollama |
| `idle_killer` / `hold` | systemd на VM + hold-файл |
| `ready` / `snapshot` | Backend ready + boot snapshot |
| `access_card` | URL / MCP panel после ready |
На `up` leftover-юнит `gpu-rent-llamacpp` (если остался с прошлых версий) стопается; рантайма llama.cpp в продукте больше нет (`LLM_RUNTIME` = `none` \| `ollama`).
Каталог сервисов — из Keystone, не из выдуманного `api.selectel.ru/v3/`.
## Два диска
### Boot volume
Сетевой, тот же сегмент, что VM. Первый раз — GPU-optimized образ **без Docker**. `delete_on_termination=false`. Локальный boot запрещён (preemptible сотрёт ОС).
После bootstrap: NVIDIA из образа, `/opt/swarmui` + systemd, unit idle-killer, application credential в `/root/.gpu-rent/` (mode 600).
После **первого** успешного `waiting_ui` + backend Idle: один snapshot boot-диска `gpu-rent-boot-ok` (если ещё нет). Следующий create VM может идти из snapshot, не из сырого GPU-образа. Старый snapshot не плодить каждый `up`.
### Data volume
Второй BDM при create (не attach после ACTIVE). FS один раз, маркер `/mnt/swarm_data/.gpu-rent-ready`. Первый seed: [extensions.md](extensions.md), [autocomplete.md](autocomplete.md), [models.md](models.md), [local-folders.md](local-folders.md).
| На хосте (data volume) | В дереве SwarmUI (`/opt/swarmui`, bind) |
| --- | --- |
| `/mnt/swarm_data/Models` | `/opt/swarmui/Models` |
| `/mnt/swarm_data/Output` | `/opt/swarmui/Output` |
| `/mnt/swarm_data/Data` | `/opt/swarmui/Data` |
| `/mnt/swarm_data/dlbackend` | `/opt/swarmui/dlbackend` |
| `/mnt/swarm_data/Extensions` | `/opt/swarmui/src/Extensions` |
| `/mnt/swarm_data/DLNodes` | `/opt/swarmui/src/BuiltinExtensions/ComfyUIBackend/DLNodes` |
| `/mnt/swarm_data/CustomWorkflows` | `/opt/swarmui/src/BuiltinExtensions/ComfyUIBackend/CustomWorkflows` |
`mkfs` только если нет маркера **и** `blkid` подтвердил пустое устройство по serial/by-id. Не хардкодить `scsi-0Selectel_Volume_…`.
Remount bind’ов — только после `systemctl stop swarmui` и обычного `umount`. `umount -l` на живом Comfy позже опустошает вкладку Models (веса на data volume остаются).
## Стейт-машина
Фазы в `state.json` (код пишет только эти):
```
idle
│ gpu-rent up
provisioning ← create volumes / server
bootstrapping ← SSH + bootstrap + seed (пока не ready)
ready_cloud ← compute жив, idle-killer вооружён
│ gpu-rent tunnel (или up с туннелем)
ready_tunneled ← localhost:17801
│ stop | idle-killer | local-watchdog | destroy
idle
```
Подшаги seed (extensions / autocomplete / models) идут внутри `bootstrapping`, отдельными фазами в state не пишутся. `ready_cloud` ≠ Nova `ACTIVE` (ACTIVE бывает раньше SSH и UI).
На EXPIRED туннель сам делает unshelve → снова `bootstrapping`/`ready_*`.
## Idle-killer (на VM)
Пока очередь SwarmUI пуста дольше **30 минут**, скрипт удаляет **этот** compute через OpenStack (диски не трогать). Открытый браузер без джобы жизнь **не** продлевает.
Killer молчит:
- clone расширений, Civitai-seed, push локальных папок;
- первые **45 минут** после ACTIVE или unshelve;
- пока backend/ComfyUI ещё не Idle;
- пока существует hold: файл `/mnt/swarm_data/.gpu-rent-hold-until` с unix ts (пишет `gpu-rent hold`);
- пока SwarmUI качает модель в UI (Model Downloader / активный download — точный JSON на spike). Нет сигнала — пользователь жмёт `hold`.
`gpu-rent hold` без аргументов = +`IDLE_MINUTES` от сейчас. `--minutes 90` — hold до now+90 мин (заменяет предыдущий, не складывает). `--until` ISO опционально. `hold --clear` снимает.
Потом счётчик 30 минут пустой очереди.
Почему не `shutdown -h now`: у Selectel останов из гостя не обязан снять GPU с биллинга. Нужен API delete/shelve.
Почему не только локальный CLI: ноут спит — процесса нет — GPU продолжает тарифицироваться.
Учётные данные на VM: application credential (или роль без `compute:create`), только delete/shelve. Файл не попадает в Output/Models. Истечение кредов = killer слеп; тогда спасает `gpu-rent status` с ноутбука.
Дефолты: `IDLE_MINUTES=30`, `IDLE_GRACE_MINUTES=45`.
## Watchdog туннеля (на ноутбуке)
Работает только пока открыт туннель:
1. Refresh IAM-токена (TTL 24 ч, как у preemptible).
2. `EXPIRED` → unshelve → переоткрыть туннель.
3. `ERROR` / нет GPU → выход, не бесконечный recreate.
4. Туннель мёртв при ACTIVE → reconnect.
`Ctrl+C` и `Ctrl+D` вызывают `stop` (диски остаются).
## Teardown (`gpu-rent stop`)
Compute удаляется через OpenStack без SSH. Optional pull Output — только если VM ещё отвечает по SSH:
1. Если `PULL_OUTPUT` и SSH жив — забрать новые файлы в `./Output` (`--no-pull` пропускает).
2. Закрыть туннель этого процесса, если открыт.
3. Удалить compute. Volumes оставить.
4. Дождаться исчезновения сервера.
5. Удалить floating IP (`KEEP_FLOATING_IP=false`).
6. State → `idle`, сохранить volume ids.
`destroy` — то же + диски после `--i-understand-data-loss`.
Reconcile: сервер с тегом `gpu-rent` есть, локального процесса нет — это норма (`ready_cloud`). Сирота = нет тега в state и наоборот; `status` показывает «жив, туннеля нет, idle-killer: …».
## Сеть на VM
- Private net + subnet + router — один раз на пул, переиспользовать.
- SG: ingress TCP/22 с IP оператора. **Не** открывать 7801 наружу.
- SwarmUI: `--host 127.0.0.1 --port 7801` (systemd `swarmui`).
- Туннель: `127.0.0.1:17801``127.0.0.1:7801` на VM.
## Стек
| Слой | Выбор |
| --- | --- |
| Python 3.11+ | openstacksdk, paramiko, sshtunnel, Typer, Rich, dotenv, httpx, pyyaml |
| SSH | paramiko + sshtunnel (Windows без системного `ssh -L`) |
| Конфиг | `<repo>/.env` + `gpu-rent.vars` + runtime в `<repo>/.gpu-rent/` |
| Лицензия | MIT |