OOMkiller in Docker ist schwieriger als Sie denken

Hallo wieder. Im Vorfeld des Kursbeginns bereitete "Java Developer" eine Übersetzung eines weiteren kleinen Materials vor.




Kürzlich hatte einer der Plumbr APM- Benutzer ein seltsames Problem mit einem Notstopp des Docker-Containers mit Code 137. Die Konfiguration war mit mehreren verschachtelten Containern und virtuellen Maschinen einfach, ähnlich einer verschachtelten Puppe:

  • eigener Eisenserver mit Ubuntu;
  • viele Docker-Container mit Ubuntu im Inneren;
  • Java Virtual Machine in Docker-Containern.

Während der Untersuchung des Problems haben wir Docker- Dokumentation zu diesem Thema gefunden. Es wurde klar, dass der Grund entweder ein manuelles Stoppen des Containers oder ein Mangel an Speicher und das anschließende Eingreifen von Oomkiller (Out-Of-Memory Killer) war.

Wir schauen uns Syslog an und sehen, dass Oomkiller tatsächlich heißt :

[138805.608851] java invoked oom-killer: gfp_mask=0xd0, order=0, oom_score_adj=0
[138805.608887] [<ffffffff8116d20e>] oom_kill_process+0x24e/0x3b0
[138805.608916] Task in /docker/264b771811d88f4dbd3249a54261f224a69ebffed6c7f76c7aa3bc83b3aaaf71 killed as a result of limit of /docker/264b771811d88f4dbd3249a54261f224a69ebffed6c7f76c7aa3bc83b3aaaf71
[138805.608902] [<ffffffff8116da84>] pagefault_out_of_memory+0x14/0x90
[138805.608918] memory: usage 3140120kB, limit 3145728kB, failcnt 616038
[138805.608940] memory+swap: usage 6291456kB, limit 6291456kB, failcnt 2837
[138805.609043] Memory cgroup out of memory: Kill process 20611 (java) score 1068 or sacrifice child

Wie Sie sehen können, hat der Java-Prozess ein Limit von 3145728 KB (dies sind ungefähr 3 GB) erreicht, wodurch der Container gestoppt wurde. Dies ist ziemlich seltsam, da Docker selbst mit einem Limit von 4 GB (in der Datei docker-compose) gestartet wurde .

Sie wissen wahrscheinlich, dass die JVM auch die Speichernutzung einschränkt. Obwohl Docker selbst auf ein Limit von 4 GB festgelegt war, wurde die JVM mit der Option gestartet Xmx=3GB. Dies kann noch verwirrender sein. Beachten Sie jedoch, dass die JVM mehr Speicher belegen kann als von -Xmx angegeben (siehe Artikel zur Analyse der Speichernutzung in der JVM ).

Bis wir verstehen, was passiert. Docker sollte die Verwendung von 4 GB zulassen. Warum hat OOMkiller mit 3 GB gearbeitet? Die weitere Suche nach Informationen führte uns zu der Tatsache, dass es eine weitere Speicherbeschränkung im Betriebssystem gibt, das auf Hardware bereitgestellt wurde.

Bedanken Sie sich bei cgroups (Kontrollgruppen). cgroups ist die Linux-Kernel-Engine zum Einschränken, Steuern und Abrechnen der Ressourcennutzung durch Prozessgruppen. Im Vergleich zu anderen Lösungen (Team niceoder /etc/security/limits.conf) bieten cgroups mehr Flexibilität, da sie mit (unter) Prozessgruppen arbeiten können.

In unserer Situation beschränkten cgroups die Speichernutzung auf 3 GB (via memory.limit_in_bytes). Wir haben einige Fortschritte!

Eine Untersuchung des GC-Gedächtnisses und der Ereignisse unter Verwendung von Plumbr ergab, dass die JVM die meiste Zeit etwa 700 MB verwendete. Die Ausnahme war nur unmittelbar vor dem Stopp, als die Speicherzuordnung stark anstieg. Ihm folgte eine lange Pause von GC. Es scheint also Folgendes passiert zu sein:

  • Java-Code, der in der JVM ausgeführt wird, versucht, viel Speicher zu erhalten.
  • Nachdem die JVM überprüft hat, dass das Xmx-Limit von 3 GB noch weit entfernt ist, fordert sie an, Speicher für das Betriebssystem zuzuweisen.
  • Docker prüft auch, ob das 4-GB-Limit ebenfalls nicht erreicht wurde.
  • Der Betriebssystemkern überprüft das cgroup-Limit von 3 GB und beendet den Container.
  • Die JVM stoppt mit dem Container, bevor sie ihren eigenen verarbeiten kann OutOfMemoryError.

Um dies zu verstehen, haben wir alle Einschränkungen von 2,5 GB für Docker und 1,5 GB für Java konfiguriert. Danach konnte die JVM OutOfMemoryErroreine OutOfMemoryError-Ausnahme behandeln und auslösen. Dies ermöglichte es Plumbr, seine Magie zu entfalten - einen Speicher-Snapshot mit den entsprechenden Stack-Dumps zu erhalten und zu zeigen, dass es eine Abfrage an die Datenbank gab, die in einer bestimmten Situation versuchte, fast die gesamte Datenbank zu laden.

Ergebnisse


Selbst in einer so einfachen Situation gab es drei Speicherbeschränkungen:

  • JVM über Parameter -Xmx
  • Docker durch Optionen in der Datei docker-compose
  • Betriebssystem durch Parameter memory.limit_in_bytes cgroups

Wenn Sie also auf einen OOM-Killer stoßen, sollten Sie alle damit verbundenen Speicherbeschränkungen beachten.

Eine weitere Schlussfolgerung für Docker-Entwickler. Es scheint nicht sinnvoll zu sein, solche "verschachtelten Puppen" laufen zu lassen, bei denen das Speicherlimit des verschachtelten Containers höher ist als das Limit für cgroups. Eine einfache Überprüfung beim Starten des Containers mit der entsprechenden Warnung kann Ihren Benutzern Hunderte von Stunden Debugging ersparen.

Das ist alles. Wir warten auf dem Kurs auf Sie .

All Articles