Вопрос обычно звучит одинаково: ночью сайт отвечал по двадцать секунд, к утру всё прошло само. Смотреть уже нечего — top показывает текущую секунду, а спрашивают про три часа ночи. Ниже — что означают цифры нагрузки, какие из них надо читать парами и почему разовый замер чаще вводит в заблуждение, чем помогает.

Load average: три числа и одно частое заблуждение

uptime
cat /proc/loadavg
nproc

Три числа — среднее за минуту, за пять и за пятнадцать минут. Сравнивать их надо не с нулём, а с количеством ядер, которое покажет nproc. Значение 8 на восьмиядерной машине — это полная, но здоровая загрузка: работы ровно столько, сколько сервер способен выполнять. Значение 2 на одноядерном VPS — очередь вдвое длиннее возможностей, и каждый запрос ждёт своей очереди.

Соотношение чисел между собой говорит о направлении. Минутное заметно выше пятнадцатиминутного — нагрузка растёт прямо сейчас. Наоборот — пик уже прошёл, и вы смотрите на хвост.

Теперь заблуждение, из-за которого эти цифры чаще всего понимают неправильно. Load average в Linux — не «загрузка процессора». В отличие от других Unix-систем, Linux считает в него не только процессы, выполняющиеся или готовые выполняться, но и те, что находятся в состоянии D — непрерываемом сне. А это ожидание диска или сетевой файловой системы. Отсюда сервер, у которого load 12, а процессор почти простаивает: работа не идёт, все стоят в очереди.

Процессор или диск

Разделить эти два случая — первое, что стоит сделать:

vmstat 1 5
iostat -x 1 3

В выводе vmstat смотрят на три колонки. r — сколько процессов стоит в очереди на процессор, b — сколько заблокировано в ожидании ввода-вывода, wa — доля времени, которую процессор простоял, ожидая диск. Устойчивый wa выше 10–15 % при небольших us и sy означает, что сервер упирается в диск: добавлять ядер бессмысленно, они и так свободны.

Кто именно ждёт, видно поимённо:

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

Пример из собственной практики. Suricata со стандартным конфигом пишет три десятка типов событий и ротацию за собой не настраивает — на рабочем сервере это дало 15 ГБ логов за двое суток. Нагрузка при этом выражалась не в занятом процессоре, а именно в ожидании диска: система непрерывно писала, всё остальное стояло в очереди за ней. Лечится это не увеличением тарифа, а ограничением списка событий и настроенной ротацией.

Обратный случай, чисто процессорный и куда менее очевидный. Веб-страница вызывала apt от имени пользователя, под которым работает PHP-FPM. Root держит бинарный кэш /var/cache/apt/pkgcache.bin — на хосте с десятком репозиториев это 70 МБ — и подключает его к памяти бесплатно. Обычный пользователь писать в этот каталог не может и пересобирает кэш заново при каждом вызове. Замер на одной и той же машине: 0,01 секунды процессорного времени от root против 4,2 секунды от непривилегированного пользователя. Одна и та же команда, разница в четыреста раз, и умножалась она на каждое открытие страницы.

Общий вывод из обоих случаев один: нагрузку надо измерять, а не угадывать. Замер каждой подозрительной команды по отдельности занимает полчаса и обычно указывает совсем не на то, что подозревали в начале.

Память: free показывает не то, что кажется

free -h

Колонка used сама по себе почти ни о чём не говорит, а колонка free вводит в заблуждение: Linux отдаёт незанятую память под кэш страниц и возвращает её приложениям по первому требованию. Смотреть надо на available — сколько памяти можно занять, не уходя в swap. Большой buff/cache при этом не проблема, а признак нормально работающей системы.

Со swap важна не текущая величина, а форма. Вырос и вернулся к нулю — был кратковременный пик. Вырос однажды и остался — пик уже случился, страницы вытеснены, и никто их обратно не поднимает: сервер выглядит спокойным, хотя памяти ему в какой-то момент не хватило. Идёт ли обмен прямо сейчас, показывают колонки si и so в vmstat; их ненулевые значения — самая заметная для пользователя форма торможения.

Если во время ночного провала процесс просто исчез, объяснение обычно здесь:

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

Строка вида Out of memory: Killed process 1234 (mysqld) закрывает вопрос лучше любых графиков: база не «упала сама», её остановило ядро, потому что память кончилась.

Кто это делает

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

Одна оговорка: %CPU в ps — среднее за всё время жизни процесса, поэтому короткая вспышка в этом списке теряется. Для неё нужен top или pidstat 1 5, которые считают за интервал.

Когда нагрузка становится вопросом безопасности

Ровные 100 % процессора по ночам, с процессом, у которого невнятное имя и рабочий каталог в /tmp или /dev/shm, — это классическая картина майнера, а не «сайт вырос». Всплеск сетевого приёма вместе с ростом записи в логи — идущий перебор паролей. Резко подскочивший объём журналов — обычно шторм ложных срабатываний фильтров.

Отдельная категория — сами средства защиты. На одном из наших серверов load держался около трёх при полном отсутствии посетителей, и в верхушке ps по накопленному процессорному времени стояли не сайт и не база, а CrowdSec, fail2ban, Falco и Suricata. Это не авария и не повод их выключать, но цену защиты полезно знать в цифрах: на слабом VPS она заметна.

Смысл наблюдения

Всё перечисленное отвечает на вопрос «что происходит сейчас». Утреннее «что было в три часа ночи» этими командами не закрывается: данных за прошедшую ночь нигде нет, если их никто не записал. При этом разворачивать ради одного VPS связку Prometheus и Grafana сомнительно — наблюдающий стек выходит тяжелее наблюдаемого сервера.

Достаточно строки в базе раз в пять минут и одной страницы, которая рисует по ней последние сутки: load average, загрузка процессора и отдельно I/O wait, память и swap, чтение и запись диска, заполнение раздела, inodes, файловые дескрипторы, соединения. В одном масштабе времени пары читаются глазами: высокий wa при спокойном процессоре, swap, который не вернулся к нулю, дескрипторы, подошедшие к лимиту, — будущая ошибка too many open files становится видна за несколько часов до того, как случится. Как это выглядит собранным — на странице демо ниже.