Heimserver & Self-Hosting

systemd-Neustartrichtlinien für einen Home-Server - Dienste wiederherstellen, ohne Fehler zu verbergen

systemd-Neustartrichtlinien für einen Home-Server - Dienste wiederherstellen, ohne Fehler zu verbergen

Ein dauerhaft laufender Dienst auf einem Home-Server beendet sich um drei Uhr morgens. Soll der Rechner ihn abgeschaltet lassen, sofort neu starten oder bis zum Morgen weitere Versuche unternehmen? Automatische Wiederherstellung klingt nach der naheliegenden Antwort. Eine unbegrenzte Neustartschleife kann jedoch aus einem klaren Fehler Rauschen, wiederholte Last und eine schwierigere Diagnose machen.

systemd stellt mehrere Steuerungsmöglichkeiten für dieses Problem bereit. Die sinnvolle Frage lautet nicht nur: „Wie bringe ich einen Dienst zum Neustart?“ Sie lautet: Welche Fehler sollen einen weiteren Versuch auslösen, wie schnell und wann sollen die Versuche enden? Dieser Artikel entwickelt eine zurückhaltende Richtlinie für einen generischen, dauerhaft laufenden Dienst unter Debian. Er setzt nicht voraus, dass ein einziger Satz von Werten für jede Arbeitslast geeignet ist.

Wiederherstellung ist nicht dasselbe wie Reparatur

Ein Neustart kann einen vorübergehenden Zustand beseitigen: Ein Prozess kann auf einen temporären Netzwerkfehler gestoßen sein, eine beim Beenden freigegebene Ressource ausgeschöpft haben oder aufgrund eines isolierten Defekts abgestürzt sein. Ein neuer Prozess kann den Dienst wiederherstellen. Er kann aber auch einen dauerhaften Fehler wiederholen, beispielsweise eine ungültige Konfiguration, fehlende Zugangsdaten, ein inkompatibles Datenbankschema oder einen vollen Datenträger.

Diese Unterscheidung ist wichtig, weil systemd einen Prozess überwacht. Es versteht nicht, ob die Antworten einer Anwendung korrekt sind. Ein Prozess kann laufen und dennoch Fehler zurückgeben, unbegrenzt auf eine Abhängigkeit warten oder veraltete Daten ausliefern. Umgekehrt kann ein Prozess, der sich nach Abschluss seiner Arbeit absichtlich beendet, fehlerfrei sein, obwohl er nicht mehr läuft.

Automatische Neustarts sollten als eine Schicht eines Betriebskonzepts betrachtet werden, nicht als Nachweis der Funktionsfähigkeit. Protokolle, externe Prüfungen, Ressourcenüberwachung und anwendungsspezifische Bereitschaftsprüfungen haben weiterhin eigene Aufgaben.

Festlegen, welche Beendigungen einen weiteren Versuch verdienen

Die zentrale Einstellung gehört in den Abschnitt [Service] der Unit. Laut dem Debian-Trixie-Handbuch zu systemd.service versucht Restart=on-failure einen Neustart nach Ereignissen wie einem Exit-Code ungleich null, einem nicht ordnungsgemäßen Signal, einem Timeout oder einem Watchdog-Fehler. Nach einer ordnungsgemäßen Beendigung oder einem ausdrücklichen systemctl stop erfolgt normalerweise kein Neustart.

[Service]
Restart=on-failure
RestartSec=10s

Für einen dauerhaft laufenden Serverprozess ist on-failure häufig ein vernünftiger Ausgangspunkt, weil es einen Fehler von einer beabsichtigten ordnungsgemäßen Beendigung unterscheidet. Das Upstream-Handbuch empfiehlt diese Einstellung für dauerhaft laufende Dienste, doch das ist eine Empfehlung und keine allgemeingültige Regel. Eine Anwendung kann einen Exit-Code ungleich null verwenden, um einen Eingriff des Administrators anzufordern. Sie kann nach einem fatalen internen Zustand auch versehentlich null zurückgeben. Der Exit-Vertrag des Dienstes bleibt entscheidend.

