IT

Wer hat Angst vorm OOM-Killer?

Manchmal vergesse ich, warum frühere Programmierergenerationen so sparsam mit ihren Ressourcen umgegangen sind. OK, das stimmt jetzt auch nicht so 100%, die Microcontroller/IoT Leute machen immer noch einen unglaublichen Job, aus Hardware das letzte Quäntchen Leistung heraus zu drücken. Aber auf dem Desktop? Nein, nicht wirklich. Und vor der Chipkrise war es üblich sich einen neuen Rechner zu kaufen, falls der Alte zu langsam wurde.

Was ist der OOM-Killer?

Und so bekam ich eines schönen Tages einen Besuch vom OOM-Killer (Out Of Memory). Wer ihn nicht kennt, wenn dein Rechner unter Linux keinen RAM mehr hat, um zu funktionieren, dann fängt er an wahllos Programme zu beenden. Gut vielleicht nicht ganz wahllos, aber doch teilweise sehr überraschend. Genau genommen macht uns der OOM-Killer einen Gefallen. Anstatt das ganze System abstürzen zu lassen, werden einzelne Programme geopfert um den Rest zu retten. Schlecht wenn man das Programm gerade genutzt hat.
Kontrast zu Windows, da wird nix gekillt, da wird solange ausgelagert bis das System nicht mehr benutzbar ist und der User aus Frust neu startet.

Was genau ist das Problem?

Angenommen wir haben zu viele Dockerinstanzen auf dem Laptop laufen und jetzt hat sich Firefox beendet. Kein Drama, jedoch auf einem Server wäre das unschön, wenn sich einfach die Datenbank verabschiedet, nur weil wir vergessen haben den RAM-Verbrauch einzustellen. Und falls alles automatisch neu startet, dann zeigt sich, dass Problem als Freakfehler. Aus irgendeinem Grund ist immer mal wieder die Datenbank auf dem Server weg ist und keiner weiß genau wieso. Nur weil sich ein Bashscript den ganzen RAM gekrallt hat.

Wie genau läuft das ab?

Wie viel Speicher wir haben bestimmt die Hardware und wie viel wir brauchen bestimmt die Software. Bei der Hardware ist nicht viel zu machen, mehr Ram kaufen oder die Hardware effizienter Nutzen. Also RAM komprimieren (SWAP on ZRAM) oder Auslagerung (SWAP) auf einen anderen Speicher sind da die Mittel zur Wahl, aber nicht ohne ganz eigene Probleme.

Auf der Softwareseite einfach nur das laufen lassen, was wirklich benötigt wird. Klingt trivial, macht aber fast keiner. Ich meine die Sicherheitsleute sagen uns das seit Jahren, verkleinert den Angriffsvektor und keiner hört darauf. Die Softwaremöglichkeiten sind historisch gewachsen und ich versuchte das hier mal in ein Gerüst zu setzen, damit niemand verwirrt ist. In der guten alten Zeit hat ein Programm wenn es laufen wollte, sich RAM reserviert und wenn es den RAM nicht reservieren konnte, dann lief es nicht und alles war gut.

Das Problem damals, wenn das Programm mal mehr leisten wollte, als es sich RAM reserviert hatte, ging das nicht. Also wurde die RAM-Reservierung "dynamisch". Jetzt konnte ein Programm mitten im Ausführen feststellen, dass es keinen RAM mehr gab und abstürzen, da der Programmierer das Handling des Fehlers vergessen hatte oder besser noch, gleich das ganze System runterziehen. Jedoch das erste Programm, dass sich den ganzen RAM und SWAP gegriffen hat, braucht doch das ganze Zeug nicht die ganze Zeit, oder?

overcommit

Auftritt /proc/sys/vm/overcommit_memory, kurz beschrieben, es verspricht im Default Programmen mehr RAM als vorhanden ist, in der Hoffnung, dass nicht jeder zur gleichen Zeit drauf zugreifen will. Und meistens funktioniert das und alles war gut. Oder auch nicht.

Wir haben also Programme die selbst nicht wissen wie viel Speicher sie benötigen, aber zickig werden wenn sie keinen bekommen und ein System das freudig nicht vorhanden Speicher verkauft. (Alle Parallelen zum aktuellen Wirtschaftssystem sind rein zufällig.) Wenn jetzt der Speicher überraschend knapp wird, kommt der OOM-Killer und exekutiert ein Programm um das ganze System zu retten. Jedenfalls passiert das auf Systemebene und die Programmauswahl für das war auch nicht so besonders gut. Um 2005 war es dann soweit und dem User wurde mehr Einfluss gegeben.

oom_adj

Auftritt /proc//oom_adj . Ging von -17 ( nicht killen) bis 15 (weg damit und raus mit ihnen). Die eigentliche Reihenfolge wurde aber über /proc//oom_score bestimmt, das zusätzlich z.b. über Laufzeit, Speicherverbrauch,Rechte und den eben genannten oom_adj berechnet wurde. Auch nicht ganz wahr, oom_adj ist jetzt depreciated und wir nutzen bitte alle oom_score_adj von -1000 bis 1000. Jetzt konnten die OOM-Präferenzen eingestellt werden und alles war gut. Na ja, außer es war ein System, dass schnell Prozesse startete und dann wieder stoppte, dann konnte das Handling von oom_score_adj schon schwierig sein.

cgroup

