Du betreibst einen UCS 5 Domain Controller und alle paar Stunden passiert das:

oom-kill: task=univention-poli, pid=93824
Memory cgroup out of memory: Killed process 93824 (univention-poli)
  total-vm:1853704kB, anon-rss:1810820kB

Der Prozess univention-policy-update-config-registry laeuft stuendlich per Cron, laedt die gesamte LDAP-Policy-Ergebnismenge in den Python-Speicher und frisst dabei bis zu 1.8 GB RAM. Der OOM-Killer erschlaegt ihn — und reisst dabei gerne DNS (BIND9/named), Samba und andere Dienste mit.

Wenn dein DC nur 2-4 GB RAM hat (was fuer einen reinen AD/DNS/LDAP-Server voellig ausreicht), hast du ein wiederkehrendes Problem. Und nein, “RAM erhoehen” ist keine Loesung.

TL;DR

  1. Erstelle einen systemd Timer mit MemoryMax=768M als Ersatz fuer den Cron-Job
  2. Deaktiviere den UCR-managed Cron mit dem “31. Februar”-Trick
  3. Reduziere die Frequenz von stuendlich auf alle 4 Stunden

Der Prozess wird jetzt bei 768 MB per cgroup gekillt — nur er allein, ohne andere Dienste mitzureissen.

Warum das passiert

Das Python-Script /usr/lib/univention-directory-policy/univention-policy-update-config-registry ruft ucr_policy_result() auf, das die komplette Policy-Ergebnismenge aus LDAP in ein Python-Dictionary materialisiert. Keine Pagination, kein Streaming, kein Memory-Limit.

Die Aufrufkette:

Cron (5 * * * *)
  -> /usr/share/univention-directory-policy/univention-directory-policy-cron
    -> jitter 300 (zufaellige Verzoegerung bis 5 Min)
      -> run-parts /usr/lib/univention-directory-policy/
        -> univention-policy-update-config-registry  <-- hier leakt es

Stand April 2026 gibt es keinen Upstream-Fix (Paketversion 11.0.1-1A ist aktuell). Der Changelog erwaehnt kein Memory-Leak.

Warum die Standard-Antworten nicht helfen

“Fix”Warum es keiner ist
RAM erhoehenVerzoegert das Problem nur. 1.8 GB pro Lauf, stuendlich.
Prozess killenKommt in einer Stunde wieder
Swap vergroessernVerschiebt den OOM, aber jetzt stehen DNS und Samba im Swap
swapoff -aFunktioniert in LXC-Containern nicht (dazu spaeter mehr)

Die Loesung: systemd Timer mit MemoryMax

Die Idee: Ersetze den Cron-Job durch einen systemd Timer. systemd kann per cgroup ein Memory-Limit setzen — der Prozess wird bei 768 MB gekillt, nur er allein, ohne den System-OOM-Killer auszuloesen.

Schritt 1: Service erstellen

cat > /etc/systemd/system/univention-policy-update.service << 'EOF'
[Unit]
Description=Univention Policy Update Config Registry (memory-limited)
After=slapd.service

[Service]
Type=oneshot
ExecStart=/usr/share/univention-directory-policy/univention-directory-policy-cron
MemoryMax=768M
MemorySwapMax=0
TimeoutStartSec=900
EOF

Was die Werte bedeuten:

  • MemoryMax=768M: Prozess wird per cgroup bei 768 MB gekillt. Genuegend Headroom fuer normalen Betrieb, aber weit unter dem Punkt wo der System-OOM zuschlaegt.
  • MemorySwapMax=0: Der Prozess darf keinen Swap benutzen. Verhindert, dass er kalten Code anderer Dienste in den Swap drueckt.
  • TimeoutStartSec=900: 15 Minuten — das Script hat einen eingebauten Jitter von bis zu 300 Sekunden.

Schritt 2: Timer erstellen

cat > /etc/systemd/system/univention-policy-update.timer << 'EOF'
[Unit]
Description=Univention Policy Update Timer (replaces cron)

[Timer]
OnCalendar=*-*-* 01,05,09,13,17,21:05:00
RandomizedDelaySec=300
Persistent=true

[Install]
WantedBy=timers.target
EOF

Alle 4 Stunden statt stuendlich. Auf einem stabilen DC aendern sich UCR-Policies nicht jede Stunde. Persistent=true stellt sicher, dass ein verpasster Lauf nach einem Reboot nachgeholt wird.

Schritt 3: Aktivieren

systemctl daemon-reload
systemctl enable --now univention-policy-update.timer

Schritt 4: UCR-Cron deaktivieren

Hier wird es elegant. Die Cron-Datei /etc/cron.d/univention-directory-policy ist UCR-managed — wenn du sie loeschst oder editierst, wird sie beim naechsten ucr commit regeneriert. Das Template generiert die Cron-Zeile aus der UCR-Variable ldap/policy/cron.

Die Loesung: Setze den Cron-Schedule auf den 31. Februar:

ucr set 'ldap/policy/cron=0 0 31 2 *'

Das Template generiert eine syntaktisch gueltige Cron-Zeile, die nie feuert. Die UCR-Variable ueberlebt ucr commit und Paket-Updates. Kein Template-Hack, kein dpkg-divert, kein chmod -x.

Verifizierung

# Timer aktiv?
systemctl list-timers univention-policy-update.timer

# Memory-Limits gesetzt?
systemctl show univention-policy-update.service -p MemoryMax,MemorySwapMax
# -> MemoryMax=805306368 (768 MB)
# -> MemorySwapMax=0

# Cron tot?
ucr get ldap/policy/cron
# -> 0 0 31 2 *

# Manueller Test:
systemctl start univention-policy-update.service
journalctl -u univention-policy-update.service --no-pager -n 20

Wenn der Prozess bei 768 MB gekillt wird, siehst du exit code 137 (SIGKILL) — aber nur fuer diesen einen Prozess. DNS, Samba, LDAP laufen weiter.

Bonus-Pitfall: LXC und Swap

Falls dein UCS-DC in einem LXC-Container laeuft (Proxmox): swapoff -a && swapon -a hat dort keine Wirkung.

Der Swap in einem LXC-Container ist “virtual” — Typ none in swapon -s. Die Swap-Pages werden vom Host per cgroup verwaltet, nicht vom Container. swapoff meldet brav Erfolg (exit 0), aber die Swap-Belegung aendert sich nicht. Klassisches “works on my machine” — nur dass dein Monitoring das nicht weiss und weiter Alarm schlaegt.

Stale Swap-Pages altern mit der Zeit raus oder werden beim Container-Reboot freigegeben. MemorySwapMax=0 im systemd Service verhindert, dass der Policy-Prozess neuen Swap belegt.

Rollback

systemctl disable --now univention-policy-update.timer
rm /etc/systemd/system/univention-policy-update.{service,timer}
systemctl daemon-reload
ucr set 'ldap/policy/cron=5 * * * *'

Drei Befehle und du bist zurueck beim Original. Der OOM-Killer freut sich.

Fazit

Univention hat das Memory-Leak seit mindestens UCS 5.0-9 nicht gefixt. Die Standard-Antwort “mehr RAM” ist keine Loesung — sie verschiebt das Problem auf naechste Woche, wenn du statt 2 GB halt 4 GB Swap im Container hast.

systemd cgroups mit MemoryMax sind der saubere Weg: der Prozess wird isoliert gekillt, der Rest des Systems bleibt stabil. Und der “31. Februar”-Trick fuer UCR-managed Cron-Jobs — den wirst du oefter brauchen, wenn du UCS-Crons loswerden willst ohne dass ucr commit sie dir wieder hinzaubert.