Restart=always ist umfassender: Es startet den Dienst auch nach einer ordnungsgemäßen Beendigung neu. Das kann zu einem Prozess passen, der ununterbrochen vorhanden sein muss und keinen sinnvollen Zustand „erfolgreich abgeschlossen“ kennt. Zuverlässiger ist diese Einstellung nicht automatisch. Wenn ein Administrator oder ein Deployment-Skript erwartet, dass das Programm ordnungsgemäß endet und beendet bleibt, widerspricht always dieser Absicht. Es gibt außerdem engere Modi wie on-abnormal und on-watchdog. Sie sollten nur gewählt werden, wenn ihre dokumentierten Exit-Kategorien zur Anwendung passen.

Exit-Codes können die Richtlinie verfeinern

systemd bietet außerdem SuccessExitStatus=, RestartPreventExitStatus= und RestartForceExitStatus=. Damit lässt sich die dokumentierte Exit-Semantik einer Anwendung ausdrücken. Ein bestimmter Code könnte beispielsweise bedeuten: „Die Konfiguration ist ungültig; nicht erneut versuchen.“ Solche Regeln ohne einen Vertrag der Anwendung hinzuzufügen, ist jedoch riskant. Eine einprägsame Zahl ersetzt keine Dokumentation.

Sowohl Zeit als auch Anzahl der Versuche begrenzen

RestartSec=10s fügt vor einem automatischen Neustart eine Verzögerung ein. Eine ausdrückliche Verzögerung verhindert, dass ein fehlerhafter Prozess nahezu sofort erneut gestartet wird. Zehn Sekunden sind nur ein Beispiel: Ein lokaler DNS-Helfer, ein großer Java-Dienst und ein Worker, der eine ratenbegrenzte externe API aufruft, verursachen unterschiedliche Wiederherstellungskosten.

systemd 254 führte RestartSteps= und RestartMaxDelaySec= ein. Damit kann die Verzögerung über mehrere Neustartversuche hinweg erhöht werden. Der geprüfte Debian-Host verwendet systemd 257.13, sodass diese Direktiven dort verfügbar sind. Leser sollten ihre eigene Version prüfen:

systemctl --version

Eine begrenzte, zunehmende Verzögerung kann folgendermaßen geschrieben werden:

[Service]
Restart=on-failure
RestartSec=10s
RestartSteps=4
RestartMaxDelaySec=2min

Die genaue Folge berechnet systemd zwischen der anfänglichen und der maximalen Verzögerung. Der entscheidende betriebliche Punkt ist, dass die Versuche seltener werden und die Zunahme beim konfigurierten Maximum endet. Unter Versionen vor 254 sollten die beiden neueren Direktiven weggelassen und ein festes RestartSec= verwendet werden. Unbekannte Direktiven sollten nicht eingefügt und anschließend als akzeptiert betrachtet werden: systemd kann eine unbekannte Einstellung protokollieren und ignorieren.

Eine Verzögerung allein begrenzt die Anzahl der Starts nicht. Dafür sind StartLimitIntervalSec= und StartLimitBurst= im Abschnitt [Unit] zuständig:

[Unit]
StartLimitIntervalSec=10min
StartLimitBurst=5

Das Handbuch zu systemd.unit definiert dies als Begrenzung der Startrate: Mehr als die zulässige Anzahl von Starts innerhalb des Intervalls wird abgelehnt. Das gilt für alle Starts, einschließlich manueller Starts, und nicht nur für solche, die durch Restart= ausgelöst wurden. Fehlgeschlagene Unit-Bedingungsprüfungen werden nicht auf das Limit angerechnet.

Erreicht ein neu startender Dienst das Limit, enden die automatischen Versuche. Dies ist kein dauerhafter Schutzschalter. Nach Ablauf des Intervalls kann eine spätere manuelle, Timer- oder Socket-Aktivierung die Unit starten und das Neustartverhalten erneut aktivieren. Diese Feinheit ist wichtig, wenn die gewünschte Richtlinie lautet: „Beendet bleiben, bis ein Mensch die Wiederherstellung genehmigt.“ Die Begrenzung der Startrate allein bildet diesen Ablauf nicht ab.

Ein Drop-in verwenden, statt eine paketierte Unit zu bearbeiten

