Systemd timer
Systemd timer als Ersatz für cronjobs.
Datum: 2020-05, Tags: Linux, Systemd, timer, cronMit systemd werden "timer" eingeführt, die eine zeitgesteuerte Ausführung dazugehöriger services ermöglichen. Damit arbeiten sie ähnlich wie cronjobs, womit zeitgesteuert scripte oder programme ausgeführt werden können. Systemd timer sind allerdings deutlich flexibler und können wesentlich detaillierter konfiguriert werden.
Der systemd Dienst löst seit etwa 2015 auf wahrscheinlich allen aktuellen Linux Distributionen das SysVinit system ab. Zu den systemd Komponenten gehören auch "timer", eine Dienste-Konfiguration, mit der sich systemd services zeitlich steuern lassen. Dafür ist klassisch der Dienst "cron" zuständig. Jedoch können systemd und cron auch gemeinsam installiert werden und beide Dienste parallel laufen. Hier kommt eine kleine Anleitung zur Erstellung eigener timer und was im Zusammenspiel mit cronjobs beachtet werden muss.
Zunächst hier noch einmal die man-pages für systemd und journalctl auf freedesktop.org:
Die systemd services und timer liegen je nach Distribution in dem Verzeichnis /lib/systemd/system
oder
/usr/lib/systemd/system
. Das ist am einfachsten durch die symlinks in dem Verzeichnis /etc/systemd/system
zu erkennen:
~ls -al /etc/systemd/system drwxr-xr-x 2 root root 4096 20. Sep 2018 basic.target.wants drwxr-xr-x 2 root root 4096 20. Sep 2018 bluetooth.target.wants lrwxrwxrwx 1 root root 36 20. Sep 2018 bootmsg.service -> /usr/lib/systemd/system/klog.service drwxr-xr-x 2 root root 4096 20. Sep 2018 btrfs-balance.timer.d drwxr-xr-x 2 root root 4096 20. Sep 2018 btrfs-scrub.timer.d lrwxrwxrwx 1 root root 40 20. Sep 2018 default.target -> /usr/lib/systemd/system/graphical.target drwxr-xr-x 2 root root 4096 20. Sep 2018 default.target.wants drwxr-xr-x 2 root root 4096 20. Sep 2018 getty.target.wants drwxr-xr-x 2 root root 4096 20. Sep 2018 graphical.target.wants drwxr-xr-x 2 root root 4096 27. Apr 08:40 multi-user.target.wants lrwxrwxrwx 1 root root 46 20. Sep 2018 network.service -> /usr/lib/systemd/system/NetworkManager.service drwxr-xr-x 2 root root 4096 27. Apr 08:40 printer.target.wants drwxr-xr-x 2 root root 4096 20. Sep 2018 remote-fs.target.wants drwxr-xr-x 2 root root 4096 27. Apr 08:40 sockets.target.wants drwxr-xr-x 2 root root 4096 14. Apr 08:41 sysinit.target.wants lrwxrwxrwx 1 root root 39 20. Sep 2018 syslog.service -> /usr/lib/systemd/system/rsyslog.service drwxr-xr-x 2 root root 4096 11. Mai 11:05 timers.target.wants lrwxrwxrwx 1 root root 47 20. Sep 2018 xdm.service -> /usr/lib/systemd/system/display-manager.service
Wir wechseln in das Verzeichnis und finden dort alle installierten systemd services und timer. Ein gutes Beispiel ist der Dienst "logrotate":
~cd /usr/lib/systemd/system /usr/lib/systemd/systemls -al logrotate.* -rw-r--r-- 1 root root 269 17. Dez 2018 logrotate.service -rw-r--r-- 1 root root 192 17. Dez 2018 logrotate.timer
Der Dienst logrotate.service ist ohne die Sektion [Install]
definiert, dadurch wird u.a. deutlich,
dass der Dienst nicht dauerhaft läuft, sondern anderweitig - nämlich durch den timer - gesteuert wird.
Das wird gleich klar werden, wenn wir einen eigenen service und timer erstellen.
Beispiel: eigener Service "maintenance"
Erzeuge in dem "systemd/system" Verzeichnis eine Datei maintenance.service
. Die Dateie soll
root:root gehören und die permissions 0644 haben.
[Unit] Description=Maintenance service ConditionACPower=true [Service] Type=oneshot User=jens WorkingDirectory=-/some/working/directory ExecStart=/bin/bash bin/maintenance.bash Nice=19 Environment=APP_ENV=dev # Prevent writes to /usr, /boot, and /etc ProtectSystem=full # Prevent accessing /home, /root and /run/user ProtectHome=true
Der neue Service soll also als User "jens" laufen. Der Typ des services ist auf oneshot
gestellt,
es wird ein working directory angegeben, sowie ein auszuführendes script maintenace.bash
. Zusätzlich
wird noch eine environment-Variable APP_ENV
gesetzt.
Erzeuge den Verzeichnispfad und erstelle das maintenance script als root user (root:root, 0644).
~mkdir -p /some/working/directory/bin
#!/bin/bash echo "Hello ${APP_ENV} environment!" echo "My name is $(id)"
Nun können wir den Service aktivieren, erhalten aber folgende Fehlermeldung:
/some/working/directory/bin/systemctl enable maintenance.service The unit files have no installation config (WantedBy, RequiredBy, Also, Alias settings in the [Install] section, and DefaultInstance for template units). This means they are not meant to be enabled using systemctl. Possible reasons for having this kind of units are: 1) A unit may be statically enabled by being symlinked from another unit's .wants/ or .requires/ directory. 2) A unit's purpose may be to act as a helper for some other unit which has a requirement dependency on it. 3) A unit may be started when needed via activation (socket, path, timer, D-Bus, udev, scripted systemctl call, ...). 4) In case of template units, the unit is meant to be enabled with some instance name specified.
Das ist die oben angesprochene fehlende Steuerung des services. I meinem Artikel über einen Systemd iptables service wird der service über den runlevel (multiuser target) gesteuert, aber in diesem Fall wollen wir einen timer erstellen.
Erstelle die Datei maintenance.timer
in dem gleichen Verzeichnis und mit den gleichen Berechtigungen
wie maintenance.service
. Beachte, dass die beiden Dateien gleich benannt sein müssen, damit der
timer dem service zugeordnet werden kann.
[Unit] Description=Maintenance service timer [Timer] OnBootSec=1 OnCalendar=*-*-* 00,12:00:00 RandomizedDelaySec=43200 Persistent=true [Install] WantedBy=timers.target
Hier wird die flexibilität der systemd timer deutlich. Der timer ist so eingestellt, dass er um 00:00 und 12:00 Uhr
ausgeführt wird, jedoch mit einem "random delay" von 12 Stunden. Das ist ist ein großer Vorteil gegenüber den
cronjobs und erlaubt es, die Systemlast fein zu verteilen. Auf den man-pages der
systemd.timer
(oder man systemd.timer
) wird diese Funktionalität und die dazugehörigen Konfigurationen im Detail erklärt.
Auf der online version der man-pages für systemd-analyze ist ein Beispiel zur Analyse und debugging der "OnCalendar" Konfiguration aufgeführt:
~systemd-analyze calendar --iterations=5 '*-2-29 0:0:0' Original form: *-2-29 0:0:0 Normalized form: *-02-29 00:00:00 Next elapse: Sat 2020-02-29 00:00:00 UTC From now: 11 months 15 days left Iter. #2: Thu 2024-02-29 00:00:00 UTC From now: 4 years 11 months left Iter. #3: Tue 2028-02-29 00:00:00 UTC From now: 8 years 11 months left Iter. #4: Sun 2032-02-29 00:00:00 UTC From now: 12 years 11 months left Iter. #5: Fri 2036-02-29 00:00:00 UTC From now: 16 years 11 months left
Leider ist dieser Befehl in meiner Installation nicht verfügbar, weil ich noch die Version 234 installiert habe.
Nun mit dem systemd timer richtig eingestellt können wir (als root) den timer aktivieren, starten und finden die Ausgabe in dem entsprechenden log-journal. Wir starten hier den service explizit, um nicht auf den timer warten zu müssen:
~systemctl enable maintenance.timer Created symlink /etc/systemd/system/timers.target.wants/maintenance.timer → /usr/lib/systemd/system/maintenance.timer. ~systemctl start maintenance.timer ~systemctl start maintenance.service ~journalctl -u maintenance -- Logs begin at Tue 2020-05-12 09:49:00 CEST, end at Tue 2020-05-12 12:27:34 CEST. -- May 12 12:26:05 iron.limitland.lan systemd[1]: Starting Maintenance service... May 12 12:26:05 iron.limitland.lan bash[8340]: Hello dev environment! May 12 12:26:05 iron.limitland.lan bash[8340]: My name is uid=1000(jens) gid=100(users) Gruppen=100(users),462(vboxusers),486(lp),496(wheel) May 12 12:26:05 iron.limitland.lan systemd[1]: Started Maintenance service.
Und nun zu dem vielleicht wichtigsten Teil: Welche systemd timer sind überhaupt aktiviert und wann werden sie gestartet?
Der systemd-Befehlt ist nicht nur für root verfügbar, sondern auch für andere Benuzer. Mit dem Argument list-timers
werden die aktiven timer angezeigt.
~systemctl list-timers NEXT LEFT LAST PASSED UNIT ACTIVATES Tue 2020-05-12 13:00:00 CEST 11min left Tue 2020-05-12 12:00:05 CEST 48min ago snapper-timeline.timer snapper-timeline.service Wed 2020-05-13 00:00:00 CEST 11h left Tue 2020-05-12 08:43:12 CEST 4h 5min ago logrotate.timer logrotate.service Wed 2020-05-13 00:00:00 CEST 11h left Tue 2020-05-12 08:43:12 CEST 4h 5min ago mandb.timer mandb.service Wed 2020-05-13 00:00:00 CEST 11h left Tue 2020-05-12 08:43:12 CEST 4h 5min ago unbound-anchor.timer unbound-anchor.service Wed 2020-05-13 00:02:12 CEST 11h left Tue 2020-05-12 12:40:50 CEST 7min ago maintenance.timer maintenance.service Wed 2020-05-13 00:22:53 CEST 11h left Tue 2020-05-12 08:43:12 CEST 4h 5min ago backup-rpmdb.timer backup-rpmdb.service Wed 2020-05-13 01:00:59 CEST 12h left Tue 2020-05-12 08:43:12 CEST 4h 5min ago backup-sysconfig.timer backup-sysconfig.service Wed 2020-05-13 01:32:09 CEST 12h left Tue 2020-05-12 08:43:12 CEST 4h 5min ago check-battery.timer check-battery.service Wed 2020-05-13 09:59:23 CEST 21h left Tue 2020-05-12 09:59:23 CEST 2h 49min ago snapper-cleanup.timer snapper-cleanup.service Wed 2020-05-13 10:04:23 CEST 21h left Tue 2020-05-12 10:04:23 CEST 2h 44min ago systemd-tmpfiles-clean.timer systemd-tmpfiles-clean.service Mon 2020-05-18 00:00:00 CEST 5 days left Mon 2020-05-11 08:14:58 CEST 1 day 4h ago btrfs-balance.timer btrfs-balance.service Mon 2020-05-18 00:00:00 CEST 5 days left Mon 2020-05-11 08:14:58 CEST 1 day 4h ago fstrim.timer fstrim.service Mon 2020-06-01 00:00:00 CEST 2 weeks 5 days left Fri 2020-05-01 12:50:19 CEST 1 weeks 3 days ago btrfs-scrub.timer btrfs-scrub.service 13 timers listed.
Die Spalten LAST
und NEXT
zeigen an, wann der jeweilige service zuletzt ausgeführt wurde und
wann er nächstes mal wieder ausgeführt werden soll. Unser maintenance service wurde also um 12:40 Uhr zuletzt gestartet
und soll um 00:02 Uhr, also in 11 Stunden und 22 Minuten erneut gestartet werden.
Zusammenspiel mit dem cron Dienst
In unserem Beispiel oben sehen wir, dass auch ein timer für "logrotate" installiert und aktiv ist. Das ist der service,
der die Logfiles in dem Verzeichnis /var/log
auswechselt/komprimiert. Er wird klassisch von dem Dienst
cron gestartet. Und auch auf meinem System ist neben den systemd auch der service cron installiert und aktiviert. Und es gibt auch einen
aktiven cronjob für logrotate.
~service cron status ● cron.service - Command Scheduler Loaded: loaded (/usr/lib/systemd/system/cron.service; enabled; vendor preset: enabled) Active: active (running) since Tue 2020-05-12 09:49:07 CEST; 3h 7min ago Main PID: 1357 (cron) Tasks: 1 CGroup: /system.slice/cron.service └─1357 /usr/sbin/cron -n May 12 09:49:07 iron systemd[1]: Started Command Scheduler. May 12 09:49:07 iron cron[1357]: (CRON) INFO (RANDOM_DELAY will be scaled with factor 88% if used.) May 12 09:49:07 iron cron[1357]: (CRON) INFO (running with inotify support) ~ls -al /etc/cron.daily total 44 drwxr-xr-x 2 root root 4096 May 4 15:39 . drwxr-xr-x 87 root root 4096 May 12 13:13 .. -rwxr-xr-x 1 root root 539 Apr 2 2019 apache2 -rwxr-xr-x 1 root root 1478 May 28 2019 apt-compat -rwxr-xr-x 1 root root 355 Dec 29 2017 bsdmainutils -rwxr-xr-x 1 root root 1187 Apr 19 2019 dpkg -rwxr-xr-x 1 root root 377 Aug 29 2018 logrotate -rwxr-xr-x 1 root root 1123 Feb 10 2019 man-db -rwxr-xr-x 1 root root 249 Sep 27 2017 passwd -rw-r--r-- 1 root root 102 Jun 23 2019 .placeholder -rwxr-xr-x 1 root root 3302 Aug 25 2019 sendmail
Also da sind relativ viele cronjobs vorhanden und auch für ziemlich viele systemnahe Pakete. Die dürften alle mit den systemd timers in Konkurrenz stehen. Aber bis auf "logrotate" und "man-db" sehe ich keine Überschneidungen der timer mit den cronjobs. Was machen also logrotate und man-db, um nicht mit sich selbst in Konflikt zu kommen?
~cat /etc/cron.daily/logrotate #!/bin/sh # skip in favour of systemd timer if [ -d /run/systemd/system ]; then exit 0 fi # this cronjob persists removals (but not purges) if [ ! -x /usr/sbin/logrotate ]; then exit 0 fi /usr/sbin/logrotate /etc/logrotate.conf EXITVALUE=$? if [ $EXITVALUE != 0 ]; then /usr/bin/logger -t logrotate "ALERT exited abnormally with [$EXITVALUE]" fi exit $EXITVALUE ~cat /etc/cron.daily/man-db #!/bin/sh # # man-db cron daily set -e if [ -d /run/systemd/system ]; then # Skip in favour of systemd timer. exit 0 fi ...
Jau, beide prüfen, ob systemd installiert ist und steigen in dem Fall aus. Ich würde sagen, das geht auch besser.
systemd user space Konfiguration
Mit dem systemd ist es auch möglich, die services auf User-Ebene zu konfigurieren. So ist bei mir beispielsweise
pulseaudio eingestellt: /usr/lib/systemd/user/pulseaudio.service
. Dazu vielleicht ein andermal mehr.
Zusammenfassung
Systemd timer sind eine feine Sache, sehr flexibel und fein einzustellen. Im Zusammenspiel mit cron muss man ggf. als maintainer etwas aufpassen. Wer zu den systemd-hatern gehört, hat sicherlich seine Argumente, ich freue mich aber schon darauf, die aktuelle Version auszuprobieren, sobald die für meine Distribution verfügbar ist.