MAATRIX / Блог / Диск заполнился на 100%: что отвалилось первым и в каком порядке

Диск заполнился на 100%: что отвалилось первым и в каком порядке

MAATRIX

Диск на сервере закончился — и вместо одного понятного алерта вы получаете россыпь разрозненных инцидентов: сначала не пишутся логи, потом падает база, потом перестаёт отвечать сайт. Каждый по отдельности выглядит как отдельная поломка, и первые полчаса уходят на то, чтобы понять, что все они — следствие одной и той же причины. Разберём, в каком порядке обычно всё ломается, как быстро найти виновника заполнения диска и как восстановиться, не удалив по ошибке что-то нужное.

Первые симптомы: кто падает первым и почему это сбивает с толку

Диск не заполняется мгновенно — он ползёт к 100% часами или днями, и то, что ломается первым, редко совпадает с тем, что съело место. Порядок отказов обычно такой:

  1. Приложение перестаёт писать логи или временные файлы. Первым откликается тот сервис, который чаще всего пишет на диск — не обязательно тот, что его заполнил. Веб-приложение, которое активно логирует запросы, начнёт кидать ошибки записи задолго до того, как реально закончится место у того, кто виноват — например, у базы данных, которая копит WAL в соседнем разделе.
  2. База данных отказывается писать. PostgreSQL и MySQL держат минимальный запас на диске для журналов транзакций (WAL / binlog) и временных таблиц. Когда места не хватает, транзакции начинают падать с ошибками вида could not write to file или Disk full, а иногда база и вовсе уходит в read-only или останавливается, чтобы не повредить данные.
  3. systemd-journald и rsyslog перестают принимать записи. Если под /var/log кончилось место, демон логирования либо ротирует агрессивно, либо просто теряет часть сообщений — и в этот момент диагностика усложняется вдвойне: именно логи обычно подсказывают причину, а их-то и не хватает.
  4. Веб-сервер (nginx, php-fpm) начинает отдавать 502/503. Он сам почти не пишет на диск, кроме access/error-логов, поэтому падает позже остальных — обычно тогда, когда уже не может ни писать логи, ни создавать временные файлы для upstream-буферизации.
  5. SSH и системные утилиты подвисают. Это самый неприятный момент: даже apt, useradd или простой touch начинают падать с No space left on device, потому что файловой системе не хватает места даже на служебные операции журналирования (ext4/xfs при 100% забитом разделе может отказывать в записи даже для чтения метаданных).

Путаница в том, что администратор видит падение сервиса №1 (например, веб-приложения) и начинает чинить именно его — перезапускать, смотреть конфиги, — хотя корень проблемы совсем в другом месте диска, куда он ещё не заглядывал. Первое правило разбора такого инцидента: не чинить симптом, пока не подтверждено, что диск действительно полон.

Диагностика: df -h находит проблему, но не виновника

Первая команда, которую стоит выполнить при любых странных ошибках записи — df -h:

df -h
Filesystem      Size  Used Avail Use% Mounted on
/dev/vda1        40G   40G     0 100% /
/dev/vda15      105M  6.1M   99M   6% /boot/efi

Это подтверждает диагноз, но не говорит, что именно заняло место. Важный нюанс, о котором часто забывают: df показывает свободное место на файловой системе, а не то, сколько байт реально «видно» изнутри — если диск размечен как LVM или используется overlay для контейнеров, стоит также проверить:

df -hT
lsblk

df -hT покажет тип файловой системы (важно, если это overlay2 у Docker — там расчёт места идёт иначе), а lsblk — реальную разметку дисков и логических томов. Иногда оказывается, что раздел / забит на 100%, а рядом есть незанятый LVM-том, который просто забыли примонтировать или расширить — это не решение проблемы виновника, но может дать время на диагностику без спешки.

Ещё одна ловушка: df иногда показывает занятое место даже после удаления файлов. Это происходит, когда файл удалён (rm), но всё ещё открыт каким-то процессом — файловая система не освобождает блоки, пока не закроется последний файловый дескриптор. Проверить это можно так:

lsof +L1

Команда покажет файлы с нулевым числом ссылок (unlinked), которые всё ещё держит открытым какой-то процесс — классический случай, когда лог-файл удалили вручную, а сервис, который его пишет, продолжает писать в «удалённый», но не освобождённый inode. Решение — не только удалить файл, но и перезапустить (или послать сигнал на переоткрытие логов) процессу, который его держит.

Нужен сервер под эту задачу?

Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Арендовать VPS

Куда чаще всего девается место: типовые виновники

