SSD Nodes Learn Hosting plans →
Anleitungen Matt ConnorVon Matt Connor

systemd: Speicher und CPU für Prozesse begrenzen

Ein Prozesslimit kann Ihren VPS trotzdem ausbremsen. Setzen Sie MemoryHigh, MemoryMax, CPUQuota und TasksMax per Drop-in und prüfen Sie den OOM-Kill.

Prozessspeicher und CPU-Zeit mit einem systemd-Drop-in begrenzen

Sie begrenzen den Speicher und die CPU-Zeit eines Prozesses auf einem Linux-VPS, indem Sie der Unit, die den Prozess ausführt, einige Zeilen hinzufügen. MemoryMax= ist die harte Speicherobergrenze. CPUQuota= ist die Obergrenze für die Prozessorzeit. Beide Grenzen werden durch cgroup v2 (Control Groups, Version 2) erzwungen. Dabei handelt es sich um eine Kernel-Funktion, die systemd bereits verwendet, um jeden Dienst auf dem Server zu erfassen.

sudo systemctl edit myapp.service

Damit öffnen Sie eine Drop-in-Datei mit Anweisungen in Kommentaren. Fügen Sie die folgenden Zeilen oberhalb dieser Kommentare ein:

[Service]
MemoryHigh=512M
MemoryMax=768M
MemorySwapMax=0
CPUQuota=80%
TasksMax=128
sudo systemctl daemon-reload
sudo systemctl restart myapp.service
systemctl show myapp.service -p MemoryHigh -p MemoryMax -p CPUQuotaPerSecUSec -p TasksMax

systemctl show muss Ihre Werte in den Einheiten des Kernels ausgeben: MemoryMax=805306368 und CPUQuotaPerSecUSec=800ms. Wenn MemoryMax=infinity ausgegeben wird, wurde das Drop-in nicht geladen. Prüfen Sie, ob die Datei unter /etc/systemd/system/myapp.service.d/override.conf angelegt wurde und mit dem Header [Service] beginnt. Eine Einstellung ohne vorherigen Abschnitt veranlasst systemd, Assignment outside of section. Ignoring. zu protokollieren und den Dienst vollständig ohne Limits zu starten.

Im restlichen Verlauf dieses Leitfadens erfahren Sie, wie Sie diese Werte auswählen und welche Probleme nach dem Setzen der Limits weiterhin auftreten können.

Warum ein außer Kontrolle geratener Prozess einen VPS einfriert, obwohl der Speicher nicht voll ist

Ein Prozess, der ein hartes Speicherlimit erreicht, wird nach etwa einer Sekunde beendet, und der Dienst wird neu gestartet. Das ist der gute Fall. Der schlechte Fall ist der, in dem nichts beendet wird: Der Server antwortet auf Ping, SSH akzeptiert die Verbindung, aber die Shell-Eingabeaufforderung erscheint nicht. Die Maschine läuft und ist ausgelastet, aber keine dieser Aktivitäten ist nützlich.

Der Mechanismus ist nicht offensichtlich. Wenn der freie Speicher knapp wird, gibt der Kernel Seiten frei, anstatt neue Seiten zuzuweisen. Am einfachsten lassen sich dateigestützte Seiten freigeben, und der Page Cache enthält den ausführbaren Code aller laufenden Prozesse. Der Kernel entfernt also die Textseiten von sshd, und die nächste auszuführende Anweisung sshd verursacht einen Page Fault, der diese Bytes wieder aus dem Speicher einlesen muss. Jeder Prozess wartet schließlich auf den Datenträger, statt auszuführen. Dieselben Seiten werden in einer Schleife entfernt und wieder geladen. Dieser Zustand heißt Thrashing.

Zwei Faktoren verschärfen das Problem auf einem VPS gegenüber einem Laptop. Der Speicher ist häufig über das Netzwerk angebunden oder wird gemeinsam genutzt. Dadurch kostet jeder Page Fault mehr Millisekunden als auf einem lokalen NVMe-Gerät. Außerdem misst der Kernel nicht die benötigte Zeit, sondern den Erfolg: Solange das Freigeben von Seiten weiterhin eine Seite zurückgibt, unabhängig davon, wie langsam, geht der Kernel von Fortschritt aus und ruft den Out-of-Memory-Killer (OOM-Killer) nicht auf. Ein Server kann viele Minuten in diesem Zustand bleiben, bevor ein Prozess beendet wird.

Sie können diesen Vorgang beobachten. Der Kernel stellt unter Linux 4.20 und neuer Pressure Stall Information (PSI) bereit:

cat /proc/pressure/memory
cat /proc/pressure/io
some avg10=63.72 avg60=41.02 avg300=12.33 total=13729481
full avg10=48.15 avg60=30.44 avg300=8.90 total=9114233

Die Zeile full ist entscheidend. full avg10=48.15 bedeutet, dass während der letzten zehn Sekunden 48 % der Zeit jede ausführungsbereite Aufgabe auf dem Server wegen Speicheraktivitäten blockiert war. Es wurde also nichts ausgeführt. Auf einem gesunden Server liegt full nahe bei null. Über 10 wirkt der Server für Menschen langsam. Ein Wert von 40 oder mehr entspricht dem Zustand, den Benutzer als eingefroren beschreiben.

Deshalb ist ein Limit allein keine Garantie. Eine Unit unter MemoryHigh= wird gedrosselt, statt beendet zu werden. Sie bleibt also aktiv und langsam. Nichts startet sie neu, weil sie aus Sicht von systemd nie fehlgeschlagen ist. Eine begrenzte Unit, die weiterhin auslagern darf, erzeugt Lese- und Schreibzugriffe. Diese werden der Unit zugerechnet, aber von einem gemeinsam genutzten Gerät bedient. Dadurch kann sie /proc/pressure/io für jeden anderen Dienst auf dem Server erhöhen. Limits legen fest, wer für einen Engpass bezahlt. Sie schaffen jedoch keine zusätzliche Kapazität.

Prüfen, ob Ihr VPS cgroup v2 verwendet

stat -fc %T /sys/fs/cgroup

cgroup2fs ist die einheitliche Hierarchie, die alle folgenden Einstellungen benötigen. tmpfs bedeutet, dass der Server mit dem älteren v1-Layout gestartet wurde. Dort sind MemoryHigh= und MemorySwapMax= nicht vorhanden, und das OOM-Verhalten pro Unit ist anders. Ubuntu 22.04 und höher sowie Debian 11 und höher verwenden standardmäßig v2. Ein altes Image oder ein mit systemd.unified_cgroup_hierarchy=0 gestarteter Kernel verwendet es nicht.

Unter cgroup v2 aktiviert systemd standardmäßig die Speicherbuchhaltung für jede Unit. Die Werte sind daher bereits verfügbar:

systemd-cgtop -m

Damit werden die cgroups nach Speichernutzung sortiert aufgelistet. So lässt sich am schnellsten feststellen, „was diesen Server überlastet“, solange er noch antwortet. Wenn der Server neu ist, haben die Konto- und Firewall-Einstellungen unter den ersten zehn Minuten auf einem neuen VPS Vorrang vor diesem Schritt.

MemoryHigh drosselt. MemoryMax beendet Prozesse.

Der Unterschied zwischen den beiden Speichereinstellungen bestimmt, wie sich ein Fehler auswirkt.

  • MemoryHigh= ist eine weiche Obergrenze. Oberhalb dieses Werts fordert der Kernel aus dieser cgroup aggressiv Speicher zurück und verlangsamt absichtlich weitere Speicherzuweisungen. Die Nutzung kann den Wert weiterhin überschreiten, und es werden keine Prozesse beendet.
  • MemoryMax= ist eine harte Obergrenze. Wenn eine Speicherzuweisung unter dieser Grenze nicht erfüllt werden kann, wird der OOM-Killer innerhalb dieser cgroup ausgeführt und beendet einen eigenen Prozess dieser Unit.

Das ist der eigentliche Grund, MemoryMax= für alles zu setzen, dem Sie nicht vollständig vertrauen. Ohne Obergrenze betrifft ein Speichermangel das gesamte System, und der globale OOM-Killer wählt sein Opfer anhand von oom_score aus. Das bedeutet meistens: den größten Prozess. Der größte Prozess ist normalerweise Ihre Datenbank und nicht das Skript, das den Speicherverlust verursacht hat. Mit einer Obergrenze findet der Abbruch innerhalb der Unit statt, die den Speichermangel verursacht hat.

