ZURÜCK

Systemd timer

Systemd timer als Ersatz für cronjobs.

Datum: 2020-05, Tags: Linux, Systemd, timer, cron

Mit 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.

/usr/lib/systemd/system/maintenance.service
[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
/some/working/directory/bin/maintenance.bash
#!/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.

/usr/lib/systemd/system/maintenance.timer
[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.