systemd – Übersicht

Monitoring-Unit

Es mag praktisch sein, eine dedizierte Unit zu haben, an deren Zustand man sehen kann, ob alle für die relevante Funktion des Systems erforderlichen Units – im Folgenden host function units genannt – laufen. Insbesondere für Admins, die mit dem jeweiligen System nicht gut vertraut sind, dürfte es hilfreich sein, die relevante Information, welche kritischen Units gerade nicht laufen, überall unter demselben Namen finden zu können.

Ich verwende gern das Kürzel der Organisation, zu der die IT gehört, als Präfix für Unit-Namen, damit auf den ersten Blick in der Übersicht klar ist, welche Units von der Distribution (oder ggf. anderen Softwareanietern) kommen und welche die Organisations-IT selber erstellt (nicht nur verändert) hat. Deshalb verwende ich hier org- als Platzhalter-Präfix.

die Monitoring-Unit (org-are-all-host-functions-running.target)


start cmd:> systemctl cat org-are-all-host-functions-running.target
# /etc/systemd/system/org-are-all-host-functions-running.target
[Unit]
Description=%n The state of this unit shows whether all units are active which are necessary for the special function of this system
# COMMENT: 'Requisite=' so that a start of this unit does not start any of the monitored units.
#          This is important as the start of each monitored unit starts this unit.
Requisite=org-a.service org-b.service
    After=org-a.service org-b.service
# COMMENT: 'PartOf=' is necessary because this unit must reliably be stopped when any monitored service
#          is stopped for whatever reason (and 'BindsTo=' cannot be used).
PartOf=org-a.service org-b.service

[Install]
# COMMENT: not enabled on its own but activated by org-all-host-functions-start.target

Die Monitoring-Unit benötigt eine PartOf=-Abhängigkeit auf alle überwachten Units, damit sie gestoppt wird, wenn eine von ihnen (von systemd) beendet wird.

Die Monitoring-Unit verwendet Requisite= (anstelle von z.B. Requires=), weil sie keine anderen Units starten soll, sondern nur "getestet" werden soll, ob alle bereits laufen. Wenn nicht alle überwachten Units aktiv sind, schlägt der Start der Monitoring-Unit fehl.

die überwachten Units

Die überwachten Units starten die Monitoring-Unit, um einen erneuten Test auszulösen, ob alle überwachten Units laufen. Dies reicht aus, weil nur durch den Start einer überwachten Unit der Status der Monitoring-Unit von inaktiv zu aktiv wechseln kann.


start cmd:> systemctl cat org-a.service
# /etc/systemd/system/org-a.service
[Unit]
Description=%n Test unit A
Wants=org-are-all-host-functions-running.target

[Service]
ExecStart=/usr/bin/sleep infinity
Type=simple

BindsTo=-Überwachung mit Proxy-Units

Die Realität ist leider komplizierter. Die Beendigung der Monitoring-Unit durch PartOf= erfolgt nur dann, wenn die verbundene Unit von systemd beendet wird – nicht aber, wenn systemd lediglich registriert, dass die Unit sich beendet hat. Das passiert etwa dann, wenn der Prozess einer Service-Unit sich beendet oder von außen gekillt wird.

Dieses Problem kann man mit einer Proxy-Unit lösen, die eine BindsTo=-Abhängigkeit zu der überwachten Unit hat, also in jedem Fall von systemd beendet wird, wenn die überwachte Unit sich beendet oder beendet wird – und diese Beendigung wird dann sicher von dem PartOf= der Monitoring-Unit erfasst.

Es bietet sich an, eine instanziierte Proxy-Unit zu erzeugen, so dass die Namen einheitlich sind: Die zu überwachende Unit ist der Instanzname. Natürlich müssen dann statt der eigentlich relevanten Units die Proxy-Units gestartet werden. Die Proxy-Unit muss so konstruiert sein, dass sie nur von systemd beendet werden kann, aber nicht dadurch, dass ein Prozess von außen abgeschossen wird.


start cmd:> systemctl cat monitoring-target-bind@.service
# /etc/systemd/system/monitoring-target-bind@.service
[Unit]
Description=Safe stop detection for %i
BindsTo=%i
After=%i

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

Die angepasste Version der Monitoring-Unit:


start cmd:> systemctl cat org-are-all-host-functions-running.target
# /etc/systemd/system/org-are-all-host-functions-running.target
[Unit]
Description=%n The state of this unit shows whether all units are active which are necessary for the special function of this system
# COMMENT: 'Requisite=' so that a start of this unit does not start any of the monitored units.
#          This is important as the start of each monitored unit starts this unit.
Requisite=monitoring-target-bind@org-a.service.service monitoring-target-bind@org-b.service.service
    After=monitoring-target-bind@org-a.service.service monitoring-target-bind@org-b.service.service
# COMMENT: 'PartOf=' is necessary because this unit must reliably be stopped when any monitored service
#          is stopped for whatever reason (and 'BindsTo=' cannot be used).
PartOf=monitoring-target-bind@org-a.service.service monitoring-target-bind@org-b.service.service

[Install]
# COMMENT: not enabled on its own but activated by org-all-host-functions-start.target

Auf meinem System (systemd 257) funktioniert das Verschachteln von instanziierten Units:

systemctl start monitoring-target-bind@instancetest@foo.service.service

Um auf der sicheren Seite zu sein, kann man den Instanznamen escapen:


start cmd:> systemd-escape --template=monitoring-target-bind@.service instancetest@foo.service
monitoring-target-bind@instancetest\x40foo.service.service

Man muss dann natürlich darauf achten, dass man den Unit-Namen, der einen Backslash enthält, in der Shell in ASCII single quotes setzt:


systemctl start 'monitoring-target-bind@instancetest\x40foo.service.service'

Das allerdings funktioniert nur dann, wenn in der Unit monitoring-target-bind@.service nicht mehr %i, sondern %I verwendet wird – und dann muss man für viele Unit-Namen systemd-escape verwenden, auch wenn sie nur Bindestriche enthalten.