Wenn ein Paket die Hauptdatei einer Unit unter /usr/lib/systemd/system/ besitzt, kann eine direkte Bearbeitung lokale Richtlinien bei Upgrades leicht verschwinden lassen oder unübersichtlich machen. systemd unterstützt Drop-in-Dateien, die nach der Haupt-Unit zusammengeführt werden. Das systemctl-Handbuch dokumentiert systemctl edit als Schnittstelle zum Erstellen einer Überschreibung.

sudo systemctl edit example-worker.service

Für einen generischen, dauerhaft laufenden Worker könnte das resultierende Drop-in Folgendes enthalten:

[Unit]
StartLimitIntervalSec=10min
StartLimitBurst=5

[Service]
Restart=on-failure
RestartSec=10s
RestartSteps=4
RestartMaxDelaySec=2min

example-worker.service ist ein Platzhalter und kein auf diesem Server beobachteter Dienst. Er muss durch den tatsächlichen Namen der Unit ersetzt werden. Vor der Wahl der Werte sollte außerdem die Dokumentation der Anwendung gelesen werden. Zunächst sollten auch die bestehenden Einstellungen geprüft werden:

systemctl cat example-worker.service
systemctl show example-worker.service \
  --property=Restart,RestartUSec,NRestarts,StartLimitIntervalUSec,StartLimitBurst

systemctl cat zeigt die Hauptdatei und die geladenen Drop-ins auf dem Datenträger. systemctl show stellt normalisierte Eigenschaften und den Laufzeitstatus dar. Zeiteigenschaften verwenden häufig das Suffix USec, auch wenn die Direktive in der Unit-Datei auf Sec endet.

Vor einer Änderung an einem laufenden Dienst prüfen

Eine ordentlich aussehende Konfiguration kann weiterhin eine falsch geschriebene Direktive, einen falschen Abschnitt oder einen ungültigen Befehl enthalten. Laut dem Handbuch zu systemd-analyze kann verify unbekannte Direktiven, fehlende erforderliche Abhängigkeiten, fehlende referenzierte Handbuchseiten sowie nicht vorhandene oder nicht ausführbare Befehle erkennen.

systemd-analyze verify /path/to/example-worker.service

Das vollständige generische Beispiel für diesen Artikel wurde mit systemd 257.13 und einer vorhandenen ausführbaren Datei geprüft. Dies validiert die Syntax der Unit auf diesem Host. Es beweist nicht, dass eine reale Anwendung startet, bereit wird, Anfragen korrekt verarbeitet oder sich sicher erholt.

Nach dem Prüfen und Speichern eines echten Drop-ins lädt systemctl edit die Manager-Konfiguration neu, wenn der Editor erfolgreich beendet wird. Vor einem Neustart sollte das zusammengeführte Ergebnis erneut geprüft werden. Der Neustart sollte anschließend für einen Zeitpunkt geplant werden, an dem eine Unterbrechung vertretbar ist:

sudo systemctl restart example-worker.service
systemctl status example-worker.service
journalctl --unit=example-worker.service --since today

Ein Neustart ist selbst ein Stopp mit anschließendem Start und kann daher aktive Arbeit unterbrechen. Bei einer Datenbank, einem Queue-Worker oder einem Upload-Prozessor sind das Verhalten der Anwendung beim Herunterfahren und Fortsetzen wichtiger als die Eleganz der Unit-Datei.

Wenn die Wiederherstellung endet, die Beweise erhalten

Wenn die Unit den Zustand failed erreicht oder meldet, dass das Startlimit erreicht wurde, sollte die Untersuchung mit Status und Journal beginnen, statt den Zustand sofort zu löschen:

systemctl status example-worker.service
journalctl --unit=example-worker.service --since today

systemctl status ist eine für Menschen lesbare Ansicht des aktuellen oder kürzlich erfassten Laufzeitstatus mit einem kleinen Protokollauszug. Es ist kein vollständiger Verlauf. Das Journal kann ältere Versuche anzeigen, sofern diese Einträge aufbewahrt wurden.

Nachdem die zugrunde liegende Ursache gefunden und behoben wurde, löscht der folgende Befehl den Fehlerzustand sowie die Start- und Neustartzähler der Unit:

