Effektives Schreiben und Verwalten von benutzerdefinierten Systemd-Unit-Dateien
Meistern Sie die Kunst der Verwaltung Ihrer Linux-Dienste mit diesem umfassenden Leitfaden zu benutzerdefinierten Systemd-Unit-Dateien. Lernen Sie, `.service`-Dateien zu erstellen, zu konfigurieren und Fehler zu beheben, indem Sie entscheidende Direktiven wie `ExecStart`, `WantedBy` und `Type` nutzen. Dieser Artikel bietet Schritt-für-Schritt-Anleitungen und praktische Beispiele, die es Ihnen ermöglichen, den Anwendungsstart zu standardisieren, einen zuverlässigen Betrieb sicherzustellen und Ihre benutzerdefinierten Prozesse nahtlos in Ihre Linux-Systemumgebung zu integrieren. Unverzichtbar für Entwickler und Administratoren, die ein robustes Dienstmanagement anstreben.
Effektives Schreiben und Verwalten von benutzerdefinierten Systemd-Unit-Dateien
Benutzerdefinierte Systemd-Unit-Dateien verwandeln einen Befehl, der in Ihrem Terminal funktioniert, in einen Dienst, den das Betriebssystem starten, stoppen, neu starten, protokollieren und überwachen kann. Der Unterschied ist entscheidend. Ein Befehl in einer Shell erbt Ihre Umgebung und endet, wenn die Sitzung beendet wird. Ein Dienst hat einen expliziten Benutzer, ein Arbeitsverzeichnis, eine Neustartrichtlinie, Abhängigkeiten, Ressourcengrenzen und Protokolle.
Dies ist der praktische Weg, den ich für kleine interne APIs, Worker, Sidecar-Skripte und einmalige Daemons verwende: Schreiben Sie die einfachste korrekte Unit, führen Sie sie als dedizierten Benutzer aus, lassen Sie Protokolle an das Journal gehen und fügen Sie nur dann erweiterte Direktiven hinzu, wenn eine echte Anforderung auftritt.
Grundlegendes zu Systemd-Unit-Dateien
Systemd verwaltet verschiedene Systemressourcen, sogenannte Units, die durch Konfigurationsdateien definiert werden. Diese Units umfassen Dienste (.service), Mountpunkte (.mount), Geräte (.device), Sockets (.socket) und mehr. Für die Verwaltung von Anwendungen und Hintergrundprozessen ist der .service-Unit-Typ am gebräuchlichsten und relevantesten.
Systemd-Unit-Dateien sind reine Textdateien, die normalerweise in bestimmten Verzeichnissen gespeichert werden. Die primären Speicherorte in der Reihenfolge ihrer Priorität sind:
/etc/systemd/system/: Dies ist der empfohlene Speicherort für benutzerdefinierte Unit-Dateien und Überschreibungen, da sie Vorrang vor Systemstandards haben und über Systemaktualisierungen hinweg bestehen bleiben./run/systemd/system/: Wird für laufzeitgenerierte Unit-Dateien verwendet./usr/lib/systemd/system/: Enthält Unit-Dateien, die von installierten Paketen bereitgestellt werden. Ändern Sie Dateien in diesem Verzeichnis nicht direkt.
Indem Sie Ihre benutzerdefinierten Unit-Dateien in /etc/systemd/system/ platzieren, stellen Sie sicher, dass sie von systemd ordnungsgemäß erkannt und verwaltet werden.
Anatomie einer .service-Unit-Datei
Eine systemd .service-Unit-Datei ist in mehrere Abschnitte strukturiert, die jeweils mit [Abschnittsname] gekennzeichnet sind und verschiedene Direktiven (Schlüssel-Wert-Paare) enthalten. Die drei Hauptabschnitte für eine Service-Unit sind [Unit], [Service] und [Install].
Lassen Sie uns die wichtigsten Direktiven aufschlüsseln, die Sie verwenden werden:
[Unit]-Abschnitt
Dieser Abschnitt enthält generische Optionen über die Unit, ihre Beschreibung und Abhängigkeiten.
Description: Eine für Menschen lesbare Zeichenfolge, die den Dienst beschreibt. Diese erscheint in der Ausgabe vonsystemctl status.Description=Meine benutzerdefinierte Python-WebanwendungDocumentation: Eine URL, die auf die Dokumentation für den Dienst verweist (optional).Documentation=https://example.com/docs/my-appAfter: Gibt an, dass diese Unit nach den aufgeführten Units starten soll. Dies hilft bei der Verwaltung der Startreihenfolge. Für Webanwendungen möchten Sie möglicherweise sicherstellen, dass die Netzwerkverbindung hergestellt ist.After=network.targetRequires: Eine starke Abhängigkeit. Wenn die erforderliche Unit nicht startet, wird diese Unit nicht starten. Wenn die erforderliche Unit gestoppt wird, kann diese Unit ebenfalls gestoppt werden.Requires=docker.serviceWants: Eine schwächere Abhängigkeit. Wenn die gewünschte Unit fehlschlägt oder nicht gefunden wird, versucht diese Unit dennoch zu starten. Dies ist normalerweise eine bessere Standardeinstellung alsRequires.Wants=syslog.target
[Service]-Abschnitt
Dieser Abschnitt definiert die Ausführungsparameter für Ihren Dienst, einschließlich der Art und Weise, wie er startet, stoppt und sich verhält.
Type: Definiert den Prozessstarttyp. Entscheidend dafür, wie systemd Ihren Dienst überwacht.simple(Standard): DerExecStart-Befehl ist der Hauptprozess des Dienstes. Systemd betrachtet den Dienst als sofort gestartet, nachdemExecStartaufgerufen wurde. Es wird erwartet, dass der Prozess auf unbestimmte Zeit im Vordergrund läuft.forking: DerExecStart-Befehl forkt einen untergeordneten Prozess und der übergeordnete Prozess wird beendet. Systemd betrachtet den Dienst als gestartet, sobald der übergeordnete Prozess beendet ist. Verwenden Sie dies, wenn Ihre Anwendung sich selbst daemonisiert.oneshot: DerExecStart-Befehl ist ein einmaliger Prozess, der beendet wird, wenn er fertig ist. Nützlich für Skripte, die eine Aufgabe ausführen und beenden (z. B. ein Backup-Skript).notify: Ähnlich wiesimple, aber der Dienst teilt systemd mit, wann er bereit ist. Dies erfordert Anwendungsunterstützung für systemd-Benachrichtigungen.idle: DerExecStart-Befehl wird nur ausgeführt, wenn alle Jobs abgeschlossen sind, wodurch die Ausführung verzögert wird, bis das System größtenteils im Leerlauf ist.
Type=simpleExecStart: Der Befehl, der ausgeführt wird, wenn der Dienst startet. Dies ist die wichtigste Direktive in diesem Abschnitt. Verwenden Sie immer den absoluten Pfad zu Ihrer ausführbaren Datei oder Ihrem Skript.ExecStart=/usr/bin/python3 /opt/my_app/app.pyExecStop: Der Befehl, der ausgeführt wird, wenn der Dienst gestoppt wird (optional). Wenn nicht angegeben, sendet systemdSIGTERMan die Prozesse.ExecStop=/usr/bin/pkill -f 'my_app/app.py'ExecReload: Der Befehl, der ausgeführt wird, um die Konfiguration des Dienstes neu zu laden (optional).ExecReload=/bin/kill -HUP $MAINPIDUser: Das Benutzerkonto, unter dem die Prozesse des Dienstes ausgeführt werden. Wesentlich für die Sicherheit; vermeiden Sieroot, es sei denn, es ist absolut notwendig.User=myappuserGroup: Das Gruppenkonto, unter dem die Prozesse des Dienstes ausgeführt werden.Group=myappgroupWorkingDirectory: Das Arbeitsverzeichnis für die ausgeführten Befehle.WorkingDirectory=/opt/my_appRestart: Definiert, wann der Dienst automatisch neu gestartet werden soll.no(Standard): Niemals neu starten.on-success: Nur neu starten, wenn der Dienst sauber beendet wird.on-failure: Nur neu starten, wenn der Dienst mit einem Exit-Code ungleich Null beendet oder durch ein Signal beendet wird.always: Den Dienst immer neu starten, unabhängig vom Exit-Status.
Restart=on-failureRestartSec: Wie lange gewartet werden soll, bevor der Dienst neu gestartet wird (z. B.5sfür 5 Sekunden).RestartSec=5sEnvironment: Setzt Umgebungsvariablen für die ausgeführten Befehle.Environment="APP_ENV=production" "DEBUG=false"EnvironmentFile: Liest Umgebungsvariablen aus einer Datei. Jede Zeile sollteKEY=VALUEsein.EnvironmentFile=/etc/default/my_appLimitNOFILE: Legt die maximale Anzahl offener Dateideskriptoren fest, die für den Dienst zulässig sind (z. B.100000). Wichtig für Anwendungen mit hoher Parallelität.LimitNOFILE=65536
[Install]-Abschnitt
Dieser Abschnitt definiert, wie der Dienst aktiviert wird, um automatisch beim Booten zu starten.
WantedBy: Gibt die Ziel-Unit an, die diesen Dienst "möchte". Wenn die Ziel-Unit aktiviert ist, wird dieser Dienst in ihr.wants-Verzeichnis symbolisch verlinkt, wodurch er effektiv mit dem Ziel startet.multi-user.target: Der Standardzielwert für die meisten Serverdienste, der ein System mit nicht-grafischen Multi-User-Logins angibt.graphical.target: Für Dienste, die eine grafische Umgebung benötigen.
WantedBy=multi-user.targetRequiredBy: Ähnlich wieWantedBy, aber eine stärkere Abhängigkeit. Wenn das Ziel aktiviert ist, wird diese Unit ebenfalls aktiviert, und wenn diese Unit fehlschlägt, wird auch das Ziel fehlschlagen.
Für die meisten benutzerdefinierten Dienste, die im Hintergrund auf einem Server ausgeführt werden sollen, sind Type=simple und WantedBy=multi-user.target der richtige Ausgangspunkt. Wenn die Anwendung sich bereits selbst daemonisiert, deaktivieren Sie entweder dieses Verhalten oder verwenden Sie Type=forking mit Vorsicht. Ein Vordergrundprozess ist für systemd einfacher zu überwachen.
Schritt-für-Schritt: Erstellen und Verwalten eines benutzerdefinierten Systemd-Dienstes
Lassen Sie uns ein praktisches Beispiel erstellen: einen einfachen Python-HTTP-Server, der Dateien aus einem bestimmten Verzeichnis bereitstellt. Wir richten ihn als systemd-Dienst ein.
Schritt 1: Bereiten Sie Ihre Anwendung/Ihr Skript vor
Erstellen Sie zunächst das Anwendungsskript. Für dieses Beispiel verwenden wir einen einfachen Python-HTTP-Server. Erstellen Sie ein Verzeichnis für Ihre Anwendung, z. B. /opt/my_app, und platzieren Sie app.py darin.
# /opt/my_app/app.py
import http.server
import socketserver
import os
PORT = int(os.environ.get("PORT", 8000))
DIRECTORY = os.environ.get("DIRECTORY", os.getcwd())
class Handler(http.server.SimpleHTTPRequestHandler):
def __init__(self, *args, **kwargs):
super().__init__(*args, directory=DIRECTORY, **kwargs)
print(f"Serving directory {DIRECTORY} on port {PORT}")
with socketserver.TCPServer(("", PORT), Handler) as httpd:
print("Server started.")
httpd.serve_forever()
Erstellen Sie das Verzeichnis und die Datei:
sudo mkdir -p /opt/my_app
sudo nano /opt/my_app/app.py
(Fügen Sie den Python-Code ein)
Stellen Sie sicher, dass das Skript ausführbar ist (optional für den python3-Befehl, aber gute Praxis):
sudo chmod +x /opt/my_app/app.py
Erwägen Sie, aus Sicherheitsgründen einen dedizierten Benutzer für Ihren Dienst zu erstellen:
sudo useradd --system --no-create-home myappuser
Legen Sie die entsprechenden Besitzverhältnisse für Ihr Anwendungsverzeichnis fest:
sudo chown -R myappuser:myappuser /opt/my_app
Schritt 2: Erstellen Sie die Unit-Datei
Erstellen Sie nun die systemd-Unit-Datei für unsere Python-Anwendung. Wir nennen sie my_app.service.
sudo nano /etc/systemd/system/my_app.service
Fügen Sie den folgenden Inhalt ein:
# /etc/systemd/system/my_app.service
[Unit]
Description=Mein benutzerdefinierter Python-HTTP-Server
Documentation=https://github.com/example/my_app
After=network.target
[Service]
Type=simple
User=myappuser
Group=myappuser
WorkingDirectory=/opt/my_app
Environment="PORT=8080" "DIRECTORY=/var/www/html"
ExecStart=/usr/bin/python3 /opt/my_app/app.py
Restart=on-failure
RestartSec=10s
StandardOutput=journal
StandardError=journal
[Install]
WantedBy=multi-user.target
Hinweis: Wir haben
StandardOutput=journalundStandardError=journalgesetzt, um die Ausgabe des Dienstes an das systemd-Journal zu leiten, sodass Protokolle einfach mitjournalctlangezeigt werden können.
Wenn Ihre App Geheimnisse benötigt, vermeiden Sie es, sie direkt in die Unit-Datei zu setzen. Verwenden Sie eine Umgebungsdatei mit restriktiven Berechtigungen, einen Secret-Manager oder distributionsspezifische Anmeldeinformationsunterstützung. Unit-Dateien sind oft von mehr Personen lesbar, als Sie erwarten.
Schritt 3: Platzieren Sie die Unit-Datei
Wie angewiesen haben wir die Unit-Datei in /etc/systemd/system/ platziert. Dies ist der Ort, an dem benutzerdefinierte Unit-Dateien abgelegt werden sollten.
Schritt 4: Laden Sie den Systemd-Daemon neu
Nach dem Erstellen oder Ändern einer Unit-Datei muss systemd über die Änderungen informiert werden. Dies geschieht durch Neuladen des systemd-Daemons:
sudo systemctl daemon-reload
Schritt 5: Starten Sie den Dienst
Jetzt können Sie Ihren Dienst starten:
sudo systemctl start my_app.service
Schritt 6: Überprüfen Sie den Dienststatus und die Protokolle
Stellen Sie sicher, dass Ihr Dienst ordnungsgemäß läuft:
systemctl status my_app.service
Beispielausgabe (gekürzt):
● my_app.service - Mein benutzerdefinierter Python-HTTP-Server
Loaded: loaded (/etc/systemd/system/my_app.service; disabled; vendor preset: enabled)
Active: active (running) since Tue 2023-10-26 10:30:00 UTC; 5s ago
Docs: https://github.com/example/my_app
Main PID: 12345 (python3)
Tasks: 1 (limit: 1100)
Memory: 6.5M
CPU: 45ms
CGroup: /system.slice/my_app.service
└─12345 /usr/bin/python3 /opt/my_app/app.py
Oct 26 10:30:00 yourhostname python3[12345]: Serving directory /var/www/html on port 8080
Oct 26 10:30:00 yourhostname python3[12345]: Server started.
Um die Protokolle des Dienstes anzuzeigen, verwenden Sie journalctl:
journalctl -u my_app.service -f
Dieser Befehl zeigt Protokolle für my_app.service an, und -f (follow) zeigt neue Protokolle in Echtzeit an.
Sie können den Server auch von Ihrem Browser oder mit curl unter http://localhost:8080 testen (vorausgesetzt, /var/www/html existiert und enthält einige Dateien).
Schritt 7: Aktivieren Sie den Dienst für den Autostart
Damit Ihr Dienst bei jedem Systemstart automatisch startet, müssen Sie ihn aktivieren:
sudo systemctl enable my_app.service
Dieser Befehl erstellt einen symbolischen Link von /etc/systemd/system/multi-user.target.wants/my_app.service nach /etc/systemd/system/my_app.service.
Schritt 8: Stoppen und deaktivieren Sie den Dienst
Um einen laufenden Dienst zu stoppen:
sudo systemctl stop my_app.service
Um zu verhindern, dass ein Dienst automatisch beim Booten startet (während er aktiviert bleibt, um manuell gestartet zu werden):
sudo systemctl disable my_app.service
Wenn Sie den Dienst vollständig entfernen möchten, deaktivieren Sie ihn zuerst, stoppen Sie ihn dann und löschen Sie schließlich die .service-Datei aus /etc/systemd/system/ und führen Sie sudo systemctl daemon-reload aus.
Schritt 9: Aktualisieren eines Dienstes
Wenn Sie Ihr app.py-Skript oder die my_app.service-Unit-Datei ändern, müssen Sie systemd aktualisieren und den Dienst neu starten:
- Bearbeiten Sie
/opt/my_app/app.pyoder/etc/systemd/system/my_app.service. - Wenn Sie die Unit-Datei geändert haben, führen Sie
sudo systemctl daemon-reloadaus. - Starten Sie den Dienst neu:
sudo systemctl restart my_app.service.
Sicherere Muster für echte Dienste
Eine Unit, die funktioniert, ist nicht immer eine Unit, die Sie jahrelang warten möchten. Diese Muster verhindern häufige Fehler:
- Im Vordergrund ausführen. Lassen Sie systemd den Hauptprozess überwachen. Vermeiden Sie
nohup,screen,tmux, Hintergrund-&oder Anwendungs-Daemon-Modi innerhalb vonExecStart. - Halten Sie
ExecStartdirekt. Wenn Sie Shell-Funktionen wie Pipes oder Variablenerweiterung benötigen, rufen Sie absichtlich/bin/sh -c '...'auf. Andernfalls führen Sie die ausführbare Datei direkt aus. - Verwenden Sie einen dedizierten Benutzer. Ein Dienst, der nur
/opt/my_applesen und an einen nicht privilegierten Port binden muss, sollte nicht alsrootausgeführt werden. - Laden Sie nach Unit-Bearbeitungen neu.
sudo systemctl daemon-reloadist erforderlich, wenn sich die Unit-Datei ändert. - Trennen Sie Code-Bereitstellungen von Unit-Änderungen. Wenn sich nur Python-Code geändert hat, starten Sie den Dienst neu. Wenn sich die Unit geändert hat, laden Sie zuerst systemd neu.
Fehlerbehebung bei einer neuen Unit
Wenn der Dienst fehlschlägt, beginnen Sie mit:
systemctl status my_app.service
journalctl -u my_app.service -n 100 --no-pager
systemctl cat my_app.service
Häufige Fehler sind normalerweise einfach:
status=203/EXECbedeutet oft, dass der Pfad zur ausführbaren Datei falsch ist, die Datei fehlt oder die Datei nicht ausführbar ist.Permission deniedbedeutet normalerweise, dass der Dienstbenutzer eine Datei nicht lesen, ein Verzeichnis nicht betreten, Protokolle nicht schreiben oder den angeforderten Port nicht binden kann.address already in usebedeutet, dass ein anderer Prozess den Port besitzt. Überprüfen Sie mitsudo ss -tulpen | grep ':8080'.- Ein Dienst, der manuell startet, aber unter systemd fehlschlägt, hängt oft von Umgebungsvariablen, einem anderen Arbeitsverzeichnis oder Dateien in Ihrem Home-Verzeichnis ab.
Sie können den Befehl als Dienstbenutzer testen:
sudo -u myappuser /usr/bin/python3 /opt/my_app/app.py
Das ist keine perfekte Reproduktion der systemd-Umgebung, aber es fängt offensichtliche Anwendungsfehler auf, bevor Sie sich mit Unit-Datei-Details befassen.
Eine produktionsfreundlichere Variante
Für einen langlebigen internen Dienst würde ich normalerweise ein paar Schutzmaßnahmen hinzufügen:
[Unit]
Description=Mein benutzerdefinierter Python-HTTP-Server
Wants=network-online.target
After=network-online.target
[Service]
Type=simple
User=myappuser
Group=myappuser
WorkingDirectory=/opt/my_app
EnvironmentFile=-/etc/my_app/my_app.env
ExecStart=/usr/bin/python3 /opt/my_app/app.py
Restart=on-failure
RestartSec=10s
TimeoutStopSec=30s
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=full
ReadWritePaths=/opt/my_app /var/log/my_app
StandardOutput=journal
StandardError=journal
[Install]
WantedBy=multi-user.target
ProtectSystem=full macht einen Großteil des Systems für den Dienst schreibgeschützt, fügen Sie also ReadWritePaths= nur für Verzeichnisse hinzu, in die die App tatsächlich schreiben muss. Testen Sie die Härtung eine Direktive nach der anderen. Sicherheitsoptionen sind nützlich, aber ein Dienst, der seine Konfiguration nicht lesen oder seine Daten nicht schreiben kann, wird beim Start fehlschlagen.
Best Practices und Fehlerbehebung
- Absolute Pfade: Verwenden Sie immer absolute Pfade für
ExecStart,WorkingDirectoryund alle anderen Dateipfade in Ihrer Unit-Datei. Relative Pfade können zu unerwartetem Verhalten führen. - Dedizierte Benutzer: Führen Sie Dienste unter nicht privilegierten, dedizierten Benutzerkonten (z. B.
myappuser) aus, um die Sicherheit zu erhöhen und potenzielle Schäden im Falle einer Kompromittierung zu begrenzen. - Klares Logging: Nutzen Sie
StandardOutput=journalundStandardError=journal, um die Dienstausgabe an das systemd-Journal zu leiten. Verwenden Siejournalctl -u <dienstname>, um Protokolle anzuzeigen. - Abhängigkeiten: Berücksichtigen Sie sorgfältig
After,WantsundRequires, um sicherzustellen, dass Ihr Dienst in der richtigen Reihenfolge in Bezug auf seine Abhängigkeiten (z. B. Netzwerk, Datenbanken) startet. - Testen von Änderungen: Bevor Sie einen Dienst aktivieren, damit er beim Booten startet, testen Sie ihn gründlich, indem Sie ihn manuell starten und stoppen. Überprüfen Sie seinen Status und seine Protokolle.
- Ressourcengrenzen: Verwenden Sie Direktiven wie
LimitNOFILE,LimitNPROCundMemoryMax, wenn der Dienst bekannte Grenzen oder Fehlermodi hat. - Umgebungsvariablen: Verwenden Sie
Environment=oderEnvironmentFile=für Konfigurationswerte, die sich ändern oder zwischen Umgebungen variieren können, anstatt sie in der Unit-Datei oder im Skript fest zu codieren. - Fehlerbehandlung in Skripten: Stellen Sie sicher, dass Ihre Anwendungsskripte Fehler ordnungsgemäß behandeln. Ein Exit-Code ungleich Null löst
Restart=on-failureaus.
Warnung: Vermeiden Sie es, Unit-Dateien direkt in
/usr/lib/systemd/system/zu ändern. Alle Änderungen werden wahrscheinlich durch Paketaktualisierungen überschrieben. Verwenden Sie/etc/systemd/system/für benutzerdefinierte Units oder Überschreibungen.
Eine gute benutzerdefinierte Unit ist auf die beste Weise langweilig: Der Befehl ist explizit, der Benutzer ist nicht privilegiert, das Neustartverhalten ist beabsichtigt und die Protokolle sind leicht zu finden. Sobald das solide ist, sind systemd-Timer, Socket-Aktivierung und tiefere Cgroup-Steuerungen die natürlichen nächsten Schritte.