# GPU Fan — Docker vs host (raport techniczny) **Data:** 2026-07-04 **Stack:** `stacks/gpu-fan/` **Host docelowy:** gmktec-k11, RTX 3090 Ti, Ubuntu headless --- ## 1. Executive summary **GPU Fan musi działać na hoście jako usługa systemd (root), nie w Dockerze.** Aplikacja steruje wentylatorami karty NVIDIA przez zapis do NVML (`nvmlDeviceSetFanSpeed_v2`, `nvmlDeviceSetFanControlPolicy`). Na kartach GeForce wymaga to uprawnień root i bezpośredniego dostępu do sterownika hosta. Repo nie zawiera Dockerfile ani compose dla tego stacku — jedyny wspierany model to `sudo scripts/install.sh` → `/opt/gpu-fan` + `gpu-fan.service`. Docker jest teoretycznie możliwy (privileged container, host network, mount `/dev/nvidia*`), ale kruchy, nieutrzymywany i niezgodny z architekturą ubuntu-bare-metal (gpu-fan jako daemon sprzętowy obok workloadów AI w kontenerach). --- ## 2. Co robi aplikacja | Komponent | Plik | Rola | |-----------|------|------| | Pętla sterowania | `fan_controller.py` | Odczyt temp/mocy, interpolacja krzywej, zapis prędkości wentylatorów | | Web UI + API | `app.py` | FastAPI na porcie **8090**, wątek daemon NVML | | UI statyczne | `static/index.html` | Wykres krzywej, status live, edycja trybu | | Krzywa | `/etc/gpu-fan/curve.json` | Mapowanie temp °C → speed % | ### Tryby pracy | Tryb | Zachowanie | |------|------------| | `curve` | Prędkość z krzywej JSON (interpolacja liniowa, 3–7 punktów) | | `manual` | Stała prędkość 30–100% | | `auto` | Przywraca politykę drivera NVIDIA (`NVML_FAN_POLICY_TEMPERATURE_CONTINOUS_SW`) | ### API (port 8090) | Endpoint | Metoda | Uwagi | |----------|--------|-------| | `/` | GET | Web UI | | `/api/status` | GET | Metryki GPU + tryb | | `/api/curve` | GET/PUT | Odczyt/zapis krzywej | | `/api/mode` | POST | Zmiana trybu | | `/api/reload` | POST | Przeładowanie `curve.json` (jak SIGHUP) | Nagłówek `X-API-Key` wymagany gdy `GPU_FAN_HOST` ≠ localhost (domyślnie LAN bind `0.0.0.0`). ### Shutdown Przy `SIGTERM` / `SIGINT` kontroler wywołuje `_restore_auto_policy()` przed `nvmlShutdown()` — wentylatory nie zostają w trybie manual po zatrzymaniu usługi. --- ## 3. Zależności sprzętowe i software | Zależność | Wymagana | Uwagi | |-----------|----------|-------| | NVIDIA driver ≥ 520 | Tak | Testowane: 595-server-open | | `nvidia-ml-py` (pynvml) | Tak | Jedyny interfejs sterowania w kodzie | | `nvidia-smi` | Nie w kodzie | Tylko weryfikacja w dokumentacji; **nie ustawia** wentylatorów | | Root (euid 0) | Tak | `app.py` kończy się błędem bez root (chyba że `DRY_RUN=true`) | | `nvidia-persistenced` | Zalecane | `gpu-fan.service` After=/Wants= | | IPMI | Nie | Brak referencji w kodzie | | D-Bus | Nie | Brak referencji | | X11 / nvidia-settings | Nie | Headless — celowo unikane | | Python 3 + venv | Tak | FastAPI, uvicorn | ### Ścieżki produkcyjne | Ścieżka | Zawartość | |---------|-----------| | `/opt/gpu-fan/` | Kod aplikacji (rsync z repo przez `install.sh`) | | `/opt/control-plane/.env` | `API_KEY`, `GPU_FAN_API_*`, `CURVE_PATH`, … | | `/etc/gpu-fan/curve.json` | Krzywa temp → speed | | `/etc/systemd/system/gpu-fan.service` | Unit systemd | **Uwaga:** `stacks/control-plane/.env` w repo ≠ `/opt/control-plane/.env` — `setup-control-plane-env.sh` migruje i synchronizuje. --- ## 4. Obecny model wdrożenia ``` repo stacks/gpu-fan/ │ │ sudo scripts/install.sh ▼ /opt/gpu-fan/ ← kod + .venv + .env /etc/gpu-fan/curve.json /etc/systemd/system/gpu-fan.service │ │ systemctl enable --now gpu-fan ▼ Proces root: python app.py ├── wątek: fan_controller.run_loop() (co POLL_INTERVAL s) └── uvicorn: 0.0.0.0:8090 ``` Skrypty pomocnicze: | Skrypt | Cel | |--------|-----| | `scripts/install.sh` | Instalacja produkcyjna | | `scripts/enable-lan.sh` | `GPU_FAN_HOST=0.0.0.0`, API_KEY, restart | | `scripts/start.sh` | Foreground debug (wymaga stop systemd) | | `scripts/status.sh` | Diagnostyka portu/procesu | | `scripts/self-test.sh` | Test krzywej, NVML read, API dry-run | --- ## 5. Analiza Docker — dlaczego nie ### Brak artefaktów w repo - Brak `Dockerfile`, `compose.yaml`, profilu w `server-ui/stacks.yaml` - Inne stacki GPU (ComfyUI, LocalAI, vLLM) używają Docker; gpu-fan jest wyjątkiem celowym ### Blokery techniczne | Bloker | Szczegóły | |--------|-----------| | NVML write na GeForce | `nvmlDeviceSetFanSpeed_v2` wymaga root; kontenery GPU (`NVIDIA_DRIVER_CAPABILITIES=compute,utility`) nie gwarantują zapisu fan policy | | Coupling do host driver | Wersja NVML w kontenerze musi pasować do kernel drivera hosta | | Lifecycle | `docker kill` / crash kontenera może pominąć `_restore_auto_policy()` → wentylatory w manual | | `nvidia-persistenced` | Daemon na hoście; kontener nie zarządza persystencją GPU | | Privileged + host network | Minimalny „Docker” wyglądałby jak host install z dodatkową warstwą — bez korzyści | ### Hipotetyczny kontener (nie implementować) Gdyby ktoś eksperymentował: ```yaml # NIE WDRAŻAĆ — tylko dokumentacja ryzyka privileged: true network_mode: host user: root pid: host # opcjonalnie, nadal ryzykowne volumes: - /etc/gpu-fan:/etc/gpu-fan devices: - /dev/nvidia0 - /dev/nvidiactl - /dev/nvidia-uvm ``` Nawet wtedy sukces nie jest gwarantowany na RTX 3090 Ti; repo nie będzie tego utrzymywać. --- ## 6. Współistnienie z Docker AI stacks ``` ┌─────────────────────────────────────────────────┐ │ Host (gmktec-k11) │ │ │ │ gpu-fan.service (root, :8090) ──NVML──► GPU │ │ │ │ ┌─────────────┐ ┌──────────┐ ┌──────────┐ │ │ │ comfyui │ │ localai │ │ vllm │ │ │ │ :8188 │ │ :8070 │ │ :8000 │ │ │ └─────────────┘ └──────────┘ └──────────┘ │ │ Docker containers (GPU compute) │ └─────────────────────────────────────────────────┘ ``` - ComfyUI/LocalAI obciążają GPU → temperatura rośnie → gpu-fan reaguje krzywą - Zatrzymaj gpu-fan **nie** jest wymagane przed startem kontenerów AI - Polityka GPU w Server UI (jeden duży workload) dotyczy LLM/SD, nie gpu-fan - Firewall (NPMPlus): port 8090 nie powinien być publiczny; LAN + API key Źródło: `manual-tutorial/06-gpu-fan-control.md` — „gpu-fan.service na hoście (NVML, root)”. --- ## 7. Checklist operacyjny (coding-agent) ### Instalacja / upgrade - [ ] `nvidia-smi` działa - [ ] `sudo scripts/install.sh` z katalogu `stacks/gpu-fan` - [ ] `sudo scripts/enable-lan.sh` jeśli dostęp z LAN - [ ] `sudo systemctl enable --now gpu-fan` - [ ] `curl -s http://127.0.0.1:18090/api/status -H "X-API-Key: $(grep ^API_KEY= /opt/control-plane/.env | cut -d= -f2)"` → JSON z `temperature_c` ### Po zmianie kodu ```bash sudo scripts/install.sh && sudo systemctl restart gpu-fan ``` ### Diagnostyka ```bash systemctl status gpu-fan journalctl -u gpu-fan -f scripts/status.sh sudo scripts/status.sh --cleanup # tylko gdy port zajęty przez osierocony proces ``` ### Czego nie robić - Nie uruchamiać `start.sh` i systemd jednocześnie (port 8090) - Nie pakować gpu-fan do Docker bez nowego ADR i testów na sprzęcie - Nie edytować tylko `stacks/control-plane/.env` — produkcja czyta `/opt/control-plane/.env` --- ## 8. Rekomendacja | Decyzja | Uzasadnienie | |---------|--------------| | **Zostaw na hoście (systemd)** | Wspierane, przetestowane, bezpieczny shutdown, zgodne z tutorial 06 | | **Nie dodawaj Docker** | Brak wartości, wysokie ryzyko, duplikacja root access | | **Dokumentacja użytkownika** | `docs/` — kroki instalacji i troubleshooting | | **Ten raport** | `coding-agent/DOCKER-VS-HOST-REPORT.md` — odniesienie dla agentów | --- ## 9. Pliki źródłowe (indeks) | Plik | Kluczowe fragmenty | |------|-------------------| | `app.py:161-164` | Wymóg root | | `fan_controller.py:250-279` | NVML fan policy + speed write | | `fan_controller.py:335-343` | Shutdown → auto policy | | `gpu-fan.service` | User=root, After=nvidia-persistenced | | `scripts/install.sh` | rsync → /opt/gpu-fan | | `requirements.txt` | fastapi, uvicorn, nvidia-ml-py |