По опыту разбора подобных инцидентов на VPS список виновников почти всегда один и тот же, в порядке убывания частоты:

  • Логи без ротации. nginx access-лог, лог приложения на Node.js/Python/PHP, который пишет в файл без ограничения размера, — самый частый кандидат. Особенно если DEBUG-уровень логирования случайно оставили включённым в проде.
  • journald без лимита. По умолчанию systemd-journald может копить логи без явного потолка, если в конфиге не задан SystemMaxUse. На сервере с активными сервисами журнал способен разрастись существенно за недели работы.
  • WAL / binlog у баз данных. PostgreSQL копит pg_wal, если не успевает архивировать или если реплика/слот репликации завис и не даёт журналам ротироваться. MySQL аналогично копит binlog при включённой репликации без expire_logs_days. Про этот сценарий подробно писали в статье о росте WAL в PostgreSQL и в разборе, когда MySQL заканчивается место под данные.
  • Забытые дампы и бэкапы. pg_dump, mysqldump или ручной tar бэкапа, который сделали «на всякий случай» перед миграцией и забыли удалить — классика жанра, особенно если дамп лежит в домашней директории, а не в специально выделенном разделе для бэкапов.
  • Docker-слои и логи контейнеров. Если на сервере крутится Docker, overlay2 может незаметно разрастись за счёт неиспользуемых образов, остановленных контейнеров и логов json-file драйвера без ограничения размера. Отдельно разбирали это в статье Docker занимает всё место на диске.
  • Временные файлы приложений. Кеш, сессии, загруженные пользователями файлы во временной директории, которые должны были удалиться после обработки, но остались из-за необработанного исключения в коде.
  • Core dump'ы. Если приложение падает по сегфолту и включена генерация core-файлов, каждый такой файл может занимать гигабайты — и накапливаться при повторяющемся крэше в цикле перезапуска systemd.

Поиск виновника: du, ncdu и точечная проверка

Когда df подтвердил проблему, но не назвал виновника, следующий шаг — du с ограничением глубины, чтобы не утонуть в выводе по каждому файлу:

du -sh --max-depth=1 / 2>/dev/null | sort -rh

Это покажет, какие директории верхнего уровня самые тяжёлые (обычно это /var, /home, иногда /opt). Дальше углубляемся в найденную директорию тем же способом:

du -sh --max-depth=1 /var/* 2>/dev/null | sort -rh
du -sh --max-depth=1 /var/log/* 2>/dev/null | sort -rh

Если под рукой есть ncdu (интерактивный аналог du с навигацией по директориям стрелками) — это заметно быстрее:

apt install ncdu -y
ncdu /

ncdu строит дерево и позволяет заходить внутрь директорий, сразу видя, где сосредоточен объём, без повторных запусков команды. Минус — на диске, который уже полон на 100%, установка нового пакета может не пройти (apt тоже требует немного места для распаковки), поэтому иногда приходится сначала руками найти и удалить хотя бы пару сотен мегабайт логов, чтобы освободить место под установку самого ncdu.

Отдельно стоит проверить пять типовых мест, не дожидаясь полного обхода du:

# Логи journald
journalctl --disk-usage

# Самые большие файлы в /var/log
find /var/log -type f -size +100M -exec ls -lh {} \;

# WAL PostgreSQL
du -sh /var/lib/postgresql/*/main/pg_wal

# Docker
docker system df

# Core dump'ы
find / -name "core.*" -size +100M 2>/dev/null

Если ни один процесс не открыл удалённый файл (см. предыдущий раздел про lsof +L1) и обход du/ncdu не находит явного тяжеловеса, стоит проверить снапшоты и точки восстановления файловой системы (например, тома LVM со снапшотами или ZFS-снапшоты) — они занимают место «невидимо» для обычного du, потому что технически принадлежат не файлам, а изменённым блокам.

Восстановление: освобождаем место и поднимаем сервисы по порядку

Найдя виновника, важно не спешить с rm -rf — на сервере в проде цена ошибки высокая. Порядок действий:

  1. Сначала — самое безопасное. Очистить кеш пакетного менеджера, удалить действительно старые логи с явно нерелевантной датой, почистить docker system prune (осторожно — это удалит незапущенные контейнеры и неиспользуемые образы, если они кому-то ещё нужны — сначала посмотрите список docker ps -a и docker images).
apt clean
docker system prune -a --volumes

Флаг --volumes в docker system prune удаляет неиспользуемые volume — это может стереть данные, если volume не подключён ни к одному контейнеру, но реально используется скриптом резервного копирования. Перед этой командой стоит явно свериться со списком используемых volume.

  1. Ротация лог-файла, который разросся без logrotate. Не удалять файл, который активно пишет процесс, а безопасно обнулить его дескриптор:
truncate -s 0 /var/log/nginx/access.log

truncate -s 0 в отличие от rm не разрывает связь inode с процессом — файл остаётся тем же самым файлом, но с нулевым размером, и процесс продолжает писать в него без перезапуска. Это самый безопасный способ быстро освободить место от разросшегося лога без остановки сервиса.

  1. Если проблема в WAL/binlog — разбираться с причиной, а не просто удалять журналы. Удаление файлов WAL PostgreSQL вручную может сломать consistency базы при следующем восстановлении. Правильный путь — найти зависший слот репликации (SELECT * FROM pg_replication_slots;) или починить архивирование, а не чистить pg_wal напрямую.
  1. Удалить подтверждённо старые дампы и бэкапы, если есть актуальная копия в другом месте (внешнее хранилище, отдельный раздел для бэкапов).
  1. Перезапуск сервисов — в порядке зависимостей, а не в порядке падения. Обычно правильная последовательность обратна порядку отказа:
