Files
infrastructure/docs/platform/llm-server/encoding/GNENC-001-PLAN_Architecture.md
T
spektr 69930f815a Encoding: Pipeline-Konten, NFS-Mount, Layout und Handover-Simulation umgesetzt und verifiziert.
- workload_accounts: gn-media (GID 970), gn-download (983), gn-dashboard (984);
  gn-media als primäre Gruppe der Pipeline-Konten — AUTH_SYS ueber NFSv4 wirkte
  serverseitig nicht mit Supplementary-Gruppen (Betriebsbefund 30.10.2026);
  Sudoers des Deploy-Kontos auf (ALL:ALL) verbreitert (become_user)
- video_encoding: NFS-Client, Mount-Unit und Pipeline-Layout auf dem Export
  (10.0.0.2:/media/datenhdd3/Encoding -> /var/lib/ghostnet-pipeline, ausserhalb
  der Homes); Layout-Anlage als Dienstkonten wegen root_squash; Verifikation
  der Verzeichnisrechte als gn-encoding (2777/2770/0750/2750, Gruppe 970)
- pve_media_cron: Legacy-Crontab (chown root:root + chmod -R 0777 auf allen
  Medienplatten, alle 10 min) entfernt — haette jedes echte Berechtigungsmodell
  plattgewalzt; Backup unter /var/backups/ghostnet/pve/; verify-production
  prueft das Fehlen ab
- verify-pipeline-handover.yml: Handover-Simulation über alle Identitaeten
  (CT-101-.dlc-Drop -> gn-download -> queue -> gn-encoding -> output/state ->
  gn-dashboard); einmal vollstaendig bestanden (30.10.2026)
- Doku: GNENC-000/001/002, GNAI-017 (6.10/6.11), GNMON-001 auf Stand gebracht

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

260 lines
17 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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](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`; 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 982, Worker), `gn-download` (983, jDownloader2 + VPN-Sidecar), `gn-dashboard` (984, Dashboard, Monitoring-Bereich); gemeinsame Gruppe `gn-media` (GID 970) als **primäre** Gruppe der drei Konten (AUTH_SYS über NFSv4 wirkte serverseitig nur mit primärer Gruppe; Befund 30.10.2026) | alle drei Konten eingerichtet und verifiziert 30.10.2026 (Idempotenz `changed=0`) |
| 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-pipeline` (außerhalb der Konto-Homes); Verzeichnislayout `input/`, `jd/`, `queue/`, `work/`, `output/`, `state/` | Betreiberentscheidung 19.10.2026; Mount aktiv, Layout und Handover verifiziert 30.10.2026 |
| 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
```mermaid
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.
```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 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/`:
```text
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:
```text
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). Stand
30.10.2026: Die externen Abhängigkeiten sind umgesetzt und verifiziert —
CT-101 per Ansible adoptiert, der PVE-Host konvergiert (Export-Root
`100000:970 2775`, NFSv4-only wirksam, Legacy-Medien-Cron entfernt — GNAI-017
Abschnitt 6.9–6.11), die drei Pipeline-Konten samt Gruppe `gn-media`
(einschließlich Primärgruppen-Umstellung, GNAI-017 Abschnitt 6.10) angelegt,
der NFS-Mount auf `/var/lib/ghostnet-pipeline` aktiv, das Verzeichnislayout
auf dem Export erzeugt und verifiziert, und die Handover-Simulation
(`verify-pipeline-handover.yml`) einmal vollständig bestanden. Die
Implementierungsphase auf ghostnet-ki baut als Nächstes auf: 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.