GNAI007: Render- und GPU-Rechte den beiden KI-Konten hinzugefügt.

This commit is contained in:
2026-09-20 22:38:44 +02:00
parent bf7431d672
commit 2f2dbc3252
12 changed files with 399 additions and 21 deletions
+3 -3
View File
@@ -2,7 +2,7 @@
Dokumentation für die Infrastruktur eines privaten Homelabs mit öffentlich erreichbaren Webdiensten, einem WireGuard-Zugang und einem dedizierten KI-Server. Dieses Repository hält Architekturentscheidungen, den belegten Betriebsstand und Verfahren für Einrichtung, Betrieb und Sicherheitsabnahme fest.
> **Projektstand: 20.09.2026.** Die Dokumentationsrevisionen sind zusammengeführt. Mehrere Sicherheitsmaßnahmen und technische Endabnahmen stehen noch aus. Auf dem KI-Server sind Host-Baseline, Podman und getrennte rootless Workload-Konten eingerichtet; LLM- und Encoding-Anwendungen sind noch nicht installiert.
> **Projektstand: 20.09.2026.** Die Dokumentationsrevisionen sind zusammengeführt. Mehrere Sicherheitsmaßnahmen und technische Endabnahmen stehen noch aus. Auf dem KI-Server sind Host-Baseline, Podman, getrennte rootless Workload-Konten und der Gerätezugriff der KI-Konten auf beide AMD-GPUs eingerichtet; LLM- und Encoding-Anwendungen sind noch nicht installiert.
## Überblick
@@ -26,7 +26,7 @@ Die drei vorgesehenen VPN-Netzwerkrollen sind **Familie** (gesamtes Heimnetz), *
| Bereich | Belegter Stand | Nächster Nachweis oder Schritt |
| --- | --- | --- |
| KI-Server | Debian-Basissystem, Deployment-Zugang, Host-Baseline, Podman 5.4.2 und drei getrennte rootless Workload-Konten | GPU-Zugriff für KI prüfen sowie Encoding-Zeitfenster und Ressourcenlimits festlegen |
| KI-Server | Debian-Basissystem, Deployment-Zugang, Podman 5.4.2, drei getrennte rootless Workload-Konten und geprüfter Zugriff beider KI-Konten auf `/dev/kfd` sowie zwei AMD-Render-Nodes | ROCm im rootless `llama.cpp`-Container prüfen und danach den Testing-Stack bereitstellen |
| VPN | wg-easy v15.4.0 auf CT 105 aktiv; ein externer Laptop-Teiltest liegt vor | Rollenrechte serverseitig umsetzen und mit erlaubten sowie gesperrten Zielen testen |
| Reverse Proxy | NPM auf CT 103 dokumentiert; Fail2Ban laut Betreiber fest gepinnt und wieder in Betrieb | Effektive Jails, Sperren und Wiederanlauf prüfen |
| Identität | Keycloak/OIDC laut Betreiber produktiv für Vaultwarden | Öffentlich erreichbare Keycloak-Administration sperren und den OIDC-Login erneut prüfen |
@@ -41,7 +41,7 @@ Der [Dokumentationsindex](docs/GNAI-000-DOC_Index.md) enthält die datierten Que
| [Plattform-Dokumentation](docs/platform/README.md) | Hostbasis, Zugriffsmodell, Reverse Proxy und VPN |
| [KI-Dokumentation](docs/ai/README.md) | KI-Zielarchitektur und nicht finale Ansible-Deployment-Planung |
| [Video-Encoding](docs/video-encoding/GNENC-000-DOC_Index.md) | getrennter Planungs- und späterer SOP-Bereich für den CPU-Encoding-Workload |
| [Ansible-Control-Projekt](ansible/README.md) | lokale Einrichtung, Preflight, Deployment-Account, Host-Baseline, Podman und Workload-Konten |
| [Ansible-Control-Projekt](ansible/README.md) | lokale Einrichtung, Preflight, Deployment-Account, Host-Baseline, Podman, Workload-Konten und AMD-GPU-Zugriff |
## Arbeiten mit dem Repository
+24 -4
View File
@@ -2,8 +2,9 @@
This directory contains the reproducible control project for `ghostnet-ki`.
The read-only `playbooks/preflight.yml` verifies the documented host identity
and hardware. The first writing milestone, `playbooks/host.yml`, applies a
small shared host baseline through the dedicated deployment account.
and hardware. The shared `playbooks/host.yml` applies the host baseline,
Podman runtime, workload identities and narrowly scoped AMD device access
through the dedicated deployment account.
## Control-node setup
@@ -166,5 +167,24 @@ UID/GID and subordinate-ID ranges:
Each account has a persistent systemd user manager and a separate,
administrator-managed directory below `/etc/containers/systemd/users/<UID>`.
Configuration is root-owned and group-readable; workload data belongs to its
service account. The role does not grant GPU access and creates no workload
containers.
service account. The two AI identities have `render` as their explicit
supplementary-group baseline; the encoding identity has no GPU group. The role
creates no workload containers.
## AMD GPU access
The `amd_gpu_access` role verifies exactly two PCI devices `1002:73bf` using
the `amdgpu` kernel driver. It maps their sysfs identities to the corresponding
DRM render nodes, checks `/dev/kfd`, derives the device groups and confirms
read/write access as both AI workload identities.
On the current host, the verified devices are `/dev/kfd`,
`/dev/dri/renderD128` and `/dev/dri/renderD129`; all use group `render`.
`gnai-production` and `gnai-testing` can access them, while `gn-encoding`
remains outside that group. The final convergence run reported
`ok=55 changed=0 unreachable=0 failed=0`.
This proves host-level device permissions only. No ROCm userspace image has
been pulled and no GPU workload has run yet. The next workload check must pass
these devices into a rootless container with `GroupAdd=keep-groups` and prove
that `llama.cpp` detects both cards as `gfx1030`.
+1
View File
@@ -12,3 +12,4 @@
- role: host_baseline
- role: podman_runtime
- role: workload_accounts
- role: amd_gpu_access
+16
View File
@@ -0,0 +1,16 @@
# AMD GPU access role
This shared host role verifies the two configured AMD GPUs and their `amdgpu`
kernel driver, discovers only the DRM render nodes belonging to their PCI IDs,
and checks `/dev/kfd`. It grants the Production and Testing service accounts
the minimal supplementary groups required by those device nodes.
When group membership changes, the role restarts both persistent systemd user
managers so later rootless Quadlets can use `GroupAdd=keep-groups`. It verifies
that each service account can read and write `/dev/kfd` and both selected
render devices.
The role installs no ROCm userspace packages, pulls no image and starts no
container. ROCm userspace will come from the pinned `llama.cpp` container. A
later workload check must still prove that the container detects both GPUs as
`gfx1030` and performs inference on them.
@@ -0,0 +1,10 @@
---
amd_gpu_vendor_id: "1002"
amd_gpu_device_id: "73bf"
amd_gpu_expected_count: 2
amd_gpu_workload_accounts:
- name: gnai-production
uid: 980
- name: gnai-testing
uid: 981
@@ -0,0 +1,10 @@
---
- name: Restart AI workload user managers
ansible.builtin.systemd_service:
name: "user@{{ amd_gpu_account.uid }}.service"
state: restarted
loop: "{{ amd_gpu_workload_accounts }}"
loop_control:
loop_var: amd_gpu_account
label: "{{ amd_gpu_account.name }}"
when: not ansible_check_mode
+212
View File
@@ -0,0 +1,212 @@
---
- name: Verify AMD GPU access declarations
ansible.builtin.assert:
that:
- amd_gpu_vendor_id is match("^[0-9a-fA-F]{4}$")
- amd_gpu_device_id is match("^[0-9a-fA-F]{4}$")
- amd_gpu_expected_count | int > 0
- >-
amd_gpu_workload_accounts | map(attribute="name") | unique | length ==
amd_gpu_workload_accounts | length
- >-
amd_gpu_workload_accounts | map(attribute="uid") | unique | length ==
amd_gpu_workload_accounts | length
fail_msg: AMD GPU identifiers and workload accounts must be valid and unique.
- name: Read matching PCI devices and kernel drivers
ansible.builtin.command:
argv:
- /usr/bin/lspci
- -Dnnk
- -d
- "{{ amd_gpu_vendor_id }}:{{ amd_gpu_device_id }}"
changed_when: false
check_mode: false
register: amd_gpu_lspci
- name: Verify expected GPUs use the amdgpu kernel driver
ansible.builtin.assert:
that:
- >-
amd_gpu_lspci.stdout_lines
| select("search", "(?i)" + amd_gpu_vendor_id + ":" + amd_gpu_device_id)
| list | length == amd_gpu_expected_count | int
- >-
amd_gpu_lspci.stdout
| regex_findall("Kernel driver in use: amdgpu")
| length == amd_gpu_expected_count | int
fail_msg: >-
Expected {{ amd_gpu_expected_count }} PCI devices
{{ amd_gpu_vendor_id }}:{{ amd_gpu_device_id }} using amdgpu.
- name: Find DRM render nodes in sysfs
ansible.builtin.find:
paths: /sys/class/drm
patterns: "renderD*"
file_type: link
recurse: false
register: amd_gpu_sysfs_render_nodes
- name: Read render node PCI vendor IDs
ansible.builtin.slurp:
src: "{{ amd_gpu_render_node.path }}/device/vendor"
loop: "{{ amd_gpu_sysfs_render_nodes.files }}"
loop_control:
loop_var: amd_gpu_render_node
label: "{{ amd_gpu_render_node.path | basename }}"
register: amd_gpu_render_vendor_ids
- name: Read render node PCI device IDs
ansible.builtin.slurp:
src: "{{ amd_gpu_render_node.path }}/device/device"
loop: "{{ amd_gpu_sysfs_render_nodes.files }}"
loop_control:
loop_var: amd_gpu_render_node
label: "{{ amd_gpu_render_node.path | basename }}"
register: amd_gpu_render_device_ids
- name: Initialize matching AMD render device list
ansible.builtin.set_fact:
amd_gpu_render_devices: []
- name: Select render devices belonging to the configured AMD GPUs
ansible.builtin.set_fact:
amd_gpu_render_devices: >-
{{ amd_gpu_render_devices
+ ['/dev/dri/'
+ (amd_gpu_sysfs_render_nodes.files[amd_gpu_render_index].path
| basename)] }}
loop: "{{ range(0, amd_gpu_sysfs_render_nodes.files | length) | list }}"
loop_control:
loop_var: amd_gpu_render_index
label: >-
{{ amd_gpu_sysfs_render_nodes.files[amd_gpu_render_index].path | basename }}
when:
- >-
amd_gpu_render_vendor_ids.results[amd_gpu_render_index].content
| b64decode | trim | lower == "0x" + amd_gpu_vendor_id | lower
- >-
amd_gpu_render_device_ids.results[amd_gpu_render_index].content
| b64decode | trim | lower == "0x" + amd_gpu_device_id | lower
- name: Verify one render device was found for each configured GPU
ansible.builtin.assert:
that:
- amd_gpu_render_devices | length == amd_gpu_expected_count
fail_msg: >-
Expected {{ amd_gpu_expected_count }} matching AMD render devices but found
{{ amd_gpu_render_devices | length }}: {{ amd_gpu_render_devices }}.
- name: Inspect the ROCm kernel device
ansible.builtin.stat:
path: /dev/kfd
register: amd_gpu_kfd
- name: Verify the ROCm kernel device exists
ansible.builtin.assert:
that:
- amd_gpu_kfd.stat.exists
- amd_gpu_kfd.stat.ischr | default(false)
- amd_gpu_kfd.stat.gr_name != "root"
fail_msg: /dev/kfd must exist as a character device with a dedicated group.
- name: Inspect matching AMD render devices
ansible.builtin.stat:
path: "{{ amd_gpu_render_device }}"
loop: "{{ amd_gpu_render_devices }}"
loop_control:
loop_var: amd_gpu_render_device
register: amd_gpu_render_device_stats
- name: Verify matching AMD render devices are usable character devices
ansible.builtin.assert:
that:
- amd_gpu_render_stat.stat.exists
- amd_gpu_render_stat.stat.ischr | default(false)
- amd_gpu_render_stat.stat.gr_name != "root"
loop: "{{ amd_gpu_render_device_stats.results }}"
loop_control:
loop_var: amd_gpu_render_stat
label: "{{ amd_gpu_render_stat.amd_gpu_render_device }}"
- name: Derive the minimal host groups required by the GPU devices
ansible.builtin.set_fact:
amd_gpu_required_group_names: >-
{{ ([amd_gpu_kfd.stat.gr_name]
+ (amd_gpu_render_device_stats.results
| map(attribute="stat.gr_name") | list))
| unique | sort | list }}
- name: Grant AI workload accounts access to the GPU device groups
ansible.builtin.user:
name: "{{ amd_gpu_account.name }}"
groups: "{{ amd_gpu_required_group_names | join(',') }}"
append: true
loop: "{{ amd_gpu_workload_accounts }}"
loop_control:
loop_var: amd_gpu_account
label: "{{ amd_gpu_account.name }}"
notify: Restart AI workload user managers
- name: Apply changed group memberships to persistent user managers
ansible.builtin.meta: flush_handlers
- name: Read effective AI workload account groups
ansible.builtin.command:
argv:
- /usr/bin/id
- -nG
- "{{ amd_gpu_account.name }}"
changed_when: false
loop: "{{ amd_gpu_workload_accounts }}"
loop_control:
loop_var: amd_gpu_account
label: "{{ amd_gpu_account.name }}"
register: amd_gpu_account_groups
when: not ansible_check_mode
- name: Verify AI workload account group memberships
ansible.builtin.assert:
that:
- >-
amd_gpu_required_group_names
| difference(amd_gpu_account_group.stdout.split()) | length == 0
fail_msg: >-
{{ amd_gpu_account_group.amd_gpu_account.name }} is missing a required GPU
device group.
loop: "{{ amd_gpu_account_groups.results }}"
loop_control:
loop_var: amd_gpu_account_group
label: "{{ amd_gpu_account_group.amd_gpu_account.name }}"
when: not ansible_check_mode
- name: Verify AI workload accounts can read and write required GPU devices
ansible.builtin.command:
argv:
- /usr/sbin/runuser
- --user
- "{{ amd_gpu_access_pair.0.name }}"
- --
- /usr/bin/python3
- -c
- >-
import os, sys; raise SystemExit(
0 if os.access(sys.argv[1], os.R_OK | os.W_OK) else 1)
- "{{ amd_gpu_access_pair.1 }}"
changed_when: false
loop: >-
{{ amd_gpu_workload_accounts
| product(['/dev/kfd'] + amd_gpu_render_devices) | list }}
loop_control:
loop_var: amd_gpu_access_pair
label: "{{ amd_gpu_access_pair.0.name }} -> {{ amd_gpu_access_pair.1 }}"
when: not ansible_check_mode
- name: Report verified AMD GPU access
ansible.builtin.debug:
msg:
pci_id: "{{ amd_gpu_vendor_id }}:{{ amd_gpu_device_id }}"
gpu_count: "{{ amd_gpu_expected_count }}"
render_devices: "{{ amd_gpu_render_devices }}"
device_groups: "{{ amd_gpu_required_group_names }}"
accounts: "{{ amd_gpu_workload_accounts | map(attribute='name') | list }}"
+4 -3
View File
@@ -11,6 +11,7 @@ service group. Workload data and the rootless Podman store belong to the
service account. The role verifies that all three Podman stores are rootless
and use distinct graph roots.
The role does not grant GPU device access and does not define containers,
networks, schedules or resource limits. Those states belong to the AI and
video-encoding workload roles.
The two AI accounts receive `render` as an explicit supplementary-group
baseline; the encoding account receives no GPU group. Hardware discovery and
effective device-access checks belong to `amd_gpu_access`. This role does not
define containers, networks, schedules or resource limits.
@@ -7,7 +7,8 @@ workload_accounts:
home: /var/lib/ghostnet-ai/production
config_directory: /etc/ghostnet-ai/production
subid_start: 362144
supplementary_groups: []
supplementary_groups:
- render
- name: gnai-testing
uid: 981
gid: 981
@@ -15,7 +16,8 @@ workload_accounts:
home: /var/lib/ghostnet-ai/testing
config_directory: /etc/ghostnet-ai/testing
subid_start: 427680
supplementary_groups: []
supplementary_groups:
- render
- name: gn-encoding
uid: 982
gid: 982
+11
View File
@@ -61,6 +61,8 @@ Die Rolle `podman_runtime` wurde am 20.09.2026 angewendet. Sie installierte Podm
Die Rolle `workload_accounts` richtete am 20.09.2026 die gesperrten Konten `gnai-production` (UID/GID 980), `gnai-testing` (981) und `gn-encoding` (982) mit festen, getrennten SubID-Bereichen ein. Konfiguration bleibt rootverwaltet; Daten, rootless Podman-Stores und persistente systemd-User-Manager sind pro Workload getrennt. Der korrigierte Gesamtlauf meldete `ok=36 changed=0 unreachable=0 failed=0` und bestätigte drei unterschiedliche rootless Graph Roots. Images und Container wurden nicht angelegt.
Die Rolle `amd_gpu_access` bestätigte am 20.09.2026 zwei Sapphire-Navi-21-Geräte `1002:73bf` mit dem Kernel-Treiber `amdgpu`, `/dev/kfd` sowie die zugehörigen Render-Nodes `/dev/dri/renderD128` und `/dev/dri/renderD129`. Alle drei Geräte verwenden die Gruppe `render`. `gnai-production` und `gnai-testing` besitzen nachweislich Lese-/Schreibzugriff; `gn-encoding` erhielt keine GPU-Gruppe. Nach Beseitigung einer konkurrierenden Gruppenverwaltung endete der Gesamtlauf mit `ok=55 changed=0 unreachable=0 failed=0`. ROCm im Container und tatsächliche Inferenz sind damit noch nicht nachgewiesen.
## 3. Sicherheitsbefunde mit Priorität
| Befund | dokumentiertes Verfahren / Status |
@@ -105,4 +107,13 @@ rootseitiges `runuser` und eindeutiger Eigentümerzuständigkeit endete der
Gesamtlauf mit `ok=36 changed=0 unreachable=0 failed=0`. Die drei rootless
Podman-Stores sind voneinander getrennt und noch leer.
Der folgende GPU-Zugriffslauf ordnete beide erkannten AMD-Render-Nodes anhand
von PCI-Vendor und Geräte-ID den Navi-21-Karten zu und leitete aus ihnen sowie
`/dev/kfd` ausschließlich die Gruppe `render` ab. Der erste Wiederholungslauf
deckte auf, dass `workload_accounts` ergänzende Gruppen wieder entfernte. Nach
Übernahme von `render` in den expliziten Kontosollzustand blieb
`amd_gpu_access` für Hardwareerkennung und effektive Rechteprüfung zuständig.
Der Abschlusslauf war mit `changed=0` idempotent; Images und Container blieben
weiterhin leer.
Die WireGuard-Evidenz bleibt der Teiltest vom 12.09.2026: CachyOS-Laptop von WAN, Handshake, Ping auf Gateway/Proxmox und direkter IPv4-Internetpfad bestanden; KI-Server war ausgeschaltet. Neue Rollen, weitere Clientvarianten, Gateway-GUI-Zugriff, WAN-/IPv6-Isolation, Neustart und Restore sind noch nicht abgenommen. GNAI-006 ist erst nach Klärung der offenen Werte, Umsetzung und dokumentierter Abnahme technisch abgeschlossen.
+100 -8
View File
@@ -28,6 +28,8 @@
| 2026-09-19 | 0.7 | spektr / Codex | minimale gemeinsame Host-Baseline mit Schutzprüfungen und getrennten Verzeichniswurzeln implementiert, angewendet und auf Idempotenz geprüft |
| 2026-09-20 | 0.8 | spektr / Codex | gemeinsame Podman-/Quadlet-Runtime implementiert, installiert und ohne Images oder Container verifiziert |
| 2026-09-20 | 0.9 | spektr / Codex | getrennte rootless Konten für AI Production, AI Testing und CPU-Encoding eingerichtet; Parallelitäts- und Repository-Grenzen ergänzt |
| 2026-09-20 | 0.10 | spektr / Codex | ROCm als Backend für zwei RX 6900 XT, initialen `llama.cpp`-Modellstand und Layer-Split mit gleicher GPU-Gewichtung festgehalten |
| 2026-09-20 | 0.11 | spektr / Codex | AMD-Gerätezugriff für beide rootless KI-Konten implementiert, angewendet und idempotent geprüft |
---
@@ -378,6 +380,75 @@ rootverwaltet und für die jeweilige Gruppe lesbar. Der Encoding-Workload
verwendet entsprechend `/etc/ghostnet-encoding` und
`/var/lib/ghostnet-encoding`.
### 7.5 ROCm- und `llama.cpp`-Ausgangskonfiguration
Der Betreiber hat zwei Sapphire Nitro+ Radeon RX 6900 XT mit jeweils 16 GiB
VRAM als Zielhardware bestätigt. Für diesen homogenen AMD-Aufbau wird ROCm als
Backend verwendet. Vulkan bleibt eine mögliche spätere Alternative, falls der
Server um GPUs mit unterschiedlichen Backend-Anforderungen erweitert wird.
Als Ausgangspunkt dient eine bereits auf einer RX 7900 XTX verwendete
Podman-Compose-Konfiguration. Die Serverumsetzung wird daraus mit folgenden
Abweichungen als rootless Quadlet erzeugt:
- das Image `ghcr.io/ggml-org/llama.cpp:server-rocm` wird auf eine geprüfte
Version und den zugehörigen Digest festgelegt;
- `/dev/kfd` und die tatsächlich benötigten `/dev/dri`-Geräte werden explizit
eingebunden; `GroupAdd=keep-groups` erhält die nötigen ergänzenden
Geräte-Gruppen im rootless Container;
- das gemeinsame Modellverzeichnis wird nur lesbar unter `/models`
eingebunden;
- `llama-server` erhält keinen Host-Port und ist ausschließlich für LiteLLM im
umgebungseigenen Podman-Netz erreichbar;
- systemd übernimmt Start, Neustart, Abhängigkeiten und Health-Überwachung;
- Modell- und Laufzeitwerte werden als Ansible-Variablen in die
Quadlet-Kommandozeile gerendert. Zusätzliche Container-Environment-Variablen
werden nur verwendet, wenn das Backend sie technisch benötigt;
- `HSA_OVERRIDE_GFX_VERSION=11.0.0` aus der RX-7900-XTX-Konfiguration wird
nicht übernommen. Die RX 6900 XT verwendet nativ `gfx1030`, das im
vorgesehenen ROCm-Image enthalten sein muss. Ein Override wäre nur nach
einem fehlgeschlagenen Geräte-/Backendtest zulässig und müsste für RDNA2
gesondert begründet werden;
- `label=disable` wird nur übernommen, falls eine tatsächlich aktive
SELinux-Konfiguration dies erfordert. Für den dokumentierten Debian-Host
wird es nicht vorsorglich gesetzt.
Der erste Testing-Modellstand verwendet vorbehaltlich Artefakt- und
Checksum-Prüfung:
| Parameter | Ausgangswert |
| --- | --- |
| Modelldatei | `Qwen3.8-27B-UD-IQ4_XS.gguf` |
| API-Alias | `qwen3.8-27b` |
| Kontext | 51.200 Token |
| parallele Slots | 1 |
| maximale Ausgabe | 4.096 Token |
| GPU-Layer | alle |
| Flash Attention | aktiviert |
| K-/V-KV-Cache | jeweils `q8_0` |
| Reasoning Effort | `medium` |
Samplingwerte und Chat-Template-Parameter gehören zum Modellprofil und nicht
zu allgemeinen Rollendefaults. Dadurch können andere Modelle später eigene
geprüfte Profile verwenden, ohne die gemeinsame `llama_cpp`-Rolle zu ändern.
Für die beiden gleich großen GPUs wird zunächst
`--split-mode layer --tensor-split 1,1` festgelegt. Der Layer-Split minimiert
GPU-übergreifende Kommunikation und passt damit zur dokumentierten
PCIe-Topologie, bei der die zweite Karte über einen langsameren, am PCH
angebundenen Slot läuft. `row` wird wegen seiner Ablösung im aktuellen
`llama.cpp` nicht als neue Betriebsvariante vorgesehen. Der experimentelle
`tensor`-Split bleibt ein späterer Benchmarkkandidat; er benötigt deutlich
mehr Kommunikation zwischen den Karten und ist mit dem geplanten
quantisierten V-KV-Cache derzeit nicht kompatibel.
Vor dem dauerhaften Start muss ein begrenzter Test `gfx1030`, beide GPUs,
Device-Reihenfolge, verfügbaren VRAM und die tatsächliche 1:1-Verteilung in
den `llama.cpp`-Startlogs bestätigen. Zusätzlich werden Prompt-Verarbeitung,
Token-Generierung, VRAM-Nutzung und Stabilität bei 51.200 Token Kontext
gemessen. Der 1:1-Wert ist erst nach diesem Test ein abgenommener
Betriebsparameter.
---
## 8. Vorgesehener Workflow
@@ -555,7 +626,8 @@ nicht als Nachweis einer erfolgreichen Einrichtung.
- Podman- und Quadlet-Grundruntime sowie getrennte rootless Workload-Konten
eingerichtet und geprüft;
- AMD-GPU-/ROCm- oder alternative Laufzeit nach technischer Prüfung;
- rootless Host-Gerätezugriff auf beide RX 6900 XT eingerichtet und geprüft;
- ROCm-Funktion im rootless Container prüfen;
- `llama.cpp` und lokaler Funktionstest;
- Wiederanlauf nach geplantem Reboot prüfen.
@@ -635,6 +707,8 @@ Herstellerdokumentationen herangezogen:
- [Ansible Vault](https://docs.ansible.com/projects/ansible/latest/vault_guide/vault.html)
- [Debian 13 – Paket `podman`](https://packages.debian.org/trixie/podman)
- [Podman – Quadlet und systemd](https://docs.podman.io/en/latest/markdown/podman-systemd.unit.5.html)
- [llama.cpp – Multi-GPU](https://github.com/ggml-org/llama.cpp/blob/master/docs/multi-gpu.md)
- [llama.cpp – ROCm-Container](https://github.com/ggml-org/llama.cpp/blob/master/.devops/rocm.Dockerfile)
Vor der Implementierung sind versionsspezifische Aussagen gegen die dann
tatsächlich eingesetzten Gitea-, Runner- und Ansible-Versionen zu prüfen.
@@ -753,25 +827,43 @@ die umgebungsspezifischen Pfade wurden daraufhin vollständig an
systemd-User-Manager sowie drei getrennte rootless Podman Graph Roots. Es
wurden keine Images oder Workload-Container angelegt.
Anschließend wurde `amd_gpu_access` in das gemeinsame Host-Playbook
aufgenommen. Die Rolle verlangt genau zwei Geräte `1002:73bf` mit `amdgpu`,
ordnet über sysfs ausschließlich deren Render-Nodes zu, prüft `/dev/kfd` und
leitet die minimal erforderlichen Gerätegruppen aus dem Istzustand ab. Auf dem
Host sind dies `/dev/kfd`, `/dev/dri/renderD128` und
`/dev/dri/renderD129`, jeweils mit Gruppe `render`.
Der Check-Modus prognostizierte nur die ergänzende Gruppenmitgliedschaft für
`gnai-production` und `gnai-testing`. Der reale Lauf bestätigte danach für
beide Konten Lese- und Schreibzugriff auf alle drei Geräte. Ein erster
Wiederholungslauf deckte auf, dass `workload_accounts` die Gruppe zunächst
entfernte und `amd_gpu_access` sie wieder hinzufügte. `render` wurde deshalb in
den expliziten Sollzustand der beiden KI-Konten übernommen; das Encoding-Konto
bleibt ohne GPU-Gruppe. Der korrigierte Gesamtlauf endete mit
`ok=55 changed=0 unreachable=0 failed=0`. Weder ein ROCm-Image noch ein
Container oder eine Inferenz wurden dabei ausgeführt.
---
## 15. Abschlussgrenze dieses Entwurfs
Mit Version 0.9 sind der aktuelle Planungsstand, der ausdrücklich begrenzte
Mit Version 0.11 sind der aktuelle Planungsstand, der ausdrücklich begrenzte
Ansible-Preflight, der verifizierte Deployment-Account und die angewendete
minimale Host-Baseline, die Podman-/Quadlet-Grundruntime und die getrennten
Workload-Konten persistiert.
Workload-Konten sowie deren begrenzter AMD-Gerätezugriff persistiert.
Insbesondere wurden noch nicht:
- ein Gitea Runner angelegt,
- Gitea Actions oder Schutzregeln konfiguriert,
- GPU- oder Anwendungsrollen, ein Production-Inventar oder ein Vault erstellt,
- eine ROCm-Container- oder Anwendungsrolle, ein Production-Inventar oder ein
Vault erstellt,
- eine Production- oder Testing-Umgebung eingerichtet oder voneinander
abgenommen,
- GPU-Komponenten oder Anwendungen auf dem KI-Server installiert,
- ROCm-Userspace, GPU-Container oder Anwendungen auf dem KI-Server installiert,
- Backups oder Rollbacks praktisch getestet oder
- die geplante Deploymentkette technisch abgenommen.
Die nächsten technischen Schritte sind der begrenzte GPU-Zugriffstest für die
beiden KI-Konten und die Festlegung von Zeitfenster sowie Ressourcenlimits im
getrennten Encoding-Plan. Darauf bauen die jeweiligen Fachrollen und SOPs auf.
Der nächste technische Schritt ist ein begrenzter rootless ROCm-Container-Test
für beide KI-Konten, der beide Karten als `gfx1030` erkennen muss. Darauf bauen
die `llama.cpp`-Rolle, das initiale Testing-Modellprofil und später LiteLLM auf.
+4 -1
View File
@@ -16,6 +16,7 @@
| 2026-09-18 | 1.3 | spektr | Workstation-spezifische Schlüsselpfade und Host-Fingerprint in den lokalen Arbeitskontext verlagert. |
| 2026-09-19 | 1.4 | spektr | Persönlichen Login `ghost` und Agentenlogin `ghost-agent` als erfolgreich getestet dokumentiert. |
| 2026-09-20 | 1.5 | spektr / Codex | Deployment- und getrennte rootless Workload-Konten mit festen IDs und Zuständigkeiten ergänzt. |
| 2026-09-20 | 1.6 | spektr / Codex | `render`-Mitgliedschaft und geprüfte AMD-Geräterechte der beiden KI-Konten ergänzt. |
---
@@ -74,7 +75,8 @@ Die drei Workload-Konten besitzen nicht überlappende SubUID-/SubGID-Bereiche,
eigene Daten- und Podman-Stores sowie persistente systemd-User-Manager. Ihre
rootverwalteten Quadlet-Verzeichnisse liegen unter
`/etc/containers/systemd/users/<UID>`. Direkte SSH-Schlüssel werden dort nicht
hinterlegt.
hinterlegt. `gnai-production` und `gnai-testing` gehören zusätzlich zur
Gerätegruppe `render`; `gn-encoding` erhält keinen GPU-Zugriff.
---
@@ -188,6 +190,7 @@ sudo systemctl restart ssh
* [ ] **Benutzerprüfung:** `ghost` und `debugofen` können ihre vorgesehenen sudo-Rechte nutzen; `ghost-agent` bleibt ohne sudo-Zuweisung.
* [x] **Workload-Konten:** `gnai-production`, `gnai-testing` und `gn-encoding` besitzen getrennte rootless Podman-Stores und aktive persistente User-Manager; geprüft am 20.09.2026.
* [x] **GPU-Geräterechte:** Beide KI-Konten können `/dev/kfd`, `/dev/dri/renderD128` und `/dev/dri/renderD129` lesen und schreiben; das Encoding-Konto gehört nicht zu `render`; geprüft am 20.09.2026.
* [ ] **Netzwerk:** Server ist unter der statischen IP `10.0.0.90` erreichbar. Ausgehender Datenverkehr funktioniert (`ping google.com`).
* [x] **SSH-Aliase:** `ghostnet-ai-ghost` meldet `ghost`, `ghostnet-ai-agent` meldet `ghost-agent`. Beide Zugänge wurden am 19.09.2026 bestätigt. Die Passphrase des persönlichen Schlüssels ist keine Server-Passwortanmeldung.
* [ ] **Root-Sperre:** Direct-Login als Root per SSH wird abgelehnt (`ssh root@10.0.0.90` -> *Permission denied*).