Диагностика сервера: OOM, забитые диски, высокая нагрузка и зависшие процессы
Ночной звонок, мониторинг красный, сервис не отвечает. В такие минуты важно не метаться, а идти по одному и тому же порядку: что случилось, что мешает, как восстановить и как не допустить повтора. Четыре самых частых причины недоступности Linux-сервера — убитый процесс из-за нехватки памяти (OOM), диск, забитый до отказа, высокая нагрузка и зависшие процессы в состоянии D или Z. Разберём каждую с реальными командами.
Все примеры на хосте srv-linux01 (192.168.10.11) в сети 192.168.10.0/24, домен itpages.lab.local. Подключайся по SSH, а если вход не удаётся из-за нагрузки, пробуй консоль виртуализации или заранее настроенный доступ.
Шаг 1. Первые две минуты: общая картина
Прежде чем лечить, собери симптомы. Не перезагружай сервер, пока не снял данные: после ребута половина улик исчезнет.
uptime
last -x | head -5
dmesg -T | tail -30
journalctl -p err -b --no-pager | tail -30
df -hT
free -h
ps aux --sort=-%cpu | head -8
Команда last -x покажет недавние перезагрузки и остановки, а журнал ошибок текущей загрузки обычно сразу указывает на виновника. Если сервер тормозит так, что команды выполняются минутами, сначала посмотри на нагрузку отдельным лёгким вызовом: cat /proc/loadavg.
Шаг 2. OOM: кто убил процесс
Когда памяти не хватает, ядро запускает OOM-killer и пристреливает процесс с наибольшим «счётом». Признак: сервис внезапно пропал, а в его журнале нет ошибок. Ищем следы:
journalctl -k | grep -iE 'out of memory|oom-kill|killed process'
dmesg -T | grep -i -B2 -A12 'invoked oom-killer'
grep -i 'killed process' /var/log/syslog | tail
В отчёте ядра смотри строку Killed process с именем и значением rss, а также таблицу процессов выше: она показывает, кто занимал память на момент убийства. Частые виновники — база данных с раздутым буфером, Java без ограничения кучи, утечка в приложении. Дальше действуй по ситуации:
ps -eo pid,user,rss,vsz,cmd --sort=-rss | head
cat /proc/$(pidof -s mysqld)/oom_score
sudo systemctl status nginx --no-pager | head
swapon --show
Для критичных служб можно защитить процесс от OOM и ограничить аппетиты остальных через systemd. Это работает надёжнее, чем ручной oom_score_adj:
sudo systemctl edit mysql
[Service]
OOMScoreAdjust=-800
sudo systemctl edit app-worker
[Service]
MemoryMax=2G
MemoryHigh=1536M
MemoryHigh включает мягкое ограничение с замедлением процесса, MemoryMax жёсткий потолок, после которого убивается только эта служба, а не соседние. Кроме того, добавь своп разумного размера, чтобы короткие всплески не заканчивались убийством: файл на 2 ГБ создаётся командами sudo fallocate -l 2G /swapfile, sudo chmod 600 /swapfile, sudo mkswap /swapfile и sudo swapon /swapfile.
Шаг 3. Диск забит: место и inode
Классика: приложение не пишет, база не стартует, а df показывает 100 процентов. Ищем, где место, и не забываем про inode, потому что диск может быть свободен по байтам и пуст по индексным дескрипторам.
df -h
df -i
sudo du -xh / --max-depth=1 2>/dev/null | sort -rh | head
sudo du -xh /var --max-depth=2 2>/dev/null | sort -rh | head -15
sudo find / -xdev -type f -size +500M -exec ls -lh {} \; 2>/dev/null
Флаг -x не даёт du и find выходить за пределы одной файловой системы, что спасает от зависания на сетевых монтированиях. Типичные источники: журналы в /var/log, journald, кэш пакетов, образы и слои Docker, дампы падений. Быстрая очистка безопасными способами:
sudo journalctl --vacuum-size=200M
sudo apt clean
sudo docker system df
sudo docker system prune -f
sudo logrotate -f /etc/logrotate.conf
Особый случай: ты удалил огромный лог, а место не освободилось. Значит, файл удалён, но процесс держит его открытым. Найди такие файлы и перезапусти держателя либо обнули файл через дескриптор:
sudo lsof +L1 | head
sudo lsof -nP | grep '(deleted)' | head
# обнулить, не перезапуская процесс (PID и FD берём из вывода lsof)
sudo truncate -s 0 /proc/1234/fd/5
Если проблема в inode (df -i показывает 100 процентов), ищи каталог с миллионами мелких файлов: sudo find /var -xdev -type d -size +1M или подсчёт for d in /var/*; do echo $(sudo find $d -xdev | wc -l) $d; done | sort -rn | head. Чаще всего это сессии PHP или очередь почты.
Шаг 4. Высокая нагрузка: разделяем CPU и ожидание диска
Высокий load average может означать совсем разные вещи. Определи, что именно:
top -b -n 1 | head -12
vmstat 1 5
ps -eo state,pid,cmd | awk '$1 ~ /^[DR]/' | head -20
sudo iotop -oPa -n 3 -b
Если много процессов в состоянии R и высок us, виноват расчёт: ищи прожорливый процесс и смотри, не накручен ли он (майнер, зациклившийся скрипт, шторм запросов). Если много в состоянии D и высок wa, всё упирается в диск или сетевое хранилище, и убивать процессы бесполезно. Проверь здоровье диска и сеть до хранилища:
sudo smartctl -H /dev/sda
sudo dmesg -T | grep -iE 'i/o error|ata|nvme|blk_update'
mount | grep nfs
sudo systemctl status nfs-client.target --no-pager
Нагрузку от лавины запросов помогает понять анализ журнала доступа: awk '{print $1}' /var/log/nginx/access.log | sort | uniq -c | sort -rn | head. Если один адрес даёт тысячи запросов, это может быть бот или атака, и тогда блокировка на уровне файрвола оказывается самым быстрым лекарством: sudo ufw deny from 203.0.113.50.
Шаг 5. Зависшие процессы: D, Z и «не убивается»
Процесс не реагирует на kill? Посмотри его состояние:
ps -eo pid,ppid,stat,wchan:30,etime,cmd | awk '$3 ~ /[DZ]/'
cat /proc/PID/status | grep -E 'State|PPid'
sudo cat /proc/PID/stack
sudo strace -p PID
Состояние Z (зомби) означает, что процесс завершился, но родитель не забрал код возврата. Зомби не потребляет ресурсов, кроме строки в таблице, и убить его нельзя, потому что он уже мёртв. Решение: сигнал родителю (kill -CHLD PPID) или перезапуск родителя. Состояние D (непрерываемый сон) почти всегда значит ожидание ввода-вывода, например зависший NFS или сбойный диск, и сигнал не подействует, пока операция не завершится. Что можно сделать:
sudo umount -f -l /mnt/nfs-share
sudo systemctl kill -s SIGKILL app-worker.service
sudo kill -9 PID
sudo fuser -vm /mnt/nfs-share
Ленивое отмонтирование -l снимает точку монтирования из дерева, и процессы, которые ждали хранилище, после восстановления связи завершатся. Если ничего не помогает, а диск или сеть не вернуть, остаётся перезагрузка, но к ней идут последней и уже с собранными данными (вывод dmesg, ps и stack).
Шаг 6. Фиксируем причину и страхуемся
После тушения пожара запиши, что произошло, и поставь сторожей, чтобы узнать о проблеме раньше пользователей. Пример простой проверки свободного места, которую можно запускать каждые пять минут через cron или systemd timer:
#!/bin/bash
THRESHOLD=85
df -hP --output=pcent,target -x tmpfs -x devtmpfs | tail -n +2 | while read -r pct mnt; do
use=${pct%\%}
if [ "$use" -ge "$THRESHOLD" ]; then
logger -t diskwatch "WARN: $mnt заполнен на ${use}%"
fi
done
Включи постоянное хранение журнала systemd (sudo mkdir -p /var/log/journal && sudo systemctl restart systemd-journald), чтобы логи переживали перезагрузку, установи sysstat для истории нагрузки (sar), а kdump или хотя бы сохранение vmstat в файл помогут после следующего инцидента. Хорошее правило: у каждого инцидента есть короткий разбор из трёх строк (что сломалось, как нашли, что изменили).
Шаг 7. Дополнительные инструменты для разбора
Когда базовых команд не хватает, подключай более глубокие средства. Для истории нагрузки поставь sysstat и включи сбор: в /etc/default/sysstat поставь ENABLED="true", и каждые десять минут будут сохраняться снимки. Потом можно ответить на вопрос «что было вчера в три часа ночи»:
sudo systemctl enable --now sysstat
sar -u -s 02:30:00 -e 03:30:00 -f /var/log/sysstat/sa$(date -d yesterday +%d)
sar -r -f /var/log/sysstat/sa$(date -d yesterday +%d) | tail
sar -q -f /var/log/sysstat/sa$(date -d yesterday +%d) | tail
Ключ -u даёт процессор, -r память, -q длину очереди и load average. Именно такая история позволяет отличить внезапный всплеск от медленной утечки, которая копилась неделю. Для процессов-убийц, которые живут по минуте и не попадают в top, помогает auditd или программа atop с записью каждую минуту: sudo atop -r /var/log/atop/atop_$(date +%Y%m%d).
Шаг 8. Когда сервер не пускает по SSH
Самый неприятный вариант, когда нагрузка такая, что даже вход не проходит. Заранее продумай запасные пути: консоль гипервизора или IPMI, второй SSH-порт с пониженным приоритетом, а также ограничения, которые не позволяют одному процессу задушить систему. Если ты уже внутри, но всё еле шевелится, используй приоритеты:
sudo renice -n 19 -p PID
sudo ionice -c3 -p PID
sudo systemctl stop heavy-job.service
sudo kill -STOP PID # заморозить, потом kill -CONT PID
Сигнал STOP замораживает процесс без потери его состояния, и ты получаешь время, чтобы разобраться, не убивая работу. Ещё один запасной вариант, магическая клавиша SysRq. Если включено, echo f | sudo tee /proc/sysrq-trigger вызывает OOM-killer вручную, а последовательность s, u, b (sync, remount read-only, reboot) безопаснее обычного нажатия на кнопку питания. Включается она так: echo 1 | sudo tee /proc/sys/kernel/sysrq и фиксируется в sysctl.d.
Шаг 9. Профилактика: что поставить заранее
Лучший разбор инцидента тот, которого не было. Минимальный набор страховок для srv-linux01 и подобных машин: оповещения о заполнении диска и inode на уровне 80 процентов, ограничение размера журналов (SystemMaxUse=500M в /etc/systemd/journald.conf), лимиты памяти для служб, своп разумного размера, отдельный раздел под /var, чтобы логи не заполнили корень, и проверка SMART раз в сутки. Добавь в мониторинг метрику «число OOM-убийств» (счётчик oom_kill в /proc/vmstat), и ты узнаешь о первой жертве до того, как умрёт основной сервис.
grep oom_kill /proc/vmstat
sudo sed -i 's/^#SystemMaxUse=.*/SystemMaxUse=500M/' /etc/systemd/journald.conf
sudo systemctl restart systemd-journald
sudo tune2fs -m 1 /dev/sdb1
Последняя команда уменьшает зарезервированное под root место на больших разделах данных с пяти процентов до одного, и это освобождает сотни гигабайт на больших дисках, но делать так на корневом разделе не стоит.
Частые ошибки и как их избежать
- Перезагрузка как первое действие. Ребут возвращает сервис на пару часов, но стирает память, состояние процессов и часть журналов, а проблема вернётся. Сначала сними dmesg, ps, free, df и журнал, потом решай про перезагрузку.
- Удаление файлов вслепую. Команда
rm -rfпо догадке в /var может снести базу или сокеты, а удалённый, но открытый лог места не освободит. Смотри du, lsof +L1 и очищай через штатные механизмы: journalctl —vacuum, logrotate, docker prune. - Лечение симптома вместо причины. Рестарт упавшей службы без разбора OOM приведёт к тому, что ядро убьёт её снова через час. Найди, кто съел память или диск, поставь лимиты через systemd и мониторинг с порогом 80-85 процентов, чтобы предупреждение приходило заранее.
Что дальше
Администрирование Linux — это набор связанных навыков, вот соседние темы серии: