In einem früheren Beitrag habe ich FreshRSS in Podman eingerichtet. Der Auto-Refresh lief über den Flag CRON_MIN – alle 30 Minuten ein Update. Hat funktioniert. Bis es eines Tages nicht mehr lief. Manuell ließ sich alles aktualisieren. Der cron-Dienst lief, die crontab war korrekt. Und trotzdem passierte nichts. Wer schon einmal gerätselt hat, warum Cron im Podman-Container manchmal einfach schweigt – hier ist die Erklärung. FreshRSS dient dabei nur als Beispiel, denn das Problem ist allgemeiner Natur.
Das Symptom
Der Dienst läuft, die crontab ist vorhanden, das Kommando funktioniert von Hand. Aber der geplante Job feuert nie. Im Log steht – wenn überhaupt – nur eine Zeile: Cannot make/remove an entry for the specified session.
Die eigentliche Log-Datei des Jobs wird gar nicht erst angelegt. Das heißt: Cron startet das Kommando überhaupt nicht. Letztlich liegt es also nicht am Aufruf / script selbst, sondern an der Sitzung, die Cron für den Job öffnen will.
Der technische Hintergrund
Schuld ist ein PAM-Modul namens pam_loginuid. Cron baut für jeden Job eine PAM-Sitzung auf. Dabei will pam_loginuid die sogenannte loginuid setzen – eine Kennung, die im Kernel unter /proc/self/loginuid liegt. Diese Kennung lässt sich nur ein einziges Mal setzen. Danach ist sie unveränderlich, was auch Sinn macht. Aber genau das erzeugt in dieser Kombi das Problem.
Warum trifft es Podman, aber nicht Docker? Das liegt an der Architektur der beiden Container-Manager. Docker arbeitet mit einem zentralen Daemon. Der Container ist ein Kind dieses Daemons. Seine loginuid ist daher noch unset – pam_loginuid darf sie setzen, alles gut. Podman arbeitet daemonless, nach einem fork/exec-Modell. Der Container ist ein Kind deiner Login-Sitzung. Startest du Podman aus einer normalen Shell, ist die loginuid bereits gesetzt. pam_loginuid will sie ändern, der Kernel verbietet das – und die ganze Sitzung scheitert. Die Podman-Doku beschreibt genau diesen Fall.
Docker und ältere Podman-Versionen
Unter Docker fällt das Problem mit den Standardeinstellungen also kaum auf. Dazu kommt ein zweiter Unterschied: die Capabilities. Docker gewährt CAP_AUDIT_WRITE per Default. Podman tut das heute nicht mehr – die Defaults sind bewusst strenger.
Früher war das anders. Ältere Podman-Versionen hatten AUDIT_WRITE noch im Standard-Set. Wer also vor ein, zwei Jahren dasselbe Setup laufen hatte, bei dem lief der Cron oft klaglos. Nach einem Update verschwand die Capability aus dem Default – und plötzlich schwieg der Job. Genau das macht den Fehler so tückisch. Man ändert nichts, und trotzdem ist er auf einmal da.
| Umgebung | Verhalten |
|---|---|
| Docker, Standardeinstellungen | Cron läuft – loginuid unset, AUDIT_WRITE vorhanden |
| Podman, ältere Version | Cron lief meist – AUDIT_WRITE war im Standard-Set |
| Podman aktuell, Start aus der Shell | Cron schweigt – loginuid bereits gesetzt, kein AUDIT_WRITE |
| Podman als systemd-Dienst + AUDIT_WRITE | Cron läuft wieder |
Die schnelle Lösung: required wird optional
Der schnellste Weg führt direkt ins Modul. In der Datei /etc/pam.d/cron steht eine Zeile mit pam_loginuid.so, meist als required. Setzt man sie auf optional, scheitert die Sitzung nicht mehr. Das ist unkritisch. pam_loginuid hat im Container ohnehin keine sinnvolle Aufgabe – die loginuid-Verfolgung ist ein Thema des Hosts, nicht des Containers.
Bei meinem Fall freshrss gibt es weder vi noch joe im container, daher nehme ich einfach sed:
sed -i 's/^\(session\s*\)required\(\s*pam_loginuid.so\)/\1optional\2/' /etc/pam.d/cron
Alternativ kopiert man die Datei einfach aus dem Container raus, bearbeitet sie, und anschließend wieder zurück.
podman cp freshrss-container:/etc/pam.d/cron .
vi cron
podman cp cron freshrss-container:/etc/pam.d/cron
Danach den cron-Dienst im Container neu starten – oder einfach den ganzen Container. Beim nächsten Intervall läuft der Job. Allerdings ist die Änderung bei einem Update bzw wenn der Container neu geschrieben wird hinfällig.
Die saubere Lösung: System-Dienst und YAML
Verlässlicher ist es, zwei Dinge zu kombinieren. Erstens: Podman nicht aus der Shell starten, sondern als systemd-Dienst. Dann ist die loginuid beim Start noch frei. Zweitens: dem Container die fehlende Capability mitgeben.
Wer schon eine Kubernetes-YAML nutzt, ergänzt die Capability einfach im securityContext:
securityContext:
capabilities:
add:
- AUDIT_WRITE
Für den Dienst-Teil bietet Podman mit Quadlet den modernen Weg. Eine .kube-Unit verweist auf die vorhandene YAML und macht daraus einen systemd-Dienst:
# /etc/containers/systemd/freshrss.kube
[Kube]
Yaml=/pfad/zu/freshrss.yaml
[Install]
WantedBy=default.target
Nach systemctl daemon-reload und systemctl start freshrss.service übernimmt systemd Start, Stop und Neustart. Das löst nebenbei auch das lästige Starten und Stoppen von Hand.
Ein ehrlicher Hinweis zum Schluss dieses Abschnitts: Läuft Podman rootless aus einer User-Session, kann die loginuid-Eigenheit trotz AUDIT_WRITE bestehen bleiben. Der PAM-Patch greift dagegen immer – unabhängig von Runtime, Capabilities und Start-Modus. Im Zweifel kombiniert man beides.
Fazit
Ist das ein Bug? Jein. FreshRSS kann wenig dafür. Podman streng genommen auch nicht – die strengeren Defaults sind eine bewusste Sicherheitsentscheidung. Es ist eher ein Zusammenspiel mehrerer Schichten, das an einer unscheinbaren PAM-Zeile hängen bleibt. Wer cron in einem Container betreibt, sollte pam_loginuid ohnehin auf optional setzen. Im Container bringt es keinen Nutzen.
Der eigentliche Gewinn liegt woanders. Wer den Schritt zum systemd-Dienst geht, repariert nicht nur den Cron. Er bekommt gleich automatischen Start, sauberes Stoppen und Updates ohne Handarbeit dazu. Manchmal ist der Umweg über das Problem die bessere Lösung.