Encoding: Ansible-Projekt und Grundzustand für gn-encoding eingerichtet und verifiziert.
llm-server/encoding/ansible/ verwaltet mit video-encoding.yml und der Rolle video_encoding den CPU-Only-Grundzustand des gesperrten Kontos gn-encoding (UID/GID 982): Konto-Identität und Passwortsperre, subordinate ID-Bereiche, Verzeichnisstruktur, Quadlet-Verzeichnis, Linger, getrennter rootless Podman-Store sowie verwehrter GPU-Zugriff (keine render/video-Gruppe, kein Lesezugriff auf /dev/kfd oder DRM-Render-Nodes). Ohne beschlossene Encoder-Entscheidungen stellt die Rolle sicher, dass kein Workload definiert ist. Check-Mode und zwei Läufe auf ghostnet-ki: jeweils ok=30 changed=0 failed=0. Preflight, host.yml (2x ok=55 changed=0) und ROCm-Probe (ok=33 changed=0) nach der Projekt-Aufteilung erneut bestanden. GNENC-000/001, GNAI-000 und GNAI-013 aktualisiert. Generated by Mistral Vibe. Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
This commit is contained in:
@@ -48,7 +48,8 @@ jeweiligen Fachdokumenten. Die priorisierte weitere Umsetzung beschreibt der
|
||||
| [GNAI-008](platform/llm-server/ai/GNAI-008-PLAN_Ansible-Deployment.md) | detaillierter Ansible-/Gitea-Planungsstand und Implementierungshistorie |
|
||||
| [GNAI-009](platform/llm-server/ai/GNAI-009-PLAN_Llama-Model-Evaluation.md) | reproduzierbarer Modellvergleich |
|
||||
| [GNAI-010](platform/llm-server/ai/GNAI-010-TECH_Llama-Serving.md) | technische Sollkonfiguration des Testing-Servings |
|
||||
| [LLM-Server-Ansible](../llm-server/ansible/README.md) | ausführbare Host-/ROCm-Playbooks und Rollen für `10.0.0.90` |
|
||||
| [LLM-Server-Host-Ansible](../llm-server/host/ansible/README.md) | ausführbare Host-/ROCm-Playbooks und Rollen für `10.0.0.90` |
|
||||
| [Encoding-Ansible](../llm-server/encoding/ansible/README.md) | ausführbares Ansible-Projekt für den CPU-Encoding-Workload auf `10.0.0.90` |
|
||||
| [Keycloak-Ansible](../proxmox/lxc/ct-102-keycloak/ansible/README.md) | ausführbare Provisionierungs-, Restore- und Prüfautomation für CT 102 |
|
||||
|
||||
### Video-Encoding
|
||||
@@ -66,6 +67,10 @@ Am 22.09.2026 wurden KI-Host und Proxmox-LXCs in getrennte Unterverzeichnisse
|
||||
Am 05.10.2026 wurde die Dokumentation geräteorientiert neu geordnet:
|
||||
`platform/proxmox-server/` bildet den Proxmox-Kontext und seine LXCs ab,
|
||||
`platform/llm-server/` den physischen KI-Server mit AI und Encoding.
|
||||
Am 19.10.2026 wurde das ausführbare LLM-Server-Projekt in die fachlichen
|
||||
Hauptverzeichnisse `llm-server/host/` (Grundkonfiguration des Servers),
|
||||
`llm-server/llm/` (LLM-Stack) und `llm-server/encoding/` (Encoding) aufgeteilt;
|
||||
die Dokumentation folgt derselben Dreiteilung.
|
||||
|
||||
## 2. Belegter Stand
|
||||
|
||||
@@ -79,7 +84,7 @@ Am 05.10.2026 wurde die Dokumentation geräteorientiert neu geordnet:
|
||||
| DNS / Pi-hole | CT 100 unter `10.0.0.14` mit digest-gepinntem Pi-hole 2026.09.0 als Podman-Quadlet; SSH/nftables, UDP/TCP-DNS, Split-DNS, Blocking/Allowlist, FRITZ!Box-Cutover, LXC-Reboot und Stop-Mode-Backup am 01.10.2026 abgenommen | vollständige App-/OIDC-Regression, praktischer FRITZ!Box-Rollback, mobile DuckDuckGo-/IPv6-Strategie, WireGuard-Profile und hostunabhängiger Restore-Test |
|
||||
| Reverse Proxy | CT-103-Zugänge, SSH-/Firewall-Baseline, Reboot und lokales Backup abgenommen; NPM 2.15.1 routet öffentlichen OIDC- und internen Admin-vHost getrennt; öffentliche Keycloak-Administration liefert 403 | Fail2Ban-Jails/Bans und Wiederanlauf; hostunabhängige Backupkopie/Restore-Test |
|
||||
| Keycloak | CT 102 unter `10.0.0.4` reproduzierbar per Ansible aufgebaut; gepinntes Keycloak 26.2.4 und PostgreSQL 16.15, Restore von CT 104, Container-/DB-Härtung, NPM-Cutover, interner Adminzugang, öffentliche Admin-Sperre, realer Vaultwarden-OIDC-Login und LXC-Neustart abgenommen | 14-tägige Stabilisierungsphase, praktischer NPM-Rückfall auf CT 104, Betriebs-/Backup-/Restore-SOP und anschließende Rückfallbereinigung |
|
||||
| Encoding | Konto, Pfade und eigener rootless Store | Workload, Zeitsteuerung, Ressourcenlimits und Lasttest |
|
||||
| Encoding | Konto, Pfade, eigener rootless Store und CPU-Isolation per Ansible verwaltet und verifiziert | Workload, Zeitsteuerung, Ressourcenlimits und Lasttest |
|
||||
|
||||
Ein Zielbild, eine Betreiberangabe oder ein erfolgreicher Teiltest gilt nicht als
|
||||
vollständige Abnahme. Datierte Details stehen im zuständigen Fachdokument.
|
||||
|
||||
@@ -39,7 +39,10 @@ Die ausführbare Infrastruktur ist seit 14.10.2026 ebenfalls hostbezogen:
|
||||
|
||||
```text
|
||||
ghostnet.ai-infrastructure/
|
||||
├── llm-server/ansible/ # physischer KI-Server
|
||||
├── llm-server/
|
||||
│ ├── host/ansible/ # Grundkonfiguration des physischen KI-Servers
|
||||
│ ├── llm/ # LLM-Stack (entsteht mit dem AI-Vertical-Slice)
|
||||
│ └── encoding/ansible/ # CPU-Encoding-Workload
|
||||
├── proxmox/
|
||||
│ ├── shared/scripts/ # nur LXC-übergreifende PVE-Helfer
|
||||
│ └── lxc/
|
||||
@@ -130,12 +133,34 @@ hostbezogenen Playbooks oder Inventare.
|
||||
Pi-hole-Pfad abstimmen.
|
||||
- [x] Lokale Ansible-`.venv` wegen des veralteten Repository-Pfads neu
|
||||
erstellen und alle Syntaxprüfungen regulär ausführen.
|
||||
- [ ] Lesenden Preflight sowie Host-/ROCm-Verifikation wiederholen.
|
||||
- [x] Lesenden Preflight sowie Host-/ROCm-Verifikation wiederholen.
|
||||
- [ ] Internes, nicht öffentliches `llama.cpp`-Testing-Vertical-Slice nach
|
||||
GNAI-010 implementieren und bei 64k abnehmen.
|
||||
|
||||
## 4. Arbeitsnachweise
|
||||
|
||||
### 19.10.2026 – LLM-Server erreichbar, Projekt fachlich aufgeteilt, Encoding-Grundzustand
|
||||
|
||||
- Das Netzwerkkabel des KI-Servers war versehentlich gezogen; nach dem
|
||||
Wiederanschluss ist `10.0.0.90` aus dem LAN erreichbar. Lesender Preflight
|
||||
aus dem neuen Pfad `llm-server/host/ansible/`:
|
||||
`ok=5 changed=0 unreachable=0 failed=0` (ghostnet-ki, Debian 13.6, 14 vCPUs,
|
||||
64 GiB RAM, zwei Navi-21-PCI-Geräte `1002:73bf`).
|
||||
- Das LLM-Server-Projekt wurde in die fachlichen Hauptverzeichnisse `host/`,
|
||||
`llm/` und `encoding/` aufgeteilt (analog zu `docs/platform/llm-server/`).
|
||||
`host.yml` lief zweimal aus `llm-server/host/ansible/` mit jeweils
|
||||
`ok=55 changed=0 unreachable=0 failed=0`; der gepinnte ROCm-Container-Probe
|
||||
(`verify-rocm-container.yml`) meldete `ok=33 changed=0 failed=0`.
|
||||
- Encoding: `video-encoding.yml` aus `llm-server/encoding/ansible/` verifiziert
|
||||
den `gn-encoding`-Grundzustand (gesperrtes Konto UID/GID 982, subordinate
|
||||
ID-Bereiche, Verzeichnisse, Linger, rootless Podman-Store, keine GPU-Gruppen
|
||||
und kein Zugriff auf `/dev/kfd` oder DRM-Render-Nodes). Check-Mode und zwei
|
||||
aufeinanderfolgende Läufe meldeten jeweils `ok=30 changed=0 failed=0`. Es
|
||||
existiert weiterhin kein Encoding-Image, -Container, -Quadlet und -Timer.
|
||||
- Offen bleibt in Abschnitt E: die Resolver-Abstimmung des KI-Servers auf den
|
||||
Pi-hole-Pfad. Ein kontrollierter Kaltstart des Servers wurde nicht
|
||||
durchgeführt; der Server lief durch, nur das Netzwerkkabel war gezogen.
|
||||
|
||||
### 05.10.2026 – Struktur, Geheimnisse und lokaler VPN-Client
|
||||
|
||||
- Dokumente geräteorientiert verschoben; lokaler Linkvalidator meldet
|
||||
|
||||
@@ -22,6 +22,9 @@
|
||||
automatisch ausgeführt werden.
|
||||
- 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`.
|
||||
- Es sind noch kein Encoding-Image, kein Container, keine Quadlet-Unit und kein
|
||||
Timer eingerichtet.
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ unabhängig konkurrierende Sollzustände auf denselben Host anwenden.
|
||||
| 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; noch keine Workload definiert |
|
||||
| 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 |
|
||||
| Parallelität | darf parallel zu AI Production oder AI Testing laufen | Planungsentscheidung |
|
||||
| Zeitsteuerung | automatische Ausführung ausschließlich außerhalb der Stoßzeiten, zunächst nachts | Richtung festgelegt; konkrete Uhrzeiten fehlen |
|
||||
@@ -77,29 +77,39 @@ in GPU-Gruppen und keine `/dev/kfd`- oder `/dev/dri`-Weitergabe.
|
||||
|
||||
## 5. Repository-Grenzen
|
||||
|
||||
Vorgesehene Ansible-Struktur:
|
||||
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/ansible/
|
||||
├── playbooks/
|
||||
│ ├── host.yml
|
||||
│ ├── ai.yml
|
||||
│ ├── video-encoding.yml
|
||||
│ └── site.yml
|
||||
└── roles/
|
||||
├── host_baseline/
|
||||
├── podman_runtime/
|
||||
├── workload_accounts/
|
||||
├── ai_stack/
|
||||
├── ai_environment_switch/
|
||||
└── video_encoding/
|
||||
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/ # CPU-Encoding-Workload
|
||||
├── playbooks/
|
||||
│ └── video-encoding.yml # video_encoding
|
||||
└── roles/
|
||||
└── video_encoding/
|
||||
```
|
||||
|
||||
`host.yml` verwaltet ausschließlich gemeinsame Plattformzustände.
|
||||
`ai.yml` darf nur KI-Zustände verändern. `video-encoding.yml` verwaltet nur das
|
||||
Konto `gn-encoding`, seine Pfade, Images, Quadlets, Timer und Health Checks. Ein
|
||||
späteres `site.yml` kann die unterstützte Gesamtkonfiguration in definierter
|
||||
Reihenfolge anwenden.
|
||||
`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.
|
||||
|
||||
Ein separates Quellcode-Repository wird erst benötigt, wenn eigene
|
||||
Encoding-Software, eine Oberfläche oder ein Queue-Dienst entwickelt wird. Ein
|
||||
@@ -126,6 +136,17 @@ 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.
|
||||
|
||||
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 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`.
|
||||
|
||||
## 7. Offene Entscheidungen
|
||||
|
||||
1. Welcher Encoder und welches fest versionierte Container-Image werden genutzt?
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
# Video encoding workload
|
||||
|
||||
CPU-based video encoding on `ghostnet-ki`, separated from the LLM stack. The
|
||||
workload runs rootless under the locked `gn-encoding` account, has no GPU
|
||||
access and is scheduled outside peak hours.
|
||||
|
||||
The executable Ansible control project lives under
|
||||
[`ansible/`](ansible/README.md). Architecture, confirmed decisions and open
|
||||
questions are documented under
|
||||
[`../../docs/platform/llm-server/encoding/`](../../docs/platform/llm-server/encoding/GNENC-000-DOC_Index.md).
|
||||
@@ -0,0 +1,46 @@
|
||||
# Video encoding Ansible control project
|
||||
|
||||
This directory contains the reproducible control project for the CPU-based
|
||||
video encoding workload on `ghostnet-ki`. It belongs to the `encoding/` task
|
||||
area of the LLM-Server host root; the shared host baseline lives in
|
||||
[`../../host/ansible/`](../../host/ansible/README.md) and must be applied first.
|
||||
|
||||
`playbooks/video-encoding.yml` applies the `video_encoding` role: it verifies
|
||||
the locked `gn-encoding` account, enforces CPU-only device isolation, manages
|
||||
the encoding directories and proves the separate rootless Podman store. No
|
||||
encoder image, Quadlet, timer or schedule is defined yet; those are open
|
||||
decisions in
|
||||
[`../../../docs/platform/llm-server/encoding/`](../../../docs/platform/llm-server/encoding/GNENC-000-DOC_Index.md).
|
||||
|
||||
## Control-node setup
|
||||
|
||||
Use the shared virtual environment at the repository root (see
|
||||
[`../../host/ansible/README.md`](../../host/ansible/README.md)). The
|
||||
`ansible-deploy` private key stays outside this repository:
|
||||
`~/.ssh/id_ed25519_ghostnet_ai_ansible_deploy`.
|
||||
|
||||
## Apply and verify
|
||||
|
||||
Run the commands from this directory:
|
||||
|
||||
```bash
|
||||
cd llm-server/encoding/ansible
|
||||
../../../.venv/bin/ansible-playbook \
|
||||
--inventory inventories/host/hosts.yml \
|
||||
playbooks/video-encoding.yml \
|
||||
--syntax-check
|
||||
|
||||
../../../.venv/bin/ansible-playbook \
|
||||
--inventory inventories/host/hosts.yml \
|
||||
playbooks/video-encoding.yml \
|
||||
--check --diff \
|
||||
--private-key ~/.ssh/id_ed25519_ghostnet_ai_ansible_deploy
|
||||
|
||||
../../../.venv/bin/ansible-playbook \
|
||||
--inventory inventories/host/hosts.yml \
|
||||
playbooks/video-encoding.yml \
|
||||
--private-key ~/.ssh/id_ed25519_ghostnet_ai_ansible_deploy
|
||||
```
|
||||
|
||||
Run the final command a second time. The second recap must report `changed=0`
|
||||
and `failed=0`; otherwise the role has not demonstrated convergence.
|
||||
@@ -0,0 +1,12 @@
|
||||
[defaults]
|
||||
home = .ansible
|
||||
host_key_checking = True
|
||||
interpreter_python = auto_silent
|
||||
local_tmp = .ansible/tmp
|
||||
retry_files_enabled = False
|
||||
roles_path = roles:../../../shared/ansible/roles
|
||||
timeout = 15
|
||||
|
||||
[ssh_connection]
|
||||
pipelining = True
|
||||
ssh_args = -o ControlMaster=auto -o ControlPersist=60s
|
||||
@@ -0,0 +1,8 @@
|
||||
---
|
||||
all:
|
||||
children:
|
||||
llm_servers:
|
||||
hosts:
|
||||
ghostnet-ki:
|
||||
ansible_host: 10.0.0.90
|
||||
ansible_user: ansible-deploy
|
||||
@@ -0,0 +1,11 @@
|
||||
---
|
||||
- name: Apply the Ghostnet video encoding workload baseline
|
||||
hosts: llm_servers
|
||||
become: true
|
||||
gather_facts: true
|
||||
gather_subset:
|
||||
- min
|
||||
- network
|
||||
|
||||
roles:
|
||||
- role: video_encoding
|
||||
@@ -0,0 +1,22 @@
|
||||
# Video encoding baseline
|
||||
|
||||
The `video_encoding` role manages the CPU-only video encoding workload
|
||||
baseline for the locked `gn-encoding` service account (UID/GID 982) on
|
||||
`ghostnet-ki`.
|
||||
|
||||
It verifies the account identity, the locked password and the deterministic
|
||||
subordinate ID ranges, enforces the absence of GPU device groups, manages the
|
||||
root-owned configuration directory, the input/output/work data directories and
|
||||
the administrator-managed rootless Quadlet directory, and proves that the
|
||||
account cannot read `/dev/kfd` or any DRM render node and uses its own rootless
|
||||
Podman store.
|
||||
|
||||
While no encoder image is selected (`video_encoding_image` empty), the role
|
||||
additionally guards that no Quadlet file and no container exists for the
|
||||
account. Encoder image, schedule, resource limits, queue handling and health
|
||||
checks are open decisions in
|
||||
`docs/platform/llm-server/encoding/GNENC-001` and are not assumed here.
|
||||
|
||||
The account itself is created by the shared `workload_accounts` role in the
|
||||
host project (`llm-server/host/ansible`); this role requires the host baseline
|
||||
to be applied first.
|
||||
@@ -0,0 +1,32 @@
|
||||
---
|
||||
video_encoding_expected_hostname: ghostnet-ki
|
||||
video_encoding_expected_distribution: Debian
|
||||
video_encoding_expected_distribution_major_version: "13"
|
||||
video_encoding_expected_ipv4_address: 10.0.0.90
|
||||
|
||||
video_encoding_account:
|
||||
name: gn-encoding
|
||||
uid: 982
|
||||
gid: 982
|
||||
home: /var/lib/ghostnet-encoding
|
||||
config_directory: /etc/ghostnet-encoding
|
||||
shell: /usr/sbin/nologin
|
||||
subid_start: 493216
|
||||
|
||||
video_encoding_subid_count: 65536
|
||||
|
||||
video_encoding_directories:
|
||||
- path: /var/lib/ghostnet-encoding/input
|
||||
mode: "0750"
|
||||
- path: /var/lib/ghostnet-encoding/output
|
||||
mode: "0750"
|
||||
- path: /var/lib/ghostnet-encoding/work
|
||||
mode: "0750"
|
||||
|
||||
video_encoding_quadlet_user_directory: /etc/containers/systemd/users
|
||||
|
||||
# Open decisions from docs/platform/llm-server/encoding/GNENC-001 section 7
|
||||
# stay unset on purpose. Until an encoder image, a schedule and resource limits
|
||||
# are decided, the role only manages the shared encoding baseline and guards
|
||||
# that no workload is defined.
|
||||
video_encoding_image: ""
|
||||
@@ -0,0 +1,337 @@
|
||||
---
|
||||
- name: Verify the target host before changing encoding state
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- ansible_facts["hostname"] == video_encoding_expected_hostname
|
||||
- ansible_facts["distribution"] == video_encoding_expected_distribution
|
||||
- >-
|
||||
ansible_facts["distribution_major_version"] ==
|
||||
video_encoding_expected_distribution_major_version
|
||||
- >-
|
||||
ansible_facts["default_ipv4"]["address"] ==
|
||||
video_encoding_expected_ipv4_address
|
||||
fail_msg: >-
|
||||
The connected host does not match the declared Ghostnet AI server. No
|
||||
encoding changes may be applied.
|
||||
success_msg: The expected Debian host ghostnet-ki is connected.
|
||||
|
||||
- name: Verify encoding account declarations
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- video_encoding_account.name == "gn-encoding"
|
||||
- video_encoding_account.uid | int == 982
|
||||
- video_encoding_account.gid | int == 982
|
||||
- video_encoding_account.home is match("^/")
|
||||
- video_encoding_account.config_directory is match("^/")
|
||||
- video_encoding_subid_count | int > 0
|
||||
fail_msg: The encoding account declaration is invalid.
|
||||
|
||||
- name: Read the encoding service account entry
|
||||
ansible.builtin.command:
|
||||
argv:
|
||||
- /usr/bin/getent
|
||||
- passwd
|
||||
- gn-encoding
|
||||
check_mode: false
|
||||
changed_when: false
|
||||
register: video_encoding_account_entry
|
||||
failed_when: false
|
||||
|
||||
- name: Verify the encoding service account identity
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- video_encoding_account_entry.rc == 0
|
||||
- >-
|
||||
video_encoding_account_entry.stdout.split(":")[2] | int ==
|
||||
video_encoding_account.uid | int
|
||||
- >-
|
||||
video_encoding_account_entry.stdout.split(":")[3] | int ==
|
||||
video_encoding_account.gid | int
|
||||
- >-
|
||||
video_encoding_account_entry.stdout.split(":")[5] ==
|
||||
video_encoding_account.home
|
||||
- >-
|
||||
video_encoding_account_entry.stdout.split(":")[6] ==
|
||||
video_encoding_account.shell
|
||||
fail_msg: >-
|
||||
The gn-encoding account is missing or does not match the declared
|
||||
identity. Apply the shared host baseline
|
||||
(llm-server/host/ansible playbooks/host.yml) first.
|
||||
success_msg: The gn-encoding service account matches the declared identity.
|
||||
|
||||
- name: Read the shadow entry of the encoding account
|
||||
ansible.builtin.command:
|
||||
argv:
|
||||
- /usr/bin/getent
|
||||
- shadow
|
||||
- gn-encoding
|
||||
check_mode: false
|
||||
changed_when: false
|
||||
register: video_encoding_shadow_entry
|
||||
failed_when: false
|
||||
|
||||
- name: Verify the encoding account password is locked
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- video_encoding_shadow_entry.rc == 0
|
||||
- video_encoding_shadow_entry.stdout.split(":")[1] == "!"
|
||||
fail_msg: The gn-encoding account password is not locked.
|
||||
|
||||
- name: Read the subordinate UID range of the encoding account
|
||||
ansible.builtin.command:
|
||||
argv:
|
||||
- /usr/bin/grep
|
||||
- -E
|
||||
- "^gn-encoding:"
|
||||
- /etc/subuid
|
||||
check_mode: false
|
||||
changed_when: false
|
||||
register: video_encoding_subuid_entry
|
||||
failed_when: false
|
||||
|
||||
- name: Read the subordinate GID range of the encoding account
|
||||
ansible.builtin.command:
|
||||
argv:
|
||||
- /usr/bin/grep
|
||||
- -E
|
||||
- "^gn-encoding:"
|
||||
- /etc/subgid
|
||||
check_mode: false
|
||||
changed_when: false
|
||||
register: video_encoding_subgid_entry
|
||||
failed_when: false
|
||||
|
||||
- name: Verify the subordinate ID ranges of the encoding account
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- video_encoding_subuid_entry.rc == 0
|
||||
- video_encoding_subgid_entry.rc == 0
|
||||
- >-
|
||||
video_encoding_subuid_entry.stdout ==
|
||||
"gn-encoding:" ~ video_encoding_account.subid_start ~ ":"
|
||||
~ video_encoding_subid_count
|
||||
- >-
|
||||
video_encoding_subgid_entry.stdout ==
|
||||
"gn-encoding:" ~ video_encoding_account.subid_start ~ ":"
|
||||
~ video_encoding_subid_count
|
||||
fail_msg: >-
|
||||
The subordinate ID ranges of gn-encoding are missing or do not match the
|
||||
declared range. Apply the shared host baseline first.
|
||||
|
||||
- name: Read the supplementary groups of the encoding account
|
||||
ansible.builtin.command:
|
||||
argv:
|
||||
- /usr/bin/id
|
||||
- -nG
|
||||
- gn-encoding
|
||||
check_mode: false
|
||||
changed_when: false
|
||||
register: video_encoding_groups
|
||||
|
||||
- name: Enforce CPU-only device isolation for the encoding account
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- "'render' not in video_encoding_groups.stdout.split()"
|
||||
- "'video' not in video_encoding_groups.stdout.split()"
|
||||
fail_msg: >-
|
||||
gn-encoding must not be a member of a GPU device group. Encoding is
|
||||
CPU-only; remove the supplementary group before applying encoding state.
|
||||
success_msg: gn-encoding has no GPU device group membership.
|
||||
|
||||
- name: Manage the root-owned encoding configuration directory
|
||||
ansible.builtin.file:
|
||||
path: "{{ video_encoding_account.config_directory }}"
|
||||
state: directory
|
||||
owner: root
|
||||
group: "{{ video_encoding_account.gid }}"
|
||||
mode: "0750"
|
||||
|
||||
- name: Manage the encoding data directories
|
||||
ansible.builtin.file:
|
||||
path: "{{ video_encoding_directory.path }}"
|
||||
state: directory
|
||||
owner: "{{ video_encoding_account.uid }}"
|
||||
group: "{{ video_encoding_account.gid }}"
|
||||
mode: "{{ video_encoding_directory.mode }}"
|
||||
loop: "{{ video_encoding_directories }}"
|
||||
loop_control:
|
||||
loop_var: video_encoding_directory
|
||||
label: "{{ video_encoding_directory.path }}"
|
||||
|
||||
- name: Manage the administrator-managed rootless Quadlet directory
|
||||
ansible.builtin.file:
|
||||
path: "{{ video_encoding_quadlet_user_directory }}/{{ video_encoding_account.uid }}"
|
||||
state: directory
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0755"
|
||||
|
||||
- name: Verify the persistent user manager is enabled
|
||||
ansible.builtin.stat:
|
||||
path: "/var/lib/systemd/linger/{{ video_encoding_account.name }}"
|
||||
register: video_encoding_linger
|
||||
|
||||
- name: Assert the persistent user manager is enabled
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- video_encoding_linger.stat.exists
|
||||
fail_msg: >-
|
||||
Linger is not enabled for gn-encoding. Enable it through the shared host
|
||||
baseline so the rootless user manager survives logout.
|
||||
|
||||
- name: Wait for the running user manager
|
||||
ansible.builtin.command:
|
||||
argv:
|
||||
- /usr/bin/systemctl
|
||||
- is-active
|
||||
- "user@{{ video_encoding_account.uid }}.service"
|
||||
check_mode: false
|
||||
changed_when: false
|
||||
register: video_encoding_user_manager
|
||||
retries: 5
|
||||
delay: 1
|
||||
until: video_encoding_user_manager.stdout == "active"
|
||||
|
||||
- name: Find DRM render nodes in sysfs
|
||||
ansible.builtin.find:
|
||||
paths: /sys/class/drm
|
||||
patterns: "renderD*"
|
||||
file_type: link
|
||||
recurse: false
|
||||
register: video_encoding_render_nodes
|
||||
|
||||
- name: Build the GPU device node list
|
||||
ansible.builtin.set_fact:
|
||||
video_encoding_gpu_device_nodes: >-
|
||||
{{ ['/dev/kfd']
|
||||
+ (video_encoding_render_nodes.files
|
||||
| map(attribute="path")
|
||||
| map("basename")
|
||||
| map("regex_replace", "^", "/dev/dri/")
|
||||
| list) }}
|
||||
|
||||
- name: Verify the host exposes the expected GPU device nodes
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- video_encoding_gpu_device_nodes | length == 3
|
||||
fail_msg: >-
|
||||
Expected /dev/kfd and two DRM render nodes on ghostnet-ki, found
|
||||
{{ video_encoding_gpu_device_nodes }}.
|
||||
|
||||
- name: Probe GPU device node read access as the encoding account
|
||||
ansible.builtin.command:
|
||||
argv:
|
||||
- /usr/sbin/runuser
|
||||
- --user
|
||||
- gn-encoding
|
||||
- --
|
||||
- /usr/bin/test
|
||||
- -r
|
||||
- "{{ video_encoding_device_node }}"
|
||||
check_mode: false
|
||||
changed_when: false
|
||||
failed_when: false
|
||||
register: video_encoding_device_access
|
||||
loop: "{{ video_encoding_gpu_device_nodes }}"
|
||||
loop_control:
|
||||
loop_var: video_encoding_device_node
|
||||
label: "{{ video_encoding_device_node }}"
|
||||
|
||||
- name: Enforce that the encoding account cannot read GPU device nodes
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- >-
|
||||
video_encoding_device_access.results
|
||||
| map(attribute="rc")
|
||||
| select("equalto", 0)
|
||||
| list | length == 0
|
||||
fail_msg: >-
|
||||
gn-encoding can read a GPU device node. Encoding is CPU-only; remove the
|
||||
device access before applying encoding state.
|
||||
success_msg: gn-encoding cannot access /dev/kfd or any DRM render node.
|
||||
|
||||
- name: Inspect the encoding rootless Podman store
|
||||
ansible.builtin.command:
|
||||
argv:
|
||||
- /usr/sbin/runuser
|
||||
- --user
|
||||
- gn-encoding
|
||||
- --
|
||||
- /usr/bin/env
|
||||
- "HOME={{ video_encoding_account.home }}"
|
||||
- "XDG_RUNTIME_DIR=/run/user/{{ video_encoding_account.uid }}"
|
||||
- /usr/bin/podman
|
||||
- info
|
||||
- --format=json
|
||||
chdir: "{{ video_encoding_account.home }}"
|
||||
check_mode: false
|
||||
changed_when: false
|
||||
register: video_encoding_podman_info_command
|
||||
|
||||
- name: Parse the encoding rootless Podman information
|
||||
ansible.builtin.set_fact:
|
||||
video_encoding_podman_info: >-
|
||||
{{ video_encoding_podman_info_command.stdout | from_json }}
|
||||
|
||||
- name: Verify the isolated rootless Podman store
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- video_encoding_podman_info.host.security.rootless
|
||||
- >-
|
||||
video_encoding_podman_info.store.graphRoot ==
|
||||
video_encoding_account.home + "/.local/share/containers/storage"
|
||||
fail_msg: The gn-encoding Podman store is not rootless or not isolated.
|
||||
success_msg: gn-encoding uses its own rootless Podman store.
|
||||
|
||||
- name: List Quadlet files for the encoding account
|
||||
ansible.builtin.find:
|
||||
paths: "{{ video_encoding_quadlet_user_directory }}/{{ video_encoding_account.uid }}"
|
||||
file_type: file
|
||||
register: video_encoding_quadlet_files
|
||||
when: video_encoding_image | length == 0
|
||||
|
||||
- name: List containers in the encoding rootless store
|
||||
ansible.builtin.command:
|
||||
argv:
|
||||
- /usr/sbin/runuser
|
||||
- --user
|
||||
- gn-encoding
|
||||
- --
|
||||
- /usr/bin/env
|
||||
- "HOME={{ video_encoding_account.home }}"
|
||||
- "XDG_RUNTIME_DIR=/run/user/{{ video_encoding_account.uid }}"
|
||||
- /usr/bin/podman
|
||||
- ps
|
||||
- -a
|
||||
- --format
|
||||
- json
|
||||
chdir: "{{ video_encoding_account.home }}"
|
||||
check_mode: false
|
||||
changed_when: false
|
||||
register: video_encoding_podman_ps_command
|
||||
when: video_encoding_image | length == 0
|
||||
|
||||
- name: Verify that no encoding workload is defined before the open decisions are made
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- video_encoding_quadlet_files.files | length == 0
|
||||
- (video_encoding_podman_ps_command.stdout | from_json) | length == 0
|
||||
fail_msg: >-
|
||||
A Quadlet file or container exists for gn-encoding although no encoder
|
||||
image has been selected. Remove it or complete the open decisions in
|
||||
docs/platform/llm-server/encoding/GNENC-001 section 7 first.
|
||||
success_msg: >-
|
||||
No encoding workload is defined; the open decisions in GNENC-001 remain
|
||||
the gate for images, Quadlets and timers.
|
||||
when: video_encoding_image | length == 0
|
||||
|
||||
- name: Report the verified encoding baseline
|
||||
ansible.builtin.debug:
|
||||
msg:
|
||||
account: "gn-encoding (UID/GID 982)"
|
||||
config_directory: /etc/ghostnet-encoding
|
||||
data_directories: "{{ video_encoding_directories | map(attribute='path') | list }}"
|
||||
quadlet_directory: /etc/containers/systemd/users/982
|
||||
podman_store: "{{ video_encoding_podman_info.store.graphRoot }}"
|
||||
gpu_access: none (verified)
|
||||
workload_defined: false
|
||||
Reference in New Issue
Block a user