systemd-Ressourcensteuerung für einen Homeserver - Einem Dienst eine Obergrenze geben, ohne sie Isolation zu nennen
Ein Homeserver verlangt oft von einem kleinen Rechner, mehrere Aufgaben gleichzeitig zu erledigen. Eine Webanwendung, eine Datenbank, ein Thumbnail-Generator und ein Hintergrunddienst können sich an einem gewöhnlichen Tag unauffällig verhalten. Die schwierige Frage stellt sich, wenn einer davon aus der Reihe fällt: Wie viele Ressourcen darf ein einzelner Dienst verbrauchen, bevor das Betriebssystem ihn begrenzt?
Für Dienste, die von systemd verwaltet werden, bieten Resource Controls eine praktische Antwort. Sie verwenden Linux Control Groups, kurz cgroups, um einen Dienst als Prozessgruppe zu erfassen und zu steuern. Dadurch kann etwas Spielraum für SSH, die Datenbank oder andere wichtige Aufgaben erhalten bleiben. Das beweist jedoch nicht, dass der begrenzte Dienst sicher, gesund oder schnell ist. Ein Limit ist eine Grenze für den Ressourcenverbrauch, keine Sicherheits-Sandbox und kein Heilmittel gegen ein Speicherleck.
Ein hilfreiches Denkmodell: Haushaltsbudget statt eigener Rechner
Linux cgroups ordnen Prozesse in einer Hierarchie. Limits und Accounting können für eine Gruppe und ihre Nachkommen gelten, und eine übergeordnete Gruppe kann eine strengere Grenze setzen, als eine untergeordnete Gruppe anfordert. systemd bildet Units und Slices auf diese Hierarchie ab und stellt Ressourceneinstellungen in Unit-Dateien bereit. Die Dokumentation zu systemd Resource Control beschreibt diesen Zusammenhang direkt.
Dabei sollten zwei Arten der Steuerung getrennt werden:
- Weights verteilen umkämpfte Kapazität. Ein Dienst mit einem niedrigeren
CPUWeight=erhält einen kleineren Anteil als seine Geschwister, wenn alle CPU-Zeit benötigen. Ein Weight ist weder eine Reservierung noch eine harte Obergrenze. Wenn niemand sonst die CPU benötigt, kann der Dienst weiterhin verfügbare Kapazität nutzen. - Obergrenzen stoppen die Nutzung an einer Grenze.
CPUQuota=,MemoryMax=undTasksMax=setzen unterschiedliche Arten von Maximalwerten. Eine Obergrenze kann den übrigen Host schützen, aber die begrenzte Workload kann langsamer werden oder scheitern, wenn sie diese Grenze erreicht.
Diese Unterscheidung verhindert einen häufigen Konfigurationsfehler. CPUWeight=50 bedeutet nicht „50 Prozent CPU“. Umgekehrt bedeutet CPUQuota=50% nicht „die Hälfte jeder CPU im Rechner“. systemd definiert eine Quote von 100% relativ zur Kapazität einer CPU; Werte über 100% können CPU-Zeit auf mehr als einer CPU erlauben.
Erst messen, dann einen Wert wählen
Es gibt kein verantwortbares universelles Speicherlimit für „einen PHP-Worker“ oder „einen Bilddienst“. Workloads haben unterschiedliche Normalzustände und legitime Lastspitzen. Der Host benötigt außerdem Platz für den Kernel, den Dateisystem-Cache, Anmeldesitzungen und Dienste außerhalb der gerade abgestimmten Unit. Beobachte zunächst normale und bewusst anspruchsvolle Abläufe und frage dann, welche Verschlechterung akzeptabel ist.
Diese Befehle zeigen einen vorhandenen Dienst aus unterschiedlichen Perspektiven:
systemctl status example-worker.service
systemctl show example-worker.service \
-p MemoryCurrent -p MemoryPeak -p MemoryHigh -p MemoryMax \
-p CPUUsageNSec -p CPUQuotaPerSecUSec \
-p TasksCurrent -p TasksMax
systemd-cgtop
journalctl -u example-worker.service --since today
systemctl show ist für die Abfrage normalisierter Properties und des Laufzeitstatus vorgesehen. systemd-cgtop bietet eine Live-Ansicht der Control Groups, wobei die Speicher- und I/O-Spalten unvollständig sein können, wenn das entsprechende Accounting nicht aktiviert ist. Metriken auf Anwendungsebene bleiben wichtig: Ein niedriger Speichergraph zeigt nicht, ob Requests in Timeouts laufen oder nützliche Arbeit abgeschlossen wird.
Eine kurze Stichprobe reicht für einen saisonalen Job, ein Backup, einen Import oder einen Thumbnail-Batch nicht aus. Erfasse repräsentative Spitzen über längere Zeit und lasse Spielraum für Schwankungen. Entscheide vor allem im Voraus, ob Drosselung, abgewiesene Arbeit oder ein gestoppter Dienst dem Druck auf den gesamten Host vorzuziehen ist.
Ein konservatives Drop-in-Beispiel
Angenommen, Beobachtungen zeigen, dass ein beispielhafter Worker gewöhnlich unter 512 MiB bleibt, gelegentlich mehr benötigt und Speicher nie ohne harte Grenze verbrauchen soll. Ein Drop-in könnte so aussehen:
[Service]
MemoryHigh=512M
MemoryMax=768M
MemorySwapMax=256M
CPUWeight=50
CPUQuota=100%
TasksMax=200
Diese Werte sind Beispiele, keine Empfehlungen. Sie ohne Messung der tatsächlichen Workload zu übernehmen, würde nur eine Vermutung durch eine andere ersetzen.
Erstelle ein Override mit:
sudo systemctl edit example-worker.service
Standardmäßig erstellt systemctl edit ein Drop-in, anstatt die Unit des Distributors zu ersetzen, und lädt nach einer erfolgreichen Bearbeitung die Manager-Konfiguration neu. So bleibt die lokale Richtlinie von paketverwalteten Dateien getrennt. Prüfe das zusammengeführte Ergebnis, bevor etwas neu gestartet wird:
systemctl cat example-worker.service
systemctl show example-worker.service \
-p MemoryHigh -p MemoryMax -p MemorySwapMax \
-p CPUWeight -p CPUQuotaPerSecUSec -p TasksMax
Für eine als Code gepflegte Unit-Datei sollte vor der Installation systemd-analyze verify auf die Datei angewendet werden. Das Werkzeug kann unbekannte Direktiven, ungültige Abschnitte und einige fehlende Abhängigkeiten oder Befehle erkennen. Es kann nicht beurteilen, ob 768 MiB für einen produktiven Import ausreichen.
systemd-analyze verify ./example-worker.service
Führe einen Neustart nur in einem Zeitraum aus, in dem eine Unterbrechung vertretbar ist:
sudo systemctl restart example-worker.service
systemctl status example-worker.service
journalctl -u example-worker.service --since "5 minutes ago"
Welche Abwägung hinter jeder Einstellung steckt
MemoryHigh=: Druck vor dem Ausfall
MemoryHigh= ist ein Schwellenwert für die Drosselung. Laut Dokumentation darf die Nutzung ihn überschreiten, wenn dies unvermeidbar ist; dabei wird Speicher aggressiv zurückgewonnen und die Prozesse werden stark verlangsamt. systemd empfiehlt diese Einstellung als Hauptmechanismus zur Steuerung des Speicherverbrauchs einer Unit. Damit eignet sie sich als frühe Druckzone, aber nicht dort, wo eine absolute Grenze erforderlich ist.
MemoryMax=: die letzte, nicht die erste Verteidigungslinie
MemoryMax= ist das absolute Speicherlimit. Wenn der Kernel die Unit nicht darunter halten kann, wird die Out-of-Memory-Behandlung innerhalb der Unit ausgelöst. Das kann den übrigen Host schützen, während Arbeit in der Unit fehlschlägt. Die Dokumentation empfiehlt deshalb MemoryHigh= als primäre Steuerung und MemoryMax= als letzte Verteidigungslinie.
Der Abstand zwischen beiden Werten ist beabsichtigt: Die Workload erhält vor der harten Grenze eine Druckzone. Wie groß dieser Abstand sein sollte, bleibt eine Entscheidung für die konkrete Workload und ist keine von systemd gelieferte Formel.
MemorySwapMax=: separater Swap-Spielraum
MemorySwapMax= begrenzt die Swap-Nutzung der Unit separat. Ein kleinerer Wert kann verhindern, dass ein Dienst den Swap füllt und den Host quälend träge macht. Er nimmt zugleich einen Ausweg aus dem Speicherdruck, sodass der Dienst früher ausfallen kann. „Kein Swap“ ist nicht automatisch sicherer; es verändert die Abwägung.
CPUWeight= und CPUQuota=: Präferenz gegenüber Obergrenze
Ein Weight eignet sich, wenn relative Fairness bei konkurrierender Last das Ziel ist. Eine Quote eignet sich, wenn ein Dienst auch bei einem ansonsten untätigen Rechner eine CPU-Zeit-Obergrenze nicht überschreiten darf. Eine harte Quote kann Latenz erzeugen und den Durchsatz verringern, insbesondere bei stoßweiser Arbeit. Beide Einstellungen zu kombinieren kann sinnvoll sein, doch jede sollte eine eigene Richtlinienfrage beantworten.
TasksMax=: Schutz vor unbegrenztem Fan-out
TasksMax= begrenzt Tasks in der Unit über den cgroup-PIDs-Controller. Dabei zählen Threads einzeln. Eine Multithread-Runtime kann sich dem Limit daher mit deutlich weniger sichtbaren Prozessen nähern, als der Wert vermuten lässt. Leite den Wert aus der beobachteten Parallelität mit bewusstem Spielraum ab, nicht allein aus einer Prozessanzahl.
Das effektive Verhalten beobachten, nicht nur die Datei
Ein syntaktisch korrektes Drop-in ist nur der Anfang. Prüfe nach der Aktivierung die effektiven Properties, führe repräsentative Abläufe aus und beobachte Logs sowie Ergebnisse auf Dienstebene. Das cgroups-Handbuch erklärt, warum die Hierarchie wichtig ist: Weiter oben im Baum gesetzte Limits begrenzen die Nachkommen. Eine Unit kann deshalb weniger erhalten, als ihre lokale Konfiguration zu erlauben scheint.
Achte auf mehrere Fehlersignale:
- Requests oder Jobs benötigen nach CPU-Drosselung mehr Zeit;
- Speicherdruck verursacht wiederholtes Reclaim, OOM-Ereignisse oder Neustarts;
- das Erzeugen von Threads oder Prozessen scheitert nahe
TasksMax=; - eine Restart Policy verwandelt einen Ressourcenfehler in eine Neustartschleife;
- ein anderer Dienst oder ein übergeordneter Slice setzt bereits ein strengeres Limit.
Ändere nach Möglichkeit jeweils nur eine Richtlinie. Wenn sich der Dienst verschlechtert, erweitere oder entferne die betreffende Grenze und untersuche den zugrunde liegenden Bedarf. Das Ziel ist nicht ein ordentlicher Graph. Es geht darum zu entscheiden, welche Komponente den Druck aufnehmen soll, wenn der Rechner nur endliche Kapazität hat.
Was diese Controls nicht leisten
Resource Limits werden manchmal als „Eindämmung“ eines Dienstes beschrieben, doch dieses Wort kann mehrere verschiedene Konzepte verdecken. CPU- und Speichersteuerungen begrenzen nicht, welche Dateien ein Prozess lesen, welche Netzwerkziele er erreichen oder welche System Calls er ausführen darf. Das sind getrennte Fragen der Sicherheit und des Sandboxing. Ein kompromittierter Prozess kann weiterhin gefährlich sein, während er unter 512 MiB bleibt.
Limits ersetzen außerdem weder Kapazitätsplanung noch Fehlerbehebungen in der Anwendung. Wenn gewöhnlicher Traffic tatsächlich mehr Speicher benötigt, als der Host bereitstellen kann, wählt eine engere Obergrenze lediglich einen lokaleren Ausfall. Auf einem gemeinsam genutzten Homeserver kann das dennoch die richtige betriebliche Entscheidung sein, sie sollte aber ehrlich beschrieben werden.
Eine vorsichtige Checkliste für den Einstieg
- Prüfe die systemd-Version und ob der Rechner cgroup v2 verwendet; nutze die Dokumentation der installierten Distribution.
- Beobachte einen repräsentativen Normalzustand und Lastspitzen, bevor Werte gewählt werden.
- Entscheide, ob jede Einstellung eine relative Priorität, sanften Druck oder eine harte Obergrenze ausdrückt.
- Verwende ein Drop-in, statt eine Unit des Distributors direkt zu bearbeiten.
- Validiere die Syntax, prüfe die zusammengeführte Unit und plane jeden Neustart bewusst.
- Teste nützliche Arbeit und nicht nur, ob der Prozess „active“ bleibt.
- Beobachte Logs und effektive Properties und passe die Limits an, wenn sich die Belege ändern.
Auf einem kleinen Server kann eine Ressourcengrenze eine hilfreiche Form der Bescheidenheit sein: Kein einzelner Dienst erhält selbstverständlich den ganzen Rechner. Die schwierige Aufgabe besteht nicht darin, MemoryMax= zu schreiben. Sie besteht darin, anhand realer Beobachtungen eine Ausfallgrenze zu wählen und anschließend zu prüfen, ob der Schutz für den Host die Kosten für den Dienst wert ist.
References
- systemd.resource-control — Resource control unit settings, systemd project.
- systemctl — Control the systemd system and service manager, systemd project.
- systemd-analyze — Analyze and debug system manager, systemd project.
- systemd-cgtop — Show top control groups by their resource usage, systemd project.
- cgroups(7) — Linux manual page, Linux man-pages project.
