Systemd-Units verstehen: Ein tiefer Einblick in die Dienstkonfiguration
Erfahren Sie, wie systemd-Dienstunits funktionieren, einschließlich Unit, Service, Install, Überschreibungen, Neustarts und Logs.
Systemd-Units verstehen: Ein tiefer Einblick in die Dienstkonfiguration
Systemd-Unit-Dateien sind die kleinen Textdateien, die bestimmen, wie Dienste starten, wovon sie abhängen, unter welchem Benutzer sie laufen und was passiert, wenn sie fehlschlagen. Wenn Sie sich jemals gefragt haben, warum systemctl restart myapp.service für eine App funktioniert, aber nicht für eine andere, liegt die Antwort normalerweise in der Unit-Datei.
Diese Anleitung konzentriert sich auf .service-Units, da sie diejenigen sind, die Administratoren und Entwickler am häufigsten bearbeiten. Das gleiche System verwaltet auch Sockets, Timer, Mounts, Geräte, Pfade und Targets, aber Dienstdateien sind der Ort, an dem die meisten betrieblichen Fehler auftreten.
Was sind Systemd-Unit-Dateien?
Systemd-Unit-Dateien sind einfache Textdateien, die Konfigurationsdirektiven für eine bestimmte Unit enthalten. Eine Unit repräsentiert eine von systemd verwaltete Ressource. Der häufigste Typ ist die Service-Unit, die definiert, wie ein Hintergrundprozess oder eine Anwendung gestartet, gestoppt, neu gestartet und verwaltet wird.
Unit-Dateien sind in Abschnitte organisiert, die jeweils durch eckige Klammern ([]) gekennzeichnet sind. Die wichtigsten Abschnitte für Service-Units sind:
[Unit]: Enthält Metadaten über die Unit, Abhängigkeiten und die Reihenfolge.[Service]: Definiert das Verhalten des Dienstes selbst, einschließlich der Ausführung.[Install]: Gibt an, wie die Unit aktiviert oder deaktiviert werden soll, normalerweise durch Verknüpfung mit Target-Units.
Systemd sucht in mehreren Standardverzeichnissen nach Unit-Dateien, wobei die häufigsten sind:
/etc/systemd/system/: Für lokal konfigurierte Units, die Standardeinstellungen überschreiben./usr/lib/systemd/system/: Für Units, die von Paketen auf vielen Distributionen installiert werden./lib/systemd/system/: Wird von einigen Debian-basierten Systemen für paketbereitgestellte Units verwendet.
Wenn Sie eine Unit überprüfen müssen, vermeiden Sie es, den Pfad zu erraten. Verwenden Sie:
systemctl cat nginx.service
systemctl show -p FragmentPath nginx.service
systemctl cat ist besonders nützlich, da es die Basis-Unit sowie alle Drop-In-Überschreibungen anzeigt. Das ist die Version, die systemd tatsächlich verwendet.
Anatomie einer .service-Unit-Datei
Lassen Sie uns eine typische .service-Unit-Datei aufschlüsseln, um ihre Komponenten zu verstehen.
Der [Unit]-Abschnitt
Dieser Abschnitt enthält beschreibende Informationen und definiert die Beziehungen zwischen Units.
Description=: Eine menschenlesbare Beschreibung des Dienstes.Documentation=: URLs oder Pfade zur Dokumentation des Dienstes.After=: Gibt an, dass diese Unit nach dem Start der aufgeführten Units starten soll.Requires=: Ähnlich wieAfter=, macht aber die aufgeführten Units obligatorisch. Wenn eine erforderliche Unit nicht startet, schlägt auch diese Unit fehl.Wants=: Eine schwächere Form der Abhängigkeit. Diese Unit versucht, ihre gewünschten Units zu starten, aber deren Fehlschlagen verhindert nicht den Start dieser Unit.Conflicts=: Gibt Units an, die nicht gleichzeitig mit dieser Unit ausgeführt werden können.
Beispiel für einen [Unit]-Abschnitt:
[Unit]
Description=Mein eigener Webserver
Documentation=https://example.com/docs/my-web-server
After=network.target
Dies zeigt an, dass unser eigener Webserver starten soll, nachdem das Netzwerk verfügbar ist.
Eine häufige Falle: After= steuert die Reihenfolge, nicht die Anforderung. Wenn Sie After=postgresql.service schreiben, startet systemd Ihren Dienst nach PostgreSQL, wenn beide Teil der Transaktion sind, zieht aber nicht automatisch PostgreSQL mit. Wenn Ihre App wirklich benötigt, dass PostgreSQL von derselben Transaktion gestartet wird, verwenden Sie zusätzlich Wants=postgresql.service oder für eine harte Abhängigkeit Requires=postgresql.service.
Selbst dann sind Abhängigkeiten keine Gesundheitschecks. After=network.target garantiert nicht, dass DNS funktioniert, eine entfernte API erreichbar ist oder eine Datenbank Verbindungen akzeptiert. Ihre Anwendung benötigt dennoch ein sinnvolles Wiederholungsverhalten.
Der [Service]-Abschnitt
Hier befindet sich die Kernlogik für die Ausführung des Dienstes.
Type=: Definiert den Prozessstarttyp. Häufige Typen sind:simple(Standard): Der Hauptprozess ist der vonExecStart=gestartete. Systemd betrachtet den Dienst als sofort gestartet, nachdem derExecStart=-Prozess abgezweigt wurde.forking: Wird für traditionelle Dämonen verwendet, die einen Kindprozess abzweigen und beenden. Systemd wartet auf das Beenden des Elternprozesses.oneshot: Für Aufgaben, die einen einzelnen Befehl ausführen und dann beenden.notify: Der Dienst sendet eine Benachrichtigung an systemd, wenn er vollständig gestartet ist.dbus: Für Dienste, die einen D-Bus-Namen erwerben.
ExecStart=: Der Befehl zum Starten des Dienstes.ExecStop=: Der Befehl zum Stoppen des Dienstes.ExecReload=: Der Befehl zum Neuladen der Konfiguration des Dienstes ohne Neustart.Restart=: Definiert, wann der Dienst neu gestartet werden soll. Optionen umfassenno(Standard),on-success,on-failure,on-abnormal,on-watchdog,on-abortundalways.RestartSec=: Die Wartezeit vor dem Neustart des Dienstes.User=/Group=: Der Benutzer und die Gruppe, unter denen der Dienst ausgeführt werden soll.WorkingDirectory=: Das Arbeitsverzeichnis für die ausgeführten Prozesse.Environment=/EnvironmentFile=: Setzt Umgebungsvariablen für den Dienst.
Beispiel für einen [Service]-Abschnitt:
[Service]
Type=simple
ExecStart=/usr/local/bin/my-web-server --config /etc/my-web-server.conf
User=www-data
Group=www-data
Restart=on-failure
RestartSec=5
Diese Konfiguration startet unseren Webserver, führt ihn als Benutzer und Gruppe www-data aus und startet ihn bei Fehlschlag automatisch mit einer Verzögerung von 5 Sekunden neu.
Type= verdient besondere Aufmerksamkeit. Viele defekte Units verwenden Type=forking, weil ein altes Init-Skript den Daemon-Modus verwendet hat. Für eine moderne Anwendung, die im Vordergrund bleibt, ist Type=simple normalerweise korrekt. Wenn Ihr Prozess in den Hintergrund abzweigt, systemd aber nicht mitgeteilt wird, wie der eigentliche Hauptprozess identifiziert wird, können Statusmeldungen und Neustarts irreführend sein.
Für einen einmaligen Job verwenden Sie Type=oneshot und oft RemainAfterExit=yes, wenn die abgeschlossene Aktion als aktiv zählen soll. Zum Beispiel kann eine Unit, die eine Firewall-Regel vorbereitet oder eine spezielle Ressource mountet, erfolgreich beenden, aber dennoch einen Zustand darstellen, der Ihnen wichtig ist.
Der [Install]-Abschnitt
Dieser Abschnitt wird verwendet, wenn eine Unit aktiviert oder deaktiviert wird. Er definiert, wie die Unit in die Target-Units von systemd integriert wird.
WantedBy=: Gibt das/die Target(s) an, das/die diese Unit "wollen" soll(en), wenn sie aktiviert ist. Für Dienste, die beim Booten starten sollen, wird üblicherweisemulti-user.targetverwendet.
Beispiel für einen [Install]-Abschnitt:
[Install]
WantedBy=multi-user.target
Wenn Sie systemctl enable my-custom-service.service ausführen, erstellt systemd einen symbolischen Link von /etc/systemd/system/multi-user.target.wants/ zu Ihrer Dienstdatei, um sicherzustellen, dass er startet, wenn das System den Multi-User-Runlevel erreicht.
Wenn eine Unit keinen [Install]-Abschnitt hat, kann sie dennoch vollkommen gültig sein. Sie kann nur nicht direkt mit systemctl enable aktiviert werden, es sei denn, es existiert ein anderer Installationsmechanismus. Einige Units sollen durch Abhängigkeiten, Sockets, Timer oder Targets gezogen werden, anstatt von Hand aktiviert zu werden.
Erstellen und Verwalten eigener Dienst-Units
Lassen Sie uns den Prozess der Erstellung einer eigenen Dienst-Unit durchgehen.
Schritt 1: Erstellen Sie die Unit-Datei
Erstellen Sie eine neue Datei in /etc/systemd/system/ mit der Erweiterung .service. Für unser Beispiel erstellen wir /etc/systemd/system/my-app.service.
[Unit]
Description=Mein eigener Anwendungsdienst
After=network.target
[Service]
Type=simple
ExecStart=/opt/my-app/bin/run-app --port 8080
User=appuser
Group=appgroup
Restart=always
RestartSec=10
[Install]
WantedBy=multi-user.target
Wichtige Überlegungen:
- Stellen Sie sicher, dass der
ExecStart-Befehl auf ein ausführbares Skript oder eine Binärdatei verweist, die zugänglich ist und Ausführungsberechtigungen hat. - Erstellen Sie den angegebenen
Userund dieGroup, falls sie nicht existieren (sudo useradd -r -s /bin/false appuser,sudo groupadd appgroup,sudo usermod -a -G appgroup appuser). - Stellen Sie sicher, dass die Anwendung mit den angegebenen Befehlen korrekt gestartet und gestoppt werden kann.
Bevor Sie einen Befehl in ExecStart= setzen, führen Sie ihn nach Möglichkeit manuell als denselben Benutzer aus:
sudo -u appuser /opt/my-app/bin/run-app --port 8080
Dies fängt fehlende Ausführungsbits, fehlende Verzeichnisse, falsche relative Pfade und Berechtigungsprobleme ab, bevor systemd involviert ist. Sobald es manuell funktioniert, verschieben Sie es in die Unit und lassen systemd die Überwachung übernehmen.
Schritt 2: Systemd-Konfiguration neu laden
Nach dem Erstellen oder Ändern einer Unit-Datei müssen Sie systemd mitteilen, seine Konfiguration neu zu laden.
sudo systemctl daemon-reload
Dieser Befehl scannt nach neuen oder geänderten Unit-Dateien und aktualisiert den internen Zustand von systemd.
Schritt 3: Dienst aktivieren und starten
Um den Dienst sofort zu starten und für den Start beim Booten zu konfigurieren:
sudo systemctl enable my-app.service # Erstellt symbolische Links für den Boot-Start
sudo systemctl start my-app.service # Startet den Dienst jetzt
Schritt 4: Dienst verwalten
Verwenden Sie systemctl-Befehle, um Ihren Dienst zu verwalten:
Status überprüfen:
sudo systemctl status my-app.serviceDies zeigt an, ob der Dienst aktiv ist, seine Prozess-ID, aktuelle Logeinträge und mehr.
Dienst stoppen:
sudo systemctl stop my-app.serviceDienst neu starten:
sudo systemctl restart my-app.serviceDienst neu laden (wenn
ExecReload=definiert ist):sudo systemctl reload my-app.serviceDienst deaktivieren (Start beim Booten verhindern):
sudo systemctl disable my-app.service
Schritt 5: Logs mit journalctl anzeigen
Systemd ist eng mit journald für die Protokollierung integriert. Sie können Logs für Ihren Dienst mit journalctl anzeigen:
Logs für einen bestimmten Dienst anzeigen:
sudo journalctl -u my-app.serviceLogs in Echtzeit verfolgen:
sudo journalctl -f -u my-app.serviceLogs seit dem letzten Boot anzeigen:
sudo journalctl -b -u my-app.service
Best Practices und Tipps
- Verwenden Sie
Type=notifyfür moderne Anwendungen: Wenn Ihre Anwendung es unterstützt, bietetType=notifyeine bessere Integration mit systemd, sodass es die Bereitschaft des Dienstes genau verfolgen kann. - Dienste als Nicht-Root-Benutzer ausführen: Geben Sie immer
User=undGroup=im[Service]-Abschnitt an, um Sicherheitsrisiken zu minimieren. - Abhängigkeiten sorgfältig definieren: Verwenden Sie
After=,Requires=undWants=, um sicherzustellen, dass Dienste in der richtigen Reihenfolge starten und kritische Abhängigkeiten erfüllt werden. - Nutzen Sie
Restart=: Konfigurieren Sie geeignete Neustartrichtlinien, um die Dienstverfügbarkeit sicherzustellen. - Halten Sie Unit-Dateien einfach: Für komplexe Startsequenzen sollten Sie Wrapper-Skripte in Betracht ziehen, die von
ExecStart=aufgerufen werden, anstatt komplexe Befehle direkt in der Unit-Datei zu verwenden. - Verwenden Sie
systemctl cat <unit>: Um den vollständigen Inhalt einer Unit-Datei so anzuzeigen, wie systemd sie sieht, einschließlich aller Überschreibungen. - Verwenden Sie
systemctl edit <unit>: Dieser Befehl öffnet einen Editor, um eine Überschreibungsdatei für eine vorhandene Unit zu erstellen, was eine sauberere Methode zum Ändern von Standard-Unit-Dateien ist, als sie direkt zu bearbeiten.
Vorhandene Units sicher bearbeiten
Bearbeiten Sie keine paketeigenen Units in /usr/lib/systemd/system/ oder /lib/systemd/system/, es sei denn, Sie debuggen eine Wegwerfmaschine. Paket-Upgrades können diese Dateien ersetzen. Verwenden Sie stattdessen eine Überschreibung:
sudo systemctl edit nginx.service
Dies erstellt ein Drop-In unter /etc/systemd/system/nginx.service.d/. Um beispielsweise eine Neustartrichtlinie hinzuzufügen:
[Service]
Restart=on-failure
RestartSec=5s
Einige Direktiven können mehrfach angegeben werden. Andere müssen vor dem Ersetzen gelöscht werden. ExecStart= ist das klassische Beispiel:
[Service]
ExecStart=
ExecStart=/usr/local/bin/my-nginx-wrapper
Die leere ExecStart=-Zeile setzt den vorherigen Wert zurück. Ohne sie könnte systemd die Unit ablehnen oder mehr Befehle behalten als beabsichtigt.
Nach jeder Unit- oder Drop-In-Änderung verwenden Sie denselben Überprüfungszyklus:
sudo systemctl daemon-reload
systemctl cat my-app.service
sudo systemctl restart my-app.service
journalctl -u my-app.service -n 50 --no-pager
Unit-Dateien sind nicht schwer, sobald Sie die drei Aufgaben trennen: [Unit] beschreibt Beziehungen, [Service] beschreibt Prozessverhalten und [Install] beschreibt die Aktivierung. Die meiste Fehlersuche in der Praxis besteht darin, herauszufinden, welche dieser Aufgaben mit der falschen Annahme konfiguriert wurde.
Ein realistischer Dienstdatei-Durchlauf
Hier ist eine kleine, aber realistische Dienstdatei für eine Python-Webanwendung:
[Unit]
Description=Inventory API
After=network-online.target postgresql.service
Wants=network-online.target
[Service]
Type=simple
User=inventory
Group=inventory
WorkingDirectory=/srv/inventory-api
EnvironmentFile=/etc/inventory-api/env
ExecStart=/srv/inventory-api/.venv/bin/gunicorn app:app --bind 127.0.0.1:9000
Restart=on-failure
RestartSec=5s
[Install]
WantedBy=multi-user.target
In dieser Datei stecken mehrere stille Entscheidungen. Der Dienst läuft als inventory, nicht als root. Der Befehl verwendet einen absoluten Pfad zum gunicorn der virtuellen Umgebung, sodass er nicht von einem interaktiven Shell-PATH abhängt. Die App bindet an localhost, da ein Reverse-Proxy sie öffentlich verfügbar macht. Die Umgebungsdatei befindet sich außerhalb der Unit, sodass die Bereitstellung die Konfiguration aktualisieren kann, ohne die paketeigenen Dienstmetadaten neu zu schreiben.
Die Abhängigkeitszeilen sind bewusst bescheiden. After=postgresql.service steuert die Reihenfolge, wenn PostgreSQL Teil derselben Starttransaktion ist. Es beweist nicht, dass die Datenbank bereit für Verbindungen ist, und es ersetzt keine Anwendungs-Wiederholungslogik. network-online.target kann auf Systemen helfen, die die Netzwerkbereitschaft korrekt implementieren, aber es ist keine universelle Garantie, dass jede entfernte Abhängigkeit erreichbar ist.
Wenn dieser Dienst fehlschlägt, sind die ersten Überprüfungen vorhersehbar:
systemctl status inventory-api.service
journalctl -u inventory-api.service -b --no-pager
systemctl cat inventory-api.service
sudo -u inventory /srv/inventory-api/.venv/bin/gunicorn app:app --bind 127.0.0.1:9000
Der letzte Befehl ist nichts, was Sie in der Produktion laufen lassen. Es ist ein Diagnosecheck, der fragt: "Kann der konfigurierte Benutzer diesen Befehl überhaupt ausführen?" Wenn er die App nicht importieren, die Umgebungsdatei lesen oder in sein Log-Verzeichnis schreiben kann, wird systemd das nicht für Sie beheben.
Ressourcen- und Sicherheitsdirektiven, die Sie oft sehen werden
Viele Produktions-Units enthalten Härtungs- oder Ressourcenkontrollen. Einige häufige Beispiele:
[Service]
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=full
ProtectHome=true
MemoryMax=512M
CPUQuota=80%
Diese Direktiven können sehr nützlich sein, aber sie können auch Annahmen brechen. PrivateTmp=true gibt dem Dienst ein privates /tmp, sodass ein anderer Prozess möglicherweise keine Dateien sieht, die er dort schreibt. ProtectHome=true kann den Zugriff auf /home, /root und /run/user blockieren. ProtectSystem=full macht einen Großteil des Systems aus Sicht des Dienstes schreibgeschützt. Wenn eine App plötzlich nicht mehr dorthin schreiben kann, wo sie es früher konnte, überprüfen Sie die Härtungseinstellungen, bevor Sie der Anwendung die Schuld geben.
Ressourcenlimits haben denselben Kompromiss. MemoryMax= kann verhindern, dass ein Dienst die gesamte Maschine verbraucht, aber wenn der Wert zu niedrig ist, kann der Dienst unter normaler Last beendet werden. Überprüfen Sie das Journal auf Out-of-Memory-Meldungen und vergleichen Sie das Limit mit der tatsächlichen Nutzung, bevor Sie es erhöhen oder entfernen.
Die nützlichsten Debugging-Befehle
Halten Sie diese bereit, wenn Sie mit Dienst-Units arbeiten:
systemctl status my-app.service
systemctl cat my-app.service
systemctl show my-app.service
systemd-analyze verify /etc/systemd/system/my-app.service
journalctl -u my-app.service -b --no-pager
systemctl show ist ausführlich, aber es zeigt die Eigenschaften, die systemd nach dem Parsen der Unit berechnet hat. Das kann einen überraschenden Wert offenbaren, der von einem Standard, einem Drop-In oder einer Reset-Direktive geerbt wurde. systemd-analyze verify fängt einige Syntax- und Abhängigkeitsfehler, bevor Sie einen Dienst neu starten. Es ist kein Ersatz für das Testen der Anwendung, aber es fängt genug Fehler ein, um es auszuführen.