Перейти к содержанию

Нехватка места на диске и исчерпание inode

У «No space left on device» бывает несколько причин. Файловая система может быть заполнена блоками данных, может исчерпать inode или держать место за файлами, которые удалены, но всё ещё открыты. Для каждого случая своё лекарство, а df -h в одиночку показывает только первый.

О выводе на этой странице

Примеры с inode сняты при реальном запуске на Ubuntu 22.04: крошечная tmpfs с намеренно малым лимитом inode, смонтированная внутри непривилегированного user namespace (unshare -rm), так что хост не затрагивался. Пути монтирования сокращены до /data. Пример с удалёнными, но открытыми файлами — иллюстративный.

Как это устроено

Что такое inode

Inode хранит метаданные файла (владелец, права, время, размер, расположение блоков данных). Имя файла находится в записи каталога, которая указывает на номер inode. Каждый файл, каталог и символическая ссылка занимают один inode. В ext4 число inode фиксируется при создании файловой системы (mkfs.ext4 -i <байт-на-inode> или -N <количество>), по умолчанию один inode на 16 КиБ места. XFS выделяет inode динамически, поэтому они почти не заканчиваются, хотя возможен упор в лимит пространства под inode (imaxpct). У Btrfs нет фиксированной таблицы inode.

Значит, файловая система с миллионами мелких файлов (очереди почты, файлы сессий PHP, кэши, слои контейнеров) может исчерпать inode, когда большинство гигабайт ещё свободно.

Как отличить одно от другого

df -h /data      # блоки: размер, занято, доступно
df -i /data      # inode: всего, занято, свободно

Реальный снимок случая с inode (tmpfs на 10 МБ с лимитом 200 inode, заполненная мелкими файлами):

$ df -i .
Filesystem     Inodes IUsed IFree IUse% Mounted on
tmpfs             200   189    11   95% /data

$ df -h .
Filesystem      Size  Used Avail Use% Mounted on
tmpfs            10M     0   10M   0% /data

Место занято на 0%, а inode — на 95%. Когда занят последний inode, создать ничего нельзя, хотя диск пуст:

touch: cannot touch 'another': No space left on device

Текст ошибки тот же, что и при полном диске. Поэтому, если df -h выглядит нормально, всегда проверяйте df -i.

Где закончились inode

Посчитайте файлы по каталогам верхнего уровня (хватает coreutils):

$ find . -xdev -type f | cut -d/ -f2 | sort | uniq -c | sort -rn
    120 sessions
     60 cache
      5 logs
      1 new

-xdev не даёт find заходить в другие точки монтирования. GNU du умеет то же одной командой:

$ du --inodes -d1 . | sort -rn
190 .
121 ./sessions
61  ./cache
6   ./logs

Повторяйте на уровень глубже внутри самого большого каталога, пока не найдёте виновника. Здесь это sessions, и удаление устаревших файлов сессий освободило inode:

$ rm -f sessions/sess_*
$ df -i .
Filesystem     Inodes IUsed IFree IUse% Mounted on
tmpfs             200    70   130   35% /data

На реальном сервере выполняйте это для затронутого раздела (/var, /var/lib/docker, /home), начиная с привычных подозреваемых: /var/spool, /var/lib/php/sessions, /tmp, очереди почты, кэши сборки.

Нехватка блоков и поиск крупных объектов

sudo du -xh --max-depth=1 /var | sort -rh | head
sudo find /var -xdev -type f -size +500M -exec ls -lh {} +

-x (du) и -xdev (find) остаются в пределах одной файловой системы. sort -rh сортирует читаемые размеры от большего к меньшему.

Место, занятое удалёнными файлами

Если df показывает полный диск, а du по всей файловой системе даёт намного меньше, вероятно, какой-то процесс держит открытым удалённый файл. Блоки освобождаются только после закрытия последнего файлового дескриптора:

sudo lsof +L1 /var                          # открытые файлы с числом ссылок 0 (удалённые)
sudo lsof -nP +L1 | awk '$7 > 100000000'    # только крупнее ~100 МБ (колонка 7 — SIZE/OFF)

Типичный случай: rm app.log, пока приложение продолжает писать в него. Решение — перезапустить процесс или послать ему сигнал, чтобы он переоткрыл лог (systemctl reload app или kill -HUP <pid>, если программа это поддерживает). Чтобы освободить место без перезапуска, обрежьте файл через открытый дескриптор: : > /proc/<pid>/fd/<n> (найти <n> можно через ls -l /proc/<pid>/fd). В дальнейшем обрезайте логи (truncate -s 0 file) или используйте logrotate вместо rm.

Зарезервированные блоки (ext4)

ext4 по умолчанию резервирует 5% блоков для root, поэтому обычные пользователи упираются в «No space» примерно на 95%, хотя df ещё показывает Avail. Проверка: sudo tune2fs -l /dev/<dev> | grep -i reserved. Уменьшить значение на больших разделах с данными (sudo tune2fs -m 1 /dev/<dev>) безопасно, но на / оставьте значение по умолчанию, чтобы системные службы продолжали работать почти на заполненном диске.

Различия Ubuntu/Debian и RHEL-подобных систем

Инструменты одинаковы. Различается файловая система по умолчанию: RHEL-подобные системы используют XFS (inode динамические, поэтому исчерпание по df -i редкость, а XFS нельзя уменьшить), а Ubuntu и Debian — ext4 (фиксированное число inode, поэтому исчерпание реальный риск на разделах с почтой или кэшем). Какая у вас, покажут df -T /path или findmnt -no FSTYPE /path.

Практическое задание

Безопасно на любой машине: используется одноразовая tmpfs в вашем собственном пространстве имён монтирования, root не нужен.

mkdir -p ~/inode-lab
unshare -rm bash        # новая оболочка в частном mount namespace (нужны включённые user namespaces)
mount -t tmpfs -o size=10m,nr_inodes=64 tmpfs ~/inode-lab
cd ~/inode-lab
df -i .
i=0; while touch f$i 2>/dev/null; do i=$((i+1)); done; echo "created $i files"
touch one-more          # завершится ошибкой "No space left on device"
df -h . ; df -i .
rm -f f*                # освободить inode
touch one-more && echo ok

Очистка: наберите exit, чтобы выйти из оболочки в namespace; tmpfs исчезнет вместе с ней. Затем rmdir ~/inode-lab. Если unshare отказывает (на усиленных системах непривилегированные user namespaces бывают отключены), используйте запасную ВМ и sudo mount -t tmpfs ..., затем sudo umount.

Как проверить, что всё сделано правильно

  • df -i . сразу после монтирования показывает 64 inode всего и около 1 занятого.
  • Цикл останавливается на 63 файлах (64 inode минус один, занятый корневым каталогом точки монтирования), а touch one-more печатает No space left on device.
  • df -h . в этот момент показывает 0% занято: это доказательство, что причина не в нехватке блоков.
  • После rm -f f* df -i . показывает много свободных inode, и touch one-more проходит.

Типичные ошибки

  • Смотреть только df -h и считать ошибку загадочной. Всегда проверяйте и df -i.
  • Запускать du без -x по / и считать /proc, /sys и другие точки монтирования.
  • Удалять через rm огромный лог, который держит процесс. Место не вернётся, пока процесс не закроет файл. Обрезайте или ротируйте.
  • Чистить не тот слой на хостах Docker. Слои контейнеров расходуют inode в /var/lib/docker. Перед очисткой посмотрите docker system df.
  • Создавать файловую систему под миллионы мелких файлов с настройками по умолчанию. Выбирайте -i/-N при mkfs (или используйте XFS): число inode в ext4 нельзя увеличить потом без пересоздания.

⚠ Опасные команды

  • rm -rf по пути из переменной (rm -rf $DIR/* при пустой $DIR превращается в /*). Берите в кавычки и используйте ${DIR:?}: rm -rf -- "${DIR:?}"/*.
  • Удаление миллионов файлов через rm -rf на рабочем диске может забить ввод-вывод и остановить сервис. Предпочитайте find <dir> -xdev -type f -delete, при необходимости под ionice -c3 nice.
  • Очистка /var/lib/php/sessions или кэша при работающем приложении разлогинивает всех. Удаляйте только файлы старше времени жизни сессии: find ... -type f -mmin +1440 -delete.
  • docker system prune -a --volumes удаляет неиспользуемые образы и тома вместе с данными. Сначала выполните docker system df и никогда не добавляйте --volumes на хосте с базами данных.
  • tune2fs -m 0 на корневой файловой системе: полный / может нарушить вход и службы, которым нужно писать на диск.

Связанные статьи

Контрольные вопросы

  1. Диск показывает 40% в df -h, но ничего нельзя создать. Какую команду выполнить следующей?
  2. Почему в ext4 inode могут закончиться, а в XFS — редко?
  3. df показывает 100%, а du -x / в сумме даёт только 60% диска. Какова вероятная причина и как её найти?
  4. Почему обрезать лог безопаснее, чем удалять его, пока служба работает?
  5. Какой параметр find не даёт ему заходить в другие смонтированные файловые системы?