Aaron Fischer Ingenieur, Vater, Heimwerker, Problemlöser

23 Januar, 2016

Cronjobs mit systemd

TL;DR:

Mit systemd kommt ein sehr praktisches Werkzeug mit, das den klassischen Cronjob problemlos ablöst und durch ein paar nützliche Features erweitert. Es lohnt sich, einen Blick darauf zu werfen und zukünftig systemd timers anstelle von *cron zu verwenden.

Möchte man ein Script oder Programm zeitgesteuert oder in Intervallen starten, verwendet man meist einen der vielen verschiedenen cron-Programmen. Die meisten (anacron, cronie, ...) nutzen drei verschiedene Wege, um Cronjobs zu definieren: Der Aufruf von crontab -e, das Ablegen von Scripts in /etc/cron.(hourly|daily|monthly) oder das Einfügen von Cron-Files in /etc/cron.d/. Hierbei sind ein paar Details wie die Benennung der Files zu beachten, sonst startet der Cronjob schlicht nicht. Auch ist die Syntax exakt einzuhalten. Ob ein Cronjob erfolgreich lief, kann man mit Glück in /var/log/syslog nachsehen.

Eine Alternative sind systemd timers, die dem alten cron System ein paar Features voraus sind und etwas komfortabler zu bedienen sind. Scripte lassen sich nicht direkt mit systemd starten, es muss sich immer um ein Service-File handeln. Dieses ist aber schnell angelegt und bringt ebenfalls ein paar Vorteile mit sich. Gehen wir im ersten Beispiel davon aus, dass einmal täglich ein Backup-Script gestartet werden soll.

Zuerst wird das Service-File unter /etc/systemd/system/website-backup.service angelegt. Das Backup-Script liegt unter /usr/local/mongodb-backup.sh.

[Unit]
Description=Create a backup of the website data and push it to AWS
Wants=network.target
Requires=mongodb.service
After=mongodb.service

[Service]
Type=oneshot
ExecStart=/bin/bash mongodb-backup.sh
WorkingDirectory=/usr/local/

Schön an den Service-Files ist, dass Abhängigkeiten definiert werden können. So kann sichergestellt werden, dass eine Netzwerkverbindung besteht und dass die Datenbank auch läuft, von der das Backup erstellt werden soll. Ist eine Abhängigkeit nicht erfüllt, wird diese zuerst aufgelöst (in diesem Fall DB-Server starten und Netzwerkverbindung herstellen). Type=oneshot ist hier wichtig, damit systemd weiß, dass das Script normal beendet werden darf und nicht als Absturz gewertet wird. Anderenfalls geht systemd davon aus, dass der Service nach Beendigung des Scripts gecrashed ist. Mit ExecStart=... kann nun das Script angegeben werden, welches im Verzeichnis WorkingDirectory=... ausgeführt wird.

Um das Service-File zu testen, muss zuerst der systemd Daemon neu geladen werden, um das neue Service-File zu berücksichtigen.

systemctl daemon-reload

Anschließend kann es wie gewohnt mit systemctl gestartet werden:

systemctl start website-backup.service

Die Log-Ausgabe und den Status des Scripts kann mit systemctl status website-backup oder mit journalctl angesehen werden. Nun soll dieser Service in regelmäßigen Abständen ausgeführt werden. Dazu wird ein zusätzliches timer-file benötigt. Dieses muss den gleichen Namen wie das Service-File mit der Endung .timer haben. Die Datei /etc/systemd/system/website-backup.timer kann nun folgendermaßen aussehen:

[Unit]
Description=Trigger the daily backup for the website

[Timer]
OnCalendar=daily
Persistent=true

[Install]
WantedBy=timers.target

Mit OnCalendar lassen sich ähnlich wie bei cron Zeitintervalle definieren. Das Flag Persistent=true sorgt dafür, dass das Service-File in jedem Fall in den angegebenen Intervallen ausgeführt wird, auch wenn es zum entsprechenden Zeitpunkt deaktiviert war.

Dieses Timer-File kann nun nach einem erneuten systemctl daemon-reload aktiviert und gestartet werden:

systemctl enable website-backup.timer
systemctl start website-backup.timer

Um nun einen Überblick zu bekommen, welche Timer wann gelaufen sind und wann sie das nächste Mal ausgeführt werden, kann der list-timers Befehl für systemctl verwendet werden.

systemctl list-timers

Der [Timer] Abschnitt lässt noch einige interessante Optionen zu. Ein Timer-File zum Aktualisieren des Caches könnte folgendermaßen aussehen:

...

[Timer]
OnBootSec=5min
OnUnitActiveSec=10min
...

Der Timer ist nicht an einen fixen Zeitslot gebunden, mehr an die Laufzeit des Systems und des Services. Der Timer wird 5 Minuten nach dem Booten getriggert und alle 10 Minuten nach Start des Services.

Ein weiteres Beispiel könnte der Abgleich eines entfernten Verzeichnisses per rsync sein. Bei einem Host ist das kein Problem, doch wenn dies 1000 Hosts gleichzeitig machen, wird der Quellhost nicht optimal ausgelastet, da alle Hosts gleichzeitig anfragen. Für solche Fälle gibt es die Option RandomSec.

...
[Timer]
OnUnitActiveSec=1h
RandomSec=30m
...

Somit wird der Service jede Stunde ein Mal ausgeführt, aber um 0-30 Minuten verzögert. Haben nun 1000 Hosts einen solchen Timer installiert, wird sich die Last auf den Quellhost optimal verteilen.

Weitere nützliche Optionen sind in der Manpage man systemd.timer zu finden.