Files
infrastructure/docs/platform/llm-server/encoding/GNENC-001-PLAN_Architecture.md
T

16 KiB
Raw Blame History

Finaler Architekturplan

Dokumenten-ID: GNENC-001-PLAN Titel: CPU-basiertes Video-Encoding auf dem KI-Server 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

Der physische Server ghostnet-ki soll neben den KI-Workloads einen getrennten Workload zum Encoding von Videodateien ausführen. Der Encoding-Workload gehört nicht zum LLM-Stack. Seine fachliche Architektur, Bedienung und späteren SOPs werden deshalb in diesem eigenen Dokumentationsbereich geführt.

Ansible, Podman, Betriebssystem und hostweite Ressourcenregeln bleiben 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 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 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; 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 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

flowchart LR
    HOST[ghostnet-ki]
    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 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

AI Production und AI Testing bleiben untereinander exklusiv. Der 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.

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 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).

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:

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

Am 19.10.2026 wurde das LLM-Server-Projekt fachlich in ein Hauptverzeichnis pro Aufgabe aufgeteilt; die Struktur folgt den Bereichen unter docs/platform/llm-server/:

llm-server/
├── host/ansible/                 # gemeinsame Plattform des physischen Servers
│   ├── playbooks/
│   │   ├── host.yml              # host_baseline, podman_runtime (shared), workload_accounts, amd_gpu_access
│   │   ├── preflight.yml
│   │   ├── bootstrap-deploy-account.yml
│   │   ├── verify-deploy-access.yml
│   │   └── verify-rocm-container.yml
│   └── roles/
│       ├── host_baseline/
│       ├── workload_accounts/
│       ├── amd_gpu_access/
│       └── rocm_container_probe/
├── llm/                          # LLM-Stack; entsteht mit dem AI-Vertical-Slice (GNAI-001 Meilenstein 2)
├── 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

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/).

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

Die gemeinsame Rolle workload_accounts hat am 20.09.2026 eingerichtet:

Konto:       gn-encoding (UID/GID 982)
Konfiguration: /etc/ghostnet-encoding
Daten:         /var/lib/ghostnet-encoding
Eingang:       /var/lib/ghostnet-encoding/input
Ausgang:       /var/lib/ghostnet-encoding/output
Arbeitsdaten:  /var/lib/ghostnet-encoding/work
Quadlets:      /etc/containers/systemd/users/982
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 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 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 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

  1. Feinjustierung der Ressourcenlimits nach einem repräsentativen Encoding-Test unter gleichzeitiger Inferenzlast (Abschnitt 4).

Diese Werte werden nicht vorab in Playbooks angenommen.