Systemd meistern: Erstellen Ihrer ersten benutzerdefinierten Service-Unit-Datei
Lernen Sie die Grundlagen der Systemd-Serviceverwaltung, indem Sie eine benutzerdefinierte Unit-Datei erstellen. Dieses Tutorial erläutert die wesentlichen Abschnitte `[Unit]`, `[Service]` und `[Install]` und bietet Schritt-für-Schritt-Anleitungen zum Definieren, Aktivieren, Starten und Überprüfen eines grundlegenden Hintergrunddienstes unter Linux mit `systemctl`.
Systemd meistern: Erstellen Ihrer ersten benutzerdefinierten Service-Unit-Datei
Eine benutzerdefinierte systemd-Service-Unit ist das Mittel der Wahl, wenn ein Skript oder eine kleine Anwendung eine Terminalsitzung, ein screen-Fenster oder eine fragile cron-Problemumgehung überwuchert hat. Vielleicht haben Sie einen Worker, der nach einem Absturz neu starten soll. Vielleicht muss eine kleine interne API starten, nachdem das Netzwerk bereit ist. Vielleicht soll ein Backup-Skript als kontrollierter Service laufen, damit seine Logs im Journal landen und Operatoren dieselben systemctl-Befehle verwenden können, die sie für alles andere verwenden.
Das Nützliche an systemd ist nicht, dass die Unit-Datei kompliziert ist. Es ist, dass die Unit-Datei den Prozess explizit macht: was läuft, als wer es läuft, wann es startet, wie es stoppt, wohin Logs gehen und was systemd tun soll, wenn es fehlschlägt. Sobald diese Entscheidungen niedergeschrieben sind, wird der Service viel einfacher zu betreiben.
Diese Anleitung erstellt einen kleinen Service von Grund auf. Das Beispiel ist bewusst einfach, aber die Muster sind dieselben, die Sie für einen Hintergrund-Worker, Queue-Consumer, Metrik-Exporter oder internen Daemon verwenden würden.
Beginnen Sie mit einem echten Befehl, nicht mit einer Unit-Datei
Eine gute Service-Unit beginnt mit einem Befehl, der bereits von Hand funktioniert. Bevor Sie die systemd-Konfiguration schreiben, stellen Sie sicher, dass Sie das Programm direkt ausführen können und verstehen, was es im Vordergrund tut.
Erstellen Sie für dieses Beispiel ein kleines Reporter-Skript:
sudo install -d -o root -g root -m 0755 /opt/my-custom-service
sudo nano /opt/my-custom-service/reporter.sh
Fügen Sie diesen Inhalt hinzu:
#!/usr/bin/env bash
set -euo pipefail
while true; do
echo "$(date --iso-8601=seconds) reporter heartbeat"
sleep 10
done
Machen Sie es ausführbar und testen Sie es:
sudo chmod 0755 /opt/my-custom-service/reporter.sh
/opt/my-custom-service/reporter.sh
Stoppen Sie es mit Strg-C, nachdem Sie ein paar Zeilen gesehen haben. Beachten Sie, dass das Skript in die Standardausgabe schreibt, anstatt direkt an /var/log/reporter.log anzuhängen. Das ist beabsichtigt. Bei den meisten benutzerdefinierten Services ist es sauberer, systemd stdout und stderr in das Journal erfassen zu lassen, als jedes Skript seine eigenen Logdatei-Berechtigungen, Rotation und Fehlerverhalten verwalten zu lassen.
Erstellen Sie einen dedizierten Service-Benutzer
Vermeiden Sie es, Anwendungsdienste als root auszuführen, es sei denn, sie benötigen tatsächlich root-Rechte. Ein Heartbeat-Skript tut das nicht. Eine Web-App tut das normalerweise nicht. Ein Worker, der aus einer Warteschlange liest und in eine Datenbank schreibt, tut das normalerweise nicht.
Erstellen Sie einen eingeschränkten Systembenutzer:
sudo useradd --system --no-create-home --shell /usr/sbin/nologin reporter
Wenn Ihre Distribution einen anderen nologin-Pfad verwendet, überprüfen Sie dies mit:
command -v nologin || command -v false
Der Service-Benutzer sollte nur die Dateien besitzen, die er beschreiben muss. In diesem Beispiel schreibt das Skript über systemd in das Journal, daher benötigt es keinen Besitz von /opt/my-custom-service.
Schreiben Sie die Service-Unit
Benutzerdefinierte, von Administratoren verwaltete System-Units befinden sich normalerweise in /etc/systemd/system/. Vendor-Paket-Units befinden sich üblicherweise unter /usr/lib/systemd/system/ oder /lib/systemd/system/, abhängig von der Distribution. Bearbeiten Sie Vendor-Unit-Dateien nicht direkt, wenn Sie es vermeiden können; verwenden Sie /etc/systemd/system/ für Ihre eigenen Units und Drop-Ins für Überschreibungen.
Erstellen Sie die Unit:
sudo nano /etc/systemd/system/my-reporter.service
Verwenden Sie dies als praktische erste Version:
[Unit]
Description=My Custom Reporter Service
Documentation=man:systemd.service(5)
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=reporter
Group=reporter
ExecStart=/opt/my-custom-service/reporter.sh
Restart=on-failure
RestartSec=5s
WorkingDirectory=/opt/my-custom-service
StandardOutput=journal
StandardError=journal
NoNewPrivileges=true
PrivateTmp=true
[Install]
WantedBy=multi-user.target
Der Abschnitt [Unit] beschreibt Beziehungen. After=network-online.target steuert die Reihenfolge; es zieht das network-online-target nicht selbstständig herein. Wants=network-online.target bittet systemd, dieses Ziel ebenfalls zu starten. Wenn Ihr Service kein Netzwerk benötigt, entfernen Sie beide Zeilen und halten Sie die Unit einfacher.
Der Abschnitt [Service] beschreibt den Prozess. Type=simple ist richtig für einen Vordergrundprozess, der sich nicht selbst in den Hintergrund forkt. Das ist der häufigste Fall für moderne Services. Wenn ein Legacy-Daemon forkt, eine PID-Datei schreibt und die Kontrolle an die Shell zurückgibt, benötigen Sie möglicherweise Type=forking, aber verwenden Sie es nicht nur, weil das Wort daemon-ähnlicher klingt.
ExecStart sollte ein absoluter Pfad sein. Shell-Funktionen wie Pipes, Redirects und && werden nicht interpretiert, es sei denn, Sie führen explizit eine Shell aus, z.B. ExecStart=/bin/bash -lc 'command one && command two'. Bevorzugen Sie ein Skript, wenn der Befehl Shell-Logik benötigt; es ist einfacher zu testen und einfacher zu lesen.
Restart=on-failure weist systemd an, den Service nach abnormalen Beendigungen neu zu starten. Es wird nicht nach einem sauberen systemctl stop neu gestartet. RestartSec=5s verhindert eine enge Neustart-Schleife, die die Maschine belastet.
Die Härtungsoptionen hier sind bescheiden, aber nützlich. NoNewPrivileges=true verhindert, dass der Prozess und seine Kinder neue Privilegien durch setuid-Binärdateien oder Datei-Capabilities erlangen. PrivateTmp=true gibt dem Service eine private /tmp-Ansicht. Diese sind normalerweise für einfache Services sicher, aber testen Sie sie mit echten Anwendungen, da einige Software gemeinsame temporäre Pfade erwartet.
Laden und starten Sie die Unit
Nachdem Sie eine Unit-Datei hinzugefügt oder geändert haben, laden Sie die systemd-Manager-Konfiguration neu:
sudo systemctl daemon-reload
Starten Sie den Service jetzt:
sudo systemctl start my-reporter.service
Überprüfen Sie seinen Status:
systemctl status my-reporter.service
Sie möchten Active: active (running) sehen. Wenn es fehlgeschlagen ist, raten Sie nicht. Lesen Sie die Logs:
journalctl -u my-reporter.service -n 50 --no-pager
Verfolgen Sie Live-Logs während des Testens:
journalctl -u my-reporter.service -f
Wenn der Skriptpfad falsch ist, Berechtigungen fehlen, der Benutzer nicht existiert oder der Befehl sofort beendet wird, wird systemd dies normalerweise klar im Journal angeben.
Aktivieren Sie den Start beim Booten
Das Starten eines Services und das Aktivieren eines Services sind unterschiedliche Aktionen. start führt ihn jetzt aus. enable bindet ihn in das Boot-Ziel ein, sodass er bei zukünftigen Bootvorgängen startet.
sudo systemctl enable my-reporter.service
Sie können beides in einem Befehl tun, nachdem die Unit getestet wurde:
sudo systemctl enable --now my-reporter.service
Um zu sehen, ob sie aktiviert ist:
systemctl is-enabled my-reporter.service
Machen Sie Fehler leichter diagnostizierbar
Die häufigsten Erstservice-Fehler sind gewöhnliche Linux-Probleme im systemd-Gewand.
Wenn Sie status=203/EXEC sehen, konnte systemd den Befehl nicht ausführen. Überprüfen Sie den Pfad, das Ausführungsbit, die Shebang-Zeile und die Zeilenenden. Ein Skript, das von Windows mit CRLF-Endungen kopiert wurde, kann fehlschlagen, obwohl es in einem Editor gut aussieht.
Wenn Sie Berechtigungsfehler sehen, denken Sie daran, dass der Service als reporter läuft, nicht als Ihr Shell-Benutzer. Testen Sie mit:
sudo -u reporter /opt/my-custom-service/reporter.sh
Wenn der Service startet und sofort stoppt, beendet sich der Prozess wahrscheinlich. Type=simple erwartet, dass der Befehl weiterläuft. Ein One-Shot-Setup-Befehl sollte Type=oneshot verwenden, nicht simple.
Wenn Logs fehlen, überprüfen Sie, ob die Anwendung in Dateien statt in stdout/stderr schreibt oder ob sie intern den Benutzer wechselt. Bei den meisten kleinen Services ist das Schreiben in stdout die am wenigsten überraschende Option.
Nützliche Verwaltungsbefehle
Sobald die Unit eingerichtet ist, ist der tägliche Betrieb unkompliziert:
sudo systemctl start my-reporter.service
sudo systemctl stop my-reporter.service
sudo systemctl restart my-reporter.service
sudo systemctl reload my-reporter.service
systemctl status my-reporter.service
journalctl -u my-reporter.service --since "1 hour ago"
systemctl cat my-reporter.service
systemctl show my-reporter.service -p User -p Restart -p ExecStart
systemctl cat ist besonders nützlich auf Maschinen mit Drop-In-Overrides, da es die effektiven Unit-Fragmente zeigt, die systemd liest.
Eine benutzerdefinierte Unit-Datei muss nicht clever sein. Sie muss langweilig, explizit und testbar sein. Bringen Sie den Befehl von Hand zum Laufen, führen Sie ihn als dedizierten Benutzer aus, schreiben Sie die kleinste Unit, die den Service genau beschreibt, laden Sie systemd neu und verwenden Sie das Journal, wenn etwas fehlschlägt. Dieser Workflow skaliert von einem Spielzeug-Reporter-Skript bis zu echten Produktions-Daemons.
Fügen Sie Umgebung und Konfiguration sauber hinzu
Früher oder später benötigt der Service eine Konfiguration: einen Port, eine Datenbank-URL, ein Feature-Flag oder einen Pfad. Vermeiden Sie es, diese Werte in der Unit-Datei zu vergraben, wenn sie je nach Umgebung variieren. Ein häufiges Muster ist eine Umgebungsdatei:
sudo nano /etc/my-reporter.env
Beispiel:
REPORT_INTERVAL=10
REPORT_LABEL=production
Sperren Sie die Datei, wenn sie etwas Sensitives enthält:
sudo chown root:reporter /etc/my-reporter.env
sudo chmod 0640 /etc/my-reporter.env
Referenzieren Sie sie dann in der Unit:
[Service]
EnvironmentFile=/etc/my-reporter.env
ExecStart=/opt/my-custom-service/reporter.sh
Im Skript lesen Sie die Variable mit einem Standardwert:
interval="${REPORT_INTERVAL:-10}"
label="${REPORT_LABEL:-default}"
Bei Geheimnissen seien Sie vorsichtig. Eine Umgebungsvariable kann je nach Systemkonfiguration und Berechtigungen durch Prozessinspektion oder Servicemetadaten offengelegt werden. Für hochsensible Werte bevorzugen Sie einen ordnungsgemäßen Secret-Manager, eine Berechtigungsdatei mit strengen Berechtigungen oder die neueren Credential-Funktionen von systemd, falls Ihre Distribution diese unterstützt. Die wichtige Gewohnheit ist, bewusst zu entscheiden, anstatt Passwörter aus Bequemlichkeit in Unit-Dateien zu streuen.
Verwenden Sie Drop-In-Overrides für lokale Änderungen
Wenn ein Paket eine Unit installiert und Sie eine Einstellung ändern müssen, bearbeiten Sie nicht die Vendor-Datei. Verwenden Sie ein Drop-In:
sudo systemctl edit my-reporter.service
Das öffnet eine Override-Datei unter /etc/systemd/system/my-reporter.service.d/. Zum Beispiel:
[Service]
RestartSec=15s
Laden Sie neu und starten Sie nach dem Speichern neu:
sudo systemctl daemon-reload
sudo systemctl restart my-reporter.service
Überprüfen Sie das zusammengeführte Ergebnis:
systemctl cat my-reporter.service
Drop-Ins sind wichtig, weil Paket-Updates Vendor-Units ersetzen können. Ihre Overrides in /etc bleiben sichtbar und beabsichtigt.
Denken Sie über das Herunterfahrverhalten nach
Starten ist nur die Hälfte des Lebenszyklus. Ein Service sollte auch sauber stoppen. Standardmäßig sendet systemd SIGTERM, wartet und sendet dann möglicherweise SIGKILL, wenn der Prozess nicht beendet wird. Für viele einfache Services ist das in Ordnung. Für Queue-Worker, Upload-Prozessoren und Datenbankschreiber müssen Sie möglicherweise die Beendigung behandeln, damit der Prozess die aktuelle Arbeit sicher beendet oder abbricht.
Sie können das Timeout anpassen:
[Service]
TimeoutStopSec=30s
KillSignal=SIGTERM
Setzen Sie keine extrem langen Stop-Timeouts, es sei denn, Sie haben einen Grund. Lange Herunterfahrvorgänge verlangsamen Bereitstellungen, Neustarts und Incident Recovery. Ein Worker sollte normalerweise aufhören, neue Arbeit anzunehmen, das aktuell verarbeitete Element beenden und innerhalb einer begrenzten Zeit beenden.
Verhindern Sie laute Neustart-Schleifen
Restart=on-failure ist nützlich, aber ein defekter Service kann trotzdem wiederholt neu starten. Fügen Sie Grenzen hinzu, wenn die Fehlerart laut sein könnte:
[Unit]
StartLimitIntervalSec=300
StartLimitBurst=5
Dies weist systemd an, nach zu vielen Fehlern innerhalb des Intervalls nicht mehr zu versuchen. Wenn Sie das Problem behoben haben, setzen Sie den fehlgeschlagenen Status zurück:
sudo systemctl reset-failed my-reporter.service
sudo systemctl start my-reporter.service
Dieser Befehl ist auch während des Testens nützlich. Ein Service kann in einem fehlgeschlagenen Zustand bleiben, selbst nachdem Sie das Skript oder die Berechtigungen korrigiert haben.
Validieren Sie Units, bevor Sie sich auf sie verlassen
Systemd hat einen nützlichen Prüfer:
systemd-analyze verify /etc/systemd/system/my-reporter.service
Er wird nicht jedes Anwendungsproblem erkennen, aber er kann Syntaxfehler, unbekannte Einstellungen in Ihrer systemd-Version und einige Reihenfolgeprobleme erkennen. Führen Sie ihn nach größeren Bearbeitungen oder beim Kopieren einer Unit zwischen Distributionen aus. Systemd-Funktionen variieren je nach Version, daher kann eine Härtungsoption, die auf einem neuen Fedora-Server funktioniert, auf einer älteren Enterprise-Distribution möglicherweise nicht existieren.
Überprüfen Sie auch die Abhängigkeitsansicht der Unit, wenn die Startreihenfolge verwirrend wird:
systemctl list-dependencies my-reporter.service
systemctl list-dependencies --reverse my-reporter.service
Der erste Befehl zeigt, was der Service einbindet oder wovon er abhängt. Die umgekehrte Ansicht zeigt, was von ihm abhängt. Dies ist nützlich, wenn ein Service unerwartet startet oder wenn das Deaktivieren einer Unit eine andere betrifft.
Entscheiden Sie, ob der Service systemweit oder benutzerebene ist
Dieser Artikel verwendet einen Systemservice unter /etc/systemd/system/. Das ist die richtige Wahl für Maschinendienste, die beim Booten starten und unabhängig von einer Login-Sitzung laufen sollen. Systemd unterstützt auch Benutzerdienste, die normalerweise mit systemctl --user verwaltet werden, für benutzerspezifische Hintergrundprozesse.
Verwenden Sie keinen Benutzerdienst für Infrastruktur-Daemons, nur um sudo zu vermeiden. Benutzerdienste haben andere Lebenszyklusregeln, Umgebungsbehandlung und Login-Verhalten. Für Anwendungs-Worker, Exporter und Host-Level-Agenten ist ein Systemservice mit einem dedizierten Benutzer mit minimalen Rechten normalerweise einfacher zu durchschauen.
Halten Sie die erste Unit langweilig
Es ist verlockend, jede Härtungsdirektive hinzuzufügen, die Sie online finden: private Geräte, schreibgeschützte Pfade, Syscall-Filter, Capability-Bounding, Namespace-Einschränkungen und mehr. Das sind wertvolle Werkzeuge, aber fügen Sie sie einzeln hinzu, nachdem der Service funktioniert. Wenn ein stark eingeschränkter Service nicht startet, können Anfänger oft nicht sagen, ob die Unit falsch ist, die Anwendung falsch ist oder eine Sandbox-Einstellung eine benötigte Datei blockiert hat.
Ein guter Produktionspfad ist inkrementell: funktionierender Service, dedizierter Benutzer, zuverlässiges Logging, Neustartrichtlinie, grundlegende Härtung, dann stärkere Sandboxing, nachdem Sie einen Test haben, der beweist, dass die Anwendung sich weiterhin korrekt verhält.