diff --git a/README.md b/README.md index e1bb5b8..be69ba8 100644 --- a/README.md +++ b/README.md @@ -346,16 +346,24 @@ Entwicklungsmaschine (Fedora, Python 3.14) — `neu 0, geändert 0, unverändert ### Betrieb -`deploy/` enthält `wurzelwerk-p2.service` und `.timer` als Vorlage. Kein -Quadlet: Phase 2 ist kein Dienst, der läuft, sondern ein Auftrag, der fällig -wird. Der Timer feuert versetzt zum 15-Minuten-Raster von Phase 1 +`deploy/` enthält `wurzelwerk-p2.container` und `.timer` als Vorlage, +dazu `INBETRIEBNAHME.md` mit der Reihenfolge. Quadlet, wie Phase 1 — aber +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 daraus kodiert wird. `phase2.py --aufholen` arbeitet alle Slots nach, denen eine Kodierung fehlt — ein ausgefallener Lauf holt sich beim nächsten Mal selbst ein. -Zugangsdaten stehen in `~/.config/wurzelwerk/p2.env` (chmod 600), nicht im -Repo und nicht in der Unit. Vorlage: `deploy/p2.env.beispiel`. +Zugangsdaten stehen in `~/.config/wurzelwerk/database.env` und +`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 diff --git a/deploy/INBETRIEBNAHME.md b/deploy/INBETRIEBNAHME.md index bb6a04c..3ae5feb 100644 --- a/deploy/INBETRIEBNAHME.md +++ b/deploy/INBETRIEBNAHME.md @@ -97,8 +97,12 @@ das Modell nie wieder ins Netz und lädt in zwei Sekunden aus dem Volume. ## 5. Units einhängen ```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 + +# 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 journalctl --user -u wurzelwerk-p2 -n 40 ``` diff --git a/deploy/p2.env.beispiel b/deploy/p2.env.beispiel index 7867915..95dd6d4 100644 --- a/deploy/p2.env.beispiel +++ b/deploy/p2.env.beispiel @@ -1,16 +1,15 @@ # Nach ~/.config/wurzelwerk/p2.env kopieren und ausfuellen. chmod 600. # Enthaelt Zugangsdaten und gehoert nicht ins Repo. -# Das Podman-Netz, in dem der Lauf haengt. Auf Prod bebop-net: dort loest -# 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 - +# Das Netz steht in der Quadlet-Unit (Network=bebop-net), nicht hier. +# # Die Datenbankverbindung steht auf Prod bereits in # ~/.config/wurzelwerk/database.env; die Unit liest beide Dateien. Hier nur # 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 # Threadzahl. Geht in die coder_version ein - eine Aenderung erzeugt einen diff --git a/deploy/wurzelwerk-p2.container b/deploy/wurzelwerk-p2.container new file mode 100644 index 0000000..777fc48 --- /dev/null +++ b/deploy/wurzelwerk-p2.container @@ -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 diff --git a/deploy/wurzelwerk-p2.service b/deploy/wurzelwerk-p2.service deleted file mode 100644 index 5e03bd4..0000000 --- a/deploy/wurzelwerk-p2.service +++ /dev/null @@ -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