systemctl restart postgresql   # или mysql — база первой должна принять соединения
systemctl restart php-fpm      # приложение, зависящее от базы
systemctl restart nginx        # веб-сервер поверх приложения
systemctl status postgresql php-fpm nginx

Если перезапустить nginx раньше базы, он поднимется, но начнёт отдавать 502, пока backend недоступен — это создаёт ложное впечатление, что проблема не решена, хотя на самом деле просто не соблюдён порядок. После перезапуска стоит явно проверить логи каждого сервиса на предмет ошибок, оставшихся от периода нехватки места — некоторые демоны (в частности PostgreSQL) при обнаружении повреждения на диске могут не подняться автоматически и потребуют ручного вмешательства.

  1. Проверить целостность данных. После инцидента с нехваткой места стоит явно прогнать проверку базы (pg_dump в /dev/null как дешёвый способ проверить читаемость всех таблиц, или CHECK TABLE в MySQL) — записи, сделанные в момент почти полного диска, иногда завершаются частично.

Профилактика: чтобы это не повторилось

Разбор инцидента без профилактики — это просто отложенный повтор той же истории через несколько недель. Три вещи, которые стоит настроить сразу после восстановления:

Мониторинг места на диске с алертом по порогу. Правило «алерт при 100% заполнении» бесполезно — к этому моменту сервисы уже падают. Разумный порог — предупреждение на 80% и критический алерт на 90%, чтобы оставалось время на плановую чистку или расширение диска. Как это настроить пошагово, разбирали в статье про мониторинг диска на VPS, а частые ошибки такой настройки — в материале про типовые проблемы мониторинга диска.

logrotate для всего, что пишет логи без ограничения. Проверить, что каждый активный лог-файл в /var/log действительно попадает под ротацию:

cat /etc/logrotate.d/nginx
logrotate -d /etc/logrotate.conf   # dry-run, показывает, что будет сделано

Для journald — явный лимит в /etc/systemd/journald.conf:

[Journal]
SystemMaxUse=1G
SystemMaxFileSize=100M

После изменения — systemctl restart systemd-journald. Подробный разбор настройки описан в статье про ротацию логов, чтобы не забивался диск.

Автоматическое устаревание бэкапов и дампов. Скрипт бэкапа должен не только создавать копию, но и удалять версии старше N дней — иначе рано или поздно один и тот же диск, на который льются дампы, заполнится сам. Для WAL/binlog — явно настроенное expire_logs_days в MySQL и корректно работающее архивирование в PostgreSQL, чтобы журналы не копились из-за зависшей репликации.

Отдельно стоит держать в голове простое правило распределения ресурсов: если сервер уже балансирует на грани по диску при обычной нагрузке, докупать место постфактум — не решение, а отсрочка. Дешевле сразу закладывать запас процентов в 30-40% сверх текущего использования, особенно если на сервере крутится база данных с растущим объёмом — сколько именно закладывать, зависит от профиля нагрузки, разбирали в статье сколько дискового пространства закладывать с запасом.

Нужен сервер под эту задачу?

Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Арендовать VPS

Нужны сами нейросети для контента?

Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.

Частые вопросы

Почему df -h показывает 100%, а du -sh / считает намного меньше?

Чаще всего это удалённые, но всё ещё открытые файлы (см. lsof +L1 выше) — файловая система не освобождает блоки, пока последний процесс не закроет дескриптор. Реже — это резервируемые файловой системой блоки (ext4 по умолчанию резервирует часть места под root, что тоже может путать расчёты) или файлы, лежащие в точке монтирования, скрытой другим смонтированным разделом.

Можно ли просто увеличить диск, не разбираясь в причине?

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

Что делать, если apt или useradd тоже падают с ошибкой нехватки места, а ncdu ещё не установлен?

Начните с ручного du -sh --max-depth=1 /var/* — эта команда почти всегда доступна из коробки без установки. Освободите хотя бы небольшой объём (например, обнулите через truncate -s 0 самый очевидный разросшийся лог), а затем уже ставьте ncdu для более удобного обхода.

Почему после очистки логов место не освободилось?

Скорее всего лог был удалён через rm, а не через truncate, и процесс, который его писал, продолжает держать открытым старый (уже несуществующий по имени) файл. Решение — либо truncate -s 0 на живой файл, либо перезапуск/сигнал HUP сервису, чтобы он переоткрыл файл логов заново.

В каком порядке лучше перезапускать сервисы после устранения нехватки места?

От нижнего уровня зависимостей к верхнему: сначала база данных, затем приложение (backend/php-fpm/бэкенд-процесс), затем веб-сервер (nginx) поверх них. Перезапуск в обратном порядке приведёт к временным 502/503, даже когда причина уже устранена.

Обсудить статью, задать вопрос или начать новую тему

Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.

Перейти в сообщество →