Setzen Sie beide Werte, wobei MemoryHigh= etwa 20 bis 30 Prozent unter MemoryMax= liegen sollte. Der Abstand ist ein Warnbereich: Ein langsamer Speicherverlust überschreitet High und zeigt sich als Dienst, der langsamer geworden ist. Ein plötzlicher Spitzenverbrauch überschreitet Max direkt, woraufhin der Prozess beendet wird.

Prozentwerte beziehen sich auf den installierten physischen Speicher. MemoryMax=25% entspricht bei einem Tarif mit 4 GB also 1 GB und bleibt auch nach einer Vergrößerung des Tarifs bei einem Viertel des gesamten Speichers. MemorySwapMax=0 hält diese Unit vollständig aus dem Swap heraus. Dadurch wird aus einem langen Einbruch der Leistung ein schneller, eindeutiger Abbruch.

Bei einigen Diensten können Sie den Speicherbedarf im Voraus festlegen, statt ihn zu messen. Eine Ollama-Unit dimensioniert ihren KV-Cache anhand des von Ihnen vorgegebenen Kontextfensters. Lesen Sie daher welche RAM-Kosten eine Erhöhung von num_ctx verursacht, bevor Sie dafür eine Obergrenze festlegen.

Neben einer Obergrenze benötigen Sie eine Restart-Richtlinie. Andernfalls bleibt nach dem Abbruch lediglich ein gestoppter Dienst zurück.

[Unit]
StartLimitIntervalSec=300
StartLimitBurst=5

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

StartLimit* gehört in [Unit] und Restart= in [Service]. Wenn Sie einen der beiden Werte im falschen Abschnitt eintragen, ignoriert systemd ihn. Fünf Neustarts innerhalb von fünf Minuten sind eher ein Speicherverlust als eine kurzfristige Störung. Danach gibt systemd auf und belässt die Unit im Zustand failed. Diesen Zustand können Sie später gezielt finden, statt einen Crash-Loop vorzufinden, der das Problem verdeckt.

CPU mit CPUQuota begrenzen oder mit CPUWeight anteilig zuweisen

CPUQuota= belegt einen prozentualen Anteil der auf einer CPU verfügbaren Zeit. CPUQuota=50% entspricht der Hälfte eines CPU-Kerns. CPUQuota=200% entspricht zwei Kernen, die die Unit nach Bedarf auf beliebig viele Threads verteilen kann. Bei einem Tarif mit 2 vCPUs entspricht CPUQuota=200% der gesamten Maschine.

CPUWeight= ist für die meisten Dienste die bessere Standardeinstellung. Der Wert legt einen relativen Anteil von 1 bis 10000 fest. Der Kernel-Standardwert ist 100. Die Einstellung wirkt sich nur aus, wenn Prozesse um CPU-Zeit konkurrieren: Ein Sicherungsjob mit CPUWeight=20 wird unter Last gegenüber einem Webserver mit 100 nachrangig behandelt. Wenn die Maschine nicht ausgelastet ist, kann der Sicherungsjob weiterhin die gesamte CPU-Kapazität nutzen. Eine harte Quote würde diese ungenutzte Kapazität verwerfen.

Seien Sie realistisch, was eine CPU-Begrenzung bewirkt. Ein CPU-intensiver Prozess bringt Linux nur selten zum Stillstand, weil der Scheduler die CPU-Zeit weiterhin auf alle Prozesse verteilt. Arbeitsspeicher bringt eine Maschine hingegen zum Absturz. Verwenden Sie CPUQuota=, wenn Sie eine vorhersehbare Obergrenze benötigen, beispielsweise für einen Build oder einen Agenten, der sonst eine Stunde lang die CPU vollständig auslasten würde. Die Dimensionierung solcher Workloads ist eine eigene Frage. Sie wird in wie viel RAM und CPU ein Coding-Agent-VPS benötigt behandelt.

Wenn die CPU als ausgelastet angezeigt wird, obwohl keiner Ihrer Prozesse nennenswert CPU-Zeit verbraucht, kann die Ursache auf der anderen Seite des Hypervisors liegen. Das ist CPU-Steal-Time durch einen ausgelasteten Nachbarn. Keine von Ihnen gesetzte Quote kann daran etwas ändern.

TasksMax verhindert eine Fork-Schleife

