systemd-Abhängigkeiten: Requires, After und Conditions
Requires, Wants, After, Before, ExecStartPre und Condition tun Verschiedenes. Dieser Leitfaden erklärt ihre Zusagen und die Fehlersuche, wenn eine Unit nie startet.
Requires bedeutet nicht After
systemd-Abhängigkeiten und Bedingungen sind vier separate Mechanismen, die in vielen Unit-Dateien so verwendet werden, als wären sie einer. Requires= und Wants= entscheiden, welche anderen Units in die Starttransaktion aufgenommen werden. After= und Before= entscheiden über die Startreihenfolge der Units. ExecStartPre= führt eine Prüfung aus, die zum Fehlschlagen der Unit führen kann. Die Familien Condition und Assert entscheiden, ob die Unit überhaupt ausgeführt wird. Jeder Mechanismus ist von den anderen unabhängig. Eine Unit kann daher eine andere Unit voraussetzen und trotzdem im selben Moment wie diese gestartet werden.
Dieser letzte Satz ist die Ursache hinter fast jedem Bericht nach dem Muster: „Beim manuellen Start funktioniert es, aber beim Booten schlägt es fehl.“
[Unit]
Description=Inventory API
Requires=postgresql.service
[Service]
ExecStartPre=/usr/bin/pg_isready -h 127.0.0.1 -t 5
ExecStart=/usr/local/bin/inventory-apiRequires=postgresql.service nimmt PostgreSQL in dieselbe Starttransaktion auf. Es wartet jedoch nicht auf PostgreSQL. systemd startet beide Jobs parallel. Daher wird pg_isready ausgeführt, während PostgreSQL sein Datenverzeichnis noch öffnet. Der Prozess beendet sich mit 2, weil noch nichts auf dem Port lauscht, und die Unit schlägt fehl, bevor ExecStart überhaupt erreicht wird. `sudo systemctl start inventory-api` eine Stunde später auszuführen funktioniert, weil PostgreSQL zu diesem Zeitpunkt bereits läuft. An der Unit-Datei wurde nichts geändert. Deshalb wirkt die Datei unverdächtig.
Die Korrektur besteht aus einer Zeile.
[Unit]
Requires=postgresql.service
After=postgresql.serviceAn derselben Stelle verbirgt sich ein wichtigeres Detail. Eine fehlschlagende Requires=-Abhängigkeit verhindert nur dann den Start Ihrer Unit, wenn Sie dafür zusätzlich After= setzen. Ohne die Startreihenfolge hat systemd Ihre Unit bereits gestartet, sobald die andere Unit fehlschlägt. Es gibt dann nichts mehr abzubrechen. Requires= allein bietet nicht den Schutz, den viele davon erwarten. Schreiben Sie neben jedes Requires= und jedes Wants= ein After=, sofern es keinen konkreten Grund dagegen gibt.
Was Requires, Wants, Requisite und BindsTo bewirken
Alle diese Einstellungen definieren Abhängigkeiten. Keine von ihnen legt eine Reihenfolge fest.
Wants=: Bindet die andere Unit ein. Wenn sie fehlschlägt oder nicht existiert, wird diese Unit trotzdem gestartet. Genau das erzeugtsystemctl enableals symbolischen Link in einem.wants/-Verzeichnis.Requires=: Bindet die andere Unit ein. Wenn sie fehlschlägt und Sie zusätzlichAfter=festgelegt haben, wird diese Unit nicht gestartet. Wenn die andere Unit später ausdrücklich gestoppt wird, wird diese Unit ebenfalls gestoppt.Requisite=: Bindet die andere Unit nicht ein. Wenn sie nicht bereits aktiv ist, schlägt diese Unit sofort fehl.BindsTo=: WieRequires=. Zusätzlich wird diese Unit beendet, sobald die andere Unit aus irgendeinem Grund stoppt, auch wenn Hardware nicht mehr verfügbar ist.PartOf=: Stoppen und Neustarten werden von der anderen Unit auf diese Unit weitergegeben. Das Starten wird nicht weitergegeben.Conflicts=: Beim Starten dieser Unit wird die andere Unit gestoppt.
Für einen Daemon, der mit einem anderen Daemon kommuniziert, ist Wants= zusammen mit After= normalerweise die richtige Kombination. Requires= koppelt die Laufzeiten: Wenn Sie die Datenbank für Wartungsarbeiten stoppen, wird auch Ihre Anwendung beendet. Sie wird nicht erneut gestartet, wenn die Datenbank wieder verfügbar ist. Wants= zusammen mit After= stellt die Startreihenfolge beim Booten sicher, ohne diese Kopplung. Eine Neustartregel behandelt den Fall, dass die Abhängigkeit später wegfällt.
Sie erben außerdem Abhängigkeiten, die Sie nicht selbst festgelegt haben. Mit DefaultDependencies=yes, der Standardeinstellung, erhält ein gewöhnlicher Dienst automatisch Requires=sysinit.target, After=sysinit.target basic.target und Conflicts=shutdown.target. Deshalb startet ein Dienst mit einem nahezu leeren Abschnitt [Unit] trotzdem spät während des Bootvorgangs und wird beim Herunterfahren sauber beendet.
After und Before ordnen die Transaktion, mehr nicht
After= und Before= dienen ausschließlich der Reihenfolge. Sie stellen keinerlei Anforderung. After=redis.service in einer Unit, die Redis durch nichts anderes in die Transaktion aufnimmt, hat keine Wirkung: Wenn redis.service nicht Teil der Transaktion ist, gibt es nichts, worauf gewartet werden kann. Ihre Unit startet daher sofort.
Das muss man zweimal sagen, weil dies genau der Form des späteren network-online.target-Fehlers entspricht. Die Reihenfolge wartet nur auf Units, die bereits in derselben Transaktion gestartet werden.
Das Paar ist symmetrisch. After=b.service in a.service bedeutet dasselbe wie Before=a.service in b.service. Verwenden Sie daher eine der beiden Varianten und tragen Sie sie in der Unit ein, die Sie verwalten. Beim Herunterfahren wird die Reihenfolge automatisch umgekehrt. After=b.service bedeutet daher auch, dass Ihre Unit vor b.service beendet wird.
After= wartet auf „gestartet“, und Type= legt fest, was das bedeutet
After= wartet, bis die andere Unit ihren Start abgeschlossen hat. Was „Start abgeschlossen“ bedeutet, wird vollständig durch das Type= dieser Unit festgelegt.
Type=simple: sobald systemd den Prozess per fork gestartet hat. Das Programm hat möglicherweise noch nicht einmal seine Konfiguration eingelesen, geschweige denn einen Socket geöffnet.Type=exec: sobaldexecve()erfolgreich war. Das ist etwas verbindlicher. Über die Bereitschaft sagt es trotzdem nichts aus.Type=forking: sobald der ursprüngliche Elternprozess beendet wird.Type=oneshot: sobald der Prozess beendet wird. Hier bedeutet „gestartet“ tatsächlich, dass die Arbeit abgeschlossen ist.Type=notify: sobald der DienstREADY=1über seinen Notification-Socket sendet. Nur dieser Typ meldet eine tatsächliche Bereitschaft.
Damit ist After= bei einem Type=simple-Daemon eine schwache Zusage. Das ist die zweite Hälfte des Race-Problems im ersten Beispiel. Wenn die Unit, von der Sie abhängen, als Type=simple ausgeliefert wird, bedeutet die Reihenfolge danach nicht, dass sie Verbindungen annimmt. Dafür gibt es zwei zuverlässige Lösungen. Legen Sie die Reihenfolge stattdessen nach der zugehörigen Socket-Unit fest. Dann stellt der Kernel eingehende Verbindungen in die Warteschlange, während der Daemon noch startet. Oder lassen Sie Ihren eigenen Dienst wiederholt versuchen, und überlassen Sie das weitere Starten der Restart-Richtlinie. Welchen Typ eine Unit verwendet, sehen Sie in systemctl cat. Die Einstellung Type= und die Bedeutung der einzelnen Werte für systemd sollten Sie lesen, bevor Sie sich auf eine Startreihenfolge verlassen.
ExecStartPre ist ein Gate, das die Unit fehlschlagen lassen kann
ExecStartPre= wird vor ExecStart= ausgeführt. Wenn der Prozess mit einem Exit-Code ungleich 0 endet, wird die Aktivierung abgebrochen und die Unit wechselt in failed. ExecStart= wird nie ausgeführt. Das ist der Grund, warum viele Units fehlschlagen, ohne dass das eigentliche Programm eine Meldung ausgibt: Das Programm wurde nie gestartet.
Wichtige Punkte:
- Es handelt sich nicht um eine Shell. Es gibt keine Pipes, keine Umleitungen, keine Globs und kein
&&. Das erste Token muss ein absoluter Pfad sein. Setzen Sie die Zeile in/bin/sh -c '...', wenn Sie Shell-Syntax benötigen. - Ein Präfix
-macht einen Exit-Code ungleich 0 nicht fatal:ExecStartPre=-/usr/bin/optional-check. - Jede
ExecStartPre=muss beendet sein, bevor die nächste ausgeführt wird. Sie kann keinen langlebigen Prozess starten. - Alle
ExecStartPre=-Zeilen teilen sichTimeoutStartSec=mitExecStart=. Eine Vorprüfung, die auf eine Datenbank wartet, verbraucht das Start-Timeout. Danach schlägt die Unit mitResult: timeoutfehl, sobaldstart operation timed out. Terminating.im Journal erscheint.
Die Fehlerzeile nennt den Steuerprozess, nicht den Hauptprozess:
inventory-api.service: Control process exited, code=exited, status=2/INVALIDARGUMENT
inventory-api.service: Failed with result 'exit-code'.Lesen Sie diesen symbolischen Namen genau. systemd ordnet kleine Exit-Codes anhand einer festen Tabelle zu. Deshalb gibt 2 unabhängig von der Bedeutung des Programms immer INVALIDARGUMENT aus. status=203/EXEC enthält die entscheidende Information: systemd konnte die Binärdatei überhaupt nicht ausführen, weil der Pfad falsch ist oder die Datei nicht ausführbar ist.
Verwenden Sie ExecStartPre= nicht zum Erstellen von Verzeichnissen. RuntimeDirectory=, StateDirectory=, LogsDirectory= und CacheDirectory= erstellen sie mit dem richtigen Besitzer und Modus. RuntimeDirectory= wird beim Beenden des Dienstes bereinigt. Diese Mechanismen verhalten sich auch unter DynamicUser= korrekt. Das gilt für ein manuell geschriebenes mkdir nicht.
Bedingungen schlagen still fehl. Assertions schlagen laut fehl.
Die Familien Condition und Assert führen dieselben Tests aus. Sie unterscheiden sich nur darin, was bei einem fehlgeschlagenen Test passiert.
Ein fehlgeschlagenes Condition...= überspringt die Unit. Der Startauftrag wird als erfolgreich gemeldet. Die Unit bleibt inactive (dead), nichts wird als fehlgeschlagen markiert, es wird kein Alarm ausgelöst, und das Journal zeichnet eine Zeile auf:
Condition check resulted in Inventory API being skipped.Unter systemd 250 und neuer gibt systemctl status den Grund direkt aus:
Active: inactive (dead)
Condition: start condition unmet at Thu 2026-08-20 09:14:02 UTC; 2min agoDie eingerückte Zeile darunter nennt die genaue Direktive, die fehlgeschlagen ist, zum Beispiel ConditionPathExists=/etc/inventory/api.conf was not met.
Ein fehlgeschlagenes Assert...= schlägt für die Unit fehl. Im Journal steht Assertion failed for Inventory API., und die Unit endet in failed (Result: assert). Das ist für die Überwachung deutlich genug, damit sie den Fehler erkennt.
Wählen Sie zwischen beiden, indem Sie fragen, was ein fehlgeschlagener Test bedeutet. Condition bedeutet: „Diese Unit gilt auf diesem Rechner nicht.“ Assert bedeutet: „Das muss erfüllt sein. Falls nicht, muss jemand informiert werden.“ Für die meisten Units ist Condition die richtige Wahl. Verwenden Sie Assert nur dann, wenn ein stilles Nichtstun schlimmer wäre als eine fehlgeschlagene Unit.
Mit der Familie Condition sind zwei Fallstricke verbunden.
Erstens: Eine fehlgeschlagene Bedingung lässt die Units, die von ihr abhängen, nicht fehlschlagen. Wenn a.service über Requires=b.service verfügt und b.service aufgrund einer Bedingung übersprungen wird, gilt der Startauftrag für b.service trotzdem als abgeschlossen. a.service startet dann normal, obwohl b nicht läuft. Eine Bedingung schützt nur die Unit, in der sie eingetragen ist.
Zweitens werden Bedingungen jedes Mal ausgewertet, wenn die Unit startet, und zwar zu dem Zeitpunkt, an dem der Auftrag ausgeführt wird. Eine Unit, die durch einen systemd-Timer auf einem VPS ausgelöst wird, kann hundertmal hintereinander übersprungen werden und dabei kein einziges Mal als fehlgeschlagen erscheinen. Das ist dieselbe Art von stillem Nichtstun wie bei einem Cron-Auftrag, der läuft, aber nichts tut. Sie finden die Ursache auf dieselbe Weise: Lesen Sie das Journal der Unit, statt sich auf ihren Exit-Status zu verlassen.
Die wichtigsten Bedingungen für einen Server:
ConditionPathExists=/etc/inventory/api.confund die NegationConditionPathExists=!/etc/inventory/api.conf.ConditionFileNotEmpty=undConditionDirectoryNotEmpty=für eine Konfigurationsdatei oder ein Datenverzeichnis, das ein Paket angelegt, aber leer gelassen hat.ConditionVirtualization=, damit eine Unit, die eine echte Kernel-Schnittstelle benötigt,ConditionVirtualization=!containerenthalten kann. Prüfen Sie mitsystemd-detect-virt, was Ihr Rechner meldet.ConditionHost=vergleicht den Hostnamen oder die Rechner-ID. Damit kann sich eine gemeinsame Unit-Datei auf zwei Servern unterschiedlich verhalten.ConditionKernelCommandLine=undConditionKernelVersion=für Units, die an einen Boot-Parameter oder an eine minimale Kernel-Version gebunden sind.
Eine leere Zuweisung leert die Liste. So entfernt ein Drop-in eine Bedingung, die ein Paket mitgeliefert hat:
[Unit]
ConditionPathExists=
ConditionPathExists=/srv/inventory/api.confWarum network.target nicht bedeutet, dass das Netzwerk verfügbar ist
network.target ist ein Synchronisationspunkt, kein Zustand. Wenn eine Unit beim Booten danach eingeordnet wird, bedeutet das, dass die Netzwerkverwaltungssoftware gestartet wurde. Es bedeutet nicht, dass ein Interface eine Adresse hat oder eine Route ins Internet existiert. Das Target dient hauptsächlich der umgekehrten Richtung: Eine Unit, die mit After=network.target eingeordnet ist, wird beim Herunterfahren beendet, bevor das Netzwerk abgebaut wird.
network-online.target wartet tatsächlich. Dahinter steht ein wait-online-Dienst, der zum jeweils verwendeten Netzwerkmanager gehört:
systemd-networkd-wait-online.service, wennsystemd-networkddie Links verwaltet. Das ist der Normalfall auf einem Ubuntu-Server, der übernetplankonfiguriert wurde.NetworkManager-wait-online.serviceunterNetworkManager.
Ältere ifupdown-Installationen erzielen denselben Effekt mit networking.service. Unabhängig davon, welches Target Sie verwenden, besteht die korrekte Konfiguration aus zwei Zeilen, nicht aus einer.
[Unit]
Wants=network-online.target
After=network-online.targetnetwork-online.target ist nicht Teil der standardmäßigen Boot-Transaktion. Es wird nicht automatisch eingebunden. Wenn Sie nur After= schreiben, ordnen Sie die Unit einem Target zu, das nie in die Transaktion aufgenommen wurde. Die Anordnung bewirkt dann überhaupt nichts. Das ist der zuvor beschriebene No-op, hier in seiner kostspieligsten Form. Die Zeile Wants= nimmt das Target in die Transaktion auf, sodass die Zeile After= auf etwas warten kann.
Der zweite wichtige Punkt ist, dass die Implementierung von wait-online festlegt, was „online“ bedeutet, nicht systemd. systemd-networkd-wait-online kehrt zurück, sobald die von ihm verwalteten Links einen konfigurierten Zustand erreicht haben. Es prüft nicht, ob DNS-Namen aufgelöst werden, und prüft auch nicht, ob ein entfernter Host erreichbar ist.
Diese Definition führt bei VPS häufig zu einem Fehler. Wenn ein Rechner ein zweites Interface für ein privates Netzwerk hat, das in netplan deklariert, aber nie mit einer Adresse versehen wurde, wartet der Dienst, bis er aufgibt:
systemd-networkd-wait-online[612]: Timeout occurred while waiting for network connectivity.
systemd-networkd-wait-online.service: Failed with result 'exit-code'.Der Bootvorgang dauert zwei zusätzliche Minuten, weil das Standard-Timeout 120 Sekunden beträgt. Es gibt zwei Möglichkeiten zur Behebung. Markieren Sie das ungenutzte Interface mit optional: true in der netplan-Datei. Dann wartet networkd nicht mehr darauf. Oder legen Sie einen Drop-in für den Wait-Dienst an. Geben Sie darin mit --interface= den gewünschten Link an oder übergeben Sie --any, damit der Dienst zurückkehrt, sobald ein Link verfügbar ist.
Noch besser ist es, das Target nicht zu benötigen. Viele Dienste werden nur deshalb nach network-online.target eingeordnet, weil sie an eine bestimmte Adresse gebunden werden und beim Booten mit einer Meldung wie dieser fehlschlagen:
nginx: [emerg] bind() to 203.0.113.10:443 failed (99: Cannot assign requested address)Der Kernel verweigert die Bindung, weil diese Adresse noch nicht verfügbar ist. Mit net.ipv4.ip_nonlocal_bind=1 kann ein Prozess eine Adresse binden, die der Rechner noch nicht besitzt. Eine Restart-Richtlinie behebt die verbleibenden Probleme. Den gesamten Bootvorgang von der Netzwerkbereitschaft abhängig zu machen, ist ein schweres Werkzeug für ein Problem, das normalerweise nur einen Socket betrifft.
So lesen Sie die tatsächlichen systemd-Abhängigkeiten auf einem laufenden System
Leiten Sie Abhängigkeiten niemals allein aus der Unit-Datei ab. Drop-ins, .wants/-Symlinks und implizite Standardabhängigkeiten fügen Kanten hinzu, die in der Datei nicht sichtbar sind.
systemctl cat inventory-api.serviceDieser Befehl gibt die Unit-Datei und jedes Drop-in in der Reihenfolge aus, in der sie angewendet werden. Über jedem Block steht der Quellpfad. Führen Sie ihn zuerst aus. Ein Override mit fünf Zeilen in /etc/systemd/system/inventory-api.service.d/ hat Vorrang vor der Paketdatei und bleibt andernfalls unsichtbar.
systemctl show inventory-api.service -p Requires -p Wants -p After -p Before -p ConditionResult -p AssertResultDieser Befehl gibt die aufgelösten Werte aus, nachdem Drop-ins angewendet und die impliziten Abhängigkeiten von systemd hinzugefügt wurden. ConditionResult=no beantwortet direkt die Frage: „Die Unit meldete Erfolg, hat aber nichts getan.“
systemctl list-dependencies inventory-api.service
systemctl list-dependencies --reverse inventory-api.service
systemctl list-dependencies --after inventory-api.service
systemctl list-dependencies --before inventory-api.serviceDie einfache Form durchläuft Requires= und Wants= abwärts. --reverse zeigt, welche Units Ihre Unit einbinden. So finden Sie das Target, das sie beim Booten startet. --after und --before zeigen die Reihenfolge. Diese beiden Ausgaben sind relevant, wenn Sie prüfen möchten, ob tatsächlich auf etwas gewartet wurde.
journalctl -b -u inventory-api.service --no-pager
journalctl -b -o short-precise -u inventory-api.service -u postgresql.serviceDer zweite Befehl verschachtelt zwei Units mit Zeitstempeln im Millisekundenbereich. So weisen Sie eine Race Condition bei der Reihenfolge nach, statt sie nur zu vermuten. Der Fehler in pg_isready tritt auf, bevor PostgreSQL database system is ready to accept connections protokolliert. Der Abstand zwischen beiden Ereignissen ist direkt in der Ausgabe sichtbar.
systemd-analyze verify /etc/systemd/system/inventory-api.service
systemd-analyze critical-chain inventory-api.serviceverify lädt die Unit so, wie systemd sie laden würde, und meldet unbekannte Direktiven, Abhängigkeiten zu nicht vorhandenen Units, Zyklen in der Startreihenfolge und nicht parsebare Syntax. Am System wird nichts geändert. critical-chain gibt die Reihenfolge der Schritte aus, die den Start der Unit verzögert haben, einschließlich des Zeitpunkts, zu dem jeder Schritt aktiv wurde. Der Befehl funktioniert nur für eine Unit, die während des aktuellen Bootvorgangs gestartet wurde.
Führen Sie nach jeder Änderung an einer Unit-Datei sudo systemctl daemon-reload aus. Um eine Paket-Unit zu ändern, verwenden Sie sudo systemctl edit inventory-api.service. Dieser Befehl erstellt automatisch ein Drop-in. Änderungen an der Herstellerdatei unter /usr/lib/systemd/system/ bleiben nur bis zum nächsten Paket-Upgrade bestehen, das die Datei ersetzt. Mit demselben Drop-in-Mechanismus können Sie Speicher- und CPU-Limits für einen Dienst festlegen, ohne eine Datei zu ändern, die dem Paket gehört.
Zyklen bei der Reihenfolge und die entsprechende Zeile im Journal
Fügen Sie die Abhängigkeit in beiden Richtungen hinzu, und systemd durchbricht die Schleife, indem es einen der Jobs löscht:
systemd[1]: Found ordering cycle on inventory-api.service/start
systemd[1]: Job postgresql.service/start deleted to break ordering cycle starting with inventory-api.service/startsystemd entscheidet, welchen Job es löscht. Dabei muss es nicht den Job auswählen, den Sie erwarten. Das Ergebnis wirkt wie ein Dienst, der nach manchen Reboots fehlt und nach anderen vorhanden ist. Von außen lässt sich dieses Verhalten nur schwer diagnostizieren. Die meisten Zyklen entstehen durch Units, die DefaultDependencies=no setzen und sich anschließend trotzdem gegenüber basic.target einordnen. Eine weitere häufige Ursache ist Before= bei einer Unit, für die bereits After= zurück auf Ihre Unit verwies. systemd-analyze verify findet solche Zyklen ohne Reboot.
Die feste Unit
[Unit]
Description=Inventory API
Wants=postgresql.service network-online.target
After=postgresql.service network-online.target
ConditionPathExists=/etc/inventory/api.conf
[Service]
Type=notify
StateDirectory=inventory
ExecStart=/usr/local/bin/inventory-api
Restart=on-failure
RestartSec=5s
[Install]
WantedBy=multi-user.targetJede Zeile erfüllt eine Aufgabe. Wants= nimmt beide Abhängigkeiten in die Transaktion auf, ohne die Lebensdauer dieser Unit an sie zu binden. After= übernimmt das Warten und muss beide Namen wiederholen, weil Abhängigkeiten und Reihenfolge getrennte Einstellungen sind. ConditionPathExists= sorgt dafür, dass ein Rechner mit installiertem Paket, aber ohne Konfiguration die Unit still überspringt, statt einen Fehler zu melden. Das ist das richtige Verhalten für einen konfigurationsgesteuerten Dienst. Type=notify bedeutet, dass alles, was nach dieser Unit eingeordnet ist, auf die tatsächliche Bereitschaft wartet und nicht nur auf einen Fork. Restart=on-failure deckt den Fall ab, dass die Datenbank lange nach dem Bootvorgang ausfällt, weil die Reihenfolge nur für den ersten Start gilt. Wie aggressiv dieser erneute Versuch erfolgen soll, wird durch die Einstellungen Restart= und RestartSec= gesteuert.
Prüfen Sie die Unit, bevor Sie ihr vertrauen:
sudo systemctl daemon-reload
systemd-analyze verify /etc/systemd/system/inventory-api.service
systemctl list-dependencies --after inventory-api.service
sudo systemctl start inventory-api.service
systemctl show inventory-api.service -p ConditionResult -p ActiveState -p ResultEine funktionsfähige Unit liest ConditionResult=yes mit ActiveState=active, und Result=success bestätigt, dass beim letzten Lauf kein Fehler aufgetreten ist. ConditionResult=no zusammen mit ActiveState=inactive bedeutet, dass die Unit übersprungen wurde. Die Journal-Zeile mit der angegebenen Bedingung zeigt, welcher Test fehlgeschlagen ist.
FAQ
Wartet Requires= darauf, dass die andere Unit gestartet wird?
Nein. Requires= und After= sind separate Einstellungen. Requires= nimmt die andere Unit in dieselbe Transaktion auf. Anschließend startet systemd beide Jobs parallel. Wenn Sie warten müssen, ergänzen Sie After= mit dem Namen derselben Unit. Es gibt noch einen zweiten Grund dafür: Eine Requires=-Abhängigkeit, die fehlschlägt, verhindert den Start Ihrer Unit nur, wenn zusätzlich After= gesetzt ist. Ohne eine Startreihenfolge wurde Ihre Unit bereits gestartet, bevor die andere Unit fehlschlägt.
Soll ich die Unit nach network.target oder network-online.target einordnen?
Beim Booten bedeutet network.target lediglich, dass die Netzwerkverwaltungssoftware gestartet wurde. Über Adressen oder Routen sagt das nichts aus. Verwenden Sie network-online.target, wenn Ihr Dienst beim Start eine funktionierende Adresse benötigt, und setzen Sie sowohl Wants=network-online.target als auch After=network-online.target. Das Target ist nicht Teil der standardmäßigen Boot-Transaktion. After= allein wartet daher auf eine Unit, die niemand eingeplant hat. Wenn der Dienst nur fehlschlägt, weil er an eine bestimmte IP-Adresse gebunden wird, ist net.ipv4.ip_nonlocal_bind=1 zusammen mit Restart=on-failure eine leichtere Lösung als eine Verzögerung des Bootvorgangs.
Warum meldet meine Unit Erfolg, wird aber nie ausgeführt?
Ein fehlgeschlagener Condition...=-Test überspringt die Unit und meldet den Startjob als erfolgreich. Daher wird nichts als fehlgeschlagen markiert. Führen Sie systemctl show <unit> -p ConditionResult aus. ConditionResult=no bestätigt das Ergebnis. Lesen Sie anschließend journalctl -b -u <unit> nach der Zeile Condition check resulted in <description> being skipped. Bei systemd 250 und neueren Versionen nennt systemctl status <unit> außerdem genau die Direktive, deren Bedingung nicht erfüllt wurde.
Was ist der Unterschied zwischen Condition und Assert?
Beide führen identische Tests aus. Ein fehlgeschlagenes Condition überspringt die Unit stillschweigend. Der Job bleibt trotzdem erfolgreich. Ein fehlgeschlagenes Assert lässt die Unit fehlschlagen, protokolliert Assertion failed for <description>. und belässt sie in failed (Result: assert). Verwenden Sie Condition für den Fall „Diese Unit gilt auf diesem Rechner nicht“. Das deckt fast alle praktischen Fälle ab. Verwenden Sie Assert nur, wenn eine fehlende Voraussetzung für Personen sichtbar sein muss, die fehlgeschlagene Units überwachen.
Warum schlägt ExecStartPre mit status=203/EXEC fehl?
203/EXEC bedeutet, dass systemd den Befehl überhaupt nicht ausführen konnte. Häufige Ursachen sind ein nicht absoluter Pfad, eine auf diesem Rechner nicht vorhandene Binärdatei, eine Datei ohne Execute-Bit oder ein Script, dessen #!-Zeile auf einen nicht vorhandenen Interpreter verweist. Die übrigen kleinen Statuscodes von systemd stammen aus einer festen Tabelle. status=2/INVALIDARGUMENT bedeutet daher lediglich, dass der Befehl mit 2 beendet wurde, und sagt nichts über die Argumente aus. Beachten Sie, dass ExecStartPre= nicht über eine Shell ausgeführt wird. Pipes und Globs benötigen daher /bin/sh -c '...'.