На сервере с четырьмя потоками и 3 ГБ памяти любая лишняя нагрузка видна сразу: сайты начинают отвечать медленнее, дашборд краснеет, load average уходит за десять. Я разбирал такие случаи несколько раз, и в каждом виновник был не там, где казалось. А мой первый способ замера почти каждый раз оказывался неверным. Пять разборов и приёмы, которые остались после них.
Это двенадцатая часть цикла «Домашняя инфраструктура».
1. «Процессор 99%»: процесс, работающий 21 час
Load 4,41 на четырёх потоках. Виновник нашёлся по процессорному времени: прогон линтера в одноразовом контейнере, 100% ядра, запущен 21 час назад и забыт. Вывод он отправлял в tail -20, а tail ничего не печатает, пока процесс не закончится, — поэтому со стороны казалось, что он просто молчит. После остановки load упал 4,41 → 1,95 → 0,66.
Рядом нашёлся забытый наблюдатель сборки, 45 часов и 300 МБ памяти. С ним я поступил иначе: наблюдатель может быть живой работой, которую человек оставил специально, поэтому гасить — только спросив. А прогон линтера, 21 час не выдавший ни строчки, брошен точно.
Как правильно узнать возраст процесса
ELAPSEDвида21:23:57— это 21 час, а не 21 минута. Формат[[дни-]часы:]минуты:секунды;docker inspect … .Createdпечатает время в UTC, а сервер живёт по Москве — отсюда «контейнер создан в будущем»;- надёжно:
ps -o pid,lstart,etimes,cmd— точное время старта и возраст в секундах; - пустой
whoне доказывает, что процесс никто не ждёт: его могли запустить из скрипта или IDE.
2. Зомби, которых никто не забирает
В списке процессов — 16 штук [node] <defunct>. Пятнадцать из них — дети sleep infinity, который служил главным процессом контейнера для разработки. sleep не умеет «забирать» завершившихся детей, и они висят зомби вечно. kill -CHLD не помог — вылечил только перезапуск контейнера. Профилактика — одна строка в compose:
services:
dev:
init: true # маленький init в роли PID 1 забирает осиротевшие процессы
3. «Процессор 50%, я ничего не делал»
Фоновая нагрузка без единого посетителя. Разбор показал две вещи. Первая: wp-cron пяти сайтов запускается раз в минуту, и все пять — в одну и ту же секунду. Каждый прогон занимает 2,6–3,5 с, и раз в минуту сервер получает пять PHP-процессов разом. Вторая: прогрев кэша на тестовой копии одного сайта за два часа съел 3150 секунд процессорного времени — обходил страницы, которые никто не открывает.
Когда сервер нагружен «без причины», первым делом смотрите расписания: крон, таймеры systemd, задания планировщиков в контейнерах. Посетителей может не быть, а заданий — полно.
4. Полтора ядра круглые сутки
Через три недели — новая фоновая нагрузка: полтора ядра днём и ночью. Тут было три источника сразу:
- очередь перекодирования картинок в AVIF после обновления данных тестовой копии: 18 350 задач по 8 секунд — около 46 часов работы;
- тот же обходчик прогрева кэша на тестовой копии: 18 512 запросов в сутки;
- база одной из копий — 126–171% процессора и 36 запросов в секунду при нуле посетителей.
Отдельный вопрос был про очередь картинок: она за это время выросла с 6 099 до 11 831 задачи. Зациклилась или работает над новым? Отличить помог приём с временем изменения файлов:
# перезаписанные за час (зацикливание переписывает старое)
find /path/to/cache -mmin -60 ! -cmin -60 | wc -l
# созданные за час (новая работа)
find /path/to/cache -cmin -60 | wc -l
Перезаписанных — ноль: очередь росла потому, что в теме добавили новые размеры картинок, а не потому что что-то крутилось по кругу. Решение для тестовых копий — выключать на них прогрев кэша совсем: посетителей нет, а цена — те же полтора ядра.
5. Load 41 и load 11 — две разные болезни
Load 41 — очередь из 39 процессов на четыре потока. Обходчик прогрева сделал 377 запросов за 10 минут, пул PHP-FPM разогнался с 8 до 52 процессов за 12 секунд. Такие пики на минуту кладут всё на сервере — включая журнал работ и сторожа, которые должны были бы об этом сообщить.
Load 11 при почти нулевой загрузке процессора контейнерами — совсем другое. Это подкачка: vmstat показывал подкачку до 300 страниц в секунду, в своп уехало 2,2 ГБ, из них 710 МБ — процесс сборки фронта. Load average считает не только тех, кто работает, но и тех, кто ждёт диска. На машине с малой памятью высокий load без загрузки процессора — признак нехватки памяти, а не процессора.
vmstat 1 5 # si/so — подкачка, wa — ожидание диска; если они высокие, а us/sy низкие — дело в памяти
Бонус: «диск отъедается по гигабайту в сутки»
Последний разбор — про место. Поиск find -mtime -1 показывал около гигабайта изменённых файлов за сутки. Но это были перезаписанные файлы — журналы баз, перепаковка git, — а не новые. Реальный прирост загрузок сайтов за 30 дней — около 115 МБ. И ещё две ловушки:
- в Docker 29 образы лежат в
/var/lib/containerd(у меня 9,4 ГБ), а не в/var/lib/docker(123 МБ). Ищете, куда делось место, — смотрите оба; - «неиспользуемые образы» по списку контейнеров считать нельзя: выключенные через
downсайты контейнеров не имеют вовсе, а часть образов — базовые для сборок. Я так однажды удалил образ, которым пользуется обёртка сборки, и возвращал его. Правильно — искать упоминания образа вimage:иFROM.
Реальная находка — архивы плагина резервного копирования WordPress: 2,26 ГБ в 29 файлах, часть — двухлетней давности. Внутри — копии wp-config.php с паролями баз. Снаружи они отдавали 404, то есть не утекали, но лежать им было незачем.
Что я вынес
- нагрузка без посетителей — смотрите расписания и фоновые очереди;
- возраст процесса — через
etimes, а не на глаз; - высокий load без процессора — память и диск;
- «растёт» — проверьте, создаётся новое или перезаписывается старое;
- чужие процессы и образы не удаляйте по догадке: спросите или найдите, кто ими пользуется.
Сервер тормозит, а в top ничего особенного?
Пришлите вывод vmstat и список процессов — найду, кто на самом деле ест ресурсы.
// загружаю комментарии…