Auftritt von cgroup. Idee, wir stecken einfach alle Instanzen eines Programmes in eine (c)group und geben ihr z.B. einen Maximalwert von RAM den es verbrauchen darf. Dann kann uns egal sein, wie viele Unterprozess gespawn werden, das läuft speichermäßig nicht aus dem Ruder. Wichtig, der ganzen cgroup wird ein Max Wert gegeben und wenn der überschritten wird, dann wird in der cgroup mit Töten angefangen oder gleich die ganze cgroup exekutiert, je nach Geschmack. Durch die Gruppenzusammenfassung gibt es dann noch weiter Einstellmöglichkeiten, z.B. wie viel RAM braucht jeder auf jeden Fall, ob sich die cgroup breit machen darf, wenn das System gerade idlet und wie es den RAM wieder zurück geben soll, wenn das System wieder was zu tun hat. Das hier nur mal als Anhaltspunkt zum Weiterlesen, falls das einen Interessiert. Direkt im System oder Systemd als Wrapper Das ist die Systemebene, aber was ist, wenn wir auf der Userebene schon eingreifen, bevor der Kernel reagieren muss?

systemd-oomd

Auftritt systemd-oomd. Das besondere hier ist, wie entschieden wird ob und was gekillt wird und zwar mit PSI (Pressure Stall Information). Wurde von Facebook entwickelt und misst, grob gesagt, wie viel Zeit flöten geht, weil der Prozess benötigte Ressourcen nicht erhält. PSI wurde standardmäßig in den Kernel aufgenommen und die Werte finden sich in /sys/fs/cgroup//memory.pressure bzw. cpu.pressure und io.pressure. (Falls nur generelle Werte vorhanden sind, ist höchst wahrscheinlich cgroup v2 nicht gemountet.) Nochmal zum Verständnis, das ist jetzt eine neue, extra Ebene und hat mit dem System OOM-Killer praktisch nix zu tun, d.h. selbst wenn systemd-oomd sagt, ich lass den Prozess leben, heißt das nicht, dass der System OOM-Killer das genauso sieht. Jedoch kann Systemd sehr wohl die cgroupwerte und auch den oom-score-adj Wert manipulieren und damit den System OOM-Killer steuern. Jedoch ist systemd-oomd im default kaum aktiv und cgroups müssen explizit für systemd-oomd markiert werden.

Soweit erstmal die kurze Zusammenfassung zum OOM-Killer. Wie wir das Ganze nun einstellen können, beschreibe ich im nächsten Abschnitt.

Einstellmöglichkeiten:

Bleiben wir erstmal bei der Hardware. Swap an sich ist ein wunderschönes großes Thema, das einen eigenen Artikel verdient, hier nur ein kurzer Anriss. ZRAM ist meistens eine Möglichkeit den vorhanden RAM besser zu nutzen. Natürlich ist das ein Kompromiss, da die Kompression wieder CPU-Zyklen frisst. Swap kommt in den unterschiedlichsten Formen. Als Partition, als Datei oder direkt im RAM als ZSWAP ( funktioniert nicht mit ZRAM zusammen). Die Auslagerung findet meist auf der HDD oder auf einem Chipspeichermedien, wie SSD oder SD-Karte statt. Chipspeichermedium sind zwar sehr schnell, jedoch kann Swappen darauf das Medium zerstören, da diese nur eine bestimmte Menge Schreibbefehle vertragen. Weitere Probleme können sein, wie ist die Anbindung bzw. der Schreibdurchsatz des Speichermediums, wie oft werden die Dirty-Pages geschrieben und ist die Stromversorgung sichergestellt, spannend bei USB-Anbindung. Während in cgroup v1 RAM und SWAP gleichbehandelt wurden, sind die Einstellmöglichkeiten in cgroup v2 getrennt worden.

So wie nutzen wir jetzt das Ganze?

Beispiel wir haben ein Bashscript und möchten dort den oom-score-adj einstellen.

systemd-run --scope \
  -p OOMScoreAdjust=-900 \
  ./wichtigesScript.sh

Das Ding wird so ziemlich als letztes vom OOM-Killer angefasst. Da oom_score_adj auf -900 steht.

Jetzt wollen wir, dass es generell 4GB bekommt, sollte der Rechner aber nix zu tun haben, kann es gern mehr verbrauchen.

systemd-run --scope \
  -p OOMScoreAdjust=-900 \
  -p MemoryHigh=4G \
  ./wichtigesScript.sh

Wir haben Angst vor einem Memory-Leak? Bei 16GB auf jeden Fall stoppen.

systemd-run --scope \
  -p OOMScoreAdjust=-900 \
  -p MemoryHigh=4G \
  -p MemoryMax=16G \
  ./wichtigesScript.sh

Swap-Einstellung läuft analog über MemorySwapHigh und MemorySwapMax.

Wenn wir direkt mit Systemd Units arbeiten dann können wir die Anweisungen in die Systemd Dateien schreiben

[Service]

#Kernel-oom_score_adj direkt einstellen 
    OOMScoreAdjust=-500
    
#cgroup-Limit hier stellen wir die cgroup ein  
    MemoryMax=512M  
    MemoryHigh=400M
    
#systemd-oomd anweisen dies hier zu überwachen und einzugreifen.
    ManagedOOMMemoryPressure=kill  
    ManagedOOMMemoryPressureLimit=50%
    

Ich hoffe, diese kleine Zusammenfassung hilft, die Begriffe weiter einzuordnen. Vor allem die cgroup ist es Wert sich weiter mitzubeschäftigen, da nicht nur der RAM, sondern auch z.B. der CPU Verbrauch eingestellt werden kann.

In diesem Sinne

Kowo

Quellen:

https://lwn.net/Articles/317814/
https://docs.kernel.org/accounting/psi.html
https://wiki.archlinux.org/title/Zram
https://kernel-internals.org/mm/memcg/
https://docs.kernel.org/admin-guide/cgroup-v2.html#memory-interface-files
https://www.freedesktop.org/software/systemd/man/latest/systemd.resource-control.html

Tagged

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert