Files
infrastructure/docs/platform/llm-server/encoding/GNENC-001-PLAN_Architecture.md
T
spektr 88b063a234 Encoding/CT-101: PVE-Host-NFS konvergiert, Berechtigungsmodell entschieden, Verifikation dokumentiert.
- pve_nfs_export: NFSv4-only via /etc/nfs.conf ([nfsd] vers2/vers3=n; Debian-13-Unit
  startet nfsd ohne EnvironmentFile, RPCNFSDARGS wirkungslos), Restart-Handler,
  selbstheilende nfsd-Versionspruefung; verify-production um Export-Root- und
  Versions-Assertions erweitert
- Autorisierte Konvergenz auf dem PVE-Host: Export-Root 100000:970 2775,
  /proc/fs/nfsd/versions -3 +4 +4.1 +4.2, verify ok=17 changed=0, Idempotenz changed=0
- Berechtigungsmodell (Betreiberentscheidung 30.10.2026): Mode 2777 auf input/- und
  output/-Baum der Pipeline (LXC-idmap-Diskrepanz guest-root vs. UID 982/983:970)
- GNENC-000/001/002, GNAI-017/018/019 auf NFS-auf-PVE-Host-Stand gebracht;
  CT-101-Verifikation ok=47 changed=0 dokumentiert; AGENTS.md-Seccomp-Hinweis korrigiert

Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
2026-10-30 19:54:42 +01:00

17 KiB
Raw Blame History

Finaler Architekturplan

Dokumenten-ID: GNENC-001-PLAN Titel: CPU-basiertes Video-Encoding auf dem KI-Server Stand: 2026-10-30 Status: Finaler Plan nach Planungsabschluss am 19.10.2026; externe Abhängigkeiten (CT-101, PVE-Host-NFS) umgesetzt (GNAI-019); 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 dem PVE-Host-Medienverbund (/media/datenhdd3; per LXC-Mount auch in CT-101 sichtbar): dort liegt die Medienbibliothek; ghostnet-ki selbst hat nur eine 2-TB-NVMe (OS + LLM) Betreiberentscheidung 19.10.2026; NFS seit 30.10.2026 auf dem PVE-Host (GNAI-019 Entscheidung 2)
Ein-/Ausgabe NFS-Export 10.0.0.2:/media/datenhdd3/Encoding (PVE-Host, Rolle pve_nfs_export, NFSv4-only, nur für 10.0.0.90, root_squash), gemountet auf /var/lib/ghostnet-encoding; Verzeichnislayout input/, jd/, queue/, work/, output/, state/ Betreiberentscheidung 19.10.2026; Umsetzung GNAI-019 (Revision 30.10.2026); Mount-Abnahme durch das Encoding-Ansible ausstehend
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 ist per Ansible adoptiert (GNAI-019; verifiziert 30.10.2026, ok=47 changed=0): Shares [Encode]/[Encode-In]/[Encoding], Gruppe gn-media (GID 970) mit ghost als Mitglied; .dlc-Drop über [Encode-In]. NFS liegt nicht auf CT-101 (PVE-Host); die guest-seitige GID 970 gleicht die Host-GID 970 wegen des LXC-idmap nicht ab — Berechtigungsmodell: Mode 2777 auf den SMB-berührten Verzeichnissen (Betreiberentscheidung 30.10.2026, GNENC-002 Abschnitt 4) Betreiberentscheidung 19.10.2026; GNAI-017/GNAI-019
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[PVE-Host NFS 10.0.0.2\nCT-101 SMB 10.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 externen Abhängigkeiten sind umgesetzt: CT-101 ist per Ansible adoptiert (GNAI-019, verifiziert 30.10.2026), der NFS-Export läuft auf dem PVE-Host (Rolle pve_nfs_export); der PVE-Host ist konvergiert (Export-Root 100000:970 2775, NFSv4-only wirksam, verify-production ok=17 changed=0, Idempotenz changed=0; autorisierter Lauf 30.10.2026). Das Berechtigungsmodell für die SMB-Schreibpfade ist entschieden (Mode 2777, GNENC-002 Abschnitt 4); offener Restpunkt ist die NFSv4.2-Mount-Abnahme von ghostnet-ki. Die Implementierungsphase auf ghostnet-ki baut als Nächstes auf: die Konten gn-download und gn-dashboard samt Gruppe gn-media (GID 970), den NFS-Mount von 10.0.0.2, die Image-Bauten mit Digest-Pinning (Worker, VPN-Sidecar, Dashboard), die Quadlet-Units für jDownloader2, VPN-Sidecar, Worker und Dashboard, den host-seitigen 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. Die am 30.10.2026 offenen Punkte Berechtigungsmodell (Mode 2777, GNENC-002 Abschnitt 4) und PVE-Host-Konvergenz (GNAI-017 Abschnitt 6.9) sind entschieden beziehungsweise erledigt.