TasksMax= ist die maximale Anzahl von Prozessen und Threads, die eine Unit enthalten darf. Threads werden mitgezählt. Ein Java- oder Go-Dienst benötigt daher mehr Spielraum, als die Prozessliste vermuten lässt. Dies ist der einfachste Schutz gegen ein Skript, das in einer Schleife neue Prozesse erzeugt. Der Fork-Aufruf schlägt dann innerhalb der Unit fehl, bevor dem System die Prozess-IDs ausgehen.

TasksMax=128

Wenn eine Unit das Limit erreicht, schreibt der Kernel eine Zeile mit dem Namen der Cgroup ins Log:

cgroup: fork rejected by pids controller in /system.slice/myapp.service

Das Programm selbst meldet in der Regel fork: retry: Resource temporarily unavailable. Prüfen Sie mit systemctl show -p DefaultTasksMax, welche Vorgabe der Manager verwendet.

Ein einmaliges Job mit systemd-run begrenzen

Dafür benötigen Sie keine Unit-Datei. systemd-run erstellt für einen einzelnen Befehl eine temporäre Unit.

sudo systemd-run --scope -p MemoryMax=1G -p MemorySwapMax=0 -p CPUQuota=50% -p TasksMax=64 ./import-data.sh

--scope führt den Befehl in Ihrem Terminal aus, nachdem Running scope as unit: run-r7c1a....scope ausgegeben wurde. Die Ausgabe bleibt auf Ihrem Bildschirm. Die Begrenzungen werden aufgehoben, sobald der Befehl beendet wird. Jede Eigenschaft aus systemd.resource-control funktioniert nach -p.

Für einen längeren Job lassen Sie --scope weg und vergeben einen Namen. Der Job läuft dann im Hintergrund als temporärer Dienst. Die Protokolle werden in das Journal geschrieben:

sudo systemd-run --unit=nightly-import -p MemoryMax=1G -p CPUWeight=20 ./import-data.sh
journalctl -u nightly-import -f

Dieselben Optionen funktionieren mit --user, wenn Sie nicht als root arbeiten. Der Benutzer-Manager verfügt jedoch nur über die Controller, die an ihn delegiert wurden. Daher kann eine Eigenschaft dort abgelehnt werden. Verwenden Sie in diesem Fall sudo. Wenn ein Job dauerhaft eingerichtet werden soll, übernehmen Sie die Einstellungen unverändert in eine echte Unit: siehe ein Skript als systemd-Dienst und Timer ausführen.

Die Swap-Frage, ehrlich beantwortet

Swap verhindert den Fehler nicht, sondern verändert seinen Verlauf.

Ohne Swap erreicht ein Speicherleck die Obergrenze, und innerhalb von Sekunden fällt etwas aus. Der Ausfall ist deutlich, kurz und lässt sich anschließend im Journal leicht nachvollziehen. Mit Swap schreibt der Kernel selten verwendete anonyme Pages auf die Festplatte und gewinnt dadurch Zeit. Wenn sich der Prozess stabilisiert hätte, hilft Swap. Bei einem außer Kontrolle geratenen Prozess verlängert Swap einen fünf Sekunden langen Ausfall zu einem zwanzigminütigen Stillstand. Dieser Stillstand ist schlimmer, weil ein beendeter Prozess eine funktionierende Shell hinterlässt, ein thrashender Rechner jedoch nicht.

swapon --show
free -h

Ein praktikabler Mittelweg auf einem kleinen VPS ist eine moderate Swap-Datei für Pages, die einmal zugewiesen und danach nie wieder verwendet werden. Setzen Sie außerdem MemorySwapMax=0 bei den Units, auf die Sie im Notfall verzichten können. Die wichtigen Dienste behalten ihren Swap. Unvorhersehbare Dienste erreichen schnell die Obergrenze und werden neu gestartet.

vm.swappiness zu verringern, ist ein schwacher Stellhebel. Es verschiebt lediglich das Verhältnis zwischen dem Verdrängen des Page Cache und dem Auslagern anonymer Pages. Beides verursacht später einen Lesezugriff auf die Festplatte. Dadurch ändert sich, welche Pages thrashen, nicht aber, ob der Rechner thrashend arbeitet.

Ein früher OOM-Daemon beendet Prozesse vor dem Stillstand

Der Kernel wartet, bis die Speicherfreigabe vollständig fehlschlägt. Auf einem kleinen VPS ist genau dieses Zeitfenster der Moment, in dem Sie den Rechner verlieren. Zwei Daemons im Userspace schließen dieses Zeitfenster, indem sie den Speicher selbst überwachen und Prozesse früher beenden.

earlyoom prüft bis zu zehnmal pro Sekunde den verfügbaren Speicher und den freien Swap. Er beendet den Prozess mit dem höchsten oom_score nur, wenn beide Werte unter ihre Schwellenwerte fallen. Auf einem System ohne Swap entscheidet allein der Speicherschwellenwert.

sudo apt install earlyoom
systemctl status earlyoom

Das Debian- und Ubuntu-Paket startet den Dienst bei der Installation. Seine Optionen befinden sich in /etc/default/earlyoom:

EARLYOOM_ARGS="-m 5,2 -s 5,2 --avoid ^(sshd|systemd)$ --prefer ^(node|python3)$"

-m PERCENT legt das Minimum für den verfügbaren Speicher fest, -s PERCENT das Minimum für den freien Swap. Beide Werte betragen standardmäßig 10 Prozent. Die zweite Zahl in jedem Paar ist der SIGKILL-Schwellenwert: earlyoom sendet SIGTERM, sobald Speicher und Swap unter ihre jeweils ersten Werte fallen. Anschließend sendet es SIGKILL, sobald beide Werte unter dem jeweils zweiten Wert liegen. Dieser entspricht standardmäßig der Hälfte des ersten Werts. Lassen Sie die Muster --avoid und --prefer ohne Anführungszeichen und ohne Leerzeichen, da systemd Anführungszeichen wörtlich übergibt und ein Muster mit Anführungszeichen nie übereinstimmt. Wenden Sie eine Änderung mit sudo systemctl restart earlyoom an. Lesen Sie anschließend journalctl -u earlyoom, um zu sehen, welchen Prozess der Dienst beendet hat und wie viel Speicher dieser Prozess belegt hatte.

systemd-oomd ist die andere Option. Die Manpage beschreibt sie als „einen Systemdienst, der cgroups-v2 und Pressure Stall Information (PSI) verwendet, um einen OOM-Zustand im Kernelbereich zu überwachen und vorher Gegenmaßnahmen einzuleiten“. Der Dienst arbeitet mit ganzen cgroups statt mit einzelnen Prozessen. Er beendet daher eine Unit und kein verwaistes Kind. Units werden mit ManagedOOMMemoryPressure=kill oder ManagedOOMSwap=kill aktiviert. Die Schwellenwerte befinden sich in /etc/systemd/oomd.conf.

systemctl status systemd-oomd
oomctl

oomctl gibt aus, was der Dienst derzeit überwacht. Auf einem Server-Image ist das häufig nichts, weil die Einstellung pro Unit aktiviert werden muss. Wählen Sie einen der beiden Daemons und belassen Sie es dabei. Wenn Sie beide ausführen, konkurrieren zwei Dienste darum, ein Opfer auszuwählen. Dadurch lässt sich der Grund für eine Beendigung schwerer rekonstruieren.

Welche Unit war verantwortlich?

Beginnen Sie mit dem Kernel, weil er jeden von ihm ausgelösten Kill protokolliert.

journalctl -k --grep "Killed process" --since "2 hours ago"

Ein Kill durch den globalen OOM-Killer sieht folgendermaßen aus:

Out of memory: Killed process 4127 (node) total-vm:2731084kB, anon-rss:1874232kB, file-rss:0kB, shmem-rss:0kB, UID:1000 pgtables:4212kB oom_score_adj:0

anon-rss ist der Speicher, den der Prozess beim Beenden im RAM belegt hatte, hier etwa 1.8 GB. Lesen Sie den Namen in eckigen Klammern mit Vorsicht. Er bezeichnet das Opfer, das der Kernel ausgewählt hat. Der Kernel wählt den größten Prozess aus. Das ist nicht immer der Prozess, der den Speichermangel verursacht hat.

Ein Kill aufgrund eines cgroup-Limits hat ein anderes Präfix. Der darüber ausgegebene Bericht nennt die cgroup, die ihr eigenes Limit erreicht hat:

Memory cgroup out of memory: Killed process 8811 (python3) total-vm:1044320kB, anon-rss:769112kB, file-rss:0kB, shmem-rss:0kB, UID:998 pgtables:1720kB oom_score_adj:0

Dieses Präfix liefert den größten Teil der Diagnose. Memory cgroup out of memory bedeutet, dass eine Unit das von Ihnen festgelegte MemoryMax= erreicht hat und der restliche Server ausreichend Speicher hatte. Ein einfaches Out of memory bedeutet, dass der gesamte Rechner keinen Speicher mehr hatte. Ihre Limits fehlten also entweder oder waren zusammengenommen zu großzügig.

Fragen Sie anschließend systemd, was es erkannt hat:

systemctl status myapp.service
journalctl -u myapp.service -n 50
myapp.service: A process of this unit has been killed by the OOM killer.
myapp.service: Main process exited, code=killed, status=9/KILL
myapp.service: Failed with result 'oom-kill'.

systemctl status sagt dasselbe in einer Zeile aus, und zwar als Active: failed (Result: oom-kill).

Die cgroup-Zähler sind die dritte Quelle. Sie sind die einzige Quelle, die eine Drosselung protokolliert. Eine Drosselung erzeugt überhaupt keinen Logeintrag:

cat /sys/fs/cgroup/system.slice/myapp.service/memory.events
cat /sys/fs/cgroup/system.slice/myapp.service/memory.peak
low 0
high 4213
max 118
oom 12
oom_kill 12

high zählt, wie oft die Unit über MemoryHigh= gedrückt und gedrosselt wurde. max zählt, wie oft sie das harte Limit erreicht hat. oom_kill zählt die tatsächlich beendeten Prozesse. Ein hoher Wert für high zusammen mit oom_kill 0 bezeichnet den bereits beschriebenen stillen Fall: Der Dienst läuft, wird stark verlangsamt und hat keinen Fehler gemeldet. memory.peak (Linux 5.19 und neuer) enthält die höchste Nutzung, die die cgroup erreicht hat. An diesem Wert sollten Sie MemoryMax= ausrichten. Beide Dateien werden beim Neustart der Unit zurückgesetzt, weil systemd die cgroup erneut erstellt.

Eine Voraussetzung liegt allen diesen Prüfungen zugrunde. Wenn /var/log/journal nicht vorhanden ist, befindet sich das Journal im RAM. Nach dem Reboot, den Sie zur Wiederherstellung des Servers benötigen, ist jede Zeile verschwunden.

sudo mkdir -p /var/log/journal
sudo systemd-tmpfiles --create --prefix /var/log/journal
sudo systemctl restart systemd-journald
journalctl --list-boots

Wenn journalctl --list-boots mehr als den aktuellen Boot anzeigt, bleibt die Historie nun erhalten. Dann kann journalctl -k -b -1 die Kernelmeldungen aus dem fehlgeschlagenen Boot anzeigen.

Ein Ausgangspunkt für einen kleinen VPS

Planen Sie bei einem Tarif mit 2 GB 300 bis 400 MB für den Kernel und den Page Cache ein. Lassen Sie die Limits nicht zusammen die vollen 2 GB erreichen, weil jede Unit gleichzeitig ihren Spitzenverbrauch erreichen kann. Geben Sie dem wichtigsten Dienst den größten Anteil. Begrenzen Sie anschließend alle weniger wichtigen Dienste, die ihn umgeben.

[Service]
MemoryHigh=256M
MemoryMax=384M
MemorySwapMax=0
CPUWeight=20
TasksMax=64
Restart=on-failure
RestartSec=5s

Eine Möglichkeit für den weiteren Zugriff ist eine zusätzliche Einstellung wert. OOMScoreAdjust=-500 in einem Drop-in für ssh.service macht es deutlich unwahrscheinlicher, dass der globale OOM-Killer Ihren SSH-Daemon als Opfer auswählt. Das kann den Unterschied zwischen der Reparatur des Systems und einem Neustart über das Control Panel ausmachen. Die Einstellung ändert nur die Auswahl des Prozesses durch den Kernel. Sie verkürzt die Dauer des Stillstands nicht.

