Pytanie brzmi niemal zawsze tak samo: w nocy strona odpowiadała po dwadzieścia sekund, a nad ranem wszystko samo wróciło do normy. Nie ma już czego oglądać — top odpowiada za bieżącą sekundę, a pytanie dotyczy trzeciej nad ranem. Poniżej: co naprawdę znaczą liczby obciążenia, które z nich trzeba czytać parami i dlaczego pojedynczy pomiar częściej wprowadza w błąd, niż pomaga.

Load average: trzy liczby i jedno częste nieporozumienie

uptime
cat /proc/loadavg
nproc

Te trzy liczby to średnie z jednej, pięciu i piętnastu minut. Porównuje się je nie z zerem, lecz z liczbą rdzeni, którą poda nproc. Wartość 8 na maszynie ośmiordzeniowej to pełne, ale zdrowe obciążenie: pracy jest dokładnie tyle, ile serwer jest w stanie wykonać. Wartość 2 na jednordzeniowym VPS to kolejka dwa razy dłuższa niż możliwości, a każde żądanie czeka na swoją kolej.

Stosunek trzech liczb do siebie wskazuje kierunek. Wartość minutowa wyraźnie wyższa od piętnastominutowej oznacza, że obciążenie rośnie właśnie teraz. Odwrotnie — szczyt już minął i widać jedynie jego ogon.

Teraz nieporozumienie, przez które te liczby najczęściej są źle rozumiane. Load average w Linuksie to nie „użycie procesora”. W odróżnieniu od innych systemów uniksowych Linux wlicza do niego nie tylko procesy wykonywane lub gotowe do wykonania, lecz także te w stanie D — w nieprzerywalnym śnie. Czyli czekające na dysk albo na sieciowy system plików. Stąd serwer z obciążeniem 12 i niemal bezczynnym procesorem: praca nie idzie, wszyscy stoją w kolejce.

Procesor czy dysk

Rozdzielenie tych dwóch przypadków to pierwsza rzecz, którą warto zrobić:

vmstat 1 5
iostat -x 1 3

W wyniku polecenia vmstat liczą się trzy kolumny. r to liczba procesów w kolejce do procesora, b — liczba zablokowanych w oczekiwaniu na wejście/wyjście, a wa — udział czasu, który procesor spędził bezczynnie, czekając na dysk. Utrzymujące się wa powyżej 10–15 % przy niewielkich us i sy znaczy, że serwer opiera się o dysk: dokładanie rdzeni nic nie da, te, które są, i tak są wolne.

Które procesy czekają, można wypisać z nazwy:

ps -eo state,pid,comm,wchan:30 | awk '$1 ~ /^D/'

Przykład z własnej praktyki. Suricata w domyślnej konfiguracji zapisuje około trzydziestu typów zdarzeń i nie ustawia dla nich żadnej rotacji — na serwerze produkcyjnym dało to 15 GB logów w dwa dni. Obciążenie objawiało się przy tym nie jako zajęty procesor, lecz właśnie jako I/O wait: system pisał bez przerwy, a cała reszta stała w kolejce za nim. Lekarstwem nie jest większy pakiet, tylko krótsza lista zdarzeń i skonfigurowana rotacja.

Przypadek odwrotny, czysto procesorowy i znacznie mniej oczywisty. Strona internetowa wywoływała apt na koncie użytkownika, na którym działa PHP-FPM. Root trzyma binarną pamięć podręczną w /var/cache/apt/pkgcache.bin — na hoście z kilkunastoma repozytoriami to 70 MB — i mapuje ją do pamięci za darmo. Zwykły użytkownik nie może pisać do tego katalogu i przy każdym wywołaniu buduje ten bufor od nowa. Pomiar na tej samej maszynie: 0,01 sekundy czasu procesora jako root wobec 4,2 sekundy jako użytkownik bez uprawnień. To samo polecenie, różnica czterysta razy, pomnożona przez każde otwarcie strony.

