Betrieb: Umstieg aufs Image vollzogen, Binary-Weg eingestellt

This commit is contained in:
2026-09-09 07:41:21 +02:00
parent e14403420d
commit d509d975e4
+46 -22
View File
@@ -18,12 +18,12 @@ keine Format-Übersetzung — alle Upstreams sprechen OpenAI-Format.
| Was | Wo | | Was | Wo |
|---|---| |---|---|
| Router (`llmrouter`) | LXC **185020** (`llmlite`, `10.18.5.20`) auf `s18p1`, systemd-Unit `llmrouter`, Port **4010** | | Router (`llmrouter`) | LXC **185020** (`llmlite`, `10.18.5.20`) auf `s18p1`, **OCI-Container** `llmrouter_llmrouter_1` (podman-compose, systemd-Unit `llmrouter`), Bild `git.42i.org/spine/llmrouter:latest`, Port **4010** |
| Alter Port **4000** | seit 2026-09-07 von Caddy auf 4010 geleitet (vorher LiteLLM). Hart verdrahtete Clients — Agenten-Images, spark-desktop, ältere Skripte — laufen darüber weiter | | Alter Port **4000** | seit 2026-09-07 von Caddy auf 4010 geleitet (vorher LiteLLM). Hart verdrahtete Clients — Agenten-Images, spark-desktop, ältere Skripte — laufen darüber weiter |
| LiteLLM | **abgeschaltet am 2026-09-07** (`systemctl stop/disable llmlite`). Dateien und Container liegen unangetastet in `/srv/llmlite`, letzte Kosten-Datenbank als `spend-final-20260907.sql.gz` | | LiteLLM | **abgeschaltet am 2026-09-07** (`systemctl stop/disable llmlite`). Dateien und Container liegen unangetastet in `/srv/llmlite`, letzte Kosten-Datenbank als `spend-final-20260907.sql.gz` |
| Caddy (`llm.lan`, TLS über `acme.lan`) | derselbe LXC, **eigener oci-Dienst `caddy`** (`/srv/caddy`, systemd-Unit `caddy.service`), `reverse_proxy localhost:4010` | | Caddy (`llm.lan`, TLS über `acme.lan`) | derselbe LXC, **eigener oci-Dienst `caddy`** (`/srv/caddy`, systemd-Unit `caddy.service`), `reverse_proxy localhost:4010` |
| Dateien | `/srv/llmrouter/`: Binaries `llmrouter`, `llmrouter-resolve`, `llmrouter-stats`, `llmrouter-keys`; `config.json`, `key-acls.json`, `model-blocklist.json`; `keys.json` (Token → Name, 0600); `.env` (0600); `log/` | | Dateien | `/srv/llmrouter/`: `compose.yml`, `.env` (0600); Laufzeit unter `data/`: `config.json`, `key-acls.json`, `keys.json` (0600), `model-blocklist.json`, `log/` (requests.jsonl, changes.log, state.json, aa-cache.json). Sidecar-Binaries daneben, werden nicht mehr gebaut (Ersatz: `podman run` aus dem Image, siehe Deploy) |
| Docker-Image (Demo/Basis) | `git.42i.org/spine/llmrouter:latest`, gebaut von CI; `docker compose up` mit `./data`-Mount als Demo | | Docker-Image (Prod) | `git.42i.org/spine/llmrouter:latest`, gebaut von CI — seit 2026-09-09 läuft der Prod-Einsatz darauf |
| Lokales Qwen | LLM-Knoten LXC 189080, `10.18.5.30:11507` | | Lokales Qwen | LLM-Knoten LXC 189080, `10.18.5.30:11507` |
| Sprachdienste lokal | `10.18.5.30:8803` (Qwen3-TTS + whisper.cpp), Ollama `:11434` (Embeddings) | | Sprachdienste lokal | `10.18.5.30:8803` (Qwen3-TTS + whisper.cpp), Ollama `:11434` (Embeddings) |
| Kira im Prod-RZ (deizo) | `10.6.42.111:8000/8001/8002` über den WireGuard-Peer des LXC | | Kira im Prod-RZ (deizo) | `10.6.42.111:8000/8001/8002` über den WireGuard-Peer des LXC |
@@ -47,7 +47,7 @@ Claude-Code-Pfad) steht in [referenz.md](referenz.md#namensräume). Kurzfassung:
| alles andere | **404** — kein stiller Fallback mehr seit der LiteLLM-Abschaltung | | alles andere | **404** — kein stiller Fallback mehr seit der LiteLLM-Abschaltung |
Ein Modellwechsel steht in `log/changes.log` (im LXC: Ein Modellwechsel steht in `log/changes.log` (im LXC:
`/srv/llmrouter/log/changes.log`). `/srv/llmrouter/data/log/changes.log`).
## Rollen und Profile — drei Ebenen ## Rollen und Profile — drei Ebenen
@@ -116,7 +116,7 @@ endpoint"`). Nachmessen: `tools/telefon-latenz.py` in diesem Repo.
## Keys und Rechte ## Keys und Rechte
- Client-Token: `keys.json` (Token → Name; im LXC - Client-Token: `keys.json` (Token → Name; im LXC
`/srv/llmrouter/keys.json`, in der Docker-Demo `./data/keys.json`). Die `/srv/llmrouter/data/keys.json`, in der Docker-Demo `./data/keys.json`). Die
Werte der Personas liegen in OpenBao `agents/<name>/litellm` Feld `key`; es Werte der Personas liegen in OpenBao `agents/<name>/litellm` Feld `key`; es
sind **dieselben Token wie bei LiteLLM**. Noch ohne Token im Router: sind **dieselben Token wie bei LiteLLM**. Noch ohne Token im Router:
`inka-mx`, `nightjob`, `lab-xo`, `team-copilot` (LiteLLM kennt nur Hashes). `inka-mx`, `nightjob`, `lab-xo`, `team-copilot` (LiteLLM kennt nur Hashes).
@@ -147,9 +147,9 @@ curl -s "http://llm.lan:4010/stats?days=7" \
# aktuelle Zuordnung aller Stufen und alt/-Referenzen # aktuelle Zuordnung aller Stufen und alt/-Referenzen
curl -s http://llm.lan:4010/zuordnung -H "Authorization: Bearer <ADMIN_KEY>" | python3 -m json.tool curl -s http://llm.lan:4010/zuordnung -H "Authorization: Bearer <ADMIN_KEY>" | python3 -m json.tool
# einzelne Anfragen / Modellwechsel / Journal # einzelne Anfragen / Modellwechsel / Journal
ssh s18p1.lan 'pct exec 185020 -- tail -20 /srv/llmrouter/log/requests.jsonl' ssh s18p1.lan 'pct exec 185020 -- tail -20 /srv/llmrouter/data/log/requests.jsonl'
ssh s18p1.lan 'pct exec 185020 -- cat /srv/llmrouter/log/changes.log' ssh s18p1.lan 'pct exec 185020 -- cat /srv/llmrouter/data/log/changes.log'
ssh s18p1.lan 'pct exec 185020 -- journalctl -u llmrouter -n 50 --no-pager -o cat' ssh s18p1.lan 'pct exec 185020 -- podman logs -l -n 50'
``` ```
Die Antwort trägt `x-llmrouter-model`, damit ein Client sieht, was wirklich Die Antwort trägt `x-llmrouter-model`, damit ein Client sieht, was wirklich
@@ -158,25 +158,40 @@ lief. Weitere Log-Wege (SQLite, Stats-Binary): [referenz.md](referenz.md).
## Wartung und Deploy ## Wartung und Deploy
Quelle dieses Projekts ist dieses Repo (`spine/llmrouter`). Der Prod-Einsatz Quelle dieses Projekts ist dieses Repo (`spine/llmrouter`). Der Prod-Einsatz
läuft weiter als statisches Binary im LXC (Stand 2026-09-08); der Umstieg aufs läuft seit dem **2026-09-09 als OCI-Container** im LXC (podman-compose hinter
Image ist **geprüft und machbar**, das Rezept steht unten — vollzogen ist er der Unit `llmrouter`, Bild `git.42i.org/spine/llmrouter:latest`, Laufzeitdaten
noch nicht. unter `/srv/llmrouter/data/`). **Der alte Binary-Weg ist eingestellt**
Server-Updates nur noch über das CI-Image; die Sidecar-Binaries
(`resolve`, `stats`, `keys`) werden nicht mehr gebaut, dafür läuft das
Werkzeug aus dem Image (siehe Schritt 3).
1. Änderung in diesem Repo (Config, Code), committen, pushen. CI baut und 1. Änderung in diesem Repo (Config, Code), committen, pushen. CI baut und
published danach `git.42i.org/spine/llmrouter:latest` — für den published danach `git.42i.org/spine/llmrouter:latest`.
Binary-Weg irrelevant, aber der Stand, den jede Demo zieht. 2. Im LXC Bild ziehen und Unit neu starten:
2. Binaries bauen: `bun run build` (dazu ```sh
`bun build --compile --target=bun-linux-x64 src/resolver.ts --outfile dist/llmrouter-resolve`). ssh s18p1.lan 'pct exec 185020 -- podman pull git.42i.org/spine/llmrouter:latest'
3. Ausrollen: `scp` nach `s18p1`, dann `pct push 185020 <datei> /srv/llmrouter/<datei>.neu`, ssh s18p1.lan 'pct exec 185020 -- systemctl restart llmrouter'
im LXC `mv`, `chmod 755`, `systemctl restart llmrouter`. ```
4. Nach Config-Änderungen an Stufen oder `alt/`: `rm /srv/llmrouter/log/state.json` Nach Config-Änderungen an Stufen oder `alt/`: `rm /srv/llmrouter/data/log/state.json`
vor dem Neustart, sonst bleibt die alte Zuordnung. Der Erstlauf fährt für jede vor dem Neustart, sonst bleibt die alte Zuordnung. Der Erstlauf fährt für jede
Stufe die Last-Probe (≈210k Token je Stufe). Stufe die Last-Probe (≈210k Token je Stufe).
3. Sidecar-Werkzeuge ohne Binary — Beispiel Tagesstatistik:
```sh
ssh s18p1.lan "pct exec 185020 -- podman run --rm --env-file /srv/llmrouter/.env \
-e LLMROUTER_CONFIG=/data/config.json -e KEYS_FILE=/data/keys.json \
-v /srv/llmrouter/data:/data git.42i.org/spine/llmrouter:latest bun src/stats.ts 7"
```
(analog `src/keys.ts`, `src/resolver.ts`)
4. Rollback auf die Binary (Notfall): `podman-compose down` im
`/srv/llmrouter`, dann die gesicherte Unit zurück
(`cp /etc/systemd/system/llmrouter.service.binary-bak /etc/systemd/system/llmrouter.service`,
`systemctl daemon-reload`, `systemctl enable --now llmrouter`) — die flachen
Dateien und `log/` liegen unverändert daneben.
5. **Caddyfile-Falle:** die Datei ist als Bind-Mount im Container; ein `mv` 5. **Caddyfile-Falle:** die Datei ist als Bind-Mount im Container; ein `mv`
tauscht den Inode und `caddy reload` sieht die alte Datei. Nach Änderung tauscht den Inode und `caddy reload` sieht die alte Datei. Nach Änderung
`oci restart caddy`. Quelle: [caddy/Caddyfile](caddy/Caddyfile). `oci restart caddy`. Quelle: [caddy/Caddyfile](caddy/Caddyfile).
### Umstieg aufs Image (geprüft am 2026-09-08, ausstehend) ### Umstieg aufs Image (geprüft am 2026-09-08, vollzogen am 2026-09-09)
Voraussetzungen alle erfüllt: podman 5.4.2 im LXC (kein Docker nötig — Voraussetzungen alle erfüllt: podman 5.4.2 im LXC (kein Docker nötig —
Muster wie `caddy.service`: podman-compose + systemd-oneshot), das Image ist Muster wie `caddy.service`: podman-compose + systemd-oneshot), das Image ist
@@ -243,9 +258,9 @@ Image danach rückstandsfrei entfernt.
| Symptom | Prüfen | Tun | | Symptom | Prüfen | Tun |
|---|---|---| |---|---|---|
| Agenten bekommen 502/Timeout | `curl http://llm.lan:4010/health`; `systemctl status llmrouter` im LXC | `systemctl restart llmrouter`; Journal lesen | | Agenten bekommen 502/Timeout | `curl http://llm.lan:4010/health`; `podman ps` im LXC | `systemctl restart llmrouter`; `podman logs -l` |
| `… ist noch nicht aufgelöst (503)` | `/zuordnung` leer oder alt | `POST /auffrischen` mit Admin-Key, oder `rm log/state.json` + Neustart; braucht OpenRouter- und AA-Zugang | | `… ist noch nicht aufgelöst (503)` | `/zuordnung` leer oder alt | `POST /auffrischen` mit Admin-Key, oder `rm /srv/llmrouter/data/log/state.json` + Neustart; braucht OpenRouter- und AA-Zugang |
| 401 für einen Agenten | Journal zeigt `401 … (Key n Zeichen)` | Token fehlt in `keys.json` → mit `keys.ts add --token` nachtragen | | 401 für einen Agenten | `podman logs -l` zeigt `401 … (Key n Zeichen)` | Token fehlt in `data/keys.json` → mit `keys.ts add --token` nachtragen |
| 403 `darf … nicht rufen` | `key-acls.json` | Recht ergänzen (versioniert in diesem Repo), Neustart | | 403 `darf … nicht rufen` | `key-acls.json` | Recht ergänzen (versioniert in diesem Repo), Neustart |
| Zahlen im TTS falsch gesprochen | Sprechtext-Regel greift nur für PLZ/Nummern | Fish über OpenRouter zerlegt deutsche Beträge/Daten — deshalb Grok Voice bis Fish bei Uwe läuft (live/live#2247) | | Zahlen im TTS falsch gesprochen | Sprechtext-Regel greift nur für PLZ/Nummern | Fish über OpenRouter zerlegt deutsche Beträge/Daten — deshalb Grok Voice bis Fish bei Uwe läuft (live/live#2247) |
| `llm.lan` antwortet mit LiteLLM-Fehlern statt Router | Caddy zeigt auf 4000 | Caddyfile prüfen, `oci restart caddy` | | `llm.lan` antwortet mit LiteLLM-Fehlern statt Router | Caddy zeigt auf 4000 | Caddyfile prüfen, `oci restart caddy` |
@@ -268,6 +283,15 @@ starten — LiteLLM bedient die `2608`-Namen weiter, die `2609`-, `alt/`- und
## Verlauf ## Verlauf
- 2026-09-09: **Umstieg aufs Image vollzogen.** Prod läuft als OCI-Container
(`llmrouter_llmrouter_1`, podman-compose hinter der Unit `llmrouter`, Bild
`:latest` von Run #7/e144034). Laufzeitdaten wanderten nach
`/srv/llmrouter/data/` (Hausmuster `./data:/data`), `state.json` und
`aa-cache.json` mitkopiert — Zuordnung unverändert, keine Auffrischung.
Verifiziert: `/health` lokal und über `https://llm.lan` 200, Chat 200.
**Der Binary-Deploy-Weg (bun build, scp, pct push) ist eingestellt**;
Updates nur noch über CI-Image + `podman pull` + Neustart. Unit-Rollback
liegt als `llmrouter.service.binary-bak`.
- 2026-09-08: **Umstieg aufs Image geprüft.** Probelauf des CI-Images auf - 2026-09-08: **Umstieg aufs Image geprüft.** Probelauf des CI-Images auf
dem LXC neben dem Binary (Port 4011): Konfig-/ACL-/Keys-Parität dem LXC neben dem Binary (Port 4011): Konfig-/ACL-/Keys-Parität
(`/models` byte-identisch), Chat 200 über beide Wege, Zuordnungs-Refresh (`/models` byte-identisch), Chat 200 über beide Wege, Zuordnungs-Refresh