sudo systemctl reset-failed example-worker.service
sudo systemctl start example-worker.service

reset-failed korrigiert keine Konfiguration, stellt keine Zugangsdaten wieder her, schafft keinen Speicherplatz und repariert keine Anwendungsdaten. Es löscht nur den von systemd aufgezeichneten Zustand und die Zähler. Werden diese Informationen vor dem Sammeln von Beweisen gelöscht, kann die Abfolge der Ereignisse schwerer nachvollziehbar werden.

Was ein automatischer Neustart nicht garantieren kann

Ein Prozess-Supervisor sieht den Prozesszustand und die Signale, die eine Anwendung bereitstellt. Er kann allein mehrere größere Fragen nicht beantworten:

  • Liefert der Dienst korrekte Antworten, statt lediglich Verbindungen anzunehmen?
  • Ist eine benötigte Datenbank oder externe API bereit?
  • Hat der vorherige Prozess einen nur teilweise abgeschlossenen Auftrag hinterlassen?
  • Erhöhen wiederholte Starts die Last auf einer fehlerhaften Abhängigkeit?
  • Wird jemand benachrichtigt, nachdem die automatische Wiederherstellung beendet wurde?

Aus diesen Gründen sollte eine Neustartrichtlinie mit Beobachtbarkeit und Anwendungsdesign verbunden werden. Eine Zustandsprüfung kann die Perspektive des Benutzers einnehmen. Strukturierte Protokolle können die Fehlerursache bewahren. Eine idempotente Auftragsverarbeitung kann Schäden durch unterbrochene Arbeit reduzieren. Eine Warnung kann eine begrenzte Wiederholungsrichtlinie handlungsfähig machen, statt sie still scheitern zu lassen.

Es gibt außerdem ein Gegenargument zu aggressiver Wiederherstellung: Bei einem nicht wesentlichen Home-Server-Dienst kann es sicherer sein, beendet zu bleiben, als wiederholt auf Speicher, eine externe API oder beschädigte Zustände zuzugreifen. Verfügbarkeit ist nicht das einzige Ziel. Ressourcenverbrauch, Datenintegrität und Diagnostizierbarkeit verdienen eine ausdrückliche Gewichtung.

Eine praktische Prüfliste

  • Sicherstellen, dass die Unit einen dauerhaft laufenden Prozess und keine erfolgreich abgeschlossene One-Shot-Aufgabe darstellt.
  • Die Dokumentation der Anwendung zu Exit-Codes und Herunterfahren lesen.
  • on-failure, always oder einen engeren Modus bewusst wählen.
  • Eine ausdrückliche Neustartverzögerung festlegen, die zum Dienst und seinen Abhängigkeiten passt.
  • Eine Begrenzung der Startrate hinzufügen und berücksichtigen, dass auch manuelle Starts gezählt werden.
  • Vor der Verwendung neuerer Direktiven die installierte systemd-Version prüfen.
  • Ein Drop-in verwenden, die zusammengeführte Unit prüfen und systemd-analyze verify ausführen.
  • Festlegen, wie eine beendete Wiederherstellungsschleife für einen Menschen sichtbar wird.
  • Nur in einem sicheren Zeitfenster und mit einem Dienst testen, dessen Fehlerverhalten verstanden wird.

Fazit

Eine sinnvolle Neustartrichtlinie ist nicht diejenige, die unbegrenzt weiter versucht. Sie klassifiziert erwartete Fehler, lässt genügend Zeit zwischen den Versuchen, endet, bevor die Schleife selbst zum Vorfall wird, und bewahrt eine Spur für die Diagnose.

Für viele dauerhaft laufende Dienste bilden Restart=on-failure, ein ausdrückliches RestartSec= und eine überlegte Begrenzung der Startrate eine vertretbare Grundlage. Es bleibt jedoch nur eine Grundlage. Die richtigen Werte hängen davon ab, was die Anwendung beim Start tut, was bei einer Unterbrechung beschädigt werden kann und wie ein Betreiber erfährt, dass die Wiederherstellung nicht funktioniert hat.

References