systemd – Übersicht

Abhängigkeiten und Reihenfolge

Die Hauptaufgabe von systemd ist der Start des Systems, also die Organisation der einzelnen Schritte, nachdem der Kernel den ersten Prozess (PID 1, init) erzeugt hat. Die wichtigsten Aspekte dabei sind

Weitere relevante Aspekte sind:

Abhängigkeiten

Es gibt drei Arten von Abhängigkeiten:

systemd bietet mehrere Abstufungen von Abhängigkeiten:

Direktive Bedeutung
Wants Die andere Unit wird gestartet, aber ein Scheitern ihres Starts beeinflusst die konfigurierte Unit nicht
Requires Die andere Unit wird gestartet, aber ein Scheitern ihres Starts verhindert den Start der konfigurierten Unit und eine spätere fehlerhafte (interne) Beendigung oder eine Beendigung durch systemd führt dazu, dass die konfigurierte Unit durch systemd beendet wird.
BindsTo Die andere Unit wird gestartet, aber jede Form der Beendigung führt dazu, dass die konfigurierte Unit durch systemd beendet wird. Zusammen mit einer
Requisite Nur wenn die andere Unit bereits erfolgreich gestartet wurde, wird die konfigurierte Unit gestartet. Aus offensichtlichen Gründen ist dies im allgemeinen nur sinnvoll in Kombination mit einer Startreihenfolge.
Conflicts Der Start der konfigurierten Unit führt zur Beendigung der anderen Unit. Mit einer beliebigen Startreihenfolge muss die andere Unit erst fertig beendet sein, bevor die konfigurierte Unit gestartet wird.

Wenn systemd startet, wird die Ziel-Unit (zumeist multi-user.target) bestimmt, und dann werden von dort aus die Abhängigkeiten heruntergebrochen. Wenn klar ist, welche Units alle gestartet werden müssen, wird die Reihenfolge bestimmt. Dabei kann es zu zyklischen Abhängigkeiten kommen (z.B. die gleichzeitigen Anforderungen A vor B, B vor C und C vor A), die den Start der Units verhindern.

Startreihenfolge

Die Reihenfolge-Direktiven können in beide Richtungen genutzt werden. After=a.service in Unit b.service bewirkt dasselbe wie Before=b.service in Unit a.service.

Direktive Bedeutung
After Die konfigurierte Unit wird erst gestartet, nachdem der Start der anderen Unit abgeschlossen (ggf. oder gescheitert) ist.
Before Die konfigurierte Unit wird erst gestartet, nachdem der Start der anderen Unit abgeschlossen (ggf. oder gescheitert) ist.

Bei negativen Abhängigkeiten (Conflicts=b.service) muss eine Reihenfolge definiert werden, um sicherzustellen, dass die konfigurierte Unit erst gestartet wird, nachdem die andere Unit beendet wurde. Ob dafür After=a.service oder Before=a.service genutzt wird, ist egal, da stop jobs immer vor start jobs ausgeführt werden.

Definition des Endes des Startvorgangs

Für die korrekte (d.h. ausreichend, aber nicht unnötig lange) Verzögerung des Starts einer in eine Reihenfolge gebrachte Unit ist wichtig, in welchem Moment der Startvorgang als abgeschlossen angesehen wird. Das ist von außen typischerweise nicht sicher zu beurteilen.

Nur die Applikation selber weiß wirklich, wann sie sich fertig initialisiert hat. Die beste (aber nicht oft genutzte) Möglichkeit ist deshalb, dass die Applikation systemd mitteilt, in welchem Status sie sich befindet. Dies wird bei Service-Units mit Type=notify erreicht.

Orthogornalität der Abhängigkeiten und Reihenfolge

Ein wichtiger Punkt beim Verständnis von systemd ist der intuitiv nur mäßig gut zugängliche Aspekt, dass diese beiden Kategorien von Verhältnissen zwischen Units völlig unabhängig voneinander sind:

  1. Dass Unit B eine Abhängigkeit auf Unit A hat, bedeutet nicht, dass A vor B gestartet wird.

  2. Dass Unit B eine Abhängigkeit auf Unit A hat und der Start von A fehlschlägt, bedeutet nicht, dass B nicht gestartet wird (das ist nur dann nicht der Fall, wenn der Startversuch von A beendet ist, bevor B gestartet werden konnte).

  3. Dass Unit B nach Unit A gestartet wird, bedeutet nicht, dass A überhaupt gestartet wird.

