ci / test (push) Failing after 23s
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
298 lines
33 KiB
Markdown
298 lines
33 KiB
Markdown
# Betrieb: Skill-Lebenszyklus, Hermes, Pflege, Freischaltung, Proxmox-Cluster
|
||
|
||
## 1. Lebenszyklus eines Skills
|
||
|
||
```
|
||
Hermes schreibt SKILL.md
|
||
│
|
||
draft ──(Mensch/Agent: aktivieren)──▶ active, Stufe 0
|
||
│ läuft auf dem starken Modell mit Skill-Text,
|
||
│ sammelt verifizierte Fälle
|
||
Service-Routine: stabil ≥ 95 % ────┤
|
||
▼
|
||
request_graduation (Freigabe)
|
||
│ Hermes erzeugt Varianten →
|
||
│ POST /v1/skills/{name}/graduate
|
||
▼
|
||
promote_skill (Freigabe) ──▶ active, Stufe 1 (schwaches Modell)
|
||
│
|
||
Drift (Pass-Rate < 80 %) ──▶ demote_skill (automatisch) ──▶ demoted (Stufe 0)
|
||
│
|
||
≥ 300 Trainingsfälle, Stufe-1-Rate < 90 % ─▶ train_adapter (Freigabe) ──▶ Lifecycle-Pipeline
|
||
```
|
||
|
||
Stufe 2 (Programm) und Stufe 3 (Classifier) gibt es, wenn der Skill `execution: program|classifier` deklariert und die passende Stufe freigegeben ist. Programme liegen in `knaster/programs.py` und gehen wie normaler Code durch ein Review. Classifier entstehen aus gelabelten Fällen (`EmbeddingClassifier.train`) und liegen unter `config/classifiers/<name>.json`.
|
||
|
||
## 2. Rolle von Hermes
|
||
|
||
Hermes läuft als Skill-Autor mit dem starken Modell. Er zeigt auf LiteLLM, Alias `tier0-strong`. Hermes steht **nicht** im Pfad jeder Anfrage.
|
||
|
||
| Auslöser | Hermes tut | Schnittstelle |
|
||
|---|---|---|
|
||
| Job `graduate_skill` (über n8n `knaster-jobs`) | 10–30 realistische Varianten erzeugen, Graduierung aufrufen. Schlägt sie fehl, den Skill anhand von `failures` überarbeiten | `POST /v1/skills/{name}/graduate` |
|
||
| Job `merge_skills` | zwei Skills zu einem zusammenführen, alten Skill auf `retired` setzen | Git-Commit in `skills/`, dann `POST /v1/skills/reload` |
|
||
| Neue Aufgabenklasse (häufige `no_skill`-Fälle) | neuen `SKILL.md`-Entwurf schreiben (`status: draft`) | Git-Commit + Reload |
|
||
|
||
Skills sind Dateien im Repo. Jede Änderung ist dadurch ein nachvollziehbarer Commit und lässt sich zurückdrehen.
|
||
|
||
## 3. Pflege per Prompt (Beispiele)
|
||
|
||
```
|
||
> routine
|
||
Service-Routine
|
||
Befunde:
|
||
- Drift: zusammenfassung Pass-Rate 72% (n=40)
|
||
Automatisch erledigt:
|
||
- P-12: Skill zusammenfassung auf Stufe 0 zurückstufen → Skill zusammenfassung auf Stufe 0 zurückgestuft
|
||
Offene Vorschläge (genehmige P-x / lehne P-x ab):
|
||
- P-13 [low] LoRA-Adapter für rechnungen trainieren – 412 verifizierte Fälle, Stufe-1-Rate 86%
|
||
|
||
> genehmige P-13
|
||
P-13 executed: Kandidat 7f3e… (adapter) angelegt und Build gestartet
|
||
|
||
> wechsle tier1-general auf qwen3-32b-awq
|
||
Vorschlag P-14 [medium] Basismodell von tier1-general auf qwen3-32b-awq wechseln …
|
||
Bestätige mit „genehmige P-14“.
|
||
```
|
||
|
||
Freitext, der keinem Befehl entspricht, bildet ein kleines Modell auf einen Befehl ab. Der Agent fragt dann mit „Meinst du …?“ zurück.
|
||
|
||
## 4. Dry-Run aufheben (schrittweise)
|
||
|
||
1. Eine Woche mit `KNASTER_DRY_RUN=true` laufen lassen. Die Healer-Ergebnisse stehen als `[dry-run] …` in den Antworten bzw. Logs.
|
||
2. `KNASTER_DRY_RUN=false` setzen. Interne Aktionen (Slot-Status, Skill-Stufen, Kandidaten) liefen schon vorher. Jetzt werden auch Jobs per Webhook verschickt.
|
||
3. Infrastruktur-Aktionen einzeln freischalten, über `config/healer-rules.yaml` → `enabled_actions: [restart_container]`, später `restart_vm`. `reboot_host` bleibt freigabepflichtig (Policy) und läuft nur im Wartungsfenster.
|
||
|
||
## 5. Rollback-Wege
|
||
|
||
| Was | Wie |
|
||
|---|---|
|
||
| Adapter/Basismodell | Prompt `rollback <slot>` → Freigabe |
|
||
| Skill-Stufe | Prompt `stufe <skill> zurück` bzw. Git-Revert der SKILL.md |
|
||
| Treiber/Engine | Proxmox-Snapshot der GPU-VM (vor jedem Update anlegen) |
|
||
| Konfiguration | Git-Revert und Neustart des Router-Containers |
|
||
|
||
## 6. Proxmox: Updates und Neustarts (rollierend, Knoten für Knoten)
|
||
|
||
Stand 12.10.2026, getestet mit dem Wartungsmodus auf pve44. Gilt für pve44, pve46, pve48, pve52 und pve61.
|
||
|
||
**Wichtig vorab**
|
||
- Auf den GPU-Knoten gehört die M4000 (Boot-Grafik) der VM. Nach dem frühen Start bleibt der lokale Bildschirm schwarz. Zugang nur per SSH oder Weboberfläche; das GRUB-Menü ist beim Start noch sichtbar (Notzugang: Eintrag bearbeiten, `initcall_blacklist=sysfb_init` entfernen).
|
||
- pve61 hängt am Netz nur über die USB-NIC im Dock. Neustart nur, wenn jemand vor Ort ist. Kommt er nach etwa 2 Minuten nicht zurück: Dock aus- und wieder einstecken.
|
||
- Nie zwei Knoten gleichzeitig. Mit 5 Stimmen bleibt das Quorum bei einem fehlenden Knoten erhalten.
|
||
|
||
**Ablauf je Knoten**
|
||
1. Vorher: `pvecm status` (Quorate: Yes), `ha-manager status`, `pvesr status` (alle Jobs OK).
|
||
2. Wartungsmodus: `ha-manager crm-command node-maintenance enable <knoten>`. HA-Gäste ziehen um (`infra`: Test 36 s nach pve48).
|
||
3. GPU-VM des Knotens sauber stoppen: `qm shutdown <vmid>` (244/246/248/252). Sie hat kein HA und kann nicht wandern (Passthrough).
|
||
4. Update: `platform/proxmox/scripts/bootstrap-node.sh <knoten>` vom Laptop (idempotent, meldet `Neustart nötig`), dann `reboot`.
|
||
5. Nachher prüfen: Kernel, `systemctl --failed`, `ip -br a show vmbr0`, NTP; auf GPU-Knoten `lspci -nnk -d 10de:` → überall `vfio-pci`.
|
||
6. GPU-VM startet von selbst (`onboot`). Danach auf ops: `cd /root/cluster-knaster/platform/proxmox/tofu && . /root/.config/knaster/tofu.env && bash ../scripts/refresh-vm-access.sh` (DHCP-Adressen können wechseln, Hostschlüssel kommen über den Gast-Agenten) und `ansible gpu_vms -m shell -a "nvidia-smi -L"`.
|
||
7. Wartungsmodus aus: `ha-manager crm-command node-maintenance disable <knoten>`. HA holt `infra` auf seinen Heimatknoten pve44 zurück (Test 41 s).
|
||
|
||
**GPU-VMs aktualisieren**
|
||
- Pakete per `apt` in der VM, die NVIDIA-Pakete sind auf `hold` (R580). Vor Treiberwechseln einen Snapshot anlegen: `qm snapshot <vmid> vor-treiber`.
|
||
- Nach einem VM-Neustart immer `refresh-vm-access.sh`, solange es keine DHCP-Reservierungen gibt (D5).
|
||
|
||
## 7. Infra-Stack und Monitoring (LXC `infra`)
|
||
|
||
Stand 12.10.2026 (Task 5.2). Der LXC `infra` (vmid 101, HA, Heimat pve44, Replikation nach pve48/pve52) führt einen Compose-Stack `knaster-infra`: Router (Dry-Run), TTS (Piper), Postgres, Prometheus, Alertmanager, Grafana, pve-exporter, node-exporter, autoheal. Kein eigener LiteLLM (D4).
|
||
|
||
**Adressen** (DHCP, die IP kann sich nach einem Umzug ändern; aktuelle IP: `ansible infra -m debug -a var=ansible_host` auf ops oder `refresh-vm-access.sh`)
|
||
|
||
| Dienst | URL | Anmeldung |
|
||
|---|---|---|
|
||
| Grafana | `http://<infra-IP>:3000` | `admin` bzw. `kiosk` (Viewer) |
|
||
| Prometheus | `http://<infra-IP>:9090` | ohne (nur LAN) |
|
||
| Alertmanager | `http://<infra-IP>:9093` | ohne (nur LAN) |
|
||
| Router | `http://<infra-IP>:8080` | Bearer-Token |
|
||
|
||
**Wo liegt was**
|
||
- Konfiguration im Repo: `deploy/compose/infra`, `deploy/prometheus`, `deploy/alertmanager`, `deploy/grafana` (Datasource, Dashboards). Im LXC als Kopie unter `/opt/knaster/repo` (wird bei jedem Ansible-Lauf abgeglichen, dort nichts ändern).
|
||
- Geheimnisse nur im LXC, nie im Repo: `/opt/knaster/infra/.env` (0600) mit `POSTGRES_PASSWORD`, `KNASTER_API_TOKEN`, `GRAFANA_ADMIN_PASSWORD`, `GRAFANA_KIOSK_PASSWORD` (beim ersten Lauf mit `openssl rand` erzeugt, danach nie überschrieben). Lesen: `ssh root@<infra-IP> grep ^GRAFANA_KIOSK_PASSWORD= /opt/knaster/infra/.env` (für den Kiosk, Task 5.3).
|
||
- `/opt/knaster/infra/secrets/router_token` (Kopie von `KNASTER_API_TOKEN` für den Alertmanager) und `secrets/pve-exporter.env` (Kopie von ops `/root/.config/knaster/pve-exporter.env`, Token `monitoring@pve!prom`, PVEAuditor).
|
||
- Einstellungen ohne Geheimnis (Dry-Run, `LITELLM_BASE_URL` …) stehen in `platform/proxmox/ansible/roles/infra-stack/defaults/main.yml` und werden bei jedem Lauf in die `.env` geschrieben.
|
||
|
||
**Ausrollen** (auf ops, idempotent): `cd /root/cluster-knaster && git pull`, Adressen auffrischen (Abschnitt 6, Schritt 6), dann in `platform/proxmox/ansible`:
|
||
- `ansible-playbook playbooks/monitoring.yml` – Exporter in den GPU-VMs (ohne Treiber-Rolle, kein Neustart) und Stack im LXC.
|
||
- Ein Image-Update heißt: Tag im Compose-File ändern, committen, Playbook laufen lassen.
|
||
|
||
**Ziele der GPU-VMs (file_sd) auffrischen**
|
||
Die GPU-VMs haben DHCP-Adressen (D5). `platform/proxmox/scripts/monitoring-targets.py` schreibt aus `tofu output -json gpu_vms` und der Registry `node.json` und `gpu.json` (Labels `instance` = VM-Name, `pve_node`, `vmid`, `slot`, `slots`). Nach jedem VM-Neustart:
|
||
```bash
|
||
cd /root/cluster-knaster/platform/proxmox/tofu && . /root/.config/knaster/tofu.env && bash ../scripts/refresh-vm-access.sh
|
||
cd ../ansible && ansible-playbook playbooks/monitoring-targets.yml
|
||
```
|
||
Prometheus liest die Dateien in `/opt/knaster/infra/file_sd` selbst neu, ein Neustart ist nicht nötig.
|
||
|
||
**Exporter in den GPU-VMs:** `prometheus-node-exporter` (Port 9100, Ubuntu-Paket, liest `/var/lib/node_exporter/textfile` mit den Xid-Zählern) und `nvidia_gpu_exporter` (Port 9835, nvidia-smi; DCGM unterstützt Maxwell nicht). ufw-Regeln mit Kommentar `knaster-monitoring` erlauben beide Ports aus `192.168.100.0/24`; ufw selbst wird von der Rolle nicht ein- oder ausgeschaltet.
|
||
|
||
**Alerts:** Alertmanager meldet an den Router (`/v1/healer/alertmanager`, Healer im Dry-Run). Der Receiver für n8n `knaster-notify` ist in `deploy/alertmanager/alertmanager.yml` nur vorbereitet (auskommentiert). Test: `docker exec knaster-infra-alertmanager-1 amtool --alertmanager.url=http://localhost:9093 alert add test severity=warning` und `… alert query`.
|
||
|
||
**Dashboards** (Ordner `knaster`, Startseite „knaster – Übersicht“): Proxmox via Prometheus (grafana.com 10347), Node Exporter Full (1860), Nvidia GPU Metrics (14574), knaster – Übersicht (eigenes), k3s – Pods (eigenes, Task 6.5).
|
||
|
||
**k3s-Pods (Task 6.5, seit 19.10.):** Prometheus fragt drei Jobs ab. `kubelet` und `cadvisor` gehen direkt an Port 10250 jedes k3s-Knotens (TLS gegen die k3s-CA, Token des ServiceAccounts `prometheus-infra`, darf nur `nodes/metrics` lesen). `kube-state-metrics` geht an NodePort 30808 (bevorzugt auf der Dienst-VM k3s). Die Ziele erzeugt `monitoring-targets.py` aus `kubectl get nodes` auf ops (`kubelet.json`, `kube-state-metrics.json`), sie werden mit `playbooks/monitoring-targets.yml` aufgefrischt wie die der GPU-VMs. Token und CA: auf ops `/root/.config/knaster/k3s-prometheus.token` und `k3s-ca.crt`, im LXC `/opt/knaster/infra/secrets/k3s/` (nur dieser Ordner ist in Prometheus eingehängt). Von cAdvisor bleiben nur CPU, Speicher, OOM und Netz je Pod (`metric_relabel_configs`), weil infra nur 4 GB hat. Alerts `K3s*` (Knoten nicht bereit, CrashLoop, Deployment ohne bereite Pods, Speicher > 90 % des Limits) melden nur, der Healer greift nicht ein. Ausrollen: `ansible-playbook playbooks/monitoring-k3s.yml` (Cluster-Teil, dann Infra-Stack).
|
||
|
||
**Achtung HA-Umzug und ZFS-Seiten-Cache (gefunden 12.10.2026 bei der HA-Probe)**
|
||
Nach `zfs receive` (Replikation) liefert ein Knoten für Dateien, die dort früher per mmap geöffnet waren, weiter den alten Inhalt aus seinem Seiten-Cache (ZFS 2.4.4, Kernel 7.0.14; Beleg auf pve44: `zfs diff <snap>` leer, `written` 0, md5 der Datei trotzdem ungleich der im Snapshot). Folge beim ersten Umzug nach pve48: dockerd las veraltete bbolt-Datenbanken, brach mit `panic: invalid freelist page` ab, danach fehlten Netzwerk und Volume-Labels. Betroffen ist jeder replizierte LXC mit mmap-Nutzern (Docker, Prometheus-TSDB, später `grp-test`), sobald er auf einen Knoten zurückkehrt, auf dem er schon lief.
|
||
- Schutz im Container: Dienst `knaster-drop-stale-cache.service` (Rolle `infra-stack`) verwirft vor containerd/docker per `posix_fadvise(DONTNEED)` den Cache aller Dateien des Containers. Protokoll: `journalctl -u knaster-drop-stale-cache -b` (zeigt geänderte Datenbank-Prüfsummen).
|
||
- Eine Lösung auf den Knoten (OpenZFS-Fehler melden bzw. Update, Hookscript) entscheidet der Nutzer.
|
||
|
||
**Wiederherstellung, falls Docker nach einem Umzug nicht startet** (im LXC, nichts löschen):
|
||
1. `systemctl stop docker containerd`; `journalctl -u docker -b | grep -A12 panic` zeigt die betroffene Datenbank (z. B. `buildkit/cache.db`).
|
||
2. Die defekte Datenbank bzw. das Verzeichnis beiseiteschieben, z. B. `mv /var/lib/docker/buildkit /var/lib/docker/buildkit.defekt-<datum>`.
|
||
3. `systemctl start docker`, dann `docker compose -p knaster-infra --env-file /opt/knaster/infra/.env -f /opt/knaster/repo/deploy/compose/infra/docker-compose.yml up -d --force-recreate` (Volumes bleiben, kein `down -v`).
|
||
4. Danach auf ops `ansible-playbook playbooks/monitoring.yml --limit infra`.
|
||
|
||
## 8. Zugang: Benutzer, Rollen, 2FA, Token (Phase 4.1/4.2)
|
||
|
||
Stand 13.10.2026, Quelle `sites/knaster/proxmox.yaml` (`access`), ausgerollt mit `playbooks/cluster.yml` (Rolle `pve-cluster`).
|
||
|
||
| Konto | Zweck | Rechte | Geheimnis liegt |
|
||
|---|---|---|---|
|
||
| `r.rapp@pve` | persönlicher Admin (Weboberfläche) | Gruppe `admins` = Administrator auf `/` | Passwort `PVE_ADMIN_R_RAPP_PASS` in `it/proxmox/.env`; Passwort, TOTP und Recovery-Keys in Password Depot (Datei `tfa-r.rapp.txt` am 13.10. gelöscht) |
|
||
| `root@pam` | Notfallzugang, SSH-Schlüssel, Ansible | alles | Passwörter `PVExx_PASS` in `.env`, Schlüssel `keys/id_ed25519_pve` |
|
||
| `tofu@pve!ops` | OpenTofu auf ops | VM-, Storage-, Mapping-, Pool-Rechte | ops `/root/.config/knaster/tofu.env` |
|
||
| `monitoring@pve!prom` | pve-exporter | PVEAuditor | ops `/root/.config/knaster/pve-exporter.env`, Kopie in infra |
|
||
| `healer@pve!knaster` | Healer (SPEC-healer) | Rolle `KnasterHealer` (`VM.PowerMgmt`, `VM.Audit`) nur auf `/pool/gpu` | ops `/root/.config/knaster/healer.env` |
|
||
|
||
- **Anmeldung Weboberfläche:** Benutzer `r.rapp`, Realm „Proxmox VE authentication server“, danach TOTP-Code. Die Recovery-Keys gelten je einmal.
|
||
- **TOTP:** liegt in Password Depot (Feld „Einmalpasswort / 2FA“). Für neue Konten schreibt `pve-admin-user.sh` eine Datei `tfa-<name>.txt`; Secret und Recovery-Keys daraus in Password Depot übernehmen und die Datei löschen.
|
||
- **Anmeldung und Vertretung:** `docs/ANMELDUNG.md` (auch Browser-Falle: Bitwarden-Leiste blockiert den TOTP-Dialog, Inkognito oder Domain ausschließen).
|
||
- **Neues Admin-Konto:** Name in `access.admin_users` eintragen, `playbooks/cluster.yml` auf ops, dann vom Laptop `platform/proxmox/scripts/pve-admin-user.sh <name>` (setzt Passwort, TOTP, Recovery-Keys; idempotent).
|
||
- **2FA für root@pam** ist bewusst nicht gesetzt: root bleibt Notfallzugang und läuft über SSH-Schlüssel. Wenn gewünscht, gleiches Vorgehen über `pvesh create /access/tfa/root@pam` – vorher den Code in einer App haben, sonst sperrt man sich aus der Weboberfläche aus (SSH bleibt).
|
||
- **Token rotieren:** `pveum user token remove <user> <token>`, Datei auf ops löschen, Playbook erneut (Healer) bzw. Token neu anlegen und Datei schreiben (tofu, monitoring).
|
||
- Verloren gegangenes TOTP: `pvesh get /access/tfa/<user>` listet die Einträge, `pvesh delete /access/tfa/<user>/<id>` entfernt sie, danach das Skript erneut.
|
||
|
||
## 9. Benachrichtigungen (Phase 4.3)
|
||
|
||
Ziel `knaster-mail` (Sendmail an `ki@mygodel.de`, aus `access.mail_to`), Matcher `knaster-alles` ohne Filter (Backup, Fencing, Replikation, Paket-Updates), `default-matcher` abgeschaltet (sonst doppelt). Postfix auf den Knoten liefert direkt an die MX von mygodel.de (13.10. getestet: `250 … Queued mail for delivery`). Kein SMTP-Relay, keine Zugangsdaten.
|
||
|
||
- Test: `pvesh create /cluster/notifications/targets/knaster-mail/test` auf einem Knoten, Zustellung im Journal: `journalctl -u postfix --since -10min | grep status=`.
|
||
- Landet die Mail im Spam: Absender `root@pveXX.buero-godel.local` hat kein SPF. Dann in Exchange Online eine Transportregel oder einen Connector für die Knoten-IPs anlegen.
|
||
- Empfänger ändern: `access.mail_to` in der Site-Datei, dann `pvesh set /cluster/notifications/endpoints/sendmail/knaster-mail --mailto <adresse>` (das Playbook legt nur an, es ändert bestehende Ziele nicht).
|
||
|
||
## 10. Firewall (Phase 4.4)
|
||
|
||
Datacenter-Firewall aktiv auf allen Knoten (`/etc/pve/firewall/cluster.fw`, Vorlage `roles/pve-cluster/templates/cluster.fw.j2`). Eingehend DROP, ausgehend ACCEPT.
|
||
|
||
- **IPSET `admin`:** die fünf Knoten, Admin-Laptop `.151` (WLAN) und `.181` (USB-LAN im Dock), appsrv08 `.29`, ops `.173`, infra `.188`. Nur diese erreichen 22, 8006, 3128, 5900–5999 und 60000–60050. Alle anderen Adressen im LAN bekommen auf diesen Ports DROP, Ping aus dem LAN geht. Corosync zwischen den Knoten erlaubt Proxmox selbst.
|
||
- **Warum eigene Regeln statt IPSET `management`:** Proxmox ergänzt `management` immer um das lokale Netz (`local_network`, hier das ganze /24). Eine Einschränkung auf einzelne Adressen wirkt deshalb nur mit ACCEPT für `+admin` und anschließendem DROP für das /24 (13.10. geprüft: aus einer GPU-VM sind 22 und 8006 zu, vom Laptop und aus infra offen).
|
||
- **DHCP-Risiko (D5):** Seit 14.10. haben ops (.173) und infra (.188) feste Adressen (infra über `containers.infra.ip` in der Site-Datei und tofu, ops per `pct set 100 -net0 …ip=…` auf pve46; DNS .120, Suchdomäne buero-godel.local). Am DC sind sie noch nicht reserviert, der Support soll die Reservierungen nachziehen (Task 0.2). Seit 19.10. hat der Laptop Reservierungen (WLAN .151, Dock .181), ebenso pve44 und pve52. Für infra, pve46 und pve48 ist laut DC derzeit keine Reservierung möglich; sie sind statisch eingestellt, das Restrisiko ist eine doppelt vergebene Adresse. 14.10. passiert: Laptop kam per Dock mit .181 und war ausgesperrt, Rückweg über ops hat funktioniert. Wechselt eine Adresse, ist diese Quelle ausgesperrt. Rückweg: von einer noch zugelassenen Adresse (z. B. `ssh ops`, dann `ssh pve44`) die Adresse in `sites/knaster/proxmox.yaml` ändern und das Playbook laufen lassen, oder direkt `cluster.fw` bearbeiten. Letzter Rückweg: Konsole am Knoten, dort `pve-firewall stop` bzw. `enable: 0` in `cluster.fw`.
|
||
- **Totmannschalter:** Das Playbook stellt bei jeder Änderung einen Timer (`knaster-fw-totmann`, 600 s), der `enable: 0` schreibt, und hebt ihn erst nach erfolgreicher Prüfung (SSH und 8006 zu allen Knoten von ops aus) wieder auf. Bricht der Lauf ab, geht die Firewall von selbst wieder aus.
|
||
- Neue Verwaltungsquelle (z. B. zweiter Laptop): Eintrag unter `firewall.management`, Playbook laufen lassen.
|
||
- Gast-Firewalls (VM/LXC) sind nicht aktiviert; die GPU-VMs schützen ihre Ports mit ufw (Abschnitt 7).
|
||
|
||
## 11. Zertifikat der Weboberfläche (Phase 4.6)
|
||
|
||
Die Knotenzertifikate stammen von der Cluster-CA (`/etc/pve/pve-root-ca.pem`, gültig bis 2036) und enthalten die IP-Adresse als SAN. Es reicht, die CA einmal auf dem Laptop als vertrauenswürdig einzutragen, dann sind `https://192.168.100.2xx:8006` für alle fünf Knoten ohne Warnung erreichbar.
|
||
|
||
- Kopie der CA: `it/proxmox/pve-root-ca.pem` (SHA-256 `A2:71:C0:AB:…:77:3E`, zum Abgleich `openssl x509 -in … -noout -fingerprint -sha256`).
|
||
- Import ohne Adminrechte (Benutzerspeicher, Windows fragt einmal nach): `certutil -user -addstore Root C:\Users\r.rapp\it\proxmox\pve-root-ca.pem`. Danach Browser neu starten.
|
||
- Kein ACME/Let's Encrypt: die Knoten haben keine öffentlichen Namen (IP-Regel, kein DNS).
|
||
|
||
## 12. Netzwerk-Prüfung (Stand 13.10.2026)
|
||
|
||
| Knoten | NIC | Treiber | Link | Bridge |
|
||
|---|---|---|---|---|
|
||
| pve44, pve46, pve48, pve52 | Intel onboard `nic0` | `e1000e` | 1 GBit/s | `vmbr0` → `nic0`, kein VLAN-aware |
|
||
| pve61 | USB-NIC im Dock `enx3ce1a1c0839d` | `r8152` | 1 GBit/s | `vmbr0` → USB-NIC, ein Link-Reset seit Start |
|
||
|
||
- Keine `e1000e`-Hänger („Detected Hardware Unit Hang“) im Kernel-Log. Sollten sie auftreten: `ethtool -K nic0 tso off gso off` als bekannter Ausweg, dann in `/etc/network/interfaces` festhalten.
|
||
- **VLAN:** nicht aktiviert (Global Constraint „vorerst kein VLAN“). Wenn nötig: `bridge-vlan-aware yes` und `bridge-vids 2-4094` auf `vmbr0`, `ifreload -a`; auf pve61 nur vor Ort (USB-NIC).
|
||
- Eine zweite NIC für ein getrenntes Corosync- oder Migrationsnetz gibt es auf keinem Knoten. Migration läuft `secure` über das LAN.
|
||
- Feste Adressen: Knoten sind statisch, Gäste und Laptop per DHCP (D5 offen, Task 0.2). Erst mit Reservierungen ist die Firewall-Liste dauerhaft stabil.
|
||
|
||
## 13. Testvorgehen
|
||
|
||
`platform/proxmox/scripts/healthcheck.sh` (Laptop oder ops, nur lesend) prüft in einem Lauf: Quorum und Stimmen, HA-Dienste, Replikation, Firewall aktiv und Regel `+admin`, Matcher, Benutzer und 2FA; je Knoten Kernel, fehlgeschlagene Dienste, Knoten-Firewall, NTP, `vmbr0`, Platz auf `/`, `tank`, Zertifikatslaufzeit, GPU-Bindung an `vfio-pci`, NIC-Hänger, Weboberfläche; dazu den Negativtest aus einer GPU-VM. Ausgabe `OK`/`FEHLER` je Prüfung, Exit 1 bei Fehlern.
|
||
|
||
Wann laufen lassen:
|
||
1. Nach jedem Playbook-Lauf gegen die Knoten (`site.yml`, `cluster.yml`) und nach jedem Neustart (Abschnitt 6, Schritt 5).
|
||
2. Vor einer Änderung an Firewall, Benutzern oder Netzwerk als Vergleichsbasis.
|
||
3. Wöchentlich als Routine; Grafana (Abschnitt 7) deckt den Rest kontinuierlich ab.
|
||
|
||
Manuelle Ergänzungen, die das Skript nicht abdeckt: Anmeldung mit `r.rapp@pve` und TOTP in der Weboberfläche, Testmail (Abschnitt 9), Dock-Test an pve61 (Task 1.3, vor Ort), Wartungsmodus-Probe (Abschnitt 6).
|
||
|
||
## 14. Kubernetes (k3s) und Portainer (D6, seit 15.10.2026)
|
||
|
||
Die GPU-VMs bilden mit der Dienst-VM `k3s` einen k3s-Cluster (v1.36.5+k3s1). Steuerung auf `k3s` (VM 110 auf pve48, tank, HA mit Replikation nach pve52 und pve44, 2 Kerne, 3 GB), Agents `gpu-pve44/46/48/52`. Docker bleibt in den VMs für noch nicht umgezogene Compose-Stacks (llama.cpp auf pve48, Exporter).
|
||
|
||
| Was | Wo |
|
||
|---|---|
|
||
| Server-Adresse | per DHCP (heute 192.168.100.124, MAC `bc:24:11:a5:d5:52`); Agents nutzen den Namen `k3s.knaster` aus `/etc/hosts` (Rolle k3s-agent). Ändert sich die Adresse: `refresh-vm-access.sh`, dann `playbooks/k3s.yml` |
|
||
| Token und kubeconfig | nur auf ops: `/root/.config/knaster/k3s.env`, `/root/.kube/config`; `kubectl` liegt auf ops |
|
||
| Manifeste | `deploy/k8s/` (RuntimeClass `nvidia`, NVIDIA Device Plugin v0.20.1, Portainer Agent 2.45.2, `nex-speech/`, `nex-searxng/`, `rustdesk/`) |
|
||
| Playbooks | `playbooks/k3s.yml` (Server, Agents, Grundausstattung), `playbooks/nex-speech-k3s.yml`, `playbooks/nex-searxng-k3s.yml`, `playbooks/rustdesk-k3s.yml` (Umzüge), `playbooks/monitoring-k3s.yml` (Pod-Monitoring) |
|
||
| Monitoring | kube-state-metrics v2.19.1 in `kube-system` (NodePort 30808), Prometheus auf infra liest kubelet/cAdvisor; Grafana-Dashboard „k3s – Pods“ (Abschnitt 7). `kubectl top` zeigt weiter den metrics-server von k3s |
|
||
| GPU in Pods | `runtimeClassName: nvidia` + `resources.limits: {nvidia.com/gpu: 1}` vergibt seit 19.10. nur die M4000 (Device Plugin sieht nur Index 0, allocatable 1/1/1/1). Die P2000 gehört fest llama.cpp (`NVIDIA_VISIBLE_DEVICES=<UUID>`, kein `nvidia.com/gpu`). Knoten-Labels `knaster.gpu=true`, `knaster.gpu.m4000=1`, `knaster.gpu.p2000=1` |
|
||
| Portainer | Umgebung „Kubernetes → Agent“ mit Adresse `<IP eines Knotens>:30778`, z. B. `192.168.100.124:30778` (Agent-Pod läuft im Namespace `portainer`) |
|
||
|
||
- **Zuteilung (seit 15.10.):** jede GPU-VM hat alle 8 Threads und 20 GB ihres Knotens (`vms.*.cores/memory_mb` in der Site-Datei), der Cluster zeigt damit 34 vCPU und 83 GB. Der Rest je Knoten (32 GB): Proxmox ~3 GB, ZFS-ARC 2 GB (`cluster.zfs_arc_max_mb`, Rolle pve-host, sofort wirksam), dazu ops 2 GB (pve46), infra 4 GB (pve44), k3s 3 GB (pve48). RAM der GPU-VMs ist wegen PCI-Passthrough fest reserviert. Ändern: Site-Datei, auf ops `tofu apply` (startet nicht automatisch neu, `reboot_after_update = false`), dann je Knoten `qm reboot <vmid>` nacheinander und `kubectl get node` abwarten. Knapp wird es nur im HA-Fall, wenn infra und k3s gleichzeitig auf denselben Ersatzknoten ziehen.
|
||
- **Prüfen:** auf ops `kubectl get nodes` (fünf Knoten Ready), `kubectl get pods -A`, `kubectl -n nex get pods` (nex-stt, nex-tts auf gpu-pve52).
|
||
- **NEX-Sprachdienste** laufen seit 15.10. als Deployments im Namespace `nex` mit Host-Netz, Ports 8178/8179 auf der VM-Adresse wie zuvor; ufw der VM gilt weiter (nur appsrv08). Umzugstest 15.10.: TTS → STT Rundlauf liefert den Text exakt, M4000 belegt, Compose-Stack ist angehalten (Rückweg: `kubectl -n nex delete deploy nex-stt nex-tts`, dann in der VM `docker compose -f /opt/nex-speech/docker-compose.yml start`). `playbooks/nex-speech.yml` bricht deshalb ab, außer mit `-e nex_speech_compose=true`.
|
||
- **NEX-Websuche (SearXNG)** läuft seit 15.10. zusätzlich in k3s (Namespace `nex`, NodePort 30880 auf jeder Knoten-Adresse, `playbooks/nex-searxng-k3s.yml`, Einstellungen = Kopie aus dem nex-Repo `deploy/nex/searxng/settings.yml`). Der Dienst auf appsrv08 läuft parallel weiter, bis die Hermes-Agenten dort auf `SEARXNG_URL=http://<Knoten-IP>:30880` umgestellt sind (nex-Admin `onboarding`), danach den Dienst `searxng` im Portainer-Stack `nex` entfernen.
|
||
- **Portainer CE im Cluster** (seit 15.10., Namespace `portainer-ce`, Pod auf der Dienst-VM k3s, Daten auf deren local-path, `https://192.168.100.124:30779`): Datenbank aus dem Volume `portainer_data` von appsrv08 (Portainer EE 2.45.1) übernommen, mit dem offiziellen Werkzeug `portainer-ee --rollback-to-ce` auf Community Edition zurückgesetzt (laut Portainer-Doku nur nach CE→BE-Upgrade möglich, hat bei der frischen BE-Installation trotzdem funktioniert; CE verweigert eine BE-Datenbank sonst mit Hinweis). Benutzer, Passwörter, API-Schlüssel und Umgebungen sind erhalten, der Schlüssel aus `it/.hermes.env` funktioniert unverändert. Sicherung `/root/.config/knaster/portainer_data.tgz` auf ops (15.10., 2,5 MB). **Umgebungen:** Nr. 4 „godel-local“ war der Docker-Socket von appsrv08 und ist im Cluster tot; auf appsrv08 läuft dafür `portainer_agent` (2.45.1, nur `192.168.100.29:9001`), den ein Admin als neue Umgebung „appsrv08“ (Agent) anlegt und für `godel-deployer` freigibt. Die Kubernetes-Umgebung (Nr. 5, Agent `:30778`) bleibt. **Noch auf appsrv08:** Portainer EE (`:9443`) läuft parallel weiter, nginx leitet `portainer.mygodel.de` dorthin; Umstellung = `proxy_pass` in `/var/docker/nginx/etc/nginx/conf.d/portainer.conf` auf `https://192.168.100.124:30779`, `nginx -t`, Reload (Runbook `IT Verbesserungen/GRP_Linux/Portainer-DNS-Runbook.md`), dann EE stoppen. Für nex-Admin und `.hermes.env` (URL `:9443`, Endpoint 4) zusätzlich Weiterleitung `:9443` → Cluster oder Werte anpassen. Lizenz: CE hat keine Knotengrenze.
|
||
- **Agenten im Cluster (NEX)** (seit 19.10., nex-Spec `docs/specs/2026-10-19-nex-cluster-agenten-design.md` im nex-Repo): Neue Hermes-Agenten entstehen als Deployment im Namespace `nex`, angelegt von nex-admin auf appsrv08 über die Kubernetes-API (`playbooks/nex-k3s.yml`: ServiceAccount `nex-admin` mit Role nur für diesen Namespace, Token auf ops `/root/.config/knaster/nex-admin-k8s.token`, Pull-Secret `gitea-registry` aus `/root/.config/knaster/gitea-registry.env`). Die Pods erreichen die NEX-Dienste auf appsrv08 über Services ohne Selector (`deploy/k8s/nex/bridge.yaml`: nex-router 4100, nex-know 14200 (Host-Port, Service-Port 4200), nex-access 4300, grp-mcp-intern 8790 → `192.168.100.29`, dort als Host-Ports nur auf dieser Adresse veröffentlicht). Nutzeradressen bleiben `https://192.168.100.29:<port>/`: Traefik auf appsrv08 leitet an den NodePort `<port>+20000` weiter. Agenten-Volumes liegen auf `local-path` (an den ersten Knoten gebunden).
|
||
- **RustDesk-Server** (ID-Server hbbs + Relay hbbr) läuft seit 15.10. zusätzlich in k3s (Namespace `rustdesk`, ein Pod auf einem GPU-Knoten, Datenbank auf `local-path`, Service-Typ LoadBalancer: Ports 21115–21119/tcp und 21116/udp auf **jeder** Knoten-Adresse). Das Schlüsselpaar ist dasselbe wie auf appsrv08 (Kopie auf ops `/root/.config/knaster/rustdesk/`, im Cluster als Secret `rustdesk-keys`), der Public Key der Clients `BPhsgjAI8yicpNRA3YsnBzfx78JUuNglrYZSZANVCUY=` bleibt gültig. hbbs nennt den Clients als Relay die Adresse `rustdesk_public_host` (Standard: k3s-VM, Variable im Playbook). **Umstellung der Clients steht aus:** sie tragen heute `192.168.100.29` als ID-Server; nötig sind eine feste Adresse (DHCP-Reservierung der k3s-VM oder DNS-Name am DC) und danach die neue Adresse in den installierten Clients sowie im Installer-Namen des Portainer-Stacks `rustdesk-updater` (`SERVER_HOST`). Bis dahin läuft der Stack `rustdesk` auf appsrv08 weiter.
|
||
- **llama.cpp** (Task 6.1, seit 19.10.) läuft in k3s, Namespace `knaster`, ein Deployment `llamacpp-<vm>` je GPU-VM: Embedding 8081 + Rerank 8082 auf gpu-pve44/46 (zwei Container, eine P2000), Chat 8080 auf gpu-pve48. Host-Netz, Ports und Prometheus-Ziele wie zuvor im Compose. Ausrollen: `playbooks/gpu-llamacpp-k3s.yml` (prüft, dass Index 0 die M4000 ist, wendet das Device Plugin an, dann VM für VM). Rückweg je VM: `kubectl -n knaster delete deploy llamacpp-<vm>`, in der VM `docker compose --project-directory /opt/knaster/gpu-llamacpp start`; `playbooks/gpu-llamacpp.yml` nur mit `-e llamacpp_compose=true`. Embedding mit `-ub 4096`, weil `-ub 8192` 5 GB Rechenpuffer bräuchte.
|
||
- **Images** aus Compose-Builds landen per `docker save | k3s ctr images import -` im containerd von k3s (Rolle nex-speech-k3s); kein Registry nötig. Für neue Dienste ohne Build reicht `image:` aus Docker Hub oder nvcr.io.
|
||
- **ufw in den VMs:** die Rolle k3s-agent erlaubt 10250/tcp, 8472/udp, 30000–32767/tcp aus dem LAN und alles aus dem Pod-Netz 10.42.0.0/16.
|
||
- **Aktualisieren:** `k3s_version` in `roles/k3s-server/defaults` und `roles/k3s-agent/defaults` anheben, `playbooks/k3s.yml`; erst Server, dann Agents (so läuft das Playbook). Kubernetes-Versionen immer nur eine Minor-Stufe je Schritt.
|
||
- **Stromausfall:** VM 110 startet automatisch (onboot, HA). Agents verbinden sich neu, Pods starten mit den Knoten. Sollte `k3s` auf einen anderen Knoten umziehen, bleibt die Adresse (gleiche MAC), sobald die DHCP-Reservierung am DC steht.
|
||
|
||
### 14.x Serveradresse und /etc/hosts der Agents (Befund 20.10.2026)
|
||
|
||
Die k3s-VM hat seit 20.10. die feste Adresse 192.168.100.124 (Site `service_vms.k3s.ip`, Cloud-Init per tofu; davor
|
||
DHCP, nach einem Netzausfall kam .131 und alle Knoten waren NotReady). DHCP-Reservierung am DC für .124
|
||
(MAC bc:24:11:a5:d5:52) bleibt sinnvoll. Die Agents erreichen den Server als `k3s.knaster` über /etc/hosts; cloud-init
|
||
darf die Datei nicht mehr verwalten (Rolle k3s-agent schreibt `/etc/cloud/cloud.cfg.d/99-knaster-hosts.cfg`).
|
||
Symptom bei Verlust: `journalctl -u k3s-agent` zeigt „dial tcp <alte IP>:6443: no route to host“, die Datei
|
||
`/var/lib/rancher/k3s/agent/etc/k3s-agent-load-balancer.json` hält die alte Adresse. Abhilfe: Eintrag in /etc/hosts
|
||
setzen, alte Adresse in der JSON-Datei ersetzen, `systemctl restart k3s-agent` (dauert, Pods laufen weiter).
|
||
nex-admin auf appsrv08 verweist mit NEX_ADMIN_K8S_URL und NEX_K3S_HOST ebenfalls auf .124.
|
||
|
||
## 15. Verwaltung übergeben oder teilen (seit 19.10.2026, „Weg 1“)
|
||
|
||
Die Verwaltung hängt an Personen, nicht an einem Gerät. Einstieg für jede Person ist **ops** (Bastion): eigener
|
||
SSH-Schlüssel dort, von ops aus laufen `kubectl`, Ansible und tofu und erreichen alle Knoten und Gäste. Ein Agent
|
||
(Claude Code o. Ä.) arbeitet auf dem Gerät der Person im Repo und nach [AGENTS.md](../AGENTS.md).
|
||
|
||
**Neue Person aufnehmen**
|
||
1. Die Person erzeugt auf ihrem Gerät einen Schlüssel (`ssh-keygen -t ed25519 -C <name>-<gerät>-<datum>`) und gibt
|
||
nur den öffentlichen Teil (`.pub`) weiter.
|
||
2. Eintrag in `sites/knaster/proxmox.yaml` unter `access.ops_ssh_keys` (`{name, key}`), committen, auf ops
|
||
`git pull` und `ansible-playbook playbooks/admins.yml` (zeigt danach die Fingerabdrücke).
|
||
3. Weboberfläche: Name in `access.admin_users`, `playbooks/cluster.yml`, dann `scripts/pve-admin-user.sh <name>`
|
||
(Passwort, TOTP, Recovery-Keys; Ergebnis in Password Depot). Geräteadresse(n) in `firewall.management`, erneut
|
||
`playbooks/cluster.yml` (Totmannschalter, Abschnitt 10).
|
||
4. Repo: Schreibzugang in Gitea (`gitea.mindboost.team/rorapp/cluster-knaster`) für die Person.
|
||
5. SSH-Konfiguration auf dem Gerät:
|
||
```
|
||
Host ops
|
||
HostName 192.168.100.173
|
||
User root
|
||
IdentityFile ~/.ssh/<schlüssel>
|
||
IdentitiesOnly yes
|
||
```
|
||
Alles Weitere läuft über ops (`ssh ops`, dann `ssh pve44`, `kubectl …`). Prüfen: `ssh ops kubectl get nodes`.
|
||
6. Zugänge, die nicht im Repo stehen, über Password Depot übergeben (Proxmox-root-Notfall, Grafana-Admin aus
|
||
`/opt/knaster/infra/.env`).
|
||
|
||
**Person oder Gerät entfernen**
|
||
1. Eintrag von `access.ops_ssh_keys` nach `access.ops_ssh_keys_revoked` verschieben, `playbooks/admins.yml`.
|
||
2. Name aus `access.admin_users` streichen und das Konto in Proxmox löschen (`pveum user delete <name>@pve`; das
|
||
Playbook legt nur an), Adresse aus `firewall.management`, `playbooks/cluster.yml`.
|
||
3. Gitea-Zugang entziehen. Hatte die Person Zugang zu Geheimnissen (Password Depot, ops), die betroffenen Token
|
||
rotieren (Abschnitt 8).
|
||
|
||
**Was ops nicht ersetzt:** Den Schlüssel des Admin-Laptops (`rrapp-laptop-2026-10-08`) gibt es zusätzlich direkt auf
|
||
den Knoten (`/etc/pve/priv/authorized_keys`), in infra und in den VMs. Neue Personen brauchen das nicht; bei einer
|
||
vollständigen Übergabe dort ebenfalls entfernen.
|
||
|
||
**Notfall (bisherige Person nicht erreichbar):** `docs/NOTFALL-ADMIN-UEBERGABE.md` (Unterlage für die Geschäftsführung, ohne Passwörter): Firewall-Freigabe vor Ort an pve61, Schlüssel über die Proxmox-Shell von pve46 (`pct enter 100`) auf ops eintragen, danach wie oben dauerhaft machen.
|
||
|
||
**Später („Weg 2“, Phase 9):** Verwaltung über nex-admin mit freigegebenen Aktionen auf ops statt persönlicher
|
||
Shell, siehe Aufbauplan.
|
||
|