Systemd-Abhängigkeiten verstehen: Unit-Konflikte verhindern und beheben
Erfahren Sie, wie Systemd-Abhängigkeiten, Reihenfolgen, Ziele und Konflikte funktionieren, damit Dienste zuverlässig starten und Fehler leichter zu diagnostizieren sind.
Systemd-Abhängigkeiten verstehen: Unit-Konflikte verhindern und beheben
Systemd-Abhängigkeiten sind leicht zur Hälfte richtig verstanden. Eine Unit-Datei enthält Requires=postgresql.service, die Anwendung startet trotzdem zu früh, und alle fragen sich, warum systemd die Abhängigkeit ignoriert hat. Es hat nichts ignoriert. Requires= und After= beantworten unterschiedliche Fragen.
Diese Unterscheidung ist der Kern der meisten Systemd-Abhängigkeitsprobleme. Eine Direktive steuert, ob eine andere Unit in dieselbe Transaktion einbezogen wird. Eine andere Direktive steuert die Reihenfolge. Weitere Direktiven verknüpfen das Herunterfahrverhalten, binden die Lebensdauer einer Unit an eine andere oder machen zwei Units gegenseitig ausschließend. Sobald Sie diese Konzepte trennen, werden Unit-Konflikte viel weniger rätselhaft.
Die Grundlage: Systemd-Unit-Abhängigkeitsdirektiven
Systemd verwendet spezifische Direktiven in Unit-Dateien (normalerweise in /etc/systemd/system/ oder /lib/systemd/system/), um festzulegen, wann eine Unit starten, stoppen oder auf eine andere warten soll. Das Verständnis dieser Direktiven ist der erste Schritt zur korrekten Verwaltung von Abhängigkeiten.
Kern-Abhängigkeitsdirektiven
Diese Direktiven steuern die Beziehungen zwischen Units. Sie bestimmen nicht automatisch die Startreihenfolge:
Requires=:- Stellt eine starke Abhängigkeit her. Wenn die erforderliche Unit nicht startet, schlägt auch die aktuelle Unit fehl.
- Es impliziert nicht
PartOf=und bedeutet nicht automatisch "starte nach dieser Unit".
Wants=:- Eine schwache Abhängigkeit. Wenn die gewünschte Unit fehlschlägt, versucht die aktuelle Unit trotzdem zu starten. Dies wird für optionale Abhängigkeiten verwendet.
BindsTo=:- Ähnlich wie
Requires=, aber stärker in Bezug auf das Stoppen. Wenn die gebundene Unit stoppt (aus irgendeinem Grund), wird auch die aktuelle Unit gestoppt.
- Ähnlich wie
PartOf=:- Zeigt an, dass die aktuelle Unit ein untergeordneter Teil einer anderen Unit ist (z.B. eine bestimmte Socket-Aktivierung in Bezug auf einen Hauptdienst). Wenn die übergeordnete Unit stoppt, stoppt auch die untergeordnete Unit.
Conflicts=:- Besagt, dass zwei Units nicht gleichzeitig aktiv sein sollten. Das Starten einer führt dazu, dass systemd die andere im Rahmen derselben Transaktion stoppt.
Kern-Start-Synchronisationsdirektiven
Diese Direktiven legen fest, wann die abhängige Unit relativ zur erforderlichen Unit starten soll:
After=:- Gibt an, dass der Start-Job der aktuellen Unit nach dem Start-Job der aufgelisteten Unit angeordnet ist. Es zieht diese Unit nicht von selbst ein.
Before=:- Gibt an, dass die aktuelle Unit vor der aufgelisteten Unit starten soll.
Bewährte Praxis: Für die typische Startreihenfolge von Diensten ist
Wants=in Kombination mitAfter=das häufigste und sicherste Muster.Requires=sollte für Abhängigkeiten reserviert werden, bei denen ein Fehlschlagen der Abhängigkeit zwingend zum Fehlschlagen des abhängigen Dienstes führen muss.
Beispiel: Definieren von Abhängigkeiten in einer Dienstdatei
Betrachten Sie einen benutzerdefinierten Anwendungsdienst, myapp.service, der mit einer von PostgreSQL (postgresql.service) verwalteten Datenbank kommunizieren muss.
# /etc/systemd/system/myapp.service
[Unit]
Description=Meine benutzerdefinierte Anwendung
# Stellen Sie sicher, dass PostgreSQL ausgeführt wird, bevor Sie versuchen, mich zu starten
Requires=postgresql.service
After=postgresql.service
[Service]
ExecStart=/usr/bin/myapp
[Install]
WantedBy=multi-user.target
Dieses Beispiel ist bewusst streng. Wenn PostgreSQL nicht starten kann, soll auch myapp.service fehlschlagen. Für einen Dienst, der im degradierten Modus ohne Datenbank laufen kann, verwenden Sie Wants=postgresql.service mit derselben After=postgresql.service-Reihenfolge. Die Anwendung bittet systemd trotzdem, zuerst PostgreSQL zu starten, darf aber fortfahren, wenn PostgreSQL nicht verfügbar ist.
Es ist keine Schande, die schwächere Beziehung zu wählen, wenn sie der Realität entspricht. Ein Metrik-Exporter, Log-Shipper oder Cache-Wärmer kann nützlich sein, wenn seine Abhängigkeit vorhanden ist, sollte aber nicht das Booten des Hauptsystems blockieren. Harte Abhängigkeiten sollten für Fälle reserviert werden, in denen ein Start ohne die andere Unit falsch oder gefährlich wäre.
Bei vernetzten Anwendungen ist mehr Vorsicht geboten. network.target bedeutet oft, dass der grundlegende Netzwerk-Stack vorhanden ist, nicht dass DHCP abgeschlossen oder DNS verwendbar ist. Wenn Ihre Anwendung wirklich konfiguriertes Netzwerk beim Start benötigt, verwenden Sie:
[Unit]
Wants=network-online.target
After=network-online.target
Stellen Sie dann sicher, dass Ihre Distribution den passenden Wait-Online-Dienst aktiviert hat, wie systemd-networkd-wait-online.service oder die Wait-Online-Unit von NetworkManager. Ohne diesen wartet network-online.target möglicherweise nicht auf das, wovon Sie denken, dass es wartet.
Diagnose von Abhängigkeitsproblemen
Wenn ein Dienst nicht startet, liefert systemd normalerweise genügend Informationen in den Logs, aber Abhängigkeitsketten können die Ursache verschleiern. Hier sind wesentliche Werkzeuge und Befehle zur Fehlerbehebung.
1. Überprüfen des Unit-Status und der Logs
Der grundlegende Ausgangspunkt ist die Überprüfung des Dienststatus und die Durchsicht seiner Logs unmittelbar nach einem fehlgeschlagenen Startversuch.
# Überprüfen Sie den Gesamtstatus, der oft Abhängigkeitsfehler erwähnt
systemctl status myapp.service
# Zeigen Sie detaillierte Logs speziell für die Unit an
journalctl -u myapp.service --since "vor 5 Minuten"
2. Analysieren des Abhängigkeitsbaums
Systemd bietet leistungsstarke Visualisierungswerkzeuge, um genau zu sehen, was auf was wartet.
systemctl list-dependencies
Dieser Befehl zeigt die Units an, die von der angegebenen Unit benötigt oder gewünscht werden, und durchläuft die gesamte Abhängigkeitskette.
Um zu sehen, was myapp.service zum Starten benötigt:
# Vorwärtsabhängigkeiten (was vor mir starten muss)
systemctl list-dependencies --after myapp.service
# Rückwärtsabhängigkeiten (was von mir abhängt)
systemctl list-dependencies --before myapp.service
Verwenden Sie --plain, wenn die Baumformatierung stört, und fügen Sie --all hinzu, wenn Sie auch inaktive Units sehen müssen:
systemctl list-dependencies --plain --all myapp.service
systemd-analyze dot
Für größere Abhängigkeitsfragen generieren Sie einen Graphen und inspizieren Sie ihn visuell:
systemd-analyze dot 'myapp.service' | dot -Tsvg > myapp-deps.svg
Auf einem Server ohne installiertem Graphviz ist die Textausgabe dennoch nützlich, da Sie nach den Unit-Namen suchen und sehen können, welche Kanten systemd kennt. Für Boot-Zeit-Probleme ist systemd-analyze critical-chain myapp.service oft einfacher zu lesen als ein vollständiger Graph.
3. Erkennen von Konflikten und Reihenfolgeproblemen
Abhängigkeitskonflikte äußern sich oft darin, dass Dienste fehlschlagen, weil sie zu früh gestartet wurden oder unerwartet stoppen.
Zirkuläre Abhängigkeiten: Dies ist der gefährlichste Konflikt, bei dem Unit A B benötigt und Unit B A benötigt. Systemd versucht, dies aufzulösen, führt aber oft dazu, dass eine oder beide Units auf unbestimmte Zeit in einem failed- oder activating-Zustand verbleiben.
Um potenzielle Probleme im gesamten System zu finden, können Sie die Logs nach spezifischen Fehlermeldungen im Zusammenhang mit der Reihenfolge durchsuchen:
journalctl -b | grep -E "failed|refused to start|dependency was not satisfied"
Führen Sie auch systemd-analyze verify für benutzerdefinierte Units aus, bevor Sie annehmen, dass das Laufzeitverhalten das Problem ist:
systemd-analyze verify /etc/systemd/system/myapp.service
Es kann frühzeitig auf Reihenfolgezyklen, unbekannte Direktiven und ungültige Unit-Verweise hinweisen. Es wird nicht beweisen, dass Ihr Design korrekt ist, aber es kann Sie davor bewahren, einen Tippfehler zu jagen, als wäre es ein komplexes Abhängigkeitsproblem.
Beheben häufiger Abhängigkeitsprobleme
Sobald identifiziert, können Abhängigkeitsprobleme durch Anpassen der Direktiven in den relevanten Unit-Dateien gelöst werden.
Szenario 1: Dienst startet, bevor seine Voraussetzung bereit ist
Symptom: Ihre Anwendungslogs zeigen Datenbankverbindungsfehler, aber postgresql.service erscheint active in systemctl status.
Diagnose: Dem Dienst fehlt möglicherweise After=postgresql.service, oder PostgreSQL ist möglicherweise aktiv, bevor die spezifische Datenbank, der Socket, die Anmeldeinformationen oder das Schema, das Ihre Anwendung benötigt, bereit ist. Systemd kann Units ordnen, aber es kann nicht automatisch jede anwendungsspezifische Bereitschaftsbedingung verstehen.
Behebung: Beginnen Sie mit der einfachen Unit-Beziehung:
[Unit]
Requires=postgresql.service
After=postgresql.service
Wenn die Anwendung immer noch mit der Datenbank um die Wette läuft, beheben Sie das Bereitschaftsproblem auf der richtigen Ebene. Einige Dienste unterstützen Type=notify und melden sich erst nach der Initialisierung als bereit. Einige Anwendungen benötigen Wiederholungslogik, da Abhängigkeiten jederzeit neu starten können, nicht nur während des Bootvorgangs. Für eine enge lokale Überprüfung kann ein ExecStartPre=-Befehl sinnvoll sein:
[Service]
ExecStartPre=/usr/bin/pg_isready -q -h 127.0.0.1 -p 5432
ExecStart=/usr/local/bin/myapp
Verwenden Sie diese Art von Vorabprüfung sparsam. Wenn sie zu einem Shell-Skript mit Schlaf- und Schleifenfunktionen anwächst, benötigt die Anwendung stattdessen wahrscheinlich ein ordentliches Wiederholungsverhalten.
Vermeiden Sie sleep 30 als Abhängigkeitsbehebung. Es kann den Wettlauf auf einer ruhigen Entwicklungsmaschine verbergen und auf langsamerem Speicher, einer ausgelasteten VM oder einem Host, der auf DNS wartet, erneut fehlschlagen. Eine echte Ordnungsdirektive, Bereitschaftsbenachrichtigung, Socket-Aktivierung oder Anwendungs-Wiederholungsschleife gibt Ihnen einen Grund, warum der Dienst bereit ist, anstatt der Hoffnung, dass genug Zeit vergangen ist.
Szenario 2: Konfliktreiche Start-/Stopp-Reihenfolge
Symptom: Das Stoppen des Systems führt dazu, dass kritische Prozesse hängen bleiben oder abrupt fehlschlagen.
Diagnose: Dies deutet oft auf eine falsche Verwendung von BindsTo= oder eine komplexe Interaktion zwischen Before=- und After=-Direktiven in Schwesterdiensten hin.
Behebung: Überprüfen Sie Dienste, die Geschwister sind (z.B. Dienste, die vom selben Ziel gestartet werden). Stellen Sie sicher, dass, wenn Dienst A laufen muss, während Dienst B läuft, Sie BindsTo= oder Requires= verwenden. Wenn Dienst A seine Aufgaben beenden muss, bevor Dienst B mit der Bereinigung beginnt, überprüfen Sie, ob die After=-Reihenfolge korrekt ist.
Denken Sie daran, dass die Herunterfahr-Reihenfolge die Umkehrung der Startreihenfolge ist. Wenn app.service After=database.service hat, stoppt systemd beim Herunterfahren app.service vor database.service. Das ist normalerweise das, was Sie wollen: Die Anwendung stoppt die Annahme von Arbeit, bevor die Datenbank verschwindet. Viele Herunterfahr-Fehler resultieren aus fehlender Startreihenfolge, nicht aus einer separaten, nur für das Herunterfahren geltenden Einstellung.
Szenario 3: Entfernen unnötiger Abhängigkeiten
Symptom: Der Systemstart ist langsam, weil unnötige Dienste in die Startkette einbezogen werden.
Diagnose: Sie haben möglicherweise Requires= verwendet, obwohl nur eine optionale Verbindung benötigt wurde.
Behebung: Ändern Sie Requires= in Wants=. Wenn der Dienst die Abhängigkeit nicht unbedingt für die Funktion benötigt, erlaubt Wants= dem System, fortzufahren, selbst wenn die Abhängigkeit fehlschlägt oder maskiert ist.
# Vorher (zu streng)
Requires=optional_logging.service
# Nachher (besser)
Wants=optional_logging.service
After=optional_logging.service
Dies ist besonders nützlich für Überwachungsagenten, Metrik-Exporter, Sidecar-Helfer und optionale lokale Caches. Wenn der Hauptdienst ohne sie nützliche Arbeit leisten kann, verwandelt Requires= eine Teilunterbrechung in eine Vollunterbrechung. Verwenden Sie strenge Abhängigkeiten für Dinge, die wirklich erforderlich sind: eine lokale Datenbank für eine Anwendung, die ohne sie nicht starten kann, einen Mountpunkt, der die Daten der Anwendung enthält, oder eine Socket-Unit, die der einzige unterstützte Aktivierungspfad ist.
Szenario 4: Ein Mount oder Gerät ist nicht bereit
Symptom: Ein Dienst schlägt beim Booten mit No such file or directory fehl, aber der Pfad existiert, nachdem Sie sich angemeldet haben. Dies passiert oft bei Diensten, die von /mnt/data, /srv/app, Wechseldatenträgern, verschlüsselten Volumes oder Netzwerkdateisystemen lesen.
Diagnose: Der Dienst startet, bevor die Mount-Unit aktiv ist, oder der Mount ist optional und fehlgeschlagen, ohne den Dienst zu stoppen.
Behebung: Finden Sie den Mount-Unit-Namen:
systemd-escape -p --suffix=mount /mnt/data
Für /mnt/data ergibt dies normalerweise mnt-data.mount. Ordnen Sie dann Ihren Dienst danach und fordern Sie ihn, wenn der Dienst ohne die Daten nicht laufen kann:
[Unit]
Requires=mnt-data.mount
After=mnt-data.mount
Wenn der Mount aus /etc/fstab stammt, beeinflussen Optionen wie nofail, x-systemd.automount und _netdev das Bootverhalten. Fügen Sie kein hartes Requires= zu einem optionalen Mount hinzu, es sei denn, Sie möchten wirklich, dass dieser Mount-Fehler den Dienst blockiert.
Szenario 5: Ein Ziel zieht zu viel ein
Symptom: Das Aktivieren eines Dienstes scheint nicht verwandte Units zu starten, oder das Deaktivieren eines Dienstes stoppt ihn nicht daran, beim Booten zu erscheinen.
Diagnose: Der Dienst kann durch ein Ziel über [Install] WantedBy=..., durch Wants= eines anderen Dienstes oder durch eine Socket-, Timer-, Pfad- oder Mount-Unit eingezogen werden. enable erstellt Symlinks für die Aktivierung beim Booten; es ist nicht der einzige Weg, wie eine Unit starten kann.
Behebung: Überprüfen Sie sowohl die Unit als auch die Rückwärtsabhängigkeiten:
systemctl cat myapp.service
systemctl list-dependencies --reverse myapp.service
systemctl list-timers --all | grep myapp
systemctl list-sockets --all | grep myapp
Wenn eine Socket-Unit den Dienst aktiviert, reicht das Deaktivieren nur von myapp.service möglicherweise nicht aus. Möglicherweise müssen Sie auch myapp.socket deaktivieren oder maskieren, je nach gewünschtem Verhalten.
Anwenden von Änderungen und Neuladen
Wenn Sie eine Unit-Datei ändern, müssen Sie systemd anweisen, seine Konfiguration neu zu laden, bevor Sie die Änderungen testen.
# 1. Laden Sie die systemd-Manager-Konfiguration neu
sudo systemctl daemon-reload
# 2. Starten Sie den betroffenen Dienst neu
sudo systemctl restart myapp.service
# 3. Überprüfen Sie den Status
systemctl status myapp.service
Eine kleine mentale Checkliste
Wenn ein Abhängigkeitsproblem verworren erscheint, reduzieren Sie es auf ein paar einfache Überprüfungen.
Fragen Sie zuerst, ob die andere Unit überhaupt gestartet werden soll. Wenn ja, verwenden Sie Wants= oder Requires=. Wenn der aktuelle Dienst ohne sie laufen kann, bevorzugen Sie Wants=. Wenn ein Fehlschlagen dieser Unit bedeutet, dass dieser Dienst fehlschlagen muss, verwenden Sie Requires=.
Zweitens fragen Sie, ob die Startreihenfolge wichtig ist. Wenn ja, fügen Sie After= oder Before= hinzu. Erwarten Sie nicht, dass Abhängigkeitsdirektiven die Reihenfolge selbstständig handhaben.
Drittens fragen Sie, ob die Lebensdauerkopplung nach dem Start wichtig ist. Wenn das Stoppen einer Unit die andere stoppen soll, schauen Sie sich BindsTo= oder PartOf= an. Verwenden Sie sie nicht leichtfertig; sie können dazu führen, dass routinemäßige Neustarts weiter kaskadieren als erwartet.
Überprüfen Sie schließlich, was systemd tatsächlich geladen hat:
systemctl cat myapp.service
systemctl show myapp.service -p Wants -p Requires -p After -p Before -p Conflicts
Diese Ausgabe ist oft nützlicher als die Datei, von der Sie denken, dass Sie sie bearbeitet haben. Drop-Ins, Vendor-Units, generierte Units, Sockets, Timer und Ziele können alle das endgültige Verhalten ändern.