So erstellen Sie eine systemd-Serviceeinheit unter Linux
Obwohl systemd Gegenstand vieler Kontroversen war, bis zu dem Punkt, an dem einige Distributionen geforkt wurden, nur um es loszuwerden (siehe Devuan, ein Fork von Debian, der standardmäßig systemd durch sysvinit ersetzt), ist es am Ende das De-facto-Standard-Init-System in der Linux-Welt geworden.
In diesem Tutorial werden wir sehen, wie ein systemd-Dienst strukturiert ist, und wir werden lernen, wie einen erstellen.
In diesem Tutorial lernen Sie:
- Was ist eine Serviceeinheit?
- Was sind die Abschnitte einer Serviceeinheit?
- Was sind die gebräuchlichsten Optionen, die in jedem Abschnitt verwendet werden können?
- Welche verschiedenen Arten von Diensten können definiert werden?
Verwendete Softwareanforderungen und Konventionen
Das systemd-Init-System
Alle großen Distributionen wie Rhel, CentOS, Fedora, Ubuntu, Debian und Archlinux haben systemd als ihr Init-System übernommen. Systemd ist eigentlich mehr als nur ein Init-System, und das ist einer der Gründe, warum einige Leute stark gegen sein Design sind, das dem etablierten Unix-Motto widerspricht: "Tu eine Sache und mach es gut". Während andere Init-Systeme einfache Shell-Skripte verwenden, um Dienste zu verwalten, verwendet systemd seine eigenen .service-Dateien (Units mit der Endung .service): In diesem Tutorial werden wir sehen, wie sie strukturiert sind und wie man eine solche erstellt und installiert.
Anatomie einer Serviceeinheit
Was ist eine Service Unit? Eine Datei mit der Endung .service enthält Informationen über einen Prozess, der von systemd verwaltet wird. Es besteht aus drei Hauptteilen:
- [Einheit]: Dieser Abschnitt enthält Informationen, die sich nicht speziell auf den Typ des Geräts beziehen, wie z. B. die Dienstbeschreibung
- [Service]: enthält Informationen über den spezifischen Typ des Geräts, in diesem Fall ein Service
- [Installieren]: Dieser Abschnitt enthält Informationen über die Installation des Geräts
Lassen Sie uns jeden von ihnen im Detail analysieren.
Der Abschnitt [Einheit]
Der Abschnitt [Unit] einer .service-Datei enthält die Beschreibung der Unit selbst und Informationen über ihr Verhalten und ihre Abhängigkeiten: (Um korrekt zu funktionieren, kann ein Dienst von einem anderen abhängen). Im Folgenden besprechen wir einige der wichtigsten Optionen, die in diesem Abschnitt verwendet werden können
Die Option "Beschreibung"
Zunächst haben wir die Option Beschreibung. Mit dieser Option können wir eine Beschreibung der Einheit bereitstellen. Die Beschreibung erscheint dann z.B. beim Aufruf des Befehls systemctl, der eine Übersicht über den Status von systemd zurückgibt. Hier ist als Beispiel, wie die Beschreibung des httpd-Dienstes auf einem Fedora-System definiert ist:
[Unit]
Description=The Apache HTTP Server
Die Option "Nach"
Mit der Option After können wir angeben, dass unsere Unit nach den von uns bereitgestellten Units in Form einer durch Leerzeichen getrennten Liste gestartet werden soll. Wenn wir uns zum Beispiel die Service-Datei ansehen, in der der Apache-Webservice definiert ist, können wir folgendes sehen:
After=network.target remote-fs.target nss-lookup.target httpd-init.serviceDie obige Zeile weist systemd an, die Service-Unit httpd.service erst nach den Zielen network, remove-fs, nss-lookup und dem Dienst httpd-init zu starten.
Festlegen harter Abhängigkeiten mit "Requires"
Wie wir oben kurz erwähnt haben, kann eine Unit (in unserem Fall ein Service) von anderen Units (nicht unbedingt "Service"-Units) abhängen, um korrekt zu funktionieren: Solche Abhängigkeiten können mit der Option Requires deklariert werden.
Wenn eine der Units, von denen ein Dienst abhängt, nicht gestartet werden kann, wird die Aktivierung des Dienstes gestoppt: Aus diesem Grund werden diese als harte Abhängigkeiten bezeichnet. In dieser Zeile, die aus der Dienstdatei des avahi-daemon extrahiert wurde, können wir sehen, wie er als abhängig von der Einheit avahi-daemon.socket deklariert wird:
Requires=avahi-daemon.socketDeklarieren von "weichen" Abhängigkeiten mit "Wants"
Wir haben gerade gesehen, wie man die sogenannten "harten" Abhängigkeiten für den Dienst mit der Option "Requires" deklariert. Wir können auch "weiche" Abhängigkeiten auflisten, indem wir die Option Wants verwenden.
Was ist der Unterschied? Wie wir oben gesagt haben, wird der Dienst selbst versagen, wenn eine "harte" Abhängigkeit fehlschlägt. Ein Ausfall einer "weichen" Abhängigkeit hat jedoch keinen Einfluss darauf, was mit der abhängigen Einheit geschieht. Im bereitgestellten Beispiel können wir sehen, wie die docker.service-Unit eine weiche Abhängigkeit von der docker-storage-setup.service-Unit hat:
[Unit]
Wants=docker-storage-setup.service
Der Abschnitt [Service]
Im Abschnitt [Service] einer Service-Unit können wir Dinge wie den Befehl angeben, der beim Start des Dienstes ausgeführt werden soll, oder den Typ des Dienstes selbst. Werfen wir einen Blick auf einige von ihnen.
Starten, Stoppen und erneutes Laden eines Diensts
Ein Dienst kann gestartet, gestoppt, neu gestartet oder neu geladen werden. Die Befehle, die beim Ausführen jeder dieser Aktionen ausgeführt werden sollen, können mit den entsprechenden Optionen im Abschnitt [Service] angegeben werden.
Der Befehl, der beim Start eines Dienstes ausgeführt werden soll, wird mit der Option ExecStart deklariert. Das Argument, das an die Option übergeben wird, kann auch der Pfad zu einem Skript sein. Optional können wir Befehle deklarieren, die vor und nach dem Start des Diensts ausgeführt werden sollen, indem wir die Optionen ExecStartPre bzw. ExecStartPost verwenden. Hier ist der Befehl, der zum Starten des NetworkManager-Dienstes verwendet wird:
[Service]
ExecStart=/usr/sbin/NetworkManager --no-daemon
Auf ähnliche Weise können wir den Befehl angeben, der ausgeführt werden soll, wenn ein Dienst neu geladen oder gestoppt wird, indem wir die Optionen ExecStop und ExecReload verwenden. Ähnlich wie bei ExecStartPost können mit der Option ExecStopPost ein Befehl oder mehrere Befehle angegeben werden, die nach dem Stoppen eines Prozesses gestartet werden sollen.
Die Art des Dienstes
Systemd definiert und unterscheidet zwischen verschiedenen Arten von Diensten, abhängig von ihrem erwarteten Verhalten. Der Typ eines Diensts kann mithilfe der Option Typ definiert werden, wobei einer der folgenden Werte angegeben wird:
- Einfach
- Gabelung
- Einmaliger Schuss
- dbus
- benachrichtigen
Der Standardtyp eines Dienstes ist einfach, wenn die Optionen Type und Busname nicht definiert sind, aber ein Befehl über die Option ExecStart bereitgestellt wird. Wenn dieser Diensttyp festgelegt ist, wird der in ExecStart deklarierte Befehl als Hauptprozess/-dienst betrachtet.
Der Forking-Typ funktioniert anders: Es wird erwartet, dass der mit ExecStart bereitgestellte Befehl einen untergeordneten Prozess forkt und startet, der zum Hauptprozess/-dienst wird. Es wird erwartet, dass der übergeordnete Prozess abstirbt, sobald der Startvorgang abgeschlossen ist.
Der Oneshot-Typ wird als Standard verwendet, wenn die Optionen Type und ExecStart nicht definiert sind. Es funktioniert ziemlich einfach: Der Unterschied besteht darin, dass der Prozess seine Arbeit beenden soll, bevor andere Einheiten gestartet werden. Die Unit wird jedoch auch nach dem Beenden des Befehls als "aktiv" betrachtet, wenn die Option RemainAfterExit auf "yes" gesetzt ist (der Standardwert ist "no").
Die nächste Art von Dienst ist dbus. Wenn diese Art von Dienst verwendet wird, wird erwartet, dass der Daemon einen Namen von Dbus erhält, wie in der Option BusName angegeben, die in diesem Fall obligatorisch wird. Im Übrigen funktioniert es wie der einfache Typ. Entsprechende Units werden jedoch erst gestartet, nachdem der DBus-Name abgerufen wurde.
Ein anderer Prozess funktioniert ähnlich wie einfach und ist notify: Der Unterschied besteht darin, dass vom Daemon erwartet wird, dass er eine Benachrichtigung über die Funktion sd_notify sendet. Erst wenn diese Benachrichtigung gesendet wurde, werden die entsprechenden Units gestartet.
Festlegen von Prozess-Timeouts
Durch die Verwendung bestimmter Optionen ist es möglich, einige Timeouts für den Dienst zu definieren. Beginnen wir mit RestartSec: Mit dieser Option können wir die Zeitspanne (standardmäßig in Sekunden) festlegen, die systemd warten soll, bevor ein Dienst neu gestartet wird. Als Wert für diese Option kann auch eine Zeitspanne verwendet werden, z. B. "5min 20s". Der Standardwert ist 100 ms.
Mit den Optionen TimeoutStartSec und TimeoutStopSec können Sie das Timeout für den Start und das Beenden eines Diensts in Sekunden angeben. Im ersten Fall, wenn der Startvorgang des Daemons nach der angegebenen Zeitüberschreitung nicht abgeschlossen ist, wird er als fehlgeschlagen betrachtet.
Im zweiten Fall, wenn ein Dienst gestoppt werden soll, aber nach dem angegebenen Timeout nicht beendet wird, wird zuerst ein SIGTERM und dann nach der gleichen Zeit ein SIGKILL-Signal an ihn gesendet. Beide Optionen akzeptieren auch eine Zeitspanne als Wert und können sofort mit einer Tastenkombination konfiguriert werden: TimeoutSec. Wenn unendlich als Wert angegeben wird, sind die Timeouts deaktiviert.
Schließlich können wir den Grenzwert für die Zeit, die ein Dienst ausgeführt werden kann, mithilfe von RuntimeMaxSec festlegen. Wenn ein Dienst diese Zeitüberschreitung überschreitet, wird er beendet und als fehlgeschlagen betrachtet.
Der Abschnitt [Installieren]
Im Abschnitt [install] können wir Optionen verwenden, die sich auf die Installation des Dienstes beziehen. Zum Beispiel können wir mit der Option Alias eine durch Leerzeichen getrennte Liste von Aliasen angeben, die für den Dienst verwendet werden sollen, wenn die Befehle systemctl verwendet werden (außer enable).
Ähnlich wie bei den Optionen Requires und Wants im Abschnitt [Unit] können wir zum Einrichten von Abhängigkeiten im Abschnitt [install] RequiredBy und WantedBy verwenden. In beiden Fällen deklarieren wir eine Liste von Einheiten, die von der Einheit abhängen, die wir konfigurieren: Bei der ersten Option sind sie stark von ihr abhängig, bei der zweiten werden sie nur als schwach abhängig betrachtet. Zum Beispiel:
[Install]
WantedBy=multi-user.target
Mit der obigen Zeile haben wir erklärt, dass das Multi-User-Ziel eine weiche Abhängigkeit von unserer Einheit hat. In der Terminologie von systemd können Units, die mit der Endung .target enden, mit dem verknüpft werden, was in anderen Init-Systemen als Sysvinit als Laufzeiten bezeichnet wurde. In unserem Fall sollte also das Multi-User-Ziel, wenn es erreicht ist, unseren Service einschließen.
Erstellen und Installieren einer Service Unit
Es gibt grundsätzlich zwei Stellen im Dateisystem, an denen systemd-Service-Units installiert werden: /usr/lib/systemd/system und /etc/systemd/system. Der erste Pfad wird für Dienste verwendet, die von installierten Paketen bereitgestellt werden, während der zweite vom Systemadministrator für seine eigenen Dienste verwendet werden kann, die die Standarddienste überschreiben können.
Lassen Sie uns ein Beispiel für einen benutzerdefinierten Dienst erstellen. Angenommen, wir möchten einen Dienst erstellen, der die Wake-on-LAN-Funktion auf einer bestimmten Ethernet-Schnittstelle (in unserem Fall ens5f5) deaktiviert, wenn sie gestartet wird, und sie wieder aktiviert, wenn sie gestoppt wird. Wir können den Befehl ethtool verwenden, um die Hauptaufgabe zu erfüllen. So könnte unsere Serviceakte aussehen:
[Unit]
Description=Force ens5f5 ethernet interface to 100Mbps
Requires=Network.target
After=Network.target
[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/usr/sbin/ethtool -s ens5f5 wol d
ExecStop=/usr/sbin/ethtool -s ens5f5 wol g
[Install]
WantedBy=multi-user.target
Wir legten eine einfache Unit-Beschreibung fest und erklärten, dass der Dienst von der network.target-Unit abhängt und gestartet werden sollte, nachdem diese erreicht wurde. Im Abschnitt [Service] haben wir den Typ des Dienstes auf Oneshot gesetzt und systemd angewiesen, den Dienst nach der Ausführung des Befehls als aktiv zu betrachten, indem wir die Option RemainAfterExit verwenden. Wir haben auch die Befehle definiert, die ausgeführt werden sollen, wenn der Dienst gestartet und gestoppt wird. Schließlich haben wir im Abschnitt [Install] im Grunde erklärt, dass unser Dienst in das Multi-User-Ziel aufgenommen werden sollte.
Um den Dienst zu installieren, kopieren wir die Datei in das Verzeichnis /etc/systemd/system als wol.service, dann starten wir sie:
sudo cp wol.service /etc/systemd/system && sudo systemctl start wol.serviceWir können mit dem folgenden Befehl überprüfen, ob der Dienst aktiv ist:
systemctl is-active wol.service
active
Die Ausgabe des Befehls ist wie erwartet aktiv. Um nun zu überprüfen, ob "wake on lan" auf d gesetzt wurde und daher deaktiviert ist, können wir Folgendes ausführen:
sudo ethtool ens5f5|grep Wake-on
Supports Wake-on: pg
Wake-on: d
Wenn Sie den Dienst nun stoppen, sollte das umgekehrte Ergebnis erzeugt werden, und aktivieren Sie wol erneut:
sudo systemctl stop wol.service && sudo ethtool ens5f5|grep Wake-on
Supports Wake-on: pg
Wake-on: g
Schlüsse
In diesem Tutorial haben wir gesehen, wie eine systemd-Dienstdatei zusammengesetzt ist, welche Abschnitte sie hat und welche Optionen in jedem von ihnen verwendet werden können. Wir haben gelernt, wie man eine Servicebeschreibung einrichtet, ihre Abhängigkeiten definiert und die Befehle deklariert, die ausgeführt werden sollen, wenn sie gestartet, gestoppt oder neu geladen wird.
Da systemd, ob es Ihnen gefällt oder nicht, zum Standard-Init-System in der Linux-Welt geworden ist, ist es wichtig, sich mit seiner Vorgehensweise vertraut zu machen. Die offizielle Dokumentation der systemd-Dienste finden Sie auf der freedesktop-Website. Sie könnten auch daran interessiert sein, unseren Artikel über die Verwaltung von Diensten mit systemd zu lesen.
