From 61f2158508014ed3f2263d5eabedaff5644445cb Mon Sep 17 00:00:00 2001 From: spektr Date: Mon, 26 Oct 2026 19:20:36 +0100 Subject: [PATCH] LLM-Server: Encoding-Pipeline (GNENC-002) und Monitoring-Dokumentation (GNMON) aufgenommen; Bereichsindizes aktualisiert Generated by Mistral Vibe. Co-Authored-By: Mistral Vibe --- docs/platform/llm-server/README.md | 9 +- .../encoding/GNENC-000-DOC_Index.md | 98 ++++--- .../encoding/GNENC-001-PLAN_Architecture.md | 215 ++++++++++------ .../encoding/GNENC-002-PLAN_Pipeline.md | 239 ++++++++++++++++++ .../monitoring/GNMON-000-DOC_Index.md | 32 +++ .../monitoring/GNMON-001-PLAN_Architecture.md | 107 ++++++++ llm-server/README.md | 1 + llm-server/monitoring/README.md | 12 + 8 files changed, 598 insertions(+), 115 deletions(-) create mode 100644 docs/platform/llm-server/encoding/GNENC-002-PLAN_Pipeline.md create mode 100644 docs/platform/llm-server/monitoring/GNMON-000-DOC_Index.md create mode 100644 docs/platform/llm-server/monitoring/GNMON-001-PLAN_Architecture.md create mode 100644 llm-server/monitoring/README.md diff --git a/docs/platform/llm-server/README.md b/docs/platform/llm-server/README.md index 3b26301..0e9d4b0 100644 --- a/docs/platform/llm-server/README.md +++ b/docs/platform/llm-server/README.md @@ -1,16 +1,17 @@ # LLM-Server `ghostnet-ki` Dieser Gerätebereich dokumentiert den physischen Server `10.0.0.90`. Er -gruppiert die gemeinsame Hostbasis, den eigentlichen LLM-Stack und den davon -fachlich getrennten CPU-Encoding-Workload. Alle drei Bereiche teilen Hardware, -Betriebssystem und Ansible-Control-Projekt. +gruppiert die gemeinsame Hostbasis, den eigentlichen LLM-Stack, den davon +fachlich getrennten CPU-Encoding-Workload und das host-weite Monitoring. Alle +vier Bereiche teilen Hardware, Betriebssystem und Ansible-Control-Projekt. | Bereich | Inhalt | | --- | --- | | [Host](host/GNAI-002-INFRA_BaseSystem.md) | Hardware, Debian-Basis und GPU-Ausgangslage | | [Zugänge](host/GNAI-003-INFRA_Users.md) | Benutzer, Netzwerk, Resolver und SSH | | [AI](ai/README.md) | Architektur, Ansible-Deployment, Modellbewertung und `llama.cpp`-Serving | -| [Encoding](encoding/GNENC-000-DOC_Index.md) | CPU-Encoding-Architektur und offener Implementierungsstand | +| [Encoding](encoding/GNENC-000-DOC_Index.md) | CPU-Encoding-Architektur und Pipeline-Plan | +| [Monitoring](monitoring/GNMON-000-DOC_Index.md) | Systemmetriken, Collector und Dashboard | Übergreifende Rollen stehen im [Zugriffskonzept](../GNAI-005-SEC_Access-Control.md), der Gesamtstand im diff --git a/docs/platform/llm-server/encoding/GNENC-000-DOC_Index.md b/docs/platform/llm-server/encoding/GNENC-000-DOC_Index.md index ecb7139..2ab25f0 100644 --- a/docs/platform/llm-server/encoding/GNENC-000-DOC_Index.md +++ b/docs/platform/llm-server/encoding/GNENC-000-DOC_Index.md @@ -1,51 +1,73 @@ # Video-Encoding – Dokumentationsindex und Projektstand **Dokumenten-ID:** GNENC-000-DOC -**Stand:** 2026-10-05 -**Status:** Nicht finaler Arbeitsstand; Encoding-Workload noch nicht installiert +**Stand:** 2026-10-19 +**Status:** Planungsabschluss; Pipeline geplant, Implementierung noch nicht begonnen ## Dokumente | Dokument | Zweck / Zielgruppe | | --- | --- | -| [GNENC-001-PLAN_Architecture.md](GNENC-001-PLAN_Architecture.md) | vorläufige Architektur, Repository-Grenzen, Ressourcenmodell und offene Entscheidungen; Infrastrukturadministratoren | +| [GNENC-001-PLAN_Architecture.md](GNENC-001-PLAN_Architecture.md) | finale Architektur, Repository-Grenzen, Ressourcenmodell und offene Entscheidungen; Infrastrukturadministratoren | +| [GNENC-002-PLAN_Pipeline.md](GNENC-002-PLAN_Pipeline.md) | operative Pipeline: Komponenten, Konten, Verzeichnislayout, Datenfluss, Validierung, Statusmodell; Infrastrukturadministratoren und Betreiber | + +Dashboard und Systemmetriken sind in den Monitoring-Bereich ausgelagert: +[Monitoring → GNMON-000](../monitoring/GNMON-000-DOC_Index.md). +Die Samba-/NFS-Anbindung auf CT-101 ist in +[GNAI-017](../../proxmox-server/lxc/ct-101-samba/GNAI-017-PLAN_Samba-NFS-Export.md) +beschrieben. ## Bestätigter Stand -- Das gemeinsame Infrastructure-Repository bleibt die einzige Quelle für den - Ansible-Sollzustand des physischen Servers. -- Encoding-Dokumentation verwendet innerhalb des LLM-Server-Gerätebereichs - `docs/platform/llm-server/encoding/` und die Dokumentenkennung `GNENC`. -- Encoding ist nach Betreiberfestlegung CPU-basiert und darf parallel zur - jeweils aktiven KI-Umgebung laufen. -- Encoding läuft automatisch nächtlich von 00:00 bis 06:00 Uhr Europe/Berlin - und darf parallel zur jeweils aktiven KI-Umgebung laufen. -- Das gesperrte Servicekonto `gn-encoding` mit UID/GID 982 und eigenem - rootless Podman-Store ist eingerichtet und verifiziert. -- Das Encoding-Ansible-Projekt unter `llm-server/encoding/ansible/` verwaltet - und verifiziert den Encoding-Grundzustand (Konto, Pfade, CPU-Isolation, - rootless Store); Check-Mode und Idempotenzlauf meldeten jeweils `changed=0`. -- Das Image `linuxserver/handbrake:1.11.2-ls48` ist mit dem amd64-Manifest-Digest - `sha256:fda92bd1b43a07a770b10ad793172b10451ba1690803112dbaf2b48a6148a3c7` - gepinnt und per Pull im rootless Store von `gn-encoding` verifiziert. -- Drei HandBrake-Presets (SVT-AV1 10-bit, MKV, Audio-Passthrough) liegen - versioniert unter `llm-server/encoding/profiles/`; die Profilwahl erfolgt - über gleichnamige Ordner unter `input/`. -- Aufbewahrung: Input-Dateien werden nach erfolgreichem Encoding und - Validierung gelöscht; Output bleibt bis zur Abholung; `work/` wird pro Lauf - geleert. -- Statusanzeige: Dashboard-Endpunkte für Arbeitsfortschritt und - System-Ressourcen des Encoding-Workloads. -- Es sind noch kein Container, keine Quadlet-Unit und kein Timer eingerichtet. +- Planungsabschluss am 19.10.2026: Die Pipeline besteht aus VPN-Sidecar + (ProtonVPN), jDownloader2 (Download), Encoding-Worker (HandBrakeCLI + + ffmpeg); Handover ausschließlich über das Filesystem, In-Progress-Schutz + über Work-Ordner und atomares `mv`. +- Speicherort: CT-101 (Samba/NAS, 10.0.0.9) — NFS-Export + `/media/datenhdd3/Encoding`, gemountet auf `/var/lib/ghostnet-encoding` + (ghostnet-ki hat nur eine 2-TB-NVMe; die Medienbibliothek liegt auf CT-101). +- Verzeichnislayout: `input//*.dlc`, `jd/`, `queue///`, + `work/`, `output///` + `output/_failed/`, `state/jobs/` + + `state/system.json`. +- Konten: `gn-encoding` (982, eingerichtet und verifiziert), `gn-download` + (983, jDownloader2 + VPN-Sidecar), `gn-dashboard` (984, Dashboard, + Monitoring-Bereich); gemeinsame Gruppe `gn-media` (GID 970, auch auf CT-101). +- VPN: gesamter jDownloader2-Verkehr über ProtonVPN-WireGuard + (gluetun-Sidecar mit Kill Switch); WireGuard-Credentials server-lokal in + `/etc/ghostnet-download/vpn.env`, nicht in git. +- Profile: drei versionierte HandBrake-Presets (SVT-AV1 10-bit, MKV, + Audio-Passthrough) unter `llm-server/encoding/profiles/`; Auswahl über + gleichnamige Ordner, keine Auto-Detection. +- Zeitplan: dauerhaft laufende Dienste; der Worker startet neue Releases nur + 00:00–06:00 Uhr Europe/Berlin, laufende Jobs laufen über das Fenster hinaus + (kein Abbruch am Fensterende). +- Validierung: Exit 0, nicht-leere Zieldatei, Videostream vorhanden, + ffmpeg-Volldecodierung ohne stderr-Fehler, Dauerabweichung ≤ 1 s. +- Fehlerbehandlung: Quarantäne pro Datei nach `output/_failed/`; Status + `failed` bis zur manuellen Prüfung. +- Abholung: fertige Outputs per SMB über CT-101 (Share `[Encode]`, RW für + `ghost`; Entnahme per `mv` räumt auf; optionaler Clean-Job nach 30 Tagen); + `.dlc`-Drop über Share `[Encode-In]`; kein smbd auf `ghostnet-ki`. +- Statusmodell: JSON pro Videodatei in `state/jobs/`, atomar geschrieben; + Systemmetriken in `state/system.json` (Metrics-Collector des + Monitoring-Bereichs). +- Ressourcenlimits: Quadlet/cgroup (Worker MemoryMax 16G, Nice 10, IOWeight 50; + jDownloader2 MemoryMax 2G, CPUWeight 50, IOWeight 20; VPN-Sidecar 256M). +- Images: Worker als Eigenbau auf Basis des gepinnten HandBrake-Digests + (`linuxserver/handbrake:1.11.2-ls48`, `sha256:fda92bd1…48a3c7`) mit python3 + und ffmpeg; `jlesage/jdownloader-2` und `qmcgaw/gluetun` gepinnt. +- Credentials: server-lokal unter `/etc/ghostnet-download/` (`jdownloader/`, + `vpn.env`), nicht in git; Platzhalter im Repository. +- Der Ansible-Grundzustand für `gn-encoding` ist eingerichtet und verifiziert + (Check-Mode und Idempotenzlauf `changed=0`); es existieren noch keine + Quadlets, Container oder Timer für den Encoding-Workload. -## Nächste Festlegungen +## Noch offen -Am 19.10.2026 festgelegt: HandBrake im Image `linuxserver/handbrake:1.11.2-ls48` -(amd64-Manifest-Digest gepinnt), nächtliches Zeitfenster 00:00–06:00 Uhr -Europe/Berlin mit geordnetem Stopp und nächtlichem Retry, gemeinsamer -NFS-Mount mit `input/`- und `output/`-Unterverzeichnissen, drei HandBrake-Presets -mit Auswahl über Input-Ordner, Aufbewahrung (Input-Löschung nach Encoding und -Validierung, Output bis Abholung, `work/` pro Lauf) sowie Dashboard-Endpunkte -für Fortschritt und Ressourcen. Noch offen: NFS-Server-Adresse und -Export-Pfad, Validierungsverfahren, technische Umsetzung der Endpunkte sowie -CPU-/I/O-Grenzen nach einem Lasttest unter Inferenzlast. +1. Feinjustierung der Ressourcenlimits nach einem repräsentativen Encoding-Test + unter gleichzeitiger Inferenzlast. +2. Rootless-TUN-Verifizierung für den VPN-Sidecar auf dem Host. +3. Implementierung: CT-101 (NFS-Export, Shares `[Encode]`/`[Encode-In]`, + Gruppe `gn-media`/GID-Abgleich — GNAI-017), Konten `gn-download`/ + `gn-dashboard`, NFS-Mount, Image-Bauten, Quadlet-Units (inkl. VPN-Sidecar), + Skripte, Monitoring-Bereich (Collector, Dashboard). diff --git a/docs/platform/llm-server/encoding/GNENC-001-PLAN_Architecture.md b/docs/platform/llm-server/encoding/GNENC-001-PLAN_Architecture.md index 37f420f..7afd4b9 100644 --- a/docs/platform/llm-server/encoding/GNENC-001-PLAN_Architecture.md +++ b/docs/platform/llm-server/encoding/GNENC-001-PLAN_Architecture.md @@ -1,9 +1,9 @@ -# Arbeitsentwurf +# Finaler Architekturplan **Dokumenten-ID:** GNENC-001-PLAN **Titel:** CPU-basiertes Video-Encoding auf dem KI-Server -**Stand:** 2026-10-05 -**Status:** Nicht finaler Arbeitsentwurf zur Persistierung der Planungsentscheidung +**Stand:** 2026-10-19 +**Status:** Finaler Plan nach Planungsabschluss am 19.10.2026; die operative Pipeline ist in GNENC-002 spezifiziert, das host-weite Monitoring in GNMON-001 ## 1. Zweck und Abgrenzung @@ -17,23 +17,43 @@ gemeinsame Plattformbestandteile. Sie werden weiterhin ausschließlich im Infrastructure-Repository verwaltet, damit nicht mehrere Repositories unabhängig konkurrierende Sollzustände auf denselben Host anwenden. +Die Pipeline im Detail — Komponenten, Konten, Verzeichnislayout, Datenfluss, +Validierung, Statusmodell — ist in +[GNENC-002-PLAN_Pipeline.md](GNENC-002-PLAN_Pipeline.md) spezifiziert. Das +host-weite Monitoring (Systemmetriken, Dashboard) wird im Monitoring-Bereich +unter `docs/platform/llm-server/monitoring/` geführt (GNMON). Die +Samba-Anbindung der Pipeline auf CT-101 ist in +[GNAI-017](../proxmox-server/lxc/ct-101-samba/GNAI-017-PLAN_Samba-NFS-Export.md) +beschrieben. + ## 2. Bestätigte Entscheidungen | Thema | Entscheidung | Nachweisgrenze | | --- | --- | --- | | Repository | Host- und Deployment-Ansible bleibt im bestehenden Infrastructure-Repository | angenommen | -| Dokumentation | Encoding liegt unter `docs/platform/llm-server/encoding/` und verwendet die Kennung `GNENC` | umgesetzt | -| Ausführungsidentität | eigenes gesperrtes Konto `gn-encoding`, UID/GID 982 | eingerichtet und mit rootless Podman geprüft | -| Containertrennung | eigener rootless Podman-Store und eigener rootverwalteter Quadlet-Pfad | eingerichtet; Grundzustand per Ansible verwaltet und verifiziert; noch keine Workload definiert | -| Rechenressource | Encoding ist nach Betreiberfestlegung rein CPU-basiert und erhält keinen GPU-Zugriff | Planungsentscheidung; reale Last noch nicht gemessen | +| Dokumentation | Encoding liegt unter `docs/platform/llm-server/encoding/` und verwendet die Kennung `GNENC`; Monitoring unter `docs/platform/llm-server/monitoring/` mit der Kennung `GNMON`; CT 101 unter `docs/platform/proxmox-server/lxc/ct-101-samba/` mit GNAI-017 | umgesetzt | +| Ausführungsidentitäten | drei gesperrte Konten: `gn-encoding` (UID/GID 982, Worker), `gn-download` (983, jDownloader2 + VPN-Sidecar), `gn-dashboard` (984, Dashboard, Monitoring-Bereich); gemeinsame Gruppe `gn-media` (GID 970) für die Übergabe-Verzeichnisse | gn-encoding eingerichtet und geprüft; weitere geplant | +| Containertrennung | je Konto ein eigener rootless Podman-Store und ein eigener rootverwalteter Quadlet-Pfad | gn-encoding eingerichtet und verifiziert; weitere geplant | +| Rechenressource | Encoding ist CPU-basiert ohne GPU-Zugriff; der host-seitige Metrics-Collector (Monitoring-Bereich) liest GPU-Metriken ausschließlich lesend für das Dashboard | Planungsentscheidung | | Parallelität | darf parallel zu AI Production oder AI Testing laufen | Planungsentscheidung | -| Zeitsteuerung | nächtlich 00:00–06:00 Uhr Europe/Berlin; am Fensterende geordneter Stopp, unfertige Jobs starten nächtlich erneut | Betreiberentscheidung 19.10.2026 | -| Anwendung | HandBrake im Image `linuxserver/handbrake:1.11.2-ls48` mit gepinntem amd64-Manifest-Digest `sha256:fda92bd1b43a07a770b10ad793172b10451ba1690803112dbaf2b48a6148a3c7` | Betreiberentscheidung 19.10.2026; Digest aufgelöst und per Pull im gn-encoding-Store bestätigt | -| Ein-/Ausgabe | ein NFS-Export, gemountet auf `/var/lib/ghostnet-encoding`; `input/` und `output/` als Unterverzeichnisse | Betreiberentscheidung 19.10.2026; Server-Adresse und Export-Pfad offen | -| Profile | drei versionierte HandBrake-Presets (SVT-AV1 10-bit, MKV, Audio-Passthrough) unter `llm-server/encoding/profiles/`; Auswahl über gleichnamige Ordner unter `input/` | Betreiberentscheidung 19.10.2026; Presets versioniert | -| Warteschlange | dateibasiert über `input//`; genau ein gleichzeitiger Job; unfertige oder fehlerhafte Jobs werden nächtlich wiederholt; Teilresultate in `work/` | Betreiberentscheidung 19.10.2026 | -| Aufbewahrung | Input-Datei wird nach erfolgreichem Encoding und Validierung gelöscht; Output bleibt bis zur Abholung; `work/` wird pro Lauf geleert | Betreiberentscheidung 19.10.2026 | -| Statusanzeige | Dashboard-Endpunkte für Arbeitsfortschritt und System-Ressourcen des Encoding-Workloads | Betreiberentscheidung 19.10.2026; technische Umsetzung offen | +| Zeitsteuerung | dauerhaft laufende Dienste; der Encoding-Worker startet neue Releases nur nächtlich 00:00–06:00 Uhr Europe/Berlin, laufende Jobs laufen über das Fenster hinaus (kein Abbruch am Fensterende) | Betreiberentscheidung 19.10.2026; geändert gegenüber dem Erststand | +| Download-Stufe | jDownloader2-Container (`jlesage/jdownloader-2`, gepinnter Digest) liest `.dlc` aus `input//`; Event-Scripter verschiebt den Release-Ordner atomar in die Queue | Betreiberentscheidung 19.10.2026 | +| VPN | der gesamte jDownloader2-Verkehr läuft über einen ProtonVPN-WireGuard-Tunnel: gluetun-Sidecar (`qmcgaw/gluetun`, gepinnt) unter `gn-download`, JD2 teilt dessen Netzwerk-Namespace, Kill Switch aktiv (kein Traffic ohne Tunnel); WireGuard-Credentials server-lokal in `/etc/ghostnet-download/vpn.env`, nicht in git | Betreiberentscheidung 19.10.2026 | +| Anwendung | Worker-Image als Eigenbau auf Basis des gepinnten HandBrake-Images `linuxserver/handbrake:1.11.2-ls48` (`sha256:fda92bd1…48a3c7`) mit python3 und ffmpeg; die Digests von Worker- und VPN-Image werden nach dem Bau gepinnt | Betreiberentscheidung 19.10.2026 | +| Speicherort | Pipeline-Daten liegen auf CT-101 (Samba/NAS, 10.0.0.9): dort liegen die Medienbibliothek und der Plattenverbund; ghostnet-ki selbst hat nur eine 2-TB-NVMe (OS + LLM) | Betreiberentscheidung 19.10.2026 | +| Ein-/Ausgabe | NFS-Export `/media/datenhdd3/Encoding` von CT-101 (10.0.0.9), gemountet auf `/var/lib/ghostnet-encoding`; Verzeichnislayout `input/`, `jd/`, `queue/`, `work/`, `output/`, `state/` | Betreiberentscheidung 19.10.2026; Server, IP und Export-Pfad festgelegt | +| Abholung | fertige Outputs per SMB über CT-101: Share `[Encode]` (`output/`, RW für `ghost`, lesend für `daidalaw`/`debugofen`) — Entnahme per `mv` in die Bibliotheks-Shares räumt `output/` auf; optionaler Clean-Job löscht Dateien nach 30 Tagen aus `output/`; kein smbd auf `ghostnet-ki` | Betreiberentscheidung 19.10.2026 | +| Samba-Integration (CT-101) | CT-101 wird ins Infrastructure-Repository aufgenommen (GNAI-017): NFS-Export nur für 10.0.0.90, Shares `[Encode]`/`[Encode-In]`, Gruppe `gn-media` (GID 970) auf CT-101 mit `ghost` als Mitglied (UID/GID-Abgleich); `.dlc`-Drop über `[Encode-In]` | Betreiberentscheidung 19.10.2026; GNAI-017 | +| Profile | drei versionierte HandBrake-Presets (SVT-AV1 10-bit, MKV, Audio-Passthrough) unter `llm-server/encoding/profiles/`; Auswahl über gleichnamige Ordner unter `input/` bzw. `queue/` | Betreiberentscheidung 19.10.2026; Presets versioniert | +| Warteschlange | dateibasiert: `.dlc`-Drop in `input//`, jDownloader2 befüllt `queue///`, der Worker arbeitet die Queue mit genau einem gleichzeitigen Encode ab | Betreiberentscheidung 19.10.2026 | +| Filter | Nicht-Video-Dateien und Dateien < 100 MB werden ignoriert; alle übrigen Videodateien eines Releases werden encodet (keine Hauptdatei-Auswahl) | Betreiberentscheidung 19.10.2026 | +| Validierung | HandBrakeCLI-Exit 0, nicht-leere Zieldatei, vorhandener Videostream, fehlerfreie ffmpeg-Volldecodierung (leere stderr) und ffprobe-Dauerabweichung ≤ 1 s | Betreiberentscheidung 19.10.2026 | +| Fehlerbehandlung | fehlerhafte Zieldatei löschen, Quelldatei nach `output/_failed//` verschieben (Quarantäne pro Datei); erfolgreiche Dateien desselben Releases bleiben verschont | Betreiberentscheidung 19.10.2026; geändert gegenüber nächtlichem Retry | +| Aufbewahrung | Quelldatei wird nach erfolgreichem Encoding und Validierung gelöscht; Output bleibt bis zur Abholung; `work/` wird pro Lauf geleert; `done`-Status wird nach 30 Tagen gelöscht | Betreiberentscheidung 19.10.2026 | +| Statusmodell | eine JSON-Datei pro Videodatei unter `state/jobs/`, atomar geschrieben (Encoding); Systemmetriken in `state/system.json` werden vom Metrics-Collector des Monitoring-Bereichs bereitgestellt | Betreiberentscheidung 19.10.2026 | +| Statusanzeige | Dashboard im Monitoring-Bereich (GNMON-001): Container unter `gn-dashboard` (Python/Flask), nur lesende Mounts; erreichbar über NPM (CT-103) mit Keycloak-Absicherung | Betreiberentscheidung 19.10.2026 | +| Ressourcenlimits | Quadlet-/cgroup-Limits: Worker MemoryMax 16G, Nice 10, IOWeight 50; jDownloader2 MemoryMax 2G, CPUWeight 50, IOWeight 20, Nice 10; VPN-Sidecar MemoryMax 256M, CPUWeight 50; keine CPU-Obergrenze fürs Encoding | Betreiberentscheidung 19.10.2026; Feinjustierung nach Lasttest offen | +| Zugangsdaten | jDownloader2-Credentials und VPN-WireGuard-Config liegen server-lokal unter `/etc/ghostnet-download/` (`jdownloader/`, `vpn.env`; root-owned, nicht in git); im Repository nur Platzhalter/Beispiele | Betreiberentscheidung 19.10.2026 | ## 3. Zielbild @@ -43,16 +63,24 @@ flowchart LR PROD[gnai-production\nrootless Podman] TEST[gnai-testing\nrootless Podman] ENC[gn-encoding\nrootless Podman] + DL[gn-download\nrootless Podman] + DASH[gn-dashboard\nrootless Podman] + NAS[CT-101 Samba/NFS\n10.0.0.9] GPU[beide GPUs] CPU[CPU / I-O Budget] - TIMER[nächtliche systemd-Zeitsteuerung] + TIMER[nächtliche Fensterlogik\nim Worker] HOST --> PROD HOST --> TEST HOST --> ENC + HOST --> DL + HOST --> DASH + HOST -->|"NFS-Mount /var/lib/ghostnet-encoding"| NAS PROD --> GPU TEST --> GPU ENC --> CPU + DL --> CPU + DASH --> CPU TIMER --> ENC ``` @@ -61,24 +89,61 @@ Encoding-Workload ist von dieser KI-Umschaltung unabhängig und darf parallel zur jeweils aktiven KI-Umgebung laufen. Hostwartung kann weiterhin einen Stopp aller Workloads erfordern. +Der Datenfluss der Pipeline folgt dem Filesystem-Prinzip: Jede Komponente liest +den Output-Ordner des vorangegangenen Schritts; Übergaben erfolgen per atomarem +`mv` innerhalb des NFS-Mounts. Der Download-Verkehr von jDownloader2 läuft +ausschließlich über den VPN-Tunnel. + +```mermaid +flowchart LR + DLC["input//*.dlc"] + VPN["gluetun-Sidecar (ProtonVPN, Kill Switch)"] + JD2["jDownloader2-Container (gn-download)"] + QUEUE["queue///"] + WORKER["Encoding-Worker (gn-encoding)"] + OUT["output///"] + FAILED["output/_failed//"] + STATE["state/jobs/*.json"] + DASH["Dashboard (gn-dashboard, Monitoring)"] + + DLC --> JD2 + JD2 -.->|"teilt Netzwerk-Namespace"| VPN + JD2 -->|"atomares mv"| QUEUE + QUEUE --> WORKER + WORKER -->|"validiert"| OUT + WORKER -->|"Fehler"| FAILED + WORKER --> STATE + STATE --> DASH +``` + ## 4. Ressourcen- und Zeitsteuerung -Die nächtliche Einplanung allein schützt Production nicht vollständig: Ein -Encoding-Lauf kann länger dauern als sein Startfenster, und Inferenz benötigt -trotz GPU-Schwerpunkt CPU für Tokenisierung, Netzwerk und Begleitdienste. Die -spätere Encoding-Unit soll deshalb mindestens verwalten: +Die Encoding-Pipeline läuft als dauerhaft laufende systemd-User-Dienste der +Konten `gn-encoding` (Worker), `gn-download` (jDownloader2 und VPN-Sidecar) mit +`Restart=on-failure`. Der Worker prüft vor jedem neuen Release, ob das +nächtliche Fenster (00:00–06:00 Uhr Europe/Berlin) aktiv ist, und idlet +außerhalb des Fensters. Ein laufendes Release wird am Fensterende nicht +abgebrochen, sondern zu Ende geführt (Betreiberentscheidung 19.10.2026; ersetzt +den früheren geordneten Stopp mit nächtlichem Neustart). -- ein eindeutig definiertes Zeitfenster mit Zeitzone; -- Verhalten bei verpasstem Start, Neustart und überlangem Lauf; -- maximale CPU-Nutzung oder CPU-Gewichtung; -- niedrigere Scheduling-Priorität gegenüber interaktiven Diensten; -- I/O-Gewichtung und freien Speicher; -- geordneten Abbruch beziehungsweise Abschluss am Ende des erlaubten Fensters; -- Begrenzung auf genau einen gleichzeitigen Encoding-Job. +Da der Worker außerhalb des Fensters idlet, entsteht tagsüber praktisch keine +Last durch das Encoding. Die Ressourcenlimits schützen daher vor allem gegen +Fehlerfälle und stellen sicher, dass selbst ein versehentlich tagsüber laufender +Encode die Inferenz nicht verdrängt: -Konkrete Grenzwerte werden erst nach einem repräsentativen Encoding-Test unter -gleichzeitiger Inferenzlast festgelegt. `gn-encoding` erhält keine Mitgliedschaft -in GPU-Gruppen und keine `/dev/kfd`- oder `/dev/dri`-Weitergabe. +| Ressource | Encoding-Worker | jDownloader2 | VPN-Sidecar | Dashboard | +| --- | --- | --- | --- | --- | +| MemoryMax | 16G | 2G | 256M | 512M | +| CPUWeight | 100 | 50 | 50 | 50 | +| Nice | 10 | 10 | — | — | +| IOWeight | 50 | 20 | — | — | + +Es gibt keine CPU-Obergrenze für den Worker: Nachts stehen alle Kerne zur +Verfügung. Konkrete Grenzwerte werden nach einem repräsentativen Encoding-Test +unter gleichzeitiger Inferenzlast feinjustiert (Abschnitt 7). `gn-encoding` +erhält keine Mitgliedschaft in GPU-Gruppen und keine `/dev/kfd`- oder +`/dev/dri`-Weitergabe; `gn-dashboard` ebenso nicht — der host-seitige +Metrics-Collector liest GPU-Metriken ausschließlich lesend für das Dashboard. ## 5. Repository-Grenzen @@ -101,27 +166,37 @@ llm-server/ │ ├── amd_gpu_access/ │ └── rocm_container_probe/ ├── llm/ # LLM-Stack; entsteht mit dem AI-Vertical-Slice (GNAI-001 Meilenstein 2) -└── encoding/ansible/ # CPU-Encoding-Workload - ├── playbooks/ - │ └── video-encoding.yml # video_encoding - └── roles/ - └── video_encoding/ +├── encoding/ +│ ├── ansible/ # Encoding-Workload (Playbooks + Rollen) +│ ├── images/ # Containerfiles (Worker) +│ ├── profiles/ # versionierte HandBrake-Presets +│ └── scripts/ # Worker- und Event-Scripter-Skripte +└── monitoring/ + ├── ansible/ # Monitoring (Collector-Service, Dashboard-Quadlet) + ├── images/ # Containerfile (Dashboard) + └── scripts/ # Metrics-Collector ``` -`host.yml` verwaltet ausschließlich gemeinsame Plattformzustände; die - gemeinsame Rolle `workload_accounts` legt dabei auch das Konto `gn-encoding` -an. `video-encoding.yml` verwaltet den Encoding-spezifischen Zustand des Kontos -`gn-encoding` — seine Pfade, Images, Quadlets, Timer und Health Checks — und -setzt die Host-Baseline voraus. Ein künftiges `ai.yml` im `llm/`-Projekt darf nur -KI-Zustände verändern. Ein späteres `site.yml` kann die unterstützte -Gesamtkonfiguration in definierter Reihenfolge anwenden. +Die Samba-LXC CT-101 wird als eigener Bereich unter `proxmox/lxc/ct-101-samba/` +geführt (Dokumentation GNAI-017 unter `docs/platform/proxmox-server/lxc/ct-101-samba/`). -Eigene Programme (z. B. das Queue-/Warteschlangen-Skript) liegen versioniert -unter `llm-server/encoding/scripts/` (Betreiberentscheidung 19.10.2026). Ein -separates Quellcode-Repository wird erst benötigt, wenn eine Oberfläche oder ein -umfangreicher Dienst eigenständig entwickelt wird. Ein solches Repository erzeugt -ein versioniertes Artefakt oder Image; ausschließlich das Infrastructure- -Repository legt fest, welche Version auf dem Server läuft. +`host.yml` verwaltet ausschließlich gemeinsame Plattformzustände; die +gemeinsame Rolle `workload_accounts` legt dabei auch die Konten `gn-encoding`, +`gn-download` und `gn-dashboard` an. `video-encoding.yml` verwaltet den +Encoding-spezifischen Zustand — Pfade, Images, Quadlets und Health Checks — +und setzt die Host-Baseline voraus. Ein künftiges `ai.yml` im `llm/`-Projekt +darf nur KI-Zustände verändern; ein `monitoring.yml` im `monitoring/`-Projekt +verwaltet den Monitoring-Zustand (Konto `gn-dashboard`, Collector, Dashboard). +Ein späteres `site.yml` kann die unterstützte Gesamtkonfiguration in definierter +Reihenfolge anwenden. + +Eigene Programme liegen versioniert unter `llm-server/encoding/scripts/` bzw. +`llm-server/monitoring/scripts/`, Containerfiles unter `images/` und die +Presets unter `llm-server/encoding/profiles/` (Betreiberentscheidung 19.10.2026). +Ein separates Quellcode-Repository wird erst benötigt, wenn eine Oberfläche oder +ein umfangreicher Dienst eigenständig entwickelt wird. Ein solches Repository +erzeugt ein versioniertes Artefakt oder Image; ausschließlich das +Infrastructure-Repository legt fest, welche Version auf dem Server läuft. ## 6. Aktueller Implementierungsstand @@ -141,7 +216,7 @@ Podman-Store: /var/lib/ghostnet-encoding/.local/share/containers/storage Passwort und interaktive Shell sind gesperrt. Der systemd-User-Manager läuft dank Linger unabhängig von einer Anmeldung. Der Podman-Store wurde als rootless und getrennt von beiden KI-Stores geprüft. Es wurden noch keine Images -oder Container angelegt. +oder Container für den Encoding-Workload angelegt. Am 19.10.2026 wurde das Encoding-Ansible-Projekt unter `llm-server/encoding/ansible/` eingerichtet. `video-encoding.yml` verwaltet und @@ -149,33 +224,27 @@ verifiziert seitdem den Encoding-Grundzustand: Konto-Identität und Passwort- sperre, subordinate ID-Bereiche, Verzeichnisstruktur, Quadlet-Verzeichnis, Linger und aktiver User-Manager, getrennter rootless Podman-Store sowie die CPU-Isolation (keine GPU-Gruppenmitgliedschaft, kein Lesezugriff auf `/dev/kfd` -oder DRM-Render-Nodes). Solange die offenen Entscheidungen in Abschnitt 7 nicht -getroffen sind, stellt die Rolle zusätzlich sicher, dass weder Quadlet-Dateien -noch Container für `gn-encoding` existieren. Check-Mode und zwei -aufeinanderfolgende Läufe meldeten jeweils `ok=30 changed=0 failed=0`. Das -gepinnte HandBrake-Image `linuxserver/handbrake:1.11.2-ls48` -(`sha256:fda92bd1…48a3c7`) wurde anschließend in den rootless Store von -`gn-encoding` gezogen; der Pull bestätigte den Manifest-Digest. +oder DRM-Render-Nodes). Solange die Rolle kein Encoder-Image gesetzt hat, stellt +sie zusätzlich sicher, dass weder Quadlet-Dateien noch Container für +`gn-encoding` existieren. Check-Mode und zwei aufeinanderfolgende Läufe meldeten +jeweils `ok=30 changed=0 failed=0`. Das gepinnte HandBrake-Image +`linuxserver/handbrake:1.11.2-ls48` (`sha256:fda92bd1…48a3c7`) wurde +anschließend in den rootless Store von `gn-encoding` gezogen; der Pull +bestätigte den Manifest-Digest. + +Am 19.10.2026 wurde die Planung abgeschlossen (Abschnitte 2 und 7). Die +Implementierungsphase baut als Nächstes auf: der NFS-Export und die Shares +`[Encode]`/`[Encode-In]` auf CT-101 (GNAI-017), die Konten `gn-download` und +`gn-dashboard` samt Gruppe `gn-media` (GID 970, auch auf CT-101), der NFS-Mount +auf ghostnet-ki, die Image-Bauten mit Digest-Pinning (Worker, VPN-Sidecar, +Dashboard), die Quadlet-Units für jDownloader2, VPN-Sidecar, Worker und +Dashboard, der host-seitige Metrics-Collector sowie die Skripte aus `scripts/`. +Die Rolle `video_encoding` stellt bis dahin sicher, dass weder Quadlet-Dateien +noch Container für `gn-encoding` existieren. ## 7. Offene Entscheidungen -Am 19.10.2026 wurden Encoder und Image (HandBrake, gepinnter Digest), das -nächtliche Zeitfenster (00:00–06:00 Uhr Europe/Berlin), das Abbruchverhalten am -Fensterende (geordneter Stopp, nächtlicher Retry), das Ein-/Ausgabeverfahren -(gemeinsamer NFS-Mount mit `input/`- und `output/`-Unterverzeichnissen), die -drei HandBrake-Presets mit profilbezogener Auswahl über Input-Ordner, die -Aufbewahrung (Input-Löschung nach Encoding und Validierung, Output bis zur -Abholung, `work/` pro Lauf) und die Statusanzeige (Dashboard-Endpunkte für -Arbeitsfortschritt und System-Ressourcen) festgelegt. Offen bleiben: - -1. Welche NFS-Server-Adresse, welcher Export-Pfad und welche Mount-Optionen - gelten für den gemeinsamen Mount auf `/var/lib/ghostnet-encoding`? -2. Wie wird ein fertiger Encode validiert, bevor die Input-Datei gelöscht wird - (Vorschlag: HandBrakeCLI-Exit 0, nicht-leere Zieldatei, fehlerfreies ffprobe)? -3. Wie werden die Dashboard-Endpunkte technisch umgesetzt (Metriken für - Arbeitsfortschritt und System-Ressourcen, Transport, Authentifizierung)? -4. Welche CPU-, Speicher- und I/O-Grenzen schützen die KI-Dienste? Konkrete - Grenzwerte werden erst nach einem repräsentativen Encoding-Test unter - gleichzeitiger Inferenzlast festgelegt (Abschnitt 4). +1. Feinjustierung der Ressourcenlimits nach einem repräsentativen Encoding-Test + unter gleichzeitiger Inferenzlast (Abschnitt 4). Diese Werte werden nicht vorab in Playbooks angenommen. diff --git a/docs/platform/llm-server/encoding/GNENC-002-PLAN_Pipeline.md b/docs/platform/llm-server/encoding/GNENC-002-PLAN_Pipeline.md new file mode 100644 index 0000000..d88f1b0 --- /dev/null +++ b/docs/platform/llm-server/encoding/GNENC-002-PLAN_Pipeline.md @@ -0,0 +1,239 @@ +# Encoding-Pipeline + +**Dokumenten-ID:** GNENC-002-PLAN +**Titel:** Encoding-Pipeline: Komponenten, Konten, Verzeichnislayout und Datenfluss +**Stand:** 2026-10-19 +**Status:** Finaler Plan nach Betreiberentscheidungen vom 19.10.2026 +**Vorgänger:** [GNENC-001-PLAN_Architecture.md](GNENC-001-PLAN_Architecture.md) +**Monitoring:** [GNMON-001-PLAN_Architecture.md](../monitoring/GNMON-001-PLAN_Architecture.md) +**Samba/NFS (CT-101):** [GNAI-017-PLAN_Samba-NFS-Export.md](../../proxmox-server/lxc/ct-101-samba/GNAI-017-PLAN_Samba-NFS-Export.md) + +## 1. Architekturprinzip + +Jede Komponente führt einen Schritt aus und lebt in einem Podman-Container +(rootless, eigener Store je Konto). Der Handover erfolgt ausschließlich über +das Filesystem auf dem NFS-Mount `/var/lib/ghostnet-encoding`: Jede Komponente +liest einen Input-Ordner, der der Output-Ordner des vorangegangenen Schritts +ist. In-Progress-Daten liegen in Work-Ordnern; Übergaben erfolgen per atomarem +`mv` (`rename` innerhalb desselben Mounts). Dadurch erkennt der nächste Schritt +niemals halbfertige Dateien. + +## 2. Komponenten + +| Komponente | Konto | Image | Aufgabe | Betrieb | +| --- | --- | --- | --- | --- | +| VPN-Sidecar | `gn-download` (983) | `qmcgaw/gluetun` (gepinnter Digest) | ProtonVPN-WireGuard-Tunnel mit Kill Switch; jDownloader2 teilt seinen Netzwerk-Namespace, gesamter Download-Verkehr läuft durch den Tunnel | dauerhaft | +| Download | `gn-download` (983) | `jlesage/jdownloader-2` (gepinnter Digest) | liest `.dlc` aus `input//`, lädt über den VPN-Tunnel herunter, entpackt nach `jd/`, Event-Scripter verschiebt den Release-Ordner atomar nach `queue///` und räumt die `.dlc` auf | dauerhaft | +| Encoding-Worker | `gn-encoding` (982) | Worker-Image (Eigenbau, gepinnt) | scannt `queue/`, filtert, encodet alle gültigen Videodateien, validiert, räumt auf, schreibt Status, Housekeeping | dauerhaft; Fenster-Check 00:00–06:00 Europe/Berlin | + +Dashboard und Metrics-Collector werden im Monitoring-Bereich betrieben +(GNMON-001); das Dashboard liest `state/` read-only. + +## 3. Konten und Berechtigungen + +| Konto | UID/GID | Sub-ID-Bereich | Home | Config | Gruppen | +| --- | --- | --- | --- | --- | --- | +| `gn-encoding` | 982 | 493216–558751 | `/var/lib/ghostnet-encoding` | `/etc/ghostnet-encoding` | `gn-media` | +| `gn-download` | 983 | 558752–624287 | `/var/lib/ghostnet-download` | `/etc/ghostnet-download` | `gn-media` | +| `gn-dashboard` | 984 | 624288–689823 | `/var/lib/ghostnet-dashboard` | `/etc/ghostnet-dashboard` | `gn-media` (nur lesend) | + +Gruppe `gn-media`: gemeinsame Gruppe der drei Konten für die Übergabe- +Verzeichnisse und den lesenden Zugriff auf `state/`; GID 970, auf `ghostnet-ki` +und CT-101 identisch angelegt (GNAI-017). Alle Konten sind gesperrt +(Passwort `!`, Shell `/usr/sbin/nologin`, Linger aktiv). + +## 4. Verzeichnislayout + +```text +/var/lib/ghostnet-encoding/ (NFS-Mount: 10.0.0.9:/media/datenhdd3/Encoding, CT-101) +├── input// gn-media 2770 Drop-Ziel, nur .dlc +├── jd/ gn-download JD2-Arbeitsverzeichnis +│ ├── / Download + Entpacken +│ ├── dlc_done/ erfolgreich abgearbeitete .dlc +│ └── failed/ .dlc mit Download-Fehler +├── queue/// gn-media 2770 Roh-Queue, atomar befüllt +├── work// gn-encoding 0750 Encode-Ziele in Arbeit +├── output/ +│ ├── // gn-encoding Erfolge bis Abholung +│ └── _failed// gn-encoding Quarantäne +└── state/ + ├── jobs/.json gn-encoding Status pro Videodatei + ├── tmp/ gn-encoding atomare Schreib-Zwischenlage + └── system.json Collector Systemmetriken (Monitoring) +``` + +`state/` ist `gn-encoding:gn-media` mit Mode `2750`, damit `gn-dashboard` lesen +kann; `state/jobs/` und `state/tmp/` bleiben `0750` (nur Worker). +`state/system.json` wird vom Metrics-Collector des Monitoring-Bereichs mit +`0644` geschrieben. + +Handover-Regeln: + +- Übergabe immer per atomarem `mv` innerhalb des Mounts; Work-Dateien liegen + nie in Übergabe-Ordnern. +- `input/`, `jd/`, `queue/` mit Mode `2770` (setgid), damit neue Dateien die + Gruppe `gn-media` erben. +- jDownloader2 läuft mit `UMASK=022`, damit der Worker die Dateien lesen kann. +- Der Worker löscht Quelldateien und geleerte Release-Ordner aus `queue/`; + Löschrechte folgen aus dem Schreibrecht der Gruppe am Verzeichnis (kein + Sticky-Bit). +- `work/` wird pro Lauf geleert; `state/tmp/` dient nur atomaren + Schreibzugriffen. + +**Abholung:** Die fertigen Outputs werden über CT-101 per SMB abgeholt: Share +`[Encode]` (Pfad `/media/datenhdd3/Encoding/output`, RW für `ghost`, lesend für +`daidalaw`/`debugofen`). Die Entnahme per `mv` in die Bibliotheks-Shares +(Filme/Serien/Anime) räumt `output/` auf. Ein optionaler Clean-Job +(Worker-Housekeeping, Abschnitt 6) löscht Dateien aus `output/`, die älter als +30 Tage sind. Auf `ghostnet-ki` läuft kein smbd. Der `.dlc`-Drop erfolgt über +Share `[Encode-In]` (Pfad `/media/datenhdd3/Encoding/input`, RW für `ghost`) +— siehe GNAI-017. + +## 5. Ablauf: Download (jDownloader2 über VPN) + +1. `.dlc`-Datei in `input//` ablegen (Profil = Preset-Name) — per SMB + über die Share `Encode-In` oder direkt auf dem Mount. +2. jDownloader2 lädt über den VPN-Tunnel herunter und entpackt nach + `jd//`. +3. Event-Scripter (versioniert in git, ohne Secrets, per Ansible in die + JD2-Config deployed): + - Paket fertig: atomares `mv jd// queue///`; + `.dlc` nach `jd/dlc_done/`. + - Download-Fehler: `.dlc` nach `jd/failed/`. +4. Credentials liegen server-lokal in `/etc/ghostnet-download/jdownloader/` + (root-owned, Gruppe `gn-download`, Mode 0750, als `/config` gemountet). Im + Repository liegt nur ein Platzhalter/Beispiel; Ansible prüft Existenz, nie + Inhalt. +5. Die Web-UI des Images wird nicht extern freigegeben; Zugriff nur über + localhost/SSH-Tunnel. + +**VPN-Tunnel (gluetun-Sidecar):** + +- Image `qmcgaw/gluetun` (Digest gepinnt), Konto `gn-download`; JD2-Container + mit `Network=container:` teilt den Netzwerk-Namespace — JD2 hat keine + eigene IP, der gesamte Verkehr verlässt den Host über ProtonVPN. +- ProtonVPN über WireGuard (`VPN_SERVICE_PROVIDER=protonvpn`); die Config + (Private Key, Server, Land) stammt aus dem ProtonVPN-Account und liegt + server-lokal in `/etc/ghostnet-download/vpn.env` (root-owned, `0640 + root:gn-download`, per `EnvironmentFile=` geladen). Im Repository nur + `vpn.env.example`. +- Kill Switch aktiv: gluetuns Firewall blockiert jeglichen Nicht-VPN-Traffic, + sobald der Tunnel fällt. +- Quadlet: `NET_ADMIN` + `--device /dev/net/tun`. TUN in der rootless User- + Namespace wird bei der Umsetzung auf dem Host verifiziert; Fallback ist ein + host-seitiger WireGuard-Service mit Policy-Routing für den Download-Pfad. +- Hinweis: Hoster limitieren gelegentlich VPN-IPs; bei stockenden Downloads + Serverland in `vpn.env` wechseln. Die Management-VPN (CT-105) bleibt davon + unberührt. + +## 6. Ablauf: Encoding-Worker + +Pro Release (älteste zuerst, genau ein gleichzeitiger Encode), pro Videodatei +sequenziell: + +1. **Scan & Filter:** `queue///` durchsuchen. Video-Endungen + (case-insensitiv): `mkv, mp4, m4v, avi, mov, wmv, flv, webm, mpg, mpeg, + ts, m2ts, vob, 3gp, ogv`. Dateien < 100 MB und Nicht-Video-Dateien werden + ignoriert. Es gibt keine Hauptdatei; alle gültigen Videodateien werden + encodet. +2. **Status `queued` → `encoding`** in `state/jobs/.json` (inkl. + Fortschritt aus dem HandBrakeCLI-Output). +3. **Encode:** `HandBrakeCLI --import-preset .json -Z "" -i + -o work//.mkv`. Presets liegen unter `profiles/`, + werden per Ansible auf den Host deployed und read-only gemountet. +4. **Validierung** (am Ziel in `work/`, vor der Übergabe); Status `verifying`: + - HandBrakeCLI-Exit 0 + - Zieldatei nicht leer + - `ffprobe` findet mindestens einen Videostream + - `ffmpeg -v error -i -f null -`: Exit 0 **und** leere stderr + (ffmpeg dekodiert tolerant; Fehlerzeilen auf stderr gelten als Defekt) + - ffprobe-Dauerabweichung Ziel vs. Quelle ≤ 1 s +5. **Erfolg:** atomares `mv` nach `output///`, Quelldatei + löschen, Status `done`. + **Fehler:** Zieldatei löschen, Quelldatei nach `output/_failed//` + verschieben, Status `failed` mit Fehlermeldung. Übrige Dateien des Releases + werden weiter verarbeitet. +6. Release-Ordner in `queue/` löschen, sobald leer. + +**Zeitfenster:** Vor jedem neuen Release prüft der Worker, ob 00:00–06:00 Uhr +Europe/Berlin aktiv ist; außerhalb idlet er. Ein laufendes Release wird nicht +abgebrochen und darf über 06:00 hinaus laufen. + +**Housekeeping (optional, standardmäßig aktiviert, deaktivierbar):** Im +Idle-Durchlauf löscht der Worker Dateien in `output/`, die älter als 30 Tage +sind (Clean-Job als Sicherheitsnetz; die Abholung per `mv` räumt regulär auf). + +## 7. Statusmodell + +Eine JSON-Datei pro Videodatei unter `state/jobs/.json`, atomar +geschrieben (Schreibzugriff in `state/tmp/`, dann `mv`): + +```json +{ + "job_id": "__", + "release": "", + "profile": "AV1 MKV 1080p30 RF25", + "source": "queue///", + "target": "output///.mkv", + "status": "queued | encoding | verifying | done | failed", + "progress_pct": 0.0, + "source_duration_s": 7231.4, + "target_duration_s": 7231.2, + "source_bytes": 0, + "target_bytes": 0, + "started_at": "…", + "finished_at": "…", + "error": null +} +``` + +Lebenszyklus: `queued` → `encoding` → `verifying` → `done` | `failed`. +Aufbewahrung: `done` nach 30 Tagen gelöscht; `failed` bis zur manuellen +Prüfung. + +`state/system.json` (Systemmetriken) wird vom Metrics-Collector des +Monitoring-Bereichs bereitgestellt (GNMON-001). + +## 8. Ressourcenlimits + +Quadlet-/cgroup-Limits, in der User-Session durchgereicht: + +| Ressource | Encoding-Worker | jDownloader2 | VPN-Sidecar | +| --- | --- | --- | --- | +| MemoryMax | 16G | 2G | 256M | +| CPUWeight | 100 | 50 | 50 | +| Nice | 10 | 10 | — | +| IOWeight | 50 | 20 | — | + +Keine CPU-Obergrenze fürs Encoding. Feinjustierung nach einem repräsentativen +Lasttest unter Inferenzlast (GNENC-001 Abschnitt 4). + +## 9. Monitoring (Auslagerung) + +Dashboard und Metrics-Collector werden im Monitoring-Bereich geplant und +betrieben (GNMON-001, `llm-server/monitoring/`). Die Encoding-Pipeline stellt +`state/jobs/*.json` bereit; der Collector schreibt `state/system.json`; das +Dashboard mountet `state/` und `queue/` read-only und zeigt Ressourcen sowie +Encoding-Übersicht. Das Dashboard hat keinen GPU- und keinen Schreibzugriff. + +## 10. Images + +| Image | Basis | Inhalt | Pinning | +| --- | --- | --- | --- | +| Worker | `linuxserver/handbrake:1.11.2-ls48` @ `sha256:fda92bd1…48a3c7` | + python3, ffmpeg | Digest nach Bau in Ansible-Vars gepinnt; Build rootless als `gn-encoding` | +| jDownloader2 | `jlesage/jdownloader-2` | — | Digest gepinnt; Pull als `gn-download` | +| VPN-Sidecar | `qmcgaw/gluetun` | — | Digest gepinnt; Pull als `gn-download` | + +Containerfiles unter `llm-server/encoding/images/`, Skripte unter +`llm-server/encoding/scripts/`, Presets unter `llm-server/encoding/profiles/`. +Skripte und Presets werden per Ansible auf den Host deployed und read-only in +die Container gemountet (Updates ohne Image-Rebuild, versioniert über git). + +## 11. Offene Punkte + +1. Feinjustierung der Ressourcenlimits nach Lasttest unter Inferenzlast + (GNENC-001 Abschnitt 4). +2. Rootless-TUN-Verifizierung für den VPN-Sidecar auf dem Host (Fallback: + host-seitiger WireGuard mit Policy-Routing). +3. UID/GID-Abgleich: Gruppe `gn-media` (GID 970) muss auf CT-101 identisch + existieren; `ghost` Mitglied für die Abholung per `mv` (GNAI-017). diff --git a/docs/platform/llm-server/monitoring/GNMON-000-DOC_Index.md b/docs/platform/llm-server/monitoring/GNMON-000-DOC_Index.md new file mode 100644 index 0000000..839e0f2 --- /dev/null +++ b/docs/platform/llm-server/monitoring/GNMON-000-DOC_Index.md @@ -0,0 +1,32 @@ +# Monitoring – Dokumentationsindex und Projektstand + +**Dokumenten-ID:** GNMON-000-DOC +**Stand:** 2026-10-19 +**Status:** Planungsabschluss; Implementierung noch nicht begonnen + +## Dokumente + +| Dokument | Zweck / Zielgruppe | +| --- | --- | +| [GNMON-001-PLAN_Architecture.md](GNMON-001-PLAN_Architecture.md) | Host-Monitoring: Metriken, Collector, Dashboard, Anbindung Encoding; Infrastrukturadministratoren | + +## Bestätigter Stand + +- Planungsabschluss 19.10.2026: host-weites Monitoring für `ghostnet-ki`, + fachlich getrennt vom Encoding-Workload (aus GNENC ausgegliedert). +- Komponenten: host-seitiger Metrics-Collector (systemd-Service, schreibt + `state/system.json` alle 10 s) und Dashboard-Container unter `gn-dashboard` + (984). +- Metriken: CPU, RAM, Disk (Root + NFS-Export), GPU-Util, VRAM. +- Dashboard: Python/Flask, read-only Mounts, kein GPU-/Schreibzugriff; + erreichbar über NPM (CT-103) mit Keycloak. +- Anbindung Encoding: Dashboard liest `state/jobs/*.json` read-only + (`state/` group-readable via `gn-media`). +- Noch nicht implementiert: Konto `gn-dashboard`, Collector-Service, + Dashboard-Quadlet, Image-Bau. + +## Noch offen + +1. NPM-Hostname und Keycloak-Client (Umsetzung). +2. Collector-Identität (root vs. Metrics-Account mit `render`-Gruppe). +3. Erweiterung um LLM-Metriken (später). diff --git a/docs/platform/llm-server/monitoring/GNMON-001-PLAN_Architecture.md b/docs/platform/llm-server/monitoring/GNMON-001-PLAN_Architecture.md new file mode 100644 index 0000000..d4ee19a --- /dev/null +++ b/docs/platform/llm-server/monitoring/GNMON-001-PLAN_Architecture.md @@ -0,0 +1,107 @@ +# Monitoring + +**Dokumenten-ID:** GNMON-001-PLAN +**Titel:** Host-Monitoring auf dem KI-Server: Systemmetriken und Workload-Dashboards +**Stand:** 2026-10-19 +**Status:** Finaler Plan nach Betreiberentscheidungen vom 19.10.2026 +**Referenzen:** [GNENC-001](../encoding/GNENC-001-PLAN_Architecture.md), [GNENC-002](../encoding/GNENC-002-PLAN_Pipeline.md), [GNAI-005](../../GNAI-005-SEC_Access-Control.md), [GNAI-006](../../proxmox-server/lxc/ct-103-reverse-proxy/GNAI-006-SEC_Reverse-Proxy.md) + +## 1. Zweck und Abgrenzung + +Host-weites Monitoring für `ghostnet-ki`: Systemmetriken (CPU, RAM, Disk, +GPU-Util, VRAM) und Dashboards für Workloads. Aktuell angebunden ist die +Encoding-Pipeline (GNENC-002); später erweiterbar um LLM-Metriken (GPU-Util +unter Inferenz, llama.cpp-Statistik). Der Bereich ist fachlich getrennt vom +Encoding-Workload (aus GNENC ausgegliedert, Betreiberentscheidung 19.10.2026), eilt aber Hardware und Host-Basis. + +## 2. Architekturprinzip + +Filesystem-Handover wie in der Encoding-Pipeline: Der Metrics-Collector +(host-seitig) schreibt `state/system.json` atomar; Workloads stellen ihre +Status-Dateien bereit (Encoding: `state/jobs/*.json`). Das Dashboard liest +ausschließlich Dateien (read-only Mounts) und hat weder GPU- noch +Schreibzugriff. + +## 3. Komponenten + +| Komponente | Konto | Image | Aufgabe | Betrieb | +| --- | --- | --- | --- | --- | +| Metrics-Collector | host-seitig (systemd-Service; root oder dedizierter Metrics-Account mit `render`-Gruppe) | — | sampelt alle 10 s CPU, RAM, Disk (Root + NFS-Export), GPU-Util und VRAM (rocm-smi/sysfs, lesend); schreibt `state/system.json` atomar | dauerhaft | +| Dashboard | `gn-dashboard` (984) | Dashboard-Image (Eigenbau, gepinnt; Python + Flask) | liest `system.json` + `state/jobs/*.json` (Encoding, read-only Mount); zeigt Ressourcen und Workload-Übersicht | dauerhaft | + +## 4. Konto `gn-dashboard` + +UID/GID 984, subid/subgid ab 624288 (65536 Stück), Home +`/var/lib/ghostnet-dashboard`, Config `/etc/ghostnet-dashboard`, Quadlets +`/etc/containers/systemd/users/984`, eigener rootless Podman-Store, gesperrt +(Passwort `!`, Shell `/usr/sbin/nologin`, Linger aktiv). Mitglied der Gruppe +`gn-media` (nur lesender Zugriff auf `state/` und `queue/` der +Encoding-Pipeline). Keine GPU-Gruppenmitgliedschaft, keine `/dev/kfd`-/ +`/dev/dri`-Weitergabe. + +## 5. Metrics-Collector + +- Sampling alle 10 s; atomares Schreiben (`state/tmp/` + `mv`) nach + `/var/lib/ghostnet-encoding/state/system.json` (Mode `0644`). +- Schema: + +```json +{ + "ts": "…", + "cpu_util_pct": 0.0, + "ram_total_bytes": 0, + "ram_used_bytes": 0, + "disks": [{"mount": "/", "total_bytes": 0, "used_bytes": 0}], + "gpu_util_pct": 0.0, + "vram_used_bytes": 0, + "vram_total_bytes": 0 +} +``` + +- GPU-Metriken werden host-seitig gelesen (rocm-smi oder sysfs); der + Dashboard-Container selbst hat keinen GPU-Zugriff. +- Ressourcenlimits: MemoryMax 512M, CPUWeight 50. + +## 6. Dashboard + +- **Views:** Ressourcen (CPU, RAM, Disk, GPU-Util, VRAM aus `system.json`); + Encoding-Übersicht (aktive Jobs mit Fortschritt, Queue-Tiefe pro Profil, + Historie done/failed der letzten 30 Tage, Fehlerfälle mit Ablageort in + `output/_failed/`); später LLM-Metriken. +- **Technik:** Python/Flask, server-seitig gerendert, ohne JS-Framework. Nur + read-only Mounts (`state/`, `queue/`); kein GPU-Zugriff, kein Schreibzugriff. +- **Erreichbarkeit:** Ingress über NPM (CT-103) mit TLS, zugriffsgeschützt + über Keycloak — analog zu den bestehenden Admin-Pfaden + (GNAI-005/GNAI-006). +- **Image:** schlankes Python-Base-Image + Flask, Eigenbau unter + `llm-server/monitoring/images/`, Digest gepinnt. + +## 7. Anbindung Encoding-Pipeline + +- Der Encoding-Worker schreibt `state/jobs/*.json` (Schema in GNENC-002 + Abschnitt 7); das Dashboard liest read-only. +- `state/` ist `gn-encoding:gn-media` mit Mode `2750`, damit `gn-dashboard` + lesen kann; Schreibzugriffe bleiben beim Worker (`state/jobs/`, `state/tmp/` + `0750`) und beim Collector (`state/system.json`). +- Das Dashboard hat keine Schreibzugriffe in die Pipeline. + +## 8. Repository-Grenzen + +```text +llm-server/monitoring/ +├── ansible/ # Playbook + Rolle (Collector-Service, Dashboard-Quadlet, Konto-Zustand) +├── images/ # Dashboard-Containerfile +└── scripts/ # Metrics-Collector +``` + +Eigene Programme liegen versioniert unter `llm-server/monitoring/scripts/`; +das Dashboard-Image wird gebaut und per Digest gepinnt (analog GNENC-001 +Abschnitt 5). + +## 9. Offene Punkte + +1. NPM-Hostname und Keycloak-Client für das Dashboard (Umsetzung mit + GNAI-005/GNAI-006). +2. Collector-Identität: root oder dedizierter Metrics-Account mit + `render`-Gruppe — finale Festlegung bei der Umsetzung. +3. Erweiterung um LLM-Metriken (später, eigenes Thema im AI-Bereich). diff --git a/llm-server/README.md b/llm-server/README.md index d2214a1..591f0bc 100644 --- a/llm-server/README.md +++ b/llm-server/README.md @@ -8,6 +8,7 @@ This host root contains the executable infrastructure for the physical server at | [`host/`](host/README.md) | shared server baseline: Ansible control project, host roles and inventories | | [`llm/`](llm/README.md) | LLM serving stack (created with the internal AI vertical slice) | | [`encoding/`](encoding/README.md) | CPU-based video encoding workload and its Ansible control project | +| [`monitoring/`](monitoring/README.md) | host-wide monitoring: metrics collector and dashboard | Reusable roles that are also consumed by other host projects live under [`../shared/ansible/`](../shared/ansible/). diff --git a/llm-server/monitoring/README.md b/llm-server/monitoring/README.md new file mode 100644 index 0000000..bc49aa3 --- /dev/null +++ b/llm-server/monitoring/README.md @@ -0,0 +1,12 @@ +# Monitoring workload + +Host-wide monitoring on `ghostnet-ki`: system metrics (CPU, RAM, disk, GPU +utilization, VRAM) collected by a host-side metrics collector and visualised by +a dashboard container. The workload is separated from the encoding and LLM +workloads; the dashboard currently shows the encoding pipeline and is +extensible to LLM metrics. + +Architecture and decisions are documented under +[`../../docs/platform/llm-server/monitoring/`](../../docs/platform/llm-server/monitoring/GNMON-000-DOC_Index.md). +The executable Ansible control project under `ansible/` entsteht mit der +Implementierung.