На сервере с четырьмя потоками и 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, то есть не утекали, но лежать им было незачем.

Что я вынес

  1. нагрузка без посетителей — смотрите расписания и фоновые очереди;
  2. возраст процесса — через etimes, а не на глаз;
  3. высокий load без процессора — память и диск;
  4. «растёт» — проверьте, создаётся новое или перезаписывается старое;
  5. чужие процессы и образы не удаляйте по догадке: спросите или найдите, кто ими пользуется.

Сервер тормозит, а в top ничего особенного?

Пришлите вывод vmstat и список процессов — найду, кто на самом деле ест ресурсы.

· полезно?
АЧ
Алексей Чикун веб-разработчик, команда TRIOHEAD

Делаю сайты и сервисы целиком: от вёрстки до сервера. Пишу о том, что проверил на своих проектах.

Комментарии

// загружаю комментарии…

Комментарии публикуются после проверки. Ссылки и HTML не принимаются.