Wniosek z obu przypadków jest ten sam: obciążenie trzeba mierzyć, a nie zgadywać. Zmierzenie osobno każdego podejrzanego polecenia zajmuje pół godziny i zwykle wskazuje coś innego niż to, co podejrzewano na początku.

Pamięć: free pokazuje nie to, na co wygląda

free -h

Kolumna used sama w sobie mówi bardzo mało, a kolumna free wręcz wprowadza w błąd: Linux oddaje niewykorzystaną pamięć na bufor stron i zwraca ją aplikacjom na pierwsze żądanie. Czytać należy kolumnę available — ile można zająć, nie wchodząc w swap. Duży buff/cache nie jest problemem, tylko oznaką systemu działającego tak, jak powinien.

Przy swapie liczy się nie bieżąca wartość, lecz kształt. Urósł i wrócił do zera — był krótki szczyt. Urósł raz i tak został — szczyt już był, strony zostały wyparte i nikt ich nie wraca; serwer wygląda spokojnie, choć w pewnym momencie zabrakło mu pamięci. Czy wymiana trwa właśnie teraz, pokazują kolumny si i so w vmstat; niezerowe wartości to najbardziej odczuwalna dla użytkownika postać spowolnienia.

Jeśli podczas nocnego załamania jakiś proces po prostu zniknął, wyjaśnienie zwykle jest tutaj:

journalctl -k --since yesterday | grep -i "out of memory"
dmesg -T | grep -i "killed process"

Wiersz w rodzaju Out of memory: Killed process 1234 (mysqld) rozstrzyga sprawę lepiej niż jakikolwiek wykres: baza nie „padła sama”, zatrzymało ją jądro, bo skończyła się pamięć.

Kto to robi

ps aux --sort=-%cpu | head -10
ps aux --sort=-%mem | head -10

Jedno zastrzeżenie: %CPU w ps to średnia z całego życia procesu, więc krótki wyskok w tej liście ginie. Do niego potrzebny jest top albo pidstat 1 5, które liczą w przedziale czasu.

Kiedy obciążenie staje się kwestią bezpieczeństwa

Równe 100 % procesora nocą, przy procesie o nic nieznaczącej nazwie i katalogu roboczym w /tmp albo /dev/shm, to klasyczny obraz koparki, a nie strony, która urosła. Skok ruchu przychodzącego wraz ze wzrostem zapisów do logów to trwający atak na hasła. Gwałtownie rosnąca objętość logów to zwykle burza fałszywych alarmów jakiegoś filtra.

Osobną kategorią są same narzędzia ochronne. Na jednym z naszych serwerów load utrzymywał się w okolicach trzech przy zupełnym braku odwiedzających, a na szczycie ps według skumulowanego czasu procesora stały nie strona i nie baza, lecz CrowdSec, fail2ban, Falco i Suricata. To nie awaria i nie powód, by je wyłączać, ale cenę ochrony warto znać w liczbach: na słabym VPS jest odczuwalna.

Po co obserwować

Wszystko powyższe odpowiada na pytanie „co dzieje się teraz”. Porannego pytania — co było o trzeciej w nocy — te polecenia nie zamykają: danych z minionej nocy nie ma, jeśli nikt ich nie zapisał. A stawianie Prometheusa i Grafany dla jednego VPS jest wątpliwe, bo stos obserwujący wychodzi cięższy od obserwowanego serwera.

Wystarczy wiersz w bazie co pięć minut i jedna strona, która rysuje z niego ostatnią dobę: load average, użycie procesora i osobno I/O wait, pamięć i swap, odczyt i zapis dysku, zapełnienie partycji, i-węzły, deskryptory plików, połączenia. Na jednej osi czasu pary czyta się gołym okiem: wysokie wa przy spokojnym procesorze, swap, który nigdy nie wrócił do zera, deskryptory zbliżające się do limitu — nadchodzący błąd too many open files staje się widoczny na kilka godzin przed tym, zanim wystąpi. Jak to wygląda zebrane razem, pokazuje strona demonstracyjna poniżej.