LLM-Server: Encoding-Pipeline (GNENC-002) und Monitoring-Dokumentation (GNMON) aufgenommen; Bereichsindizes aktualisiert
Generated by Mistral Vibe. Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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/<Profil>/*.dlc`, `jd/`, `queue/<Profil>/<Release>/`,
|
||||
`work/`, `output/<Profil>/<Release>/` + `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).
|
||||
|
||||
@@ -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/<Profilname>/`; 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/<Profil>/`; 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/<Profilname>/`, jDownloader2 befüllt `queue/<Profilname>/<Release>/`, 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/<Release>/` 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/<Profil>/*.dlc"]
|
||||
VPN["gluetun-Sidecar (ProtonVPN, Kill Switch)"]
|
||||
JD2["jDownloader2-Container (gn-download)"]
|
||||
QUEUE["queue/<Profil>/<Release>/"]
|
||||
WORKER["Encoding-Worker (gn-encoding)"]
|
||||
OUT["output/<Profil>/<Release>/"]
|
||||
FAILED["output/_failed/<Release>/"]
|
||||
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.
|
||||
|
||||
@@ -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/<Profil>/`, lädt über den VPN-Tunnel herunter, entpackt nach `jd/`, Event-Scripter verschiebt den Release-Ordner atomar nach `queue/<Profil>/<Release>/` 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/<Profil>/ gn-media 2770 Drop-Ziel, nur .dlc
|
||||
├── jd/ gn-download JD2-Arbeitsverzeichnis
|
||||
│ ├── <Release>/ Download + Entpacken
|
||||
│ ├── dlc_done/ erfolgreich abgearbeitete .dlc
|
||||
│ └── failed/ .dlc mit Download-Fehler
|
||||
├── queue/<Profil>/<Release>/ gn-media 2770 Roh-Queue, atomar befüllt
|
||||
├── work/<Release>/ gn-encoding 0750 Encode-Ziele in Arbeit
|
||||
├── output/
|
||||
│ ├── <Profil>/<Release>/ gn-encoding Erfolge bis Abholung
|
||||
│ └── _failed/<Release>/ gn-encoding Quarantäne
|
||||
└── state/
|
||||
├── jobs/<job_id>.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/<Profil>/` 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/<Release>/`.
|
||||
3. Event-Scripter (versioniert in git, ohne Secrets, per Ansible in die
|
||||
JD2-Config deployed):
|
||||
- Paket fertig: atomares `mv jd/<Release>/ queue/<Profil>/<Release>/`;
|
||||
`.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:<vpn>` 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/<Profil>/<Release>/` 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/<job_id>.json` (inkl.
|
||||
Fortschritt aus dem HandBrakeCLI-Output).
|
||||
3. **Encode:** `HandBrakeCLI --import-preset <Profil>.json -Z "<Profil>" -i
|
||||
<quelle> -o work/<Release>/<stem>.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 <ziel> -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/<Profil>/<Release>/`, Quelldatei
|
||||
löschen, Status `done`.
|
||||
**Fehler:** Zieldatei löschen, Quelldatei nach `output/_failed/<Release>/`
|
||||
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/<job_id>.json`, atomar
|
||||
geschrieben (Schreibzugriff in `state/tmp/`, dann `mv`):
|
||||
|
||||
```json
|
||||
{
|
||||
"job_id": "<Release>__<Dateiname>",
|
||||
"release": "<Release>",
|
||||
"profile": "AV1 MKV 1080p30 RF25",
|
||||
"source": "queue/<Profil>/<Release>/<Datei>",
|
||||
"target": "output/<Profil>/<Release>/<Datei>.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).
|
||||
@@ -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).
|
||||
@@ -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).
|
||||
Reference in New Issue
Block a user