Brakujący plik wymiany, który zamroził nasze serwery
24 września jeden z naszych serwerów produkcyjnych przestał odpowiadać. Load balancer oznaczył go jako niezdrowy, a nasze narzędzie do zdalnych poleceń nie mogło do niego dotrzeć — ale nic nie padło i nic nie zostało zabite za zbyt duże zużycie pamięci. Serwer się trashował: bez przestrzeni wymiany i przy kontenerach mogących zużyć więcej pamięci, niż miała maszyna, Linux spędzał czas na wyrzucaniu i ponownym czytaniu plików potrzebnych do działania, prawie nie posuwając się naprzód. Dodanie pliku wymiany 2 GB na każdym serwerze to naprawiło. Ciekawsza lekcja dotyczyła kontroli stanu.
Jak wyglądało „zawieszenie”
- Kontrole stanu load balancera przekraczały limit czasu na tym serwerze.
- Polecenia wysyłane przez AWS Systems Manager czekały w stanie „Pending” i nigdy nie zostały uruchomione.
- W logach nie było zabicia przez brak pamięci. Żaden proces nie padł.
Tydzień wcześniej przenieśliśmy produkcję na mniejsze serwery ARM z 2 GB RAM każdy. Nasz serwer stagingowy — jeszcze mniejszy — zamarzł w ten sam sposób podczas rozpakowywania obrazu kontenera w trakcie wdrożenia.
Dlaczego nie ma błędu pamięci?
Linux zabija proces dopiero wtedy, gdy naprawdę zabraknie pamięci, którą da się odzyskać. Wcześniej odzyskuje pamięć z page cache — kopii plików trzymanych w RAM, w tym kodu każdego uruchomionego programu.
Na naszych serwerach łączne limity pamięci kontenerów sumowały się do około 3,3 GB na maszynie mającej około 1,8 GB użytecznej pamięci. Zwykle nie zużywają wszystkiego naraz. Gdy jednak tak się działo, jądro ciągle wyrzucało kod programów z cache, żeby zrobić miejsce, a potem musiało go czytać z dysku z powrotem, w chwili gdy ten kod znów był potrzebny. Ten cykl — trashing — potrafi sprawić, że maszyna staje się prawie całkowicie nieosiągalna, a technicznie nigdy nie zabraknie pamięci. Więc nic nie jest zabijane i nic nie wraca do siebie.
Bez wymiany jądro nie ma dokąd odłożyć pamięci przydzielonej, ale rzadko używanej, więc musi ją zabrać z cache.
Naprawa
Skrypt bootstrapowy serwera tworzy teraz plik wymiany 2 GB z niskim parametrem swappiness (10) przy każdym starcie lub naprawie serwera. Niski swappiness oznacza, że jądro nadal woli trzymać pamięć roboczą w RAM, ale rzadko dotykane strony mogą trafiać na dysk zamiast wypychać kod programów z cache. Zastosowaliśmy to na żywo na obu serwerach produkcyjnych i na stagingu, a pipeline wdrożeniowy publikuje skrypt bootstrapowy przy każdym wdrożeniu produkcyjnym, żeby wymieniony serwer też go dostał.
Część, która martwiła nas bardziej: niezdrowy serwer został
Nasza grupa serwerów używała kontroli stanu EC2 — „czy maszyna działa?” — a nie kontroli stanu load balancera — „czy odpowiada na żądania?”. Więc serwer, z którego load balancer zrezygnował, dla grupy wciąż był zdrowy. Nigdy nie zostałby wymieniony. Produkcja po cichu działałaby na jednym serwerze, aż ktoś to zauważy i zrestartuje drugi.
Przełączenie grupy na kontrole stanu load balancera brzmi jak oczywista poprawka, ale u nas nie jest proste: podczas wdrożenia serwery celowo odpowiadają błędem na swój endpoint zdrowia, żeby balancer je wyłączył z ruchu (zobacz jak doprowadziliśmy wdrożenia do zera błędów 502). Grupa wymieniająca serwery na podstawie tego sygnału wymieniałaby je w środku każdego wdrożenia.
Dlatego na razie dodaliśmy krok do listy kontrolnej wdrożeń: po każdym wdrożeniu sprawdzamy stan każdego celu na load balancerze, a nie tylko to, czy zadanie wdrożeniowe przeszło. Jeśli polecenia do serwera czekają w stanie „Pending”, serwer się trashuje — zrestartuj go; i tak jest już wyjęty z rotacji.
Co z tego wynieśliśmy
- „Nie pada” to nie znaczy „zdrowy”. Trashing nie daje błędu, tylko spowolnienie tak skrajne, że maszyna przestaje odpowiadać.
- Małe serwery potrzebują wymiany. Nawet niewielkiej, z niskim swappiness — daje to jądru miejsce na zimną pamięć.
- Limity pamięci, które sumują się do więcej niż ma maszyna, to zakład. Czasem go przegrasz. Wiedz, kiedy go zawierasz.
- Wiedz, której kontroli stanu ufa twoje autoskalowanie. Kontrola, która pyta tylko „czy VM jest włączona?”, nie wymieni serwera, który przestał działać.