PQ
PQ.Hosting

Валюта

Диск на 100% но du показывает мало: удалённые файлы которые не освобождают место

Автор
PQ
24 марта 2026
5 мин чтения
5 просмотров
Диск на 100% но du показывает мало: удалённые файлы которые не освобождают место

Три наиболее частых виновника на продакшн-серверах: разросшийся journald которого забыли ограничить, Docker который накапливает слои образов в overlay2, и Nginx который пишет в ротированный лог потому что logrotate забыли настроить правильно. В каждом случае du молчит, df кричит.

Диагностика за 30 секунд

Сначала — убедиться что проблема именно в открытых удалённых файлах а не в чём-то другом:

lsof +L1 2>/dev/null | awk 'NR>1 {sum+=$7} END {printf "%.1f GB held by deleted open files\n", sum/1024/1024/1024}'

+L1 — показать только файлы с link count меньше 1, то есть удалённые но открытые. Если число больше нуля — вот куда делось место.

Посмотреть топ виновников:

lsof +L1 2>/dev/null | awk 'NR>1 {print $7/1024/1024 " MB\t" $1 "\t" $NF}' | sort -rn | head -15

Сценарий 1: journald съел всё

journald по умолчанию не имеет жёсткого лимита и спокойно занимает 4–10 ГБ на активном сервере. При этом старые файлы могут быть «удалены» logrotate но journald держит их открытыми.

Проверить сколько занимает journald прямо сейчас:

journalctl --disk-usage
Archived and active journals take up 8.3G in the file system.

Установить лимит и немедленно очистить:

sudo journalctl --vacuum-size=500M

Закрепить лимит навсегда в конфиге:

sudo nano /etc/systemd/journald.conf
[Journal]
SystemMaxUse=500M
SystemKeepFree=1G
MaxRetentionSec=2weeks

Перезапустить journald:

sudo systemctl restart systemd-journald

Сценарий 2: Docker overlay2

Docker хранит слои образов, build-кеш и тома в /var/lib/docker/overlay2. du /var/lib/docker/ покажет реальный размер, но после удаления контейнеров и образов место не освобождается — слои остаются как кеш.

Проверить что занимает место в Docker:

docker system df -v

Удалить всё неиспользуемое — остановленные контейнеры, образы без тегов, build-кеш:

docker system prune -a --volumes

Если Docker не установлен но /var/lib/docker осталась от предыдущей установки:

sudo du -sh /var/lib/docker/
sudo rm -rf /var/lib/docker/

Ограничить размер Docker log-файлов — по умолчанию они не ротируются:

sudo nano /etc/docker/daemon.json
{
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "50m",
    "max-file": "3"
  }
}
sudo systemctl restart docker

Сценарий 3: Nginx пишет в удалённый лог

logrotate переименовал access.log в access.log.1 и удалил старый. Nginx не знает об этом — продолжает писать в тот же файловый дескриптор. Файл физически существует под именем дескриптора в /proc, но в директории его нет. du не видит, df видит.

Найти конкретно эту ситуацию:

lsof +L1 | grep nginx

Послать Nginx сигнал переоткрыть логи (без перезапуска, без потери соединений):

sudo nginx -s reopen

Или через systemd:

sudo kill -USR1 $(cat /var/run/nginx.pid)

Предотвратить повторение — в /etc/logrotate.d/nginx должна быть секция postrotate:

/var/log/nginx/*.log {
    daily
    rotate 14
    compress
    delaycompress
    missingok
    notifempty
    sharedscripts
    postrotate
        /usr/sbin/nginx -s reopen
    endscript
}

Без postrotate — Nginx будет вечно писать в удалённый файл после каждой ротации.

Освободить место мгновенно без перезапуска процесса

Когда перезапуск невозможен — усечь файл через его дескриптор в /proc. Содержимое обнуляется, место освобождается, процесс продолжает писать в тот же дескриптор.

Найти PID и номер дескриптора:

lsof +L1 | grep -v "^COMMAND" | awk '{print $2, $4, $NF}' | head -10
14821 5w /var/log/nginx/access.log (deleted)

PID = 14821, дескриптор = 5.

Обнулить через truncate:

truncate -s 0 /proc/14821/fd/5

Место освобождается немедленно. Работает для любого процесса — MySQL, PHP-FPM, rsyslog.

Дополнительные причины расхождения df и du

Зарезервированные блоки ext4. По умолчанию 5% диска зарезервировано для root. На диске 200 ГБ это 10 ГБ которые du никогда не покажет.

Проверить:

tune2fs -l /dev/sda1 | grep -E "Reserved|Block count|Block size"

Посчитать сколько реально зарезервировано:

python3 -c "
import subprocess
out = subprocess.check_output(['tune2fs','-l','/dev/sda1']).decode()
blocks = int([l.split(':')[1] for l in out.split('\n') if 'Reserved block count' in l][0])
size = int([l.split(':')[1] for l in out.split('\n') if 'Block size' in l][0])
print(f'Reserved: {blocks * size / 1024**3:.1f} GB')
"

Уменьшить резерв до 1% на дата-дисках:

sudo tune2fs -m 1 /dev/sda1

tmpfs невидим через du /.

df -h | grep -E "tmpfs|/run|/dev/shm"

/run может занимать несколько гигабайт если в нём накопились сокеты или временные файлы. Очистить:

sudo find /run -name "*.tmp" -delete

Скрипт ежедневного контроля

Добавить в cron — будет ловить ситуацию до того как диск забьётся:

#!/bin/bash
DELETED_GB=$(lsof +L1 2>/dev/null | awk 'NR>1 {sum+=$7} END {printf "%.1f", sum/1024/1024/1024}')
DISK_PCT=$(df / | awk 'NR==2 {print $5}' | tr -d '%')

if [ "$DISK_PCT" -gt 80 ] || [ $(echo "$DELETED_GB > 1" | bc) -eq 1 ]; then
    echo "Host: $(hostname)
Disk used: ${DISK_PCT}%
Held by deleted files: ${DELETED_GB} GB
$(lsof +L1 2>/dev/null | awk 'NR>1 {print $7/1024/1024 " MB " $1}' | sort -rn | head -5)" | \
    mail -s "Disk alert: $(hostname)" admin@example.com
fi

Шпаргалка

Задача Команда
Сколько держат удалённые файлы lsof +L1 2>/dev/null | awk 'NR>1 {sum+=$7} END {print sum/1024/1024/1024 " GB"}'
Топ виновников lsof +L1 2>/dev/null | awk 'NR>1 {print $7/1024/1024 " MB\t" $1}' | sort -rn | head -10
Обнулить файл без перезапуска truncate -s 0 /proc/PID/fd/N
Очистить journald sudo journalctl --vacuum-size=500M
Состояние Docker docker system df -v
Очистить Docker docker system prune -a --volumes
Переоткрыть логи Nginx sudo nginx -s reopen
Зарезервировано в ext4 tune2fs -l /dev/sda1 | grep Reserved
Уменьшить резерв до 1% sudo tune2fs -m 1 /dev/sda1
Проверить tmpfs df -h | grep tmpfs

 

Поделиться статьей

Похожие статьи