Reihenfolge (After=a.service) Abhängigkeit (Requires=a.service)
ja nein
ja
  • a.service wird zuerst gestartet.

  • b.service wird nur gestartet, wenn a.service erfolgreich gestartet ist.

  • Bei Wants=a.service statt Requires=a.service: b.service wird auch gestartet, nachdem der Startversuch von a.service fehlgeschlagen ist.

  • a.service wird nicht wegen des Starts von b.service gestartet.

  • Sollte a.service aus anderen Gründen im Rahmen derselben Transaktion gestartet werden, dann wird b.service erst dann gestartet, wenn a.service erfolgreich gestartet ist.

nein
  • a.service und b.service werden gleichzeitig gestartet.

  • Wenn der Start von a.service fehlschlägt, wird b.service beendet.

  • a.service wird nicht wegen des Starts von b.service gestartet.

  • Sollte a.service aus anderen Gründen im Rahmen derselben Transaktion gestartet werden, dann werden a.service und b.service gleichzeitig gestartet.

Anzeige der Abhängigkeiten und Reihenfolgen

Die Abhängigkeiten kann man sich mit systemctl list-dependencies und die Reihenfolgen mit systemctl --after/--before list-dependencies in der Konsole anzeigen lassen. Eine grafische Anzeige ist mit den Daten möglich, die das Kommando systemd-analyze dot erzeugt.

Zwei Beispiel-Services, die nichts tun, aber eine Abhängigkeit und eine Reihenfolgen zwischeneinander haben:

start cmd:> cat a.service
[Unit]
Description=Test service A
DefaultDependencies=no
Before=b.service

[Service]
Type=oneshot
ExecStart=/usr/bin/true
RemainAfterExit=yes

start cmd:> cat b.service
[Unit]
Description=Test service B
DefaultDependencies=no
Wants=a.service

[Service]
Type=oneshot
ExecStart=/usr/bin/true
RemainAfterExit=yes

Auch wenn die Abhängigkeit und die Reihenfolge nur in jeweils einer der Units konfiguriert sind, kann man sie sich über beide Units anzeigen lassen. Die Anhängigkeit:

ec:0   22:05:54  root@selinux-test:/etc/systemd/system
start cmd:> systemctl list-dependencies a
a.service
● └─system.slice

ec:0   22:14:53  root@selinux-test:/etc/systemd/system
start cmd:> systemctl --reverse list-dependencies a
a.service
○ └─b.service

ec:0   22:15:03  root@selinux-test:/etc/systemd/system
start cmd:> systemctl list-dependencies b
b.service
○ ├─a.service
● └─system.slice

Die Reihenfolge:

ec:0   22:15:15  root@selinux-test:/etc/systemd/system
start cmd:> systemctl --before list-dependencies a
a.service
○ └─b.service

ec:0   22:20:48  root@selinux-test:/etc/systemd/system
start cmd:> systemctl --after --reverse list-dependencies a
a.service
○ └─b.service

ec:0   22:21:06  root@selinux-test:/etc/systemd/system
start cmd:> systemctl --after list-dependencies b
b.service
○ ├─a.service
● ├─system.slice
● └─systemd-journald.socket

Grafische Anzeige: systemd-analyze dot --from-pattern='?.service' --to-pattern='?.service' | dot -Tsvg >~/a-b.svg

Dies funktioniert leider nur, wenn die zu betrachtenden Units Teil des Systemstarts sind, also von default.target aufgerufen werden. Das gilt für die obigen Beispiel-Units nicht. Deshalb wird der Unit b.service ein [Install]-Abschnitt hinzugefügt und sie wird aktiviert.

Systemd-Abhängigkeitsgraph

Ohne die Filter (--from-pattern=, --to-pattern=) wird die Ausgabe unübersichtlich. Hier die Variante (Rocky Linux), die auf die (meisten) target-Units beschränkt ist:

Systemd-Abhängigkeitsgraph

   Color legend: black     = Requires
                 dark blue = Requisite
                 dark grey = Wants
                 red       = Conflicts
                 green     = After

Die Conflicts-Einträge stellen kein Problem dar. Sie kommen dadurch zustande, dass hier target-Units dargestellt werden, die nicht gleichzeitig aktiv sind. Hier ist vor allem die Unit shutdown.target weggelassen worden, die Conflicts-Abhängigkeiten zu so ziemlich allen Units hat. Wird die mit abgebildet, erkennt man quasi nichts mehr.

Conditions und Asserts

Für alle systemd-Units, nicht nur für Services, gibt es eine Vielzahl (ca. 30) Bedingungen, an die der Start der Unit geknüpft wird; etwa der Test darauf, ob eine bestimmte Datei existiert. Diese Bedingungen können in zwei Varianten aktiviert werden: