systemd: Speicher und CPU für Prozesse begrenzen
Ein Prozess kann trotz Limits Ihren VPS ausbremsen. Setzen Sie MemoryHigh, MemoryMax, CPUQuota und TasksMax und prüfen Sie danach den OOM-Kill.
Prozessspeicher und CPU 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 startet, einige Zeilen hinzufügen. MemoryMax= ist die harte Speichergrenze. CPUQuota= ist die Grenze für die Prozessorzeit. Beide Grenzen werden durch cgroup v2 (Control Groups, Version 2) erzwungen. Diese Kernel-Funktion verwendet systemd bereits, um jeden Dienst auf dem System zu erfassen.
sudo systemctl edit myapp.serviceDadurch wird eine Drop-in-Datei mit Anweisungen in Kommentaren geöffnet. Fügen Sie die folgenden Zeilen oberhalb dieser Kommentare ein:
[Service]
MemoryHigh=512M
MemoryMax=768M
MemorySwapMax=0
CPUQuota=80%
TasksMax=128sudo systemctl daemon-reload
sudo systemctl restart myapp.service
systemctl show myapp.service -p MemoryHigh -p MemoryMax -p CPUQuotaPerSecUSec -p TasksMaxsystemctl 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 gespeichert 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 Teil dieser Anleitung erfahren Sie, wie Sie diese Werte auswählen und welche Probleme trotz gesetzter Limits weiterhin auftreten können.
Warum ein außer Kontrolle geratener Prozess einen VPS einfriert, ohne ihn vollständig zu belegen
Ein Prozess, der eine harte Speichergrenze erreicht, wird nach etwa einer Sekunde beendet, und der Dienst wird neu gestartet. Das ist der gute Fall. Problematisch ist der Fall, in dem nichts beendet wird: Der Server antwortet auf Ping, SSH nimmt die Verbindung an, und die Shell-Eingabeaufforderung erscheint nicht. Die Maschine ist aktiv und ausgelastet, aber keine dieser Aktivitäten ist sinnvoll.
Der Mechanismus ist nicht offensichtlich. Wenn der freie Speicher knapp wird, gibt der Kernel keine neuen Seiten aus, sondern fordert vorhandene Seiten zurück. Am günstigsten lassen sich dateigestützte Seiten zurückfordern, und der Page Cache enthält den ausführbaren Code aller laufenden Programme. Der Kernel entfernt daher die Textseiten von sshd. Die nächste auszuführende Anweisung sshd löst dann einen Page Fault aus, der diese Bytes erneut vom Speichergerät einlesen muss. Jeder Prozess wartet schließlich auf das Speichergerät, statt ausgeführt zu werden. Dieselben Seiten werden in einer Schleife entfernt und wieder eingelesen. Dieser Zustand wird Thrashing genannt.
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 Zeit, sondern den Fehlschlag: Solange die Rückforderung weiterhin Seiten liefert, glaubt der Kernel, dass er Fortschritte macht, unabhängig davon, wie langsam dies geschieht. Deshalb ruft er 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 den Vorgang beobachten. Der Kernel stellt unter Linux 4.20 und neuer Pressure Stall Information (PSI) bereit:
cat /proc/pressure/memory
cat /proc/pressure/iosome avg10=63.72 avg60=41.02 avg300=12.33 total=13729481
full avg10=48.15 avg60=30.44 avg300=8.90 total=9114233Die Zeile full ist entscheidend. full avg10=48.15 bedeutet, dass während der letzten zehn Sekunden 48% der Zeit jede ausführbare Aufgabe auf dem Server wegen Speicheroperationen blockiert war und daher nichts ausgeführt wurde. Auf einem gesunden Server liegt full nahe null. Oberhalb von 10 wirkt der Server für Menschen langsam. Ab 40 wird der Zustand als eingefroren beschrieben.
Das erklärt auch, warum eine Begrenzung allein keine Zusicherung ist. Eine Unit unter MemoryHigh= wird gedrosselt, statt beendet zu werden. Sie bleibt daher aktiv und langsam. systemd startet sie nicht neu, weil sie aus Sicht von systemd nie fehlgeschlagen ist. Eine begrenzte Unit, die weiterhin auf Swap zugreifen darf, erzeugt Lese- und Schreibzugriffe. Diese werden der Unit zugerechnet, aber von einem gemeinsam genutzten Speichergerät verarbeitet. Dadurch kann sie /proc/pressure/io für jeden anderen Dienst auf dem Server erhöhen. Begrenzungen legen fest, wer für einen Engpass bezahlt. Sie schaffen keine zusätzliche Kapazität.
Prüfen, ob Ihr VPS cgroup v2 verwendet
stat -fc %T /sys/fs/cgroupcgroup2fs ist die vereinheitlichte Hierarchie, die alle folgenden Einstellungen benötigen. tmpfs bedeutet, dass das System mit dem älteren v1-Layout gestartet wurde. Dort existieren MemoryHigh= und MemorySwapMax= nicht, 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 Kernel, der mit systemd.unified_cgroup_hierarchy=0 gestartet wurde, verwendet v2 nicht.
Unter cgroup v2 aktiviert systemd standardmäßig die Speicherabrechnung für jede Unit. Die Werte sind daher bereits verfügbar:
systemd-cgtop -mDamit werden die cgroups nach Speichernutzung sortiert aufgelistet. So lässt sich am schnellsten feststellen, welcher Prozess die Ressourcen des Servers verbraucht, solange der Server noch antwortet. Wenn der Server neu ist, hat die Kontoeinrichtung und Firewall-Konfiguration unter den ersten zehn Minuten auf einem neuen VPS Vorrang.
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 aggressiver Speicher zurück und verlangsamt ihre Speicherzuweisungen absichtlich. 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, läuft der OOM-Killer innerhalb dieser cgroup und beendet einen eigenen Prozess dieser Unit.
Dieser zweite Punkt ist der eigentliche Grund, MemoryMax= für alles festzulegen, dem Sie nicht vollständig vertrauen. Ohne Begrenzung wird ein Speichermangel zum Problem des gesamten Systems, und der globale OOM-Killer wählt sein Opfer nach oom_score aus. Das bedeutet meist: nach dem größten Prozess. Der größte Prozess ist normalerweise Ihre Datenbank und nicht das Skript, das das Speicherleck verursacht hat. Mit einer Begrenzung erfolgt der Kill innerhalb der Unit, die den Speichermangel verursacht hat.
Setzen Sie beide Werte. Legen Sie MemoryHigh= etwa 20 bis 30 Prozent unter MemoryMax= fest. Der Abstand ist eine Warnzone: Ein langsames Speicherleck überschreitet High und zeigt sich als Dienst, der langsamer geworden ist. Ein plötzlicher Spitzenwert überschreitet Max direkt, und der Prozess wird beendet.
Prozentwerte beziehen sich auf den installierten physischen Speicher. MemoryMax=25% entspricht bei einem 4-GB-Tarif daher 1 GB und bleibt nach einer Vergrößerung des Tarifs bei einem Viertel des Systems. MemorySwapMax=0 hält diese Unit vollständig vom Swap fern. Dadurch wird ein langes Hängen zu einem schnellen, eindeutigen Kill.
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 festgelegten Kontextfensters. Lesen Sie daher welche RAM-Kosten eine Erhöhung von num_ctx verursacht, bevor Sie dafür eine Obergrenze festlegen.
Neben einer Begrenzung benötigen Sie eine Restart-Richtlinie. Andernfalls bleibt nach dem Kill lediglich ein gestoppter Dienst zurück.
[Unit]
StartLimitIntervalSec=300
StartLimitBurst=5
[Service]
Restart=on-failure
RestartSec=5sStartLimit* gehört in [Unit] und Restart= in [Service]. Wenn Sie einen der beiden Werte in den falschen Abschnitt eintragen, ignoriert systemd ihn. Fünf Neustarts innerhalb von fünf Minuten weisen auf ein Leck und nicht auf einen kurzen Aussetzer hin. Danach gibt systemd auf und lässt die Unit als fehlgeschlagen zurück. Genau diesen Zustand möchten Sie später vorfinden, statt eine Crash-Schleife zu erhalten, die das Problem verdeckt.
CPU mit CPUQuota begrenzen oder mit CPUWeight anteilig zuweisen
CPUQuota= belegt einen prozentualen Anteil der verfügbaren Zeit auf einer CPU. CPUQuota=50% entspricht einem halben CPU-Kern. CPUQuota=200% entspricht zwei Kernen. Die Unit kann diese Last auf beliebig viele Threads verteilen. Bei einem Tarif mit 2 vCPUs entspricht CPUQuota=200% der gesamten Maschine.
CPUWeight= ist für die meisten Dienste die bessere Standardeinstellung. Der Wert bezeichnet einen relativen Anteil von 1 bis 10000. Der Kernel verwendet standardmäßig 100. Die Einstellung greift nur bei konkurrierenden Prozessen: Ein Backup-Job mit CPUWeight=20 wird unter Last gegenüber einem Webserver mit 100 nachrangig behandelt. Wenn die Maschine nicht ausgelastet ist, kann der Backup-Job weiterhin die gesamte verfügbare CPU-Kapazität nutzen. Eine harte CPU-Quote würde diese ungenutzte Kapazität verwerfen.
Seien Sie sich darüber im Klaren, welchen Nutzen eine CPU-Begrenzung hat. Ein CPU-intensiver Prozess bringt Linux normalerweise nicht zum Stillstand, weil der Scheduler die CPU-Zeit weiterhin an alle Prozesse verteilt. Arbeitsspeicher kann eine Maschine dagegen zum Ausfall bringen. Verwenden Sie CPUQuota=, wenn Sie eine vorhersehbare Obergrenze benötigen, zum Beispiel für einen Build oder einen Agenten, der andernfalls eine Stunde lang mit voller Leistung laufen würde. Die Dimensionierung solcher Workloads ist eine eigene Frage. Sie wird unter wie viel RAM und CPU ein Coding-Agent auf einem VPS benötigt behandelt.
Wenn die CPU-Auslastung hoch ist, obwohl keiner Ihrer Prozesse nennenswert arbeitet, kann die Ursache auf der anderen Seite des Hypervisors liegen. Dabei handelt es sich um CPU-Steal-Time durch einen ausgelasteten Nachbarn. Keine von Ihnen gesetzte CPU-Quote kann daran etwas ändern.
TasksMax verhindert eine Fork-Schleife
TasksMax= gibt die Anzahl der Prozesse und Threads an, die eine Unit verwalten darf. Threads werden mitgezählt. Ein Java- oder Go-Dienst benötigt daher mehr Spielraum, als die Prozessliste vermuten lässt. Dies ist der kostengünstigste Schutz gegen ein Skript, das in einer Schleife neue Prozesse erzeugt. Der Fork schlägt dann innerhalb der Unit fehl, bevor dem System die Prozess-IDs ausgehen.
TasksMax=128Wenn eine Unit das Limit erreicht, schreibt der Kernel eine Meldung mit dem Namen der Cgroup ins Log:
cgroup: fork rejected by pids controller in /system.slice/myapp.serviceDas Programm selbst meldet normalerweise fork: retry: Resource temporarily unavailable. Prüfen Sie mit systemctl show -p DefaultTasksMax, welche Vorgabe der Manager standardmäßig anwendet.
Einen einmaligen Auftrag 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 dem Bildschirm sichtbar. Die Begrenzungen werden aufgehoben, sobald der Befehl beendet ist. Jede Eigenschaft aus systemd.resource-control funktioniert nach -p.
Für einen längeren Auftrag lassen Sie --scope weg und vergeben einen Namen. Der Auftrag läuft dann im Hintergrund als temporärer Dienst und schreibt seine Logs in das Journal:
sudo systemd-run --unit=nightly-import -p MemoryMax=1G -p CPUWeight=20 ./import-data.sh
journalctl -u nightly-import -fDieselben Optionen funktionieren mit --user, wenn Sie nicht root sind. Der Benutzer-Manager verfügt jedoch nur über die Controller, die an ihn delegiert wurden. Daher kann eine Eigenschaft dort abgelehnt werden. Führen Sie den Befehl in diesem Fall mit sudo aus. Wenn ein Auftrag dauerhaft betrieben 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 verändert die Art des Ausfalls, statt ihn zu verhindern.
Ohne Swap erreicht ein Speicherleck die Obergrenze, und innerhalb weniger 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 Seiten auf die Festplatte und gewinnt dadurch Zeit. Wenn sich der Prozess stabilisiert hätte, hilft Swap. Wenn er jedoch unkontrolliert weiterläuft, macht Swap aus einem fünf Sekunden langen Ausfall einen zwanzigminütigen Stillstand. Dieser Stillstand ist schlimmer, weil ein beendeter Prozess Ihnen weiterhin eine funktionierende Shell lässt, ein System im Thrashing-Zustand jedoch nicht.
swapon --show
free -hEin praktikabler Mittelweg auf einem kleinen VPS ist eine moderate Swap-Datei für Seiten, die einmal reserviert und danach nie wieder verwendet werden. Setzen Sie außerdem MemorySwapMax=0 für die Units, auf deren Ausfall Sie sich einlassen. Die wichtigen Dienste behalten ihren Swap. Die unvorhersehbaren Dienste erreichen schnell die Obergrenze und werden neu gestartet.
Das Absenken von vm.swappiness ist ein schwacher Stellhebel. Es ist dennoch wichtig zu wissen, warum. Der Wert verschiebt lediglich das Gleichgewicht zwischen dem Verdrängen des Page Cache und dem Auslagern anonymer Seiten. Beides verursacht später einen Lesezugriff auf die Festplatte. Der Wert ändert, welche Seiten ins Thrashing geraten, nicht, ob das System ins Thrashing gerät.
Ein früher OOM-Daemon beendet Prozesse, bevor das System ins Stocken gerät
Der Kernel wartet, bis die Speicherfreigabe vollständig fehlschlägt. Auf einem kleinen VPS ist genau dieses Warten das Zeitfenster, in dem Sie den Zugriff auf die Maschine verlieren. Zwei Userspace-Daemons schließen dieses Zeitfenster, indem sie den Speicher selbst überwachen und früher Prozesse beenden.
earlyoom überwacht den verfügbaren Speicher und den freien Swap. Sobald einer der Werte unter einen Schwellenwert fällt, beendet es den Prozess mit der höchsten Bewertung.
sudo apt install earlyoom
systemctl status earlyoomDas Debian- und Ubuntu-Paket startet den Dienst bei der Installation. Die Optionen stehen 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 sind standardmäßig auf 10 Prozent gesetzt. Die jeweils zweite Zahl gibt den Punkt für SIGKILL an: earlyoom sendet SIGTERM, sobald der erste Wert unterschritten wird, und SIGKILL unterhalb des zweiten Werts. Dieser ist standardmäßig halb so groß wie der erste. Übernehmen Sie eine Änderung mit sudo systemctl restart earlyoom. In journalctl -u earlyoom sehen Sie, welchen Prozess der Dienst beendet hat und wie viel Speicher dieser Prozess belegt hatte.
systemd-oomd ist die andere Option. Die zugehörige Manpage beschreibt es als „einen Systemdienst, der cgroups-v2 und Pressure Stall Information (PSI) verwendet, um das System zu überwachen und Korrekturmaßnahmen einzuleiten, bevor im Kernel-Space ein OOM auftritt“. Der Dienst arbeitet mit ganzen cgroups statt mit einzelnen Prozessen. Er beendet daher eine Unit und nicht nur einen verwaisten Kindprozess. Units werden mit ManagedOOMMemoryPressure=kill oder ManagedOOMSwap=kill aktiviert. Die Schwellenwerte stehen in /etc/systemd/oomd.conf.
systemctl status systemd-oomd
oomctloomctl gibt aus, was der Dienst derzeit überwacht. Auf einem Server-Image ist das häufig nichts, weil die Einstellung für jede Unit einzeln aktiviert werden muss. Wählen Sie einen der beiden Daemons und belassen Sie es dabei. Wenn beide laufen, konkurrieren zwei Dienste darum, ein Ziel auszuwählen. Dadurch lässt sich der Grund für eine Beendigung schwerer nachvollziehen.
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 so 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:0anon-rss ist der Arbeitsspeicher, den der Prozess beim Beenden belegt hatte, hier etwa 1.8 GB. Lesen Sie den Namen in den Klammern mit Vorsicht. Das ist 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 Engpass verursacht hat.
Ein Kill aufgrund eines cgroup-Limits hat ein anderes Präfix. Der darüber ausgegebene Bericht nennt die cgroup, die ihre eigene Obergrenze 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:0Dieses Präfix enthält bereits 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 Ressourcen hatte. Ein einfaches Out of memory bedeutet, dass dem gesamten Rechner der Speicher ausgegangen ist. Ihre Limits fehlten also entweder oder waren in ihrer Summe zu großzügig.
Fragen Sie anschließend systemd, was es erkannt hat:
systemctl status myapp.service
journalctl -u myapp.service -n 50myapp.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. Nur sie protokollieren Drosselungen, die niemals eine Logzeile erzeugen:
cat /sys/fs/cgroup/system.slice/myapp.service/memory.events
cat /sys/fs/cgroup/system.slice/myapp.service/memory.peaklow 0
high 4213
max 118
oom 12
oom_kill 12high zählt, wie oft die Unit MemoryHigh= überschritten hat und gedrosselt wurde. max zählt, wie oft sie die harte Obergrenze erreicht hat. oom_kill zählt tatsächlich beendete Prozesse. Ein hoher Wert für high zusammen mit oom_kill 0 bezeichnet den zuvor beschriebenen stillen Fall: Der Dienst läuft, wurde aber stark verlangsamt und hat niemandem einen Fehler gemeldet. memory.peak (Linux 5.19 und neuer) enthält die höchste Nutzung, die die cgroup erreicht hat. An diesem Wert müssen Sie MemoryMax= ausrichten. Beide Dateien werden beim Neustart der Unit zurückgesetzt, weil systemd die cgroup erneut erstellt.
Darunter liegt eine grundlegende Voraussetzung. Wenn /var/log/journal nicht existiert, liegt das Journal im RAM. Nach dem Reboot, den Sie zur Wiederherstellung des Servers benötigen, ist dann jede Zeile verschwunden.
sudo mkdir -p /var/log/journal
sudo systemd-tmpfiles --create --prefix /var/log/journal
sudo systemctl restart systemd-journald
journalctl --list-bootsEin journalctl --list-boots-Wert größer als der aktuelle Boot zeigt, dass die Historie nun erhalten bleibt. Damit kann journalctl -k -b -1 die Kernelmeldungen des fehlgeschlagenen Boots 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 ergeben, 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 entsprechend.
[Service]
MemoryHigh=256M
MemoryMax=384M
MemorySwapMax=0
CPUWeight=20
TasksMax=64
Restart=on-failure
RestartSec=5sEine zusätzliche Einstellung kann den Zugang zum System sichern. OOMScoreAdjust=-500 in einem Drop-in für ssh.service verringert die Wahrscheinlichkeit deutlich, dass der globale OOM-Killer Ihren SSH-Daemon als Prozess auswählt. Das kann darüber entscheiden, ob Sie das System reparieren oder es über das Control Panel neu starten müssen. Die Einstellung ändert nur die Auswahl des Prozesses durch den Kernel. Sie verkürzt die Blockierung nicht.
Container laufen in eigenen cgroups. Diese erstellt die Container-Runtime und nicht Ihre Unit-Dateien. Daher wird ein Limit für docker.service nicht zum 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 beschrieben.
FAQ
Warum ist mein VPS eingefroren, anstatt den außer Kontrolle geratenen Prozess zu beenden?
Weil der Kernel den Fortschritt danach beurteilt, ob das Reclaim Pages freigibt, und nicht danach, wie lange dieser Vorgang dauert. Bei knappem Arbeitsspeicher verdrängt er den Page Cache, einschließlich der ausführbaren Pages laufender Programme, und liest sie bei der nächsten Instruktion wieder ein. Alles wartet auf den Speicher, und technisch ist keine Allokation fehlgeschlagen. Deshalb wird der OOM-Killer nie aufgerufen. Prüfen Sie während des Vorfalls /proc/pressure/memory: Ein full avg10 über 40 bedeutet, dass in den letzten zehn Sekunden fast kein 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 den Prozess drosselt. Der Kernel führt für die Unit ein aggressives Reclaim durch und verlangsamt ihre Allokationen. Die Nutzung kann die Zahl jedoch überschreiten, und es wird nichts beendet. MemoryMax= ist eine harte Obergrenze: Eine Allokation, die darunter nicht erfüllt werden kann, ruft den OOM-Killer innerhalb der eigenen cgroup der Unit auf. Dadurch wird der Prozess beendet, der das Problem verursacht hat, und nicht der größte Prozess auf dem Server. Setzen Sie MemoryHigh= unter MemoryMax= und behandeln Sie den Bereich dazwischen als Warnzone.
Wie finde ich heraus, welchen Dienst der OOM-Killer beendet 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 Arbeitsspeicher 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 Informationen gingen mit dem Reboot verloren. Erstellen Sie dieses Verzeichnis daher vor dem nächsten Vorfall.
Sollte ich einem kleinen VPS Swap hinzufügen?
Eine kleine Swap-Datei hilft bei kalten Pages, die einmal allokiert und danach nicht mehr verwendet werden. Bei einem außer Kontrolle geratenen Prozess hilft sie nicht. Sie verzögert das Beenden und ersetzt einen kurzen Ausfall durch einen langen Stillstand, während dessen Sie sich nicht anmelden können, um das Problem zu beheben. Halten Sie den Swap klein und setzen Sie MemorySwapMax=0 für die Units, auf deren Ausfall Sie verzichten können. Diese erreichen dann ihre Obergrenze und werden schnell 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 Begrenzungen aus. Die Begrenzungen verschwinden, sobald der Befehl beendet ist. Nach -p steht jede Eigenschaft aus systemd.resource-control zur Verfügung. 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.