Container laufen in eigenen cgroups. Diese werden von der Container-Runtime und nicht von Ihren Unit-Dateien erstellt. Daher wird ein Limit für docker.service nicht zu einem Limit für einen einzelnen Container. Die Entsprechungen für einzelne Container von MemoryMax= und CPUQuota= werden unter Speicher- und CPU-Limits in Docker Compose festlegen erläutert.

FAQ

Warum ist mein VPS eingefroren, statt den außer Kontrolle geratenen Prozess zu beenden?

Weil der Kernel den Fortschritt danach bewertet, ob durch Reclaim Speicherseiten zurückgewonnen werden, nicht danach, wie lange das dauert. Bei knappem Speicher verdrängt er den Page Cache, einschließlich der ausführbaren Seiten laufender Programme, und liest sie bei der nächsten Instruktion wieder ein. Alles wartet auf den Speicher und keine Speicheranforderung ist technisch fehlgeschlagen. Deshalb wird der OOM-Killer nie aufgerufen. Prüfen Sie währenddessen /proc/pressure/memory: Ein full avg10 über 40 bedeutet, dass in den letzten zehn Sekunden fast keine Task ausgeführt werden konnte. Ein Userspace-Daemon wie earlyoom beendet Prozesse, bevor der Server diesen Zustand erreicht.

Was ist der Unterschied zwischen MemoryHigh und MemoryMax?

MemoryHigh= ist eine weiche Obergrenze, die drosselt. Der Kernel führt für die Unit aggressiv Reclaim durch und verlangsamt ihre Speicherzuweisungen. Die Nutzung kann die Zahl jedoch überschreiten, und es wird nichts beendet. MemoryMax= ist eine harte Obergrenze: Wenn eine Speicherzuweisung unter dieser Grenze nicht erfüllt werden kann, ruft der Kernel den OOM-Killer innerhalb der eigenen cgroup der Unit auf. Dadurch wird der Prozess beendet, der das Problem verursacht hat, statt des größten Prozesses auf dem Server. Setzen Sie MemoryHigh= unter MemoryMax= und behandeln Sie den Abstand zwischen beiden als Warnbereich.

Wie finde ich heraus, welchen Dienst der OOM-Killer getroffen hat?

Führen Sie journalctl -k --grep "Killed process" --since "2 hours ago" aus. Eine mit Memory cgroup out of memory beginnende Zeile bedeutet, dass eine Unit ihr eigenes MemoryMax= erreicht hat. Ein einfaches Out of memory bedeutet dagegen, dass der gesamte Rechner keinen Speicher mehr hatte. Führen Sie anschließend journalctl -u <unit> -n 50 aus und suchen Sie nach Failed with result 'oom-kill'. Wenn /var/log/journal auf Ihrem Server nicht vorhanden ist, wurde das Journal im RAM gehalten und die Belege sind mit dem Reboot verloren gegangen. Erstellen Sie dieses Verzeichnis daher vor dem nächsten Vorfall.

Sollte ich einem kleinen VPS Swap hinzufügen?

Eine kleine Swap-Datei hilft bei selten verwendeten Speicherseiten, die einmal zugewiesen und danach nicht mehr verwendet werden. Bei einem außer Kontrolle geratenen Prozess hilft sie nicht. Sie verzögert das Beenden und macht aus einem kurzen Ausfall einen langen Stillstand, während dessen Sie sich nicht anmelden können, um das Problem zu beheben. Halten Sie den Swap-Bereich klein und setzen Sie MemorySwapMax=0 für die Units, auf deren Ausfall Sie vorbereitet sind. Diese erreichen dann schnell ihre Obergrenze und werden neu gestartet, während die wichtigen Dienste ihren Swap behalten.

Kann ich einen Befehl begrenzen, ohne eine Unit-Datei zu schreiben?

Ja. sudo systemd-run --scope -p MemoryMax=1G -p CPUQuota=50% ./script.sh führt den Befehl in Ihrem Terminal innerhalb eines temporären Scopes mit diesen Limits aus. Die Limits verschwinden, sobald der Befehl beendet wird. Nach -p ist jede Eigenschaft aus systemd.resource-control verfügbar. Daher funktionieren dort auch MemorySwapMax=, TasksMax= und CPUWeight=. Lassen Sie --scope weg und fügen Sie --unit=name hinzu, um den Job im Hintergrund mit seiner Ausgabe im Journal auszuführen.