Blog

Die fehlende Swap-Datei, die unsere Server einfrieren ließ

Am 24. September hat einer unserer Produktionsserver aufgehört zu antworten. Der Load Balancer hat ihn als fehlerhaft markiert, und unser Werkzeug für Fernbefehle kam nicht an ihn heran. Aber nichts war abgestürzt, und nichts war wegen zu hohen Speicherverbrauchs beendet worden. Der Server lief im Thrashing: Ohne Swap-Speicher und mit Containern, die mehr Speicher nutzen durften, als die Maschine hatte, verbrachte Linux seine Zeit damit, die Dateien, die zum Laufen nötig sind, aus dem Cache zu werfen und wieder einzulesen, und kam kaum voran. Eine 2-GB-Swap-Datei auf jedem Server hat es behoben. Die interessantere Lehre betraf die Health-Checks.

Wie „hängend“ aussah

  • Die Health-Checks des Load Balancers liefen auf diesem Server in den Timeout.
  • Befehle, die über AWS Systems Manager geschickt wurden, blieben auf „Pending“ stehen und liefen nie.
  • In den Logs gab es keinen Out-of-Memory-Abbruch. Kein Prozess war gestorben.

Eine Woche zuvor hatten wir die Produktion auf kleinere ARM-Server umgestellt mit je 2 GB RAM. Unser Staging-Server, noch kleiner, war beim Entpacken eines Container-Images während eines Deployments genauso eingefroren.

Warum kein Speicherfehler?

Linux beendet einen Prozess erst dann, wenn ihm wirklich der zurückgewinnbare Speicher ausgeht. Davor holt es sich Speicher aus dem Page-Cache, also den Kopien von Dateien, die es im RAM hält, einschließlich des Codes jedes laufenden Programms.

Auf unseren Servern addierten sich die kombinierten Speichergrenzen der Container auf rund 3,3 GB, auf einer Maschine mit rund 1,8 GB nutzbarem Speicher. Normalerweise nutzen sie nicht alle gleichzeitig ihr Limit. Taten sie es doch, warf der Kernel laufend Programmcode aus dem Cache, um Platz zu schaffen, und musste ihn beim nächsten Ausführen sofort wieder von der Festplatte lesen. Dieser Kreislauf, das Thrashing, kann eine Maschine nahezu völlig reaktionslos machen, ohne dass sie technisch je den Speicher ausgeht. Also wird nichts beendet, und nichts erholt sich.

Ohne Swap hat der Kernel keinen Ort, an dem er Speicher ablegen kann, der zugewiesen, aber selten genutzt ist, also nimmt er ihn stattdessen aus dem Cache.

Die Korrektur

Das Bootstrap-Skript des Servers legt jetzt bei jedem Start oder jeder Reparatur eines Servers eine 2 GB große Swap-Datei mit einer niedrigen Swappiness von 10 an. Eine niedrige Swappiness bedeutet, dass der Kernel weiterhin bevorzugt Arbeitsspeicher im RAM hält, aber selten berührte Seiten auf die Festplatte verschieben darf, statt Programmcode aus dem Cache zu drängen. Wir haben das live auf beide Produktionsserver und auf Staging angewendet, und die Deployment-Pipeline veröffentlicht das Bootstrap-Skript bei jedem Produktions-Deployment erneut, damit auch ein ersetzter Server es bekommt.

Was uns mehr beunruhigte: Der fehlerhafte Server blieb bestehen

Unsere Servergruppe nutzte EC2-Health-Checks, also „läuft die Maschine?“, und nicht Load-Balancer-Health-Checks, also „beantwortet sie Anfragen?“. Ein Server, den der Load Balancer aufgegeben hatte, galt für die Gruppe weiterhin als gesund. Er wurde nie ersetzt. Die Produktion wäre still und leise auf einem einzigen Server gelaufen, bis jemand es bemerkt und den anderen neu gestartet hätte.

Die Gruppe auf Load-Balancer-Health-Checks umzustellen klingt nach der naheliegenden Lösung, aber für uns ist es nicht unkompliziert: Beim Deployment antworten Server absichtlich mit einem Fehler auf ihren Health-Endpoint, damit der Balancer sie leert (siehe wie wir Deployments auf null 502er gebracht haben). Eine Gruppe, die Server auf dieses Signal hin ersetzt, würde sie mitten in jedem Deployment ersetzen.

Deshalb haben wir vorerst einen Schritt in unsere Deployment-Checkliste aufgenommen: Nach jedem Deployment prüfen wir den Zustand jedes Ziels im Load Balancer und nicht nur, ob der Deployment-Job durchlief. Bleiben Befehle an einen Server auf „Pending“ hängen, thrashed er. Dann starten wir ihn neu, er ist ohnehin schon aus der Rotation genommen.

Was wir daraus mitnehmen

  • „Nicht abgestürzt“ heißt nicht „gesund“. Thrashing erzeugt keinen Fehler, nur eine so extreme Langsamkeit, dass die Maschine nicht mehr antwortet.
  • Kleine Server brauchen Swap. Schon ein wenig davon, mit niedriger Swappiness, gibt dem Kernel einen Ort, an den er selten genutzten Speicher ablegen kann.
  • Speichergrenzen, die sich zu mehr als der Maschinenkapazität addieren, sind eine Wette. Manchmal verliert man sie. Wissen Sie, wann Sie sie abgeschlossen haben.
  • Wissen Sie, auf welchen Health-Check Ihre Autoskalierung vertraut. Ein Health-Check, der nur fragt „ist die VM an?“, ersetzt keinen Server, der aufgehört hat zu arbeiten.

Mythex · Pricing · Alles mit KI bauen · Vorlagen für Websites und Apps · Mythex im Vergleich · Glossar zum App-Bau mit KI · Kostenlose Tools für den App-Bau mit KI · English

English አማርኛ العربية Български বাংলা Bosanski Català Čeština Dansk Ελληνικά Español Español (España) Eesti فارسی Suomi Français Français (Canada) ગુજરાતી हिन्दी Hrvatski Magyar Հայերեն Bahasa Indonesia Íslenska Italiano 日本語 ქართული Қазақ тілі ಕನ್ನಡ 한국어 Lietuvių Latviešu Македонски മലയാളം Монгол मराठी Bahasa Melayu မြန်မာ Norsk bokmål Nederlands ਪੰਜਾਬੀ Polski Português (Brasil) Português (Portugal) Română Русский Slovenčina Slovenščina Soomaali Shqip Srpski Svenska Kiswahili தமிழ் తెలుగు ไทย Tagalog Türkçe Українська اردو Tiếng Việt 简体中文 繁體中文(香港) 繁體中文(台灣)