Quadlet statt eigener Service-Unit, zwei Umgebungsdateien

Prod fuehrt Phase 1 als Quadlet unter
~/.config/containers/systemd/wurzelwerk-ingest.container; Phase 2 legt
sich daneben. Damit faellt die eigene .service-Unit weg - zwei Wege,
dasselbe zu tun, sind einer zuviel.

Drei Dinge, die dabei nicht offensichtlich sind:

  Type=oneshot          Quadlet setzt sonst Type=notify, und die Unit
                        gaelte als gestartet, sobald der Container
                        laeuft, nicht wenn er durch ist.
  kein RemainAfterExit  Das Handbuch empfiehlt es fuer oneshot, aber bei
                        Timer-Aktivierung bliebe der Auftrag im Zustand
                        "started" und der Timer loeste ihn nie wieder
                        aus.
  --env-file            EnvironmentFile= im Abschnitt [Container] ist
                        nicht systemds EnvironmentFile=. Quadlet reicht
                        die Datei an podman weiter, und podman entfernt
                        keine Anfuehrungszeichen um Werte.

Network=bebop-net steht jetzt direkt in der Unit; P2_NETZ entfaellt.
Type=oneshot schaltet ausserdem die Startzeitgrenze ab, womit der Grund
fuer TimeoutStartSec=1800 wegfaellt.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NupWEBPzsyohVPG47HsTz8
This commit is contained in:
irrlicht
2026-09-08 16:37:43 +02:00
co-authored by Claude Opus 5
parent c18e94e61e
commit 62e1923a72
5 changed files with 86 additions and 59 deletions
+13 -5
View File
@@ -346,16 +346,24 @@ Entwicklungsmaschine (Fedora, Python 3.14) — `neu 0, geändert 0, unverändert
### Betrieb ### Betrieb
`deploy/` enthält `wurzelwerk-p2.service` und `.timer` als Vorlage. Kein `deploy/` enthält `wurzelwerk-p2.container` und `.timer` als Vorlage,
Quadlet: Phase 2 ist kein Dienst, der läuft, sondern ein Auftrag, der fällig dazu `INBETRIEBNAHME.md` mit der Reihenfolge. Quadlet, wie Phase 1 — aber
wird. Der Timer feuert versetzt zum 15-Minuten-Raster von Phase 1 mit `Type=oneshot` und ohne `RemainAfterExit`: Phase 2 ist kein Dienst, der
läuft, sondern ein Auftrag, der fällig wird und danach verschwindet. Mit
`RemainAfterExit=yes` bliebe die Unit im Zustand „started" stehen, und der
Timer könnte sie nie wieder auslösen.
Der Timer feuert versetzt zum 15-Minuten-Raster von Phase 1
(`*:03,18,33,48`), damit der Slot vollständig in `raw_items` steht, bevor (`*:03,18,33,48`), damit der Slot vollständig in `raw_items` steht, bevor
daraus kodiert wird. `phase2.py --aufholen` arbeitet alle Slots nach, denen daraus kodiert wird. `phase2.py --aufholen` arbeitet alle Slots nach, denen
eine Kodierung fehlt — ein ausgefallener Lauf holt sich beim nächsten Mal eine Kodierung fehlt — ein ausgefallener Lauf holt sich beim nächsten Mal
selbst ein. selbst ein.
Zugangsdaten stehen in `~/.config/wurzelwerk/p2.env` (chmod 600), nicht im Zugangsdaten stehen in `~/.config/wurzelwerk/database.env` und
Repo und nicht in der Unit. Vorlage: `deploy/p2.env.beispiel`. `p2.env` (chmod 600), nicht im Repo und nicht in der Unit. Vorlage:
`deploy/p2.env.beispiel`. Beide gehen als `--env-file` an podman, nicht über
systemds `EnvironmentFile=` im Abschnitt `[Service]` — podman entfernt keine
Anführungszeichen um Werte, systemd schon.
## Modul 5: Embeddings ## Modul 5: Embeddings
+5 -1
View File
@@ -97,8 +97,12 @@ das Modell nie wieder ins Netz und lädt in zwei Sekunden aus dem Volume.
## 5. Units einhängen ## 5. Units einhängen
```sh ```sh
cp deploy/wurzelwerk-p2.{service,timer} ~/.config/systemd/user/ cp deploy/wurzelwerk-p2.container ~/.config/containers/systemd/
cp deploy/wurzelwerk-p2.timer ~/.config/systemd/user/
systemctl --user daemon-reload systemctl --user daemon-reload
# Was Quadlet daraus erzeugt hat, vor dem ersten Start ansehen:
/usr/lib/systemd/user-generators/podman-user-generator --dryrun
systemctl --user start wurzelwerk-p2.service # ein Lauf von Hand systemctl --user start wurzelwerk-p2.service # ein Lauf von Hand
journalctl --user -u wurzelwerk-p2 -n 40 journalctl --user -u wurzelwerk-p2 -n 40
``` ```
+5 -6
View File
@@ -1,16 +1,15 @@
# Nach ~/.config/wurzelwerk/p2.env kopieren und ausfuellen. chmod 600. # Nach ~/.config/wurzelwerk/p2.env kopieren und ausfuellen. chmod 600.
# Enthaelt Zugangsdaten und gehoert nicht ins Repo. # Enthaelt Zugangsdaten und gehoert nicht ins Repo.
# Das Podman-Netz, in dem der Lauf haengt. Auf Prod bebop-net: dort loest # Das Netz steht in der Quadlet-Unit (Network=bebop-net), nicht hier.
# Podmans DNS den Rechnernamen "postgres" auf, den DATABASE_URL benutzt. #
# Ohne eigenes Netz ist pasta richtig, dann aber muss in DATABASE_URL
# host.containers.internal stehen.
P2_NETZ=bebop-net
# Die Datenbankverbindung steht auf Prod bereits in # Die Datenbankverbindung steht auf Prod bereits in
# ~/.config/wurzelwerk/database.env; die Unit liest beide Dateien. Hier nur # ~/.config/wurzelwerk/database.env; die Unit liest beide Dateien. Hier nur
# setzen, wenn Phase 2 eine andere braucht. # setzen, wenn Phase 2 eine andere braucht.
# #
# Anfuehrungszeichen um den Wert weglassen: podman --env-file entfernt sie
# nicht, anders als systemd.
#
# DATABASE_URL=postgresql://wurzelwerk:PASSWORT@postgres:5432/wurzelwerk # DATABASE_URL=postgresql://wurzelwerk:PASSWORT@postgres:5432/wurzelwerk
# Threadzahl. Geht in die coder_version ein - eine Aenderung erzeugt einen # Threadzahl. Geht in die coder_version ein - eine Aenderung erzeugt einen
+63
View File
@@ -0,0 +1,63 @@
# Phase 2 als Quadlet, neben wurzelwerk-ingest.container.
#
# cp deploy/wurzelwerk-p2.container ~/.config/containers/systemd/
# cp deploy/wurzelwerk-p2.timer ~/.config/systemd/user/
# systemctl --user daemon-reload
#
# Quadlet erzeugt daraus wurzelwerk-p2.service; der Timer traegt denselben
# Namen und findet sie deshalb ohne Unit=-Zeile.
#
# Kein [Install]: die Unit soll nicht beim Anmelden starten, sondern nur,
# wenn der Timer sie faellig macht.
[Unit]
Description=Wurzelwerk Phase 2 (Aufbereitung)
# Nicht Requires: laeuft Postgres gerade nicht, soll der Slot ausfallen und
# beim naechsten Mal ueber --aufholen nachgeholt werden, nicht die
# Abhaengigkeit neu starten.
After=postgres.service
[Container]
Image=localhost/wurzelwerk-p2
# Das Abbild hat ENTRYPOINT python3; Exec= liefert nur das Argument.
Exec=phase2.py --aufholen
# Ohne dieses Netz loest der Rechnername "postgres" aus DATABASE_URL nicht
# auf. Der Fehler waere dann eine Namensaufloesung, keine Anmeldung.
Network=bebop-net
# Zwei Dateien, in dieser Reihenfolge - die spaetere gewinnt bei gleichem
# Schluessel. database.env haelt die Datenbankverbindung und gehoert Phase 1
# mit; sie wird hier nur gelesen, nicht angefasst. p2.env traegt, was nur
# Phase 2 angeht.
#
# Achtung, anderer Mechanismus als bei EnvironmentFile= im Abschnitt
# [Service]: Quadlet reicht diese Dateien als "podman run --env-file" weiter.
# Podman entfernt keine Anfuehrungszeichen. Steht in database.env
# DATABASE_URL="postgresql://..." mit Anfuehrungszeichen, landen sie im Wert
# und die Verbindung scheitert. systemd wuerde sie entfernen, podman nicht.
EnvironmentFile=%h/.config/wurzelwerk/database.env
EnvironmentFile=%h/.config/wurzelwerk/p2.env
# 2,2 GB Modellgewichte. Muessen ein Volume sein, sonst waeren sie bei jedem
# Neubau des Abbilds weg.
Volume=wurzelwerk-modelle:/cache:U
Memory=8g
ShmSize=1g
[Service]
# Der Container laeuft und ist fertig; nichts bleibt stehen. Ohne diese Zeile
# setzt Quadlet Type=notify, und die Unit gaelte als gestartet, sobald der
# Container laeuft - nicht, wenn er durch ist.
#
# Kein RemainAfterExit=yes, obwohl das Handbuch es fuer oneshot sonst
# empfiehlt: bei Timer-Aktivierung bliebe der Auftrag im Zustand "started"
# stehen und der Timer koennte ihn nie wieder ausloesen.
#
# Type=oneshot schaltet die Startzeitgrenze ab. Der Erstbestand ueber den
# gesamten Korpus - rund 17 Minuten je 5000 Items - liefe hier also nicht in
# TimeoutStartSec. Trotzdem gehoert er von Hand gefahren, siehe
# INBETRIEBNAHME.md: beim ersten Lauf werden 2,2 GB Modellgewichte geladen,
# und das will man sehen, nicht im Journal nachlesen.
Type=oneshot
-47
View File
@@ -1,47 +0,0 @@
# Phase 2 fuer den juengsten Slot. Nach ~/.config/systemd/user/ legen.
#
# systemctl --user daemon-reload
# systemctl --user enable --now wurzelwerk-p2.timer
#
# Kein Quadlet: das hier ist kein Dienst, der laeuft, sondern ein Auftrag, der
# faellig wird. Quadlet-Units sind fuer Container gedacht, die stehen bleiben.
[Unit]
Description=Wurzelwerk Phase 2 (Aufbereitung)
# Nicht Requires: laeuft Postgres gerade nicht, soll der Slot ausfallen und
# beim naechsten Mal ueber --aufholen nachgeholt werden, nicht die
# Abhaengigkeit neu starten.
After=postgres.service
[Service]
Type=oneshot
# Zwei Dateien, in dieser Reihenfolge. database.env haelt die
# Datenbankverbindung und gehoert nicht Phase 2 allein - sie steht auf Prod
# schon da und wird hier nur mitgelesen, nicht angefasst. p2.env traegt, was
# nur Phase 2 angeht; wird dort DATABASE_URL noch einmal gesetzt, gewinnt sie,
# weil systemd spaetere Dateien spaeter liest.
#
# Der Rechnername in DATABASE_URL muss aus dem Container heraus aufloesbar
# sein. Auf Prod steht dort "postgres" - der Containername, den Podmans DNS
# im Netz bebop-net aufloest. Auf einer Maschine ohne eigenes Netz waere es
# host.containers.internal; 127.0.0.1 ist es in keinem der beiden Faelle,
# das zeigt aus dem Container auf den Container.
#
# Vorgabe fuer das Netz, von den Umgebungsdateien ueberschreibbar - sie
# werden nach Environment= gelesen und gewinnen deshalb.
Environment=P2_NETZ=pasta
EnvironmentFile=%h/.config/wurzelwerk/database.env
EnvironmentFile=-%h/.config/wurzelwerk/p2.env
TimeoutStartSec=1800
ExecStart=/usr/bin/podman run --rm \
--name wurzelwerk-p2-lauf \
--network=${P2_NETZ} \
--memory=8g --shm-size=1g \
--volume wurzelwerk-modelle:/cache:U \
--env DATABASE_URL \
--env OMP_NUM_THREADS \
--env HF_HUB_OFFLINE \
localhost/wurzelwerk-p2 phase2.py --aufholen
[Install]
WantedBy=default.target