Закончилось место на диске в Linux: как найти, что его заняло, и освободить без потерь
Команда KAI Lab · · 8 мин чтения
Когда в Linux закончилось место на диске, сначала найдите заполненную файловую систему командой df -h, а потом каталог, который её занял, — через du -xh -d1 или ncdu -x. Если df показывает 100%, а du насчитывает на гигабайты меньше, ищите удалённые, но всё ещё открытые файлы (lsof +L1); если место свободно, а ошибка No space left on device не уходит, проверьте inode командой df -i.
Ниже порядок, в котором это удобно проверять на живом сервере: сначала факты, потом действие. Главное правило — ничего не удалять, пока не понятно, кто и зачем пишет на диск. Лог, удалённый наугад, часто не освобождает ни байта, а каталог базы, удалённый наугад, превращает мелкий инцидент в большой.
Симптом → команда → что смотреть
| Симптом | Команда | Что смотреть |
|---|---|---|
No space left on device, сервис не может писать | df -h | какая файловая система заполнена: столбцы Use% и Mounted on |
| Непонятно, что заняло место | du -xh -d1 /var или ncdu -x / | самый большой каталог, дальше спускаться в него |
| Нужны самые большие файлы | find / -xdev -type f -size +1G | логи, дампы, забытые архивы |
df и du расходятся на гигабайты | lsof +L1 | файлы с пометкой (deleted) и процесс, который их держит |
| Место есть, а ошибка есть | df -i | IUse% 100% — закончились inode |
Растёт /var/log/journal | journalctl --disk-usage | сколько занимает журнал systemd |
Растёт /var/lib/docker | docker system df | образы, остановленные контейнеры, тома, кэш сборки |
| Лог растёт без ограничений | logrotate -d /etc/logrotate.d/app | что сделает ротация, без реальных изменений |
Шаг 1. Какая файловая система заполнена
$ df -h
Filesystem Size Used Avail Use% Mounted on
tmpfs 392M 1.1M 391M 1% /run
/dev/vda1 49G 47G 0 100% /
tmpfs 2.0G 0 2.0G 0% /dev/shm
tmpfs 5.0M 0 5.0M 0% /run/lock
/dev/vda15 105M 6.1M 99M 6% /boot/efi
tmpfs 392M 4.0K 392M 1% /run/user/1000Заполнен корень /. Строки tmpfs — файловые системы в памяти, их можно пропустить: df -h -x tmpfs уберёт их из вывода.
Обратите внимание: Size 49G, Used 47G, а Avail уже 0. Разница — резерв ext4. По умолчанию mke2fs оставляет 5% блоков для root, чтобы системные демоны продолжали работать, когда обычным процессам писать уже некуда. Поэтому приложение от своего пользователя падает с No space left on device, а процессы root ещё какое-то время пишут. Размер резерва показывает tune2fs:
$ sudo tune2fs -l /dev/vda1 | grep -i 'reserved block count'
Reserved block count: 655360655 360 блоков по 4 КБ — это 2,5 ГБ. Уменьшать резерв через tune2fs -m посреди инцидента — плохая идея: место кончится снова, только уже без запаса для системы.
Шаг 2. Что заняло место: du, ncdu, find
du считает, сколько занимают каталоги. Флаг -x не даёт уходить в другие файловые системы — иначе в подсчёт попадут смонтированные диски и сетевые шары.
$ sudo du -xh -d1 / 2>/dev/null | sort -h | tail -5
1.3G /opt
2.8G /usr
4.9G /home
22G /var
31G /
$ sudo du -xh -d1 /var 2>/dev/null | sort -h | tail -4
312M /var/cache
2.4G /var/lib
19G /var/log
22G /varСпускайтесь по самому большому каталогу, пока не станет ясно, кто виноват. Интерактивно то же делает ncdu -x /: он один раз обходит дерево, а дальше по каталогам можно ходить стрелками и сортировать по размеру. Клавиша d в ncdu удаляет выбранный файл, поэтому на проде удобнее ncdu -r — режим только для чтения.
Самые большие файлы целиком находит find:
$ sudo find / -xdev -type f -size +1G -exec ls -lh {} + 2>/dev/null
-rw-r----- 1 app app 5.1G Sep 23 00:00 /var/log/app/app.log.1
-rw-r----- 1 app app 9.8G Sep 24 03:12 /var/log/app/app.logНебольшое расхождение между du и df нормально: метаданные файловой системы и её журнал du не видит. Но здесь du насчитал 31 ГБ, а df — 47 ГБ. Шестнадцать гигабайт лежат где-то ещё.
Шаг 3. df и du не сходятся: удалённые, но открытые файлы
Файл в Linux существует, пока на него есть ссылки: имя в каталоге или открытый дескриптор в каком-нибудь процессе. rm убирает только имя. Если процесс продолжает писать в удалённый лог, место не освобождается, а du этот файл уже не видит — по имени его больше не найти.
Такие файлы показывает lsof +L1 — открытые файлы, у которых не осталось ни одного имени:
$ sudo lsof +L1
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NLINK NODE NAME
java 2143 app 1w REG 252,1 16734789632 0 1835123 /var/log/app/app-2026-09-20.log (deleted)
java 2143 app 2w REG 252,1 16734789632 0 1835123 /var/log/app/app-2026-09-20.log (deleted)Классическая история: ночью кто-то удалил «старый» лог, чтобы освободить место, а Java-процесс писал в этот файл stdout и stderr и продолжил писать. 15,6 ГБ держат дескрипторы 1 и 2 процесса 2143.
Правильное решение — перезапустить сервис, чтобы он закрыл файл: sudo systemctl restart app. Если перезапуск прямо сейчас невозможен, файл можно обрезать до нуля через /proc:
$ sudo ls -l /proc/2143/fd/1
l-wx------ 1 app app 64 Sep 24 03:20 /proc/2143/fd/1 -> '/var/log/app/app-2026-09-20.log (deleted)'
$ sudo truncate -s 0 /proc/2143/fd/1
$ df -h /
Filesystem Size Used Avail Use% Mounted on
/dev/vda1 49G 32G 16G 68% /Вариант sudo : > /proc/2143/fd/1 не сработает: перенаправление выполняет ваша оболочка, а не sudo. Поэтому truncate. Перезапуск всё равно понадобится, чтобы сервис писал в нормальный файл, а не в удалённый.
Вывод на будущее: активный лог не удаляют, а обрезают — sudo truncate -s 0 /var/log/app/app.log. А лучше настраивают ротацию, о ней ниже.
Шаг 4. Место есть, а No space left on device: inode
На ext4 число inode задаётся при создании файловой системы. Каждый файл и каталог занимает один inode, и если приложение наплодило миллионы мелких файлов — сессий, кэша, писем в очереди, — inode закончатся раньше байтов.
$ df -h /
Filesystem Size Used Avail Use% Mounted on
/dev/vda1 49G 28G 19G 60% /
$ df -i /
Filesystem Inodes IUsed IFree IUse% Mounted on
/dev/vda1 3276800 3276800 0 100% /Где именно лежат файлы, покажет du --inodes:
$ sudo du --inodes -x -d1 /var/lib 2>/dev/null | sort -n | tail -3
14022 /var/lib/dpkg
2903114 /var/lib/app
2981455 /var/libУдалить миллионы файлов командой rm /var/lib/app/cache/* не получится: оболочка раскроет звёздочку в список, который не влезет в лимит аргументов, и вернёт Argument list too long. Надёжнее find с условием по возрасту файлов:
$ sudo find /var/lib/app/cache -type f -mmin +1440 -deleteИ сразу выясните, почему приложение не чистит за собой. Без этого через неделю будет повтор.
Журнал systemd занимает гигабайты
$ journalctl --disk-usage
Archived and active journals take up 3.9G in the file system.
$ sudo journalctl --vacuum-size=500M
Deleted archived journal /var/log/journal/0f3c2a7d9b8e4c61a5d2e7f8c9b0a1d2/system@8d1f2e3a4b5c46d7a8b9c0d1e2f3a4b5-0000000000000001-0005f9a1b2c3d4e5.journal (128.0M).
# ... и ещё два десятка таких строк
Vacuuming done, freed 3.4G of archived journals from /var/log/journal/0f3c2a7d9b8e4c61a5d2e7f8c9b0a1d2.--vacuum-size, --vacuum-time (например, --vacuum-time=14d) и --vacuum-files удаляют только архивные файлы журнала. Активные остаются; если нужно почистить и их, добавьте --rotate — journald сначала закроет текущие файлы, потом выполнит очистку.
Постоянный лимит задаётся в конфигурации journald. По умолчанию журнал может занять до 10% файловой системы, но не больше 4 ГБ. Для небольшого сервера это много:
$ sudo mkdir -p /etc/systemd/journald.conf.d
$ printf '[Journal]\nSystemMaxUse=1G\n' | sudo tee /etc/systemd/journald.conf.d/size.conf
[Journal]
SystemMaxUse=1G
$ sudo systemctl restart systemd-journaldФайлы журнала руками через rm не удаляют: для этого и есть vacuum.
logrotate: чтобы место не кончилось снова
logrotate переименовывает лог, сжимает старые копии и удаляет лишние. Пример для приложения, которое пишет в /var/log/app/:
$ cat /etc/logrotate.d/app
/var/log/app/*.log {
daily
rotate 14
maxsize 1G
compress
delaycompress
missingok
notifempty
copytruncate
}
$ sudo logrotate -d /etc/logrotate.d/appЧто здесь важно:
rotate 14— хранить 14 старых копий, более старые удалять;maxsize 1G— ротировать раньше срока, если файл вырос больше гигабайта;copytruncate— для приложений, которые не умеют переоткрывать файл: logrotate копирует лог и обрезает оригинал. Документация честно предупреждает, что между копированием и обрезкой часть строк может потеряться. Если приложение умеет переоткрывать лог по сигналу или при reload, лучшеcreateи команда в блокеpostrotate;logrotate -d— пробный прогон: покажет, что будет сделано, и ничего не изменит.
Ещё одна ловушка: logrotate обычно запускается раз в сутки — задачей cron или через logrotate.timer. maxsize проверяется только в момент запуска. Если лог растёт на 20 ГБ в час, суточный запуск не спасёт: нужен запуск чаще или меньше логов на уровне приложения.
Если это Docker или Kubernetes
На хостах с Docker место чаще всего уходит в /var/lib/docker: старые образы, остановленные контейнеры, тома, кэш сборки и json-логи контейнеров без ограничения размера. Этот случай разобран отдельно — No space left on device в Docker.
В Kubernetes заполненный диск ноды — это не только ошибки записи. По умолчанию kubelet начинает выселять поды, когда на файловой системе ноды свободно меньше 10% места или меньше 5% inode, и ставит ноде условие DiskPressure. Как это выглядит со стороны подов — в разборе Pod Evicted.
Как не доводить до 100%
Алерт «диск заполнен на 95%» ночью срабатывает слишком поздно, а на огромном разделе — слишком рано. Надёжнее прогноз: успеет ли диск заполниться за ближайшие часы при текущей скорости записи. В Prometheus с node_exporter это predict_linear:
# при текущем темпе корень заполнится меньше чем за 4 часа
predict_linear(node_filesystem_avail_bytes{mountpoint="/"}[6h], 4 * 3600) < 0Добавьте отдельный алерт на inode по метрике node_filesystem_files_free — он ловит случай из шага 4. И заведите короткий runbook: какие каталоги на этом сервере можно чистить, а какие не трогать никогда — данные баз, pg_wal у PostgreSQL, тома Docker. Если такой алерт придёт на вашем первом дежурстве, runbook сэкономит полчаса.
Полный диск ещё и маскируется под другие проблемы: сервис не стартует, потому что не может создать pid-файл или временный файл, — см. systemd-сервис не запускается.
В симуляторе DevOps KAI Lab с диска начинается второй месяц: тикет «Диск на проде заполнен на 97%», а на первом дежурстве приходит алерт о том, что диск заполнится через четыре часа. Консоль там отвечает реальными выводами команд, так что порядок из этой статьи можно отработать на сломанном сервере, а не на своём проде.
Частые вопросы
Почему после rm место на диске не освободилось? Файл ещё открыт процессом. rm удалил имя, но данные останутся на диске, пока процесс не закроет дескриптор. Найдите такой файл через sudo lsof +L1, затем перезапустите сервис или обрежьте файл через /proc/PID/fd/N.
Почему df и du показывают разное? du считает файлы, которые видит по именам, а df — занятые блоки файловой системы. Разницу в гигабайты чаще всего дают удалённые, но открытые файлы. Второй вариант — данные под точкой монтирования: если в /data что-то записали до того, как туда смонтировали диск, эти файлы скрыты, но место на корне занимают.
Что такое inode и почему они заканчиваются? Inode — служебная запись о файле: владелец, права, где лежат данные. На ext4 их число фиксируется при создании файловой системы. Миллионы мелких файлов исчерпывают inode при свободных гигабайтах; проверка — df -i.
Можно ли просто удалить /var/log/journal? Руками — не стоит. Используйте journalctl --vacuum-size= или --vacuum-time=, а постоянный лимит задайте через SystemMaxUse= в конфигурации journald.
Что чистить нельзя, даже если очень хочется? Каталоги данных баз, WAL PostgreSQL, тома Docker и всё, назначение чего вы не знаете. Удаление там не освобождает место «безопасно» — оно ломает данные. Сначала найдите процесс-виновника, потом решайте.
Источники
- man7.org: df(1), du(1) — опции
-x,-d,--inodes. - man7.org: lsof(8) —
+L1выбирает открытые файлы, у которых удалены все имена. - man7.org: unlink(2) — файл существует, пока его держит открытый дескриптор.
- man7.org: mke2fs(8) — резерв 5% блоков для root по умолчанию.
- ncdu. Manual —
-x,-rи клавиши интерфейса. - man7.org: journalctl(1) и journald.conf(5) — vacuum,
SystemMaxUse=, лимиты по умолчанию. - man7.org: logrotate(8) —
copytruncate,maxsize,-d. - Kubernetes. Node-pressure Eviction — пороги выселения по умолчанию и
DiskPressure. - Prometheus. Query functions: predict_linear.