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
dass die gewünschten Dienste gestartet werden (wenn das sinnvoll möglich ist)
dass alles gestartet wird, was diese Dienste benötigen
dass alle Aktionen in der richtigen Reihenfolge gestartet werden
dass nicht nur Dienste erfasst werden, sondern auch die anderen Aspekte des Systems (Mounten / Automount von Dateisystemen; Sockets; Timer)
dass die Konfiguration aller Aspekte der Dienstausführung einfach und sicher erfolgt (v.a. verglichen mit dem Shellscript-Ansatz von System V)
Weitere relevante Aspekte sind:
eine verlässliche Erkennung, wann ein Dienst fertig gestartet ist
Konfigurationsmöglichkeiten für den Fall einer unerwarteten Beendigung des Dienstes (z.B. Neustart oder nicht; Wartezeit zwischen Neustarts; Begrenzung der Anzahl an Neustarts; Aufruf einer Aktion)
verbessertes Logging (der Ausgaben auf stdout und stderr des Dienstes)
saubere, verlässliche Handhabung aller Prozesse, die zu dem Dienst gehören (Stoppen; Senden von Signalen; Anhalten; Fortsetzen)
Sicherheitsoptionen (User, primäre Gruppe, ergänzende Gruppen; capabilities; Blockieren des Zugriffs auf bestimmte device files; namespaces (PID, mount, user, network, ...); Blockieren bestimmter Syscalls)
Ressourcenbeschränkungen (CPU, RAM, I/O)
sicheres Identifizieren der Prozesse eines Dienstes
Es gibt drei Arten von Abhängigkeiten:
mit Startreihenfolge: Es ergibt keinen Sinn zu versuchen, den Dienst zu starten, bevor eine andere Aktion erfolgreich durchgeführt wurde (z.B. Starten eines dauerhaft laufenden Dienstes; Ausführen (und Beenden) eines Programms; Mounten eines Dateisystems; Herstellen einer Netzwerkverbindung)
ohne Startreihenfolge: Der Dienst kann ohne den anderen Dienst starten, aber er funktioniert nicht sinnvoll ohne den anderen (z.B. Webserver und Proxy; Webserver und Datenbankserver)
negative Abhängigkeit: Der Dienst darf nur dann gestartet werden, wenn ein anderer Dienst nicht läuft, bzw. erst dann, wenn der andere Dienst beendet wurde
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.
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.
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.
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:
Dass Unit B eine Abhängigkeit auf Unit A hat, bedeutet nicht, dass A vor B gestartet wird.
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).
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 |
|
|
nein |
|
|
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.
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:
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.
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:
ConditionPathExists=/some/path
Die Condition
-Tests führen beim Scheitern dazu, dass die Unit nicht gestartet wird, aber für Abhängigkeiten als erfolgreich gestartet angesehen wird.
AssertPathExists=/some/path
Die Assert
-Tests führen beim Scheitern dazu, dass die Unit nicht gestartet wird, aber für Abhängigkeiten als fehlgeschlagen angesehen wird.