Verwaltung durch Personen (Weg 1), AGENTS.md, project-map.md, known-issues.md
ci / test (push) Failing after 24s

Admin-Schlüssel je Person auf ops (Bastion) aus sites/knaster/proxmox.yaml
(access.ops_ssh_keys/_revoked), Playbook admins.yml, Test, Übergabe in
BETRIEB §15. Einstieg für Agenten und neue Admins: AGENTS.md, Projektkarte,
bekannte Probleme. Weg 2 (Verwaltung über NEX) als Entwurf in Phase 9.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Robert Rapp
2026-10-19 18:54:02 +02:00
co-authored by Claude Opus 5.5
parent 039f57a435
commit 50acfa6a95
9 changed files with 379 additions and 0 deletions
+77
View File
@@ -0,0 +1,77 @@
# AGENTS.md – Arbeitsweise für Coding-Agenten (Claude Code, Codex, Hermes …)
Dieses Repo beschreibt den Proxmox-/k3s-Cluster **knaster** und den Router-Service `knaster`. Wer hier als Agent
arbeitet, ändert **Infrastruktur, die läuft**. Erst lesen: [project-map.md](project-map.md) (was wo läuft),
[known-issues.md](known-issues.md) (bekannte Fallen), der Aufbauplan
[docs/superpowers/plans/2026-10-12-aufbau-cluster-knaster.md](docs/superpowers/plans/2026-10-12-aufbau-cluster-knaster.md)
(was offen ist) und [docs/BETRIEB.md](docs/BETRIEB.md) (wie man es betreibt).
## Sprache und Stil
- Deutsch in Doku, Kommentaren, Commit-Nachrichten und Antworten; Umlaute ausschreiben (ä, ö, ü, ß).
- Code so schreiben wie der umgebende Code: Kommentardichte, Benennung, Muster (Playbook-Kopf mit Zweck und Aufruf,
Manifest-Kopf mit Zweck und Herkunft).
- Daten mit Datum notieren (`19.10.`), keine relativen Angaben („gestern“).
## Der Weg jeder Änderung
1. **Im Repo ändern** (auf dem Admin-Laptop bzw. dem Gerät der verwaltenden Person). Nie direkt auf ops oder in
einer VM editieren; `/root/cluster-knaster` auf ops und `/opt/knaster/repo` auf infra sind Kopien.
2. **Prüfen:** `make test` (bzw. `pytest core`), `make lint`, bei Ansible `ansible-playbook --syntax-check` auf ops,
bei Prometheus `promtool check config`/`check rules` (läuft im Prometheus-Image auf infra).
3. **Committen und pushen** (Branch laut Plan, derzeit `plan/aufbau-2026-10-12`). Nur eigene Dateien stagen –
es arbeiten oft mehrere Sitzungen parallel im selben Repo. Vor dem Push `git pull --rebase`.
4. **Auf ops ausrollen:** `ssh ops`, `cd /root/cluster-knaster && git pull`, dann in `platform/proxmox/ansible`
das passende Playbook. `kubectl`, `tofu` und Ansible gibt es **nur auf ops**.
5. **Live prüfen** (Pods bereit, Dienst antwortet, Prometheus-Ziel `up`) und erst dann abhaken.
6. **Plan und Doku nachziehen:** Checkbox im Aufbauplan mit Datum und Befund, Betriebswissen in `docs/BETRIEB.md`,
neue Fallen in `known-issues.md`, neue Dienste/Adressen in `project-map.md`.
## Zugang
- Einstieg ist **ops** (`192.168.100.173`, Bastion). Von dort erreicht man alle Knoten, VMs und infra mit dem
Schlüssel `root@ops`; Ansible-Inventar `platform/proxmox/ansible/inventory.yml` (+ `inventory.generated.yml`).
- Wer verwaltet, steht in `sites/knaster/proxmox.yaml` unter `access.ops_ssh_keys` (Playbook `playbooks/admins.yml`,
Übergabe: BETRIEB §15). Proxmox-Weboberfläche nur mit persönlichem Konto und TOTP.
- Muster für Befehle vom Laptop: `ssh ops '<befehl>'`, `ssh ops 'cd /root/cluster-knaster/platform/proxmox/ansible && ansible-playbook playbooks/<x>.yml'`.
In Ansible-Ad-hoc-Befehlen keine Go-Templates (`{{.Names}}`) – Ansible wertet `{{ }}` selbst aus; besser ein
kleines Skript mit `-m script`.
## Feste Regeln
- **Keine Geheimnisse im Repo.** Tokens und Passwörter liegen auf ops unter `/root/.config/knaster/` (0600), im LXC
infra unter `/opt/knaster/infra/.env` bzw. `secrets/`, beim Menschen in Password Depot. Playbooks lesen sie von dort,
Ausgaben mit `no_log: true`.
- **Dry-Run:** Healer und Router laufen mit `KNASTER_DRY_RUN=true`, `enabled_actions: []`. Nicht ohne ausdrückliche
Freigabe des Nutzers ändern.
- **IP-Literale statt DNS-Namen** in Konfigurationen (Netz `192.168.100.0/24`, Gateway `.1`, DNS `.120`). Adressen der
GPU-VMs und der k3s-VM kommen per DHCP; Prometheus-Ziele deshalb per file_sd, nicht fest.
- **GPUs:** In jeder GPU-VM ist Index 0 die Quadro M4000, Index 1 die P2000 (gpu-pve52 hat nur die M4000).
`nvidia.com/gpu` in k3s vergibt **nur die M4000**. Die P2000 gehört llama.cpp (fest per UUID). Neue GPU-Pods
bekommen also eine M4000; eine P2000 nur nach Absprache.
- **Was nach k3s umgezogen ist, nicht wieder per Compose starten:** `playbooks/nex-speech.yml` und
`playbooks/gpu-llamacpp.yml` brechen ab (Schalter nur für den Rückweg).
- **Images pinnen** (exakte Version, kein `latest`); bei k3s-Pods mit eigenem Build `imagePullPolicy: Never` und Import
per `docker save | k3s ctr images import -` (Muster: Rolle `nex-speech-k3s`, `gpu-llamacpp` `tasks/k3s.yml`).
- **Infra-LXC hat 4 GB RAM:** neue Prometheus-Jobs mit `metric_relabel_configs` auf das Nötige begrenzen.
- **Nach außen wirkende oder schwer umkehrbare Schritte** (Produktion auf appsrv08, DNS/DHCP, Löschen, Firewall,
Neustart von Knoten) vorher mit dem Nutzer abstimmen. Die Firewall-Rolle hat einen Totmannschalter, trotzdem
vorher prüfen, dass die eigene Adresse in `firewall.management` steht.
- **Namespace `nex`** gehört NEX (nex-admin legt dort Agenten an). Änderungen dort mit dem NEX-Team abstimmen.
## Muster im Repo
| Aufgabe | Vorlage |
|---|---|
| Neuer Dienst in k3s | `deploy/k8s/<dienst>/` + `playbooks/<dienst>-k3s.yml` (z. B. `nex-searxng`, `monitoring`) |
| Dienst mit GPU/Host-Netz in einer VM | `deploy/k8s/nex-speech/`, `deploy/k8s/llamacpp/llamacpp.yaml.j2` |
| Zugang für eine Anwendung zur k8s-API | `deploy/k8s/nex/admin-access.yaml` (ServiceAccount, Role, Token-Secret), Token-Datei auf ops |
| Neues Prometheus-Ziel | `deploy/prometheus/prometheus.yml`; wechselnde Adressen über `platform/proxmox/scripts/monitoring-targets.py` |
| Neuer Alert | `deploy/prometheus/alerts.yml` **und** passende Regel in `config/healer-rules.yaml` (Test erzwingt das) |
| Grafana-Dashboard | `deploy/grafana/dashboards/*.json`, Datasource-uid `prometheus`, Herkunft in `provisioning/dashboards/knaster.yml` |
## Testen unter Windows
- `.venv\Scripts\python -m pytest core` – `test_tts_server` scheitert unter Windows (kein Piper), das ist bekannt.
- `yamllint` meldet auf geänderten Dateien „wrong new line character“, weil Git die Arbeitskopie mit CRLF auscheckt.
Für eine echte Prüfung den Stand aus Git prüfen: `git show HEAD:<datei> | .venv/Scripts/yamllint -s -c .yamllint.yml -`.
+3
View File
@@ -15,6 +15,9 @@ Anfrage → n8n → Router-Service ──▶ Stufe 3 Embedding-Classifier (CPU)
| Dokument | Inhalt | | Dokument | Inhalt |
|---|---| |---|---|
| [AGENTS.md](AGENTS.md) | **Einstieg für Agenten und neue Admins**: Arbeitsweise, Regeln, Muster |
| [project-map.md](project-map.md) | Was wo läuft: Rechner, Adressen, Dienste, Ports, Namespaces, Repo, Orte der Geheimnisse |
| [known-issues.md](known-issues.md) | Bekannte Probleme und Fallen |
| [docs/PLAN.md](docs/PLAN.md) | Architektur und Betrieb: Proxmox, Pflege, Autohealing, Lifecycle, Finetuning, Problemerkennung, Portabilität | | [docs/PLAN.md](docs/PLAN.md) | Architektur und Betrieb: Proxmox, Pflege, Autohealing, Lifecycle, Finetuning, Problemerkennung, Portabilität |
| [docs/adr/](docs/adr/) | Architekturentscheidungen | | [docs/adr/](docs/adr/) | Architekturentscheidungen |
| [docs/spec/](docs/spec/) | Spezifikationen der Komponenten | | [docs/spec/](docs/spec/) | Spezifikationen der Komponenten |
+14
View File
@@ -273,3 +273,17 @@ def test_proxmox_site_consistent_with_registry_and_prometheus():
assert vm["driver_generation"] == "modern", slot assert vm["driver_generation"] == "modern", slot
vmids = [str(v["vmid"]) for v in vms.values()] + [str(c["vmid"]) for c in site["containers"].values()] vmids = [str(v["vmid"]) for v in vms.values()] + [str(c["vmid"]) for c in site["containers"].values()]
assert len(vmids) == len(set(vmids)) assert len(vmids) == len(set(vmids))
def test_ops_admin_keys_in_site_file():
"""Weg 1: Admins kommen mit eigenem Schlüssel auf ops (playbooks/admins.yml)."""
access = yaml.safe_load((REPO_ROOT / "sites/knaster/proxmox.yaml").read_text())["access"]
keys, revoked = access["ops_ssh_keys"], access["ops_ssh_keys_revoked"]
assert keys # nie leer, sonst sperrt sich die Verwaltung aus
names = [k["name"] for k in keys]
assert len(names) == len(set(names))
for k in [*keys, *revoked]:
parts = k["key"].split()
assert parts[0] in {"ssh-ed25519", "ssh-rsa"} and len(parts) >= 2, k["name"]
assert "PRIVATE" not in k["key"], k["name"]
assert not {k["key"].split()[1] for k in keys} & {k["key"].split()[1] for k in revoked}
+42
View File
@@ -240,3 +240,45 @@ Die GPU-VMs bilden mit der Dienst-VM `k3s` einen k3s-Cluster (v1.36.5+k3s1). Ste
- **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. - **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. - **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. - **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.
## 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.
**Später („Weg 2“, Phase 9):** Verwaltung über nex-admin mit freigegebenen Aktionen auf ops statt persönlicher
Shell, siehe Aufbauplan.
@@ -432,6 +432,20 @@ Die Software-Phasen aus PLAN §8 bleiben inhaltlich gültig und bekommen jeweils
- „5 Lifecycle“: QLoRA-Training. Voraussetzung ist die Entscheidung über WSL2 bzw. vLLM auf den Ada-Rechnern nachts (D3, Folgeentscheidung). - „5 Lifecycle“: QLoRA-Training. Voraussetzung ist die Entscheidung über WSL2 bzw. vLLM auf den Ada-Rechnern nachts (D3, Folgeentscheidung).
- „6 Portabilität“ - „6 Portabilität“
### Verwaltung durch mehrere Personen (Nutzerwunsch 19.10.)
- [x] **Weg 1 (19.10.):** Verwaltung an Personen statt an den Admin-Laptop gebunden. Einstieg ist ops (Bastion):
Schlüssel je Person in `sites/knaster/proxmox.yaml` (`access.ops_ssh_keys`, `ops_ssh_keys_revoked`), Playbook
`playbooks/admins.yml` (nur hinzufügen bzw. ausdrücklich entziehen), Test `test_ops_admin_keys_in_site_file`.
Aufnahme und Entzug in BETRIEB §15. Einstieg für Agenten: `AGENTS.md`, `project-map.md`, `known-issues.md`.
- [ ] **Weg 2 (später, Entwurf):** Verwaltung über NEX statt persönlicher Shell. nex-admin bekommt einen Bereich
„Infrastruktur“ (Anmeldung, Rollen und Protokoll von NEX). Dahinter auf ops ein kleiner Dienst, der nur freigegebene
Aktionen ausführt (bestimmtes Playbook, Dienst neu starten, Zustand zeigen), keine freie Shell. Neue Dinge entstehen
weiter im Repo: Ein Agent in NEX schreibt die Änderung als Branch in Gitea, ein Mensch gibt per Merge frei, der Dienst
rollt danach aus. Agenten schlagen nur vor (Grundsatz wie beim Healer). Anknüpfen an das Vorschlag-/Freigabe-Verfahren
von knaster (Policy „approve“). Offen vor dem Start: Wer verwaltet künftig (interne IT, Dienstleister, mehrere
Rollen)? Eigener Plan.
Am Ende von Phase 9 wird das Betriebshandbuch abgeschlossen („PVE-Config: Betriebshandbuch und Testvorgehen anlegen“). Das geschieht als Erweiterung von `docs/BETRIEB.md`, nicht als neues Dokument. Am Ende von Phase 9 wird das Betriebshandbuch abgeschlossen („PVE-Config: Betriebshandbuch und Testvorgehen anlegen“). Das geschieht als Erweiterung von `docs/BETRIEB.md`, nicht als neues Dokument.
--- ---
+81
View File
@@ -0,0 +1,81 @@
# Bekannte Probleme und Fallen
Stand 19.10.2026. Je Eintrag: was passiert, Auswirkung, Umgang, wo es weitergeht. Behobene Einträge nicht löschen,
sondern unter „Erledigt“ verschieben (mit Datum), damit die Lehre erhalten bleibt. Siehe auch [AGENTS.md](AGENTS.md),
[project-map.md](project-map.md).
## Offen
### Netz und Zugang
- **DHCP-Reservierungen unvollständig.** Reserviert sind pve44, pve52, gpu-pve52 und der Laptop. Für infra, pve46 und
pve48 geht laut DC keine Reservierung (alle drei sind statisch konfiguriert, Risiko: doppelt vergebene Adresse).
Ohne Rückmeldung: ops, k3s-VM (.124), gpu-pve44/46/48, pve61, Gäste-Block `.220–.239` (am 19.10. erneut angefragt).
Auswirkung: Ändert sich die Adresse der k3s-VM, brechen Portainer CE (nginx-Ziel), `NEX_K3S_HOST` und die geplante
RustDesk-Umstellung. Ändern sich GPU-VM-Adressen: `refresh-vm-access.sh`, dann `playbooks/monitoring-targets.yml`.
→ Aufbauplan Task 0.2.
- **Firewall sperrt bei Adresswechsel aus.** Die Proxmox-Firewall lässt nur Adressen aus `firewall.management` an
22/8006. Rückweg über ops oder Konsole (BETRIEB §10).
- **Namespace `nex` ohne Pod Security.** nex-admin darf dort Pods mit `privileged`/`hostPath`/`hostNetwork` anlegen;
ein abgeflossenes Token wäre damit Root auf einer GPU-VM. → Task 6.6 (Sprachdienste nach `nex-speech`, dann
„baseline“).
### Monitoring und Healer
- **`SlotEndpointDown` feuert seit 15.10.** für tier1-flex, tier1-general (Ada-Rechner noch nicht angebunden, 6.4),
tts-de, tts-de-hq, embed-default, verifier-small, signature-extractor (Canaries laufen über LiteLLM, das kommt mit 6.3).
Erwartet, kein Handlungsbedarf bis 6.3/6.4.
- **vLLM-Ziele `gpu-main`/`gpu-flex` down** (DNS-Namen ohne Auflösung, Ada-Rechner nicht angebunden). → Task 6.4.
- **Healer kennt nur Docker-Container.** Registry und Healer-Regeln führen `docker_host`/`container`; llama.cpp und
die Sprachdienste laufen inzwischen in k3s. `restart_container` würde ins Leere greifen. Heute folgenlos
(Dry-Run, `enabled_actions: []`); vor dem Scharfschalten auf Pod-Neustart umstellen. → Task 6.1 (offen), Phase 9.
- **n8n-Receiver im Alertmanager nur vorbereitet** (auskommentiert, Format passt nicht). Alerts gehen nur an den
Router. → Task 5.2.
- **Prometheus-Neustart setzt Alert-Zustände zurück** (alles wieder „pending“). Nach einem Neustart des Stacks die
`for:`-Dauer abwarten, bevor man auf fehlende Alerts schließt.
- **Speicher von Prometheus auf infra beobachten** (4 GB RAM, seit 19.10. ~2000 Reihen mehr durch k3s). → Task 6.5.
- **`K3sPodCrashLooping` bleibt bis zu 5 min länger stehen,** nachdem ein Pod behoben oder gelöscht wurde
(`max_over_time[5m]`, nötig gegen Flackern).
### GPU und Modelle
- **Embedding auf der P2000 nur mit `-ub 4096`.** Mit 8192 braucht llama.cpp (Stand b6000) allein 5 GB Rechenpuffer
(ub × Vokabular), Flash-Attention hilft nicht. Belegung mit Rerank: 4,4–4,6 von 5 GB, wenig Reserve.
- **`nvidia.com/gpu` vergibt nur die M4000** (Device Plugin sieht nur Index 0). Wer einen GPU-Pod anlegt, bekommt
eine M4000; die P2000 gehört llama.cpp. Annahme: Index 0 ist in jeder GPU-VM die M4000 – das Playbook
`gpu-llamacpp-k3s.yml` prüft das vor jeder Änderung.
- **LiteLLM-Port auf appsrv08 nicht im LAN veröffentlicht.** Blockiert Task 6.3 (lokale Slots in LiteLLM) und damit
grüne Canaries. → Nutzer, Task 6.3.
### Knoten und Speicher
- **ZFS-Seiten-Cache nach HA-Umzug.** Nach `zfs receive` liefert ein Knoten für früher gemappte Dateien veralteten
Inhalt; Docker brach am 12.10. mit `invalid freelist page` ab. Abgefangen im LXC infra durch
`knaster-drop-stale-cache.service`; jeder neue replizierte LXC mit mmap-Nutzern braucht denselben Schutz (BETRIEB §7).
- **pve44: EFI-Partition nicht eingehängt** (`sda2` fehlt in `/etc/fstab`); GRUB-Updates erreichen sie nicht.
→ Aufbauplan Task 1.1, Nebenbefund.
- **pve61: Neustart nach dem Kernel-Update offen.** → Aufbauplan Task 1.1, Step 4.
- **Kein Backup.** Proxmox Backup Server und Ziel-Speicher stehen aus. → Task 5.1 (Entscheidung Nutzer).
### Übergänge (alt und neu laufen parallel)
- **Portainer:** EE auf appsrv08 läuft weiter, `portainer.mygodel.de` zeigt noch dorthin; CE im Cluster ist bereit.
Umstellung braucht Admin-Login und feste Adresse der k3s-VM (BETRIEB §14).
- **RustDesk:** Server läuft zusätzlich in k3s, Clients zeigen weiter auf `.29`.
- **SearXNG:** läuft in k3s und auf appsrv08; Umstellung der Agenten über `SEARXNG_URL` im nex-Admin.
### Arbeitsumgebung
- **Windows-Arbeitskopie mit CRLF:** `yamllint` meldet „wrong new line character“ für geänderte Dateien. Den Stand aus
Git prüfen (`git show HEAD:<datei> | yamllint -`), siehe AGENTS.md.
- **`test_tts_server` scheitert unter Windows** (kein Piper-Programm). Unter Linux grün.
- **Kein Python-venv auf ops:** `make test`/`make lint` laufen auf dem Laptop; auf ops nur Ansible-Syntaxprüfung.
- **Ansible-Ad-hoc mit Go-Templates** (`docker ps --format '{{.Names}}'`) scheitert, weil Ansible `{{ }}` auswertet.
Skript per `-m script` verwenden.
- **Mehrere Agenten-Sitzungen im selben Repo:** Eine Sitzung kann fremde, noch nicht committete Änderungen mit
committen. Nur eigene Dateien stagen, vor dem Push `git pull --rebase`.
## Erledigt
- 19.10.: Embedding/Rerank auf gpu-pve44/46 liefen nicht (Prometheus-Ziele 8081/8082 down) – seit Task 6.1 in k3s.
- 19.10.: Laptop wurde bei Lease-Wechsel von der Firewall ausgesperrt (14.10. passiert) – WLAN und Dock reserviert.
@@ -0,0 +1,52 @@
# Verwaltung durch Personen statt durch ein Gerät („Weg 1“, 19.10.): SSH-Schlüssel der Admins auf ops (Bastion).
# Quelle: sites/<site>/proxmox.yaml, access.ops_ssh_keys und access.ops_ssh_keys_revoked. Läuft auf ops:
# ansible-playbook playbooks/admins.yml
# Fügt nur hinzu bzw. entfernt die ausdrücklich entzogenen Schlüssel; fremde Einträge (z. B. Notfallschlüssel) bleiben.
# Proxmox-Weboberfläche: access.admin_users + playbooks/cluster.yml + scripts/pve-admin-user.sh, Gerät in
# firewall.management (BETRIEB §8, §10, §15).
- name: ops – SSH-Schlüssel der Admins
hosts: localhost
connection: local
gather_facts: false
vars:
site_file: "{{ playbook_dir }}/../../../../sites/{{ knaster_site | default('knaster') }}/proxmox.yaml"
access: "{{ (lookup('file', site_file) | from_yaml).access }}"
admin_keys: "{{ access.ops_ssh_keys | default([]) }}"
revoked_keys: "{{ access.ops_ssh_keys_revoked | default([]) }}"
tasks:
- name: Liste prüfen (mindestens ein Schlüssel, Namen eindeutig, kein Schlüssel zugleich entzogen)
ansible.builtin.assert:
that:
- admin_keys | length > 0
- admin_keys | map(attribute='name') | unique | length == admin_keys | length
- admin_keys | map(attribute='key') | select('match', '^ssh-(ed25519|rsa) ') | list | length == admin_keys | length
- (admin_keys | map(attribute='key') | map('split', ' ') | map(attribute=1) | list)
| intersect(revoked_keys | map(attribute='key') | map('split', ' ') | map(attribute=1) | list) | length == 0
fail_msg: "access.ops_ssh_keys in {{ site_file }} prüfen"
- name: Schlüssel eintragen
ansible.posix.authorized_key:
user: root
key: "{{ item.key }}"
state: present
loop: "{{ admin_keys }}"
loop_control:
label: "{{ item.name }}"
- name: Entzogene Schlüssel entfernen
ansible.posix.authorized_key:
user: root
key: "{{ item.key }}"
state: absent
loop: "{{ revoked_keys }}"
loop_control:
label: "{{ item.name }}"
- name: Schlüssel auf ops (Fingerabdrücke)
ansible.builtin.command: ssh-keygen -lf /root/.ssh/authorized_keys
register: fingerprints
changed_when: false
- name: Ergebnis
ansible.builtin.debug:
msg: "{{ fingerprints.stdout_lines }}"
+90
View File
@@ -0,0 +1,90 @@
# Projektkarte knaster
Stand 19.10.2026. Was wo läuft, wie man es erreicht und wo es im Repo steht. Bei jeder Änderung an Adressen,
Diensten oder Ports mitpflegen (siehe [AGENTS.md](AGENTS.md)). Bekannte Probleme: [known-issues.md](known-issues.md).
## Netz
Flaches Netz `192.168.100.0/24`, Gateway `.1`, DNS und DHCP auf dem DC `.120` (verwaltet vom DC-Support, nicht von uns).
Feste Adressen: Knoten, ops und infra statisch konfiguriert; GPU-VMs und k3s-VM per DHCP (Reservierungen Task 0.2).
## Rechner und Gäste
| Name | Art | Adresse | Feste Adresse | Rolle |
|---|---|---|---|---|
| pve44 | Proxmox-Knoten | .210 | statisch + Reservierung | GPU-Knoten (M4000 + P2000), Heimat von infra |
| pve46 | Proxmox-Knoten | .209 | statisch, keine Reservierung möglich | GPU-Knoten (M4000 + P2000), Heimat von ops |
| pve48 | Proxmox-Knoten | .212 | statisch, keine Reservierung möglich | GPU-Knoten (M4000 + P2000), Heimat der k3s-VM |
| pve52 | Proxmox-Knoten | .207 | statisch + Reservierung | GPU-Knoten (nur M4000) |
| pve61 | Proxmox-Knoten (Surface) | .214 | statisch | Quorum, später Kiosk (Task 5.3), kein `tank` |
| ops | LXC 100 auf pve46 | .173 | statisch | Steuerrechner/Bastion: kubectl, Ansible, tofu, Repo-Clone |
| infra | LXC 101 auf pve44 (HA) | .188 | statisch, keine Reservierung möglich | Monitoring und Router (Compose-Stack `knaster-infra`) |
| k3s | VM 110 auf pve48 (HA) | .124 | DHCP | k3s-Steuerung, Portainer CE, kube-state-metrics |
| gpu-pve44 | VM 244 | .186 | DHCP | k3s-Agent; llama.cpp Embedding + Rerank (P2000) |
| gpu-pve46 | VM 246 | .178 | DHCP | k3s-Agent; llama.cpp Embedding + Rerank (P2000) |
| gpu-pve48 | VM 248 | .183 | DHCP | k3s-Agent; llama.cpp Chat (P2000) |
| gpu-pve52 | VM 252 | .174 | DHCP + Reservierung | k3s-Agent; NEX-Sprachdienste (M4000) |
| appsrv08 | fremder Server | .29 | – | NEX-Produktion, LiteLLM (D4), Portainer EE, nex-admin, Traefik |
| Admin-Laptop | – | .151 (WLAN), .181 (Dock) | Reservierung | Arbeitsplatz von r.rapp |
## Dienste und Ports
| Dienst | Wo | Adresse | Zugang |
|---|---|---|---|
| Proxmox-Weboberfläche | jeder Knoten | `https://<knoten>:8006` | persönliches Konto + TOTP, nur Adressen aus `firewall.management` |
| Grafana | infra | `http://192.168.100.188:3000` | `admin`, `kiosk` (Viewer); Dashboards: Übersicht, Proxmox, Knoten, GPU, k3s – Pods |
| Prometheus / Alertmanager | infra | `:9090` / `:9093` | ohne Anmeldung, nur LAN |
| knaster-Router | infra | `:8080` | Bearer-Token (Dry-Run) |
| TTS (Piper, knaster) | infra | `:8090` | – |
| k3s-API | k3s | `https://192.168.100.124:6443` | kubeconfig nur auf ops; nex-admin mit eigenem Token (nur Namespace `nex`) |
| Portainer CE | k3s | `https://192.168.100.124:30779` | Portainer-Konten (Datenbank von appsrv08 übernommen) |
| Portainer-Agent | jeder k3s-Knoten | `:30778` | für Portainer |
| SearXNG (NEX) | jeder k3s-Knoten | `:30880` | – |
| kube-state-metrics | jeder k3s-Knoten | `:30808` | für Prometheus |
| RustDesk hbbs/hbbr | jeder k3s-Knoten | `:21115–21119` | Clients zeigen noch auf `.29` |
| llama.cpp Embedding / Rerank | gpu-pve44, gpu-pve46 | `:8081` / `:8082` | LAN (ufw) |
| llama.cpp Chat (verifier-small) | gpu-pve48 | `:8080` | LAN (ufw) |
| NEX STT (whisper.cpp) / TTS (Piper) | gpu-pve52 | `:8178` / `:8179` | nur von appsrv08 (ufw) |
| node_exporter / nvidia_gpu_exporter | jede GPU-VM | `:9100` / `:9835` | LAN |
| kubelet | jeder k3s-Knoten | `:10250` | Token `prometheus-infra` |
## k3s-Namespaces
| Namespace | Inhalt | Ausgerollt mit |
|---|---|---|
| `kube-system` | k3s-Grundlagen, metrics-server, NVIDIA Device Plugin (nur M4000), kube-state-metrics | `playbooks/k3s.yml`, `playbooks/monitoring-k3s.yml` |
| `knaster` | llama.cpp je GPU-VM (`llamacpp-<vm>`) | `playbooks/gpu-llamacpp-k3s.yml` |
| `nex` | NEX-Agenten (von nex-admin), nex-stt, nex-tts, nex-searxng, Brücke zu appsrv08 | nex-admin, `playbooks/nex-k3s.yml`, `nex-speech-k3s.yml`, `nex-searxng-k3s.yml` |
| `portainer`, `portainer-ce` | Portainer-Agent, Portainer CE | `playbooks/k3s.yml`, `playbooks/portainer-k3s.yml` |
| `rustdesk` | RustDesk-Server | `playbooks/rustdesk-k3s.yml` |
## Repo
| Pfad | Inhalt |
|---|---|
| `AGENTS.md`, `project-map.md`, `known-issues.md` | Einstieg für Agenten und neue Admins |
| `docs/superpowers/plans/2026-10-12-aufbau-cluster-knaster.md` | Aufbauplan mit Stand je Task |
| `docs/BETRIEB.md` | Betriebshandbuch (Monitoring §7, Zugang §8, Firewall §10, k3s §14, Übergabe §15) |
| `docs/ANMELDUNG.md` | Anmeldung und Vertretung (Password Depot) |
| `docs/PLAN.md`, `docs/adr/`, `docs/spec/` | Architektur, Entscheidungen, Spezifikationen |
| `sites/knaster/proxmox.yaml` | Zielbild: Knoten, GPUs, VMs, LXC, Zugang, Firewall |
| `sites/knaster/registry.yaml`, `catalog/` | Slots (Modelle je Rolle) und Modellkatalog |
| `config/` | Policy, Healer-Regeln, Lifecycle |
| `core/` | Python-Paket `knaster` (Router, Healer, Canary, Inventar) und Tests |
| `deploy/k8s/` | Manifeste für k3s (Übersicht: `deploy/k8s/README.md`) |
| `deploy/compose/` | Compose-Stacks (infra aktiv; gpu-llamacpp, nex-speech nur noch Rückweg) |
| `deploy/prometheus/`, `deploy/alertmanager/`, `deploy/grafana/` | Monitoring-Konfiguration für infra |
| `platform/proxmox/tofu/` | VMs und LXC (OpenTofu, `bpg/proxmox`) |
| `platform/proxmox/ansible/` | Rollen und Playbooks (laufen auf ops) |
| `platform/proxmox/scripts/` | Bootstrap, Healthcheck, Zugang auffrischen, Monitoring-Ziele, Admin-Konto |
| `n8n/workflows/` | n8n-Workflows (inaktiv importieren) |
## Geheimnisse (nur Orte, nie Inhalte)
| Was | Wo |
|---|---|
| Proxmox-Admin-Passwort, TOTP, root-Passwörter | Password Depot; Kopie Passwörter auf dem Admin-Laptop `it/proxmox/.env` |
| SSH-Schlüssel Laptop | Admin-Laptop `it/proxmox/keys/` |
| tofu-, pve-exporter-, Healer-Token, k3s-Token, nex-admin-Token, Prometheus-Token, k3s-CA, Gitea-Registry | ops `/root/.config/knaster/` |
| Postgres, Router-Token, Grafana-Passwörter, Kopien der Exporter-/k3s-Zugänge | infra `/opt/knaster/infra/.env`, `/opt/knaster/infra/secrets/` |
| kubeconfig | ops `/root/.kube/config` |
+6
View File
@@ -46,6 +46,12 @@ access:
admin_group: admins # Gruppe mit Administrator auf /; root@pam bleibt Notfallzugang admin_group: admins # Gruppe mit Administrator auf /; root@pam bleibt Notfallzugang
admin_users: [r.rapp] # persönliche Konten <name>@pve; Passwort und TOTP per scripts/pve-admin-user.sh admin_users: [r.rapp] # persönliche Konten <name>@pve; Passwort und TOTP per scripts/pve-admin-user.sh
mail_to: ki@mygodel.de # Ziel der Proxmox-Benachrichtigungen (Backup, Fencing, Updates, Replikation) mail_to: ki@mygodel.de # Ziel der Proxmox-Benachrichtigungen (Backup, Fencing, Updates, Replikation)
# Verwaltung durch Personen statt durch ein Gerät (19.10., „Weg 1“): Jede Person kommt mit einem eigenen Schlüssel
# auf ops (Bastion) und arbeitet von dort (kubectl, Ansible, tofu). Öffentliche Schlüssel, kein Geheimnis.
# Ausrollen: playbooks/admins.yml auf ops. Übergabe und Entzug: docs/BETRIEB.md Abschnitt 15.
ops_ssh_keys:
- {name: r.rapp-laptop, key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHG5/rQMO4dtO8jOdAjQksFxZhdI7oSZAGNmFXVxQcoJ rrapp-laptop-2026-10-08"}
ops_ssh_keys_revoked: [] # ehemalige Schlüssel ({name, key}); playbooks/admins.yml entfernt sie von ops
firewall: firewall:
enable: true enable: true
# IPSET management: nur diese Quellen (plus alle Knoten) erreichen 8006, 22, 5900-5999, 3128 und # IPSET management: nur diese Quellen (plus alle Knoten) erreichen 8006, 22, 5900-5999, 3128 und