Ой, ничего не найдено!

К сожалению, по вашему запросу пока ничего нет (но это только пока!), зато вы можете подписаться на нашу замечательную email-рассылку, чтобы не пропустить самое интересное в будущем.

  • 22

Диск закончился внезапно: куда деваются гигабайты на VPS

  • 3 минуты на чтение

Часть 5 из 14 · Серия «Сервер в бою»

Ещё в первой статье серии я назвал забитый диск причиной трети ночных аварий, а в четвёртой мы настроили алерт на 90% — и, возможно, он у вас уже сработал. Знакомая мысль в этот момент: «Как 90%?! Я же ничего не загружал!» Сайт весит два гигабайта, база — ещё три, диск — сорок. Где остальные тридцать пять?

Спойлер: их съел сам сервер. VPS — это организм с обменом веществ: он непрерывно производит логи, кэши, временные файлы и копии самого себя. Если продукты жизнедеятельности не убирать, они заполнят всё доступное пространство — вопрос только времени. Сегодня проведём полную ревизию: научимся быстро находить, куда делось место, разберём всех типовых пожирателей по одному, познакомимся с двумя коварными спецслучаями (inodes и файлы-призраки) — и настроим уборку так, чтобы слово «внезапно» из заголовка больше к вам не относилось.

Почему полный диск ломает всё «странно»

Короткое напоминание, почему это вообще авария, а не неудобство. Когда на диске 0 байт, отказывает не «загрузка файлов» — отказывает вся запись: MySQL не может писать в таблицы и журналы и переходит в защитный режим или падает; PHP не может создать файл сессии — пользователей разлогинивает; certbot не может сохранить обновлённый сертификат; и — вишенка — логи тоже не пишутся, то есть авария прячет собственные улики. Отсюда фирменный почерк полного диска: ломается всё сразу, странно и в разных местах. Если симптомы выглядят бессвязно — df -h должна быть первой командой, а не десятой.

Шаг первый: находим виновника за две минуты

Начинаем сверху и спускаемся, как по карте. Общая картина:

df -h
Filesystem      Size  Used Avail Use% Mounted on
/dev/vda1        40G   38G  0G   100% /

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

du -h --max-depth=1 / 2>/dev/null | sort -rh | head
1 месяц бесплатно
Сервер с нуля — без боли и гугления каждой команды
Понятная панель, готовые шаблоны и поддержка, которая объясняет по-человечески. Поднимите проект, даже если SSH видите впервые.
Попробовать бесплатно

Увидели, что раздулся /var — повторяем для него, потом для /var/log, и так до конкретного файла. Работает, но муторно. Способ поприятнее — ncdu, интерактивный анализатор:

apt install ncdu
ncdu -x /

Он просканирует диск и покажет дерево, отсортированное по размеру, по которому вы ходите стрелками, проваливаясь в самые тяжёлые ветки. Флаг -x важен: не выходить за пределы файловой системы (чтобы виртуальные /proc и смонтированные диски не путали картину). Две минуты — и виновник найден. В 9 случаях из 10 это будет кто-то из следующего списка.

Галерея пожирателей: типовые подозреваемые

Подозреваемый №1: логи. Мы уже знаем: сервер разговорчив, и за разговорчивость платят гигабайтами. Быстрая проверка обоих миров логов:

du -sh /var/log/* 2>/dev/null | sort -rh | head
journalctl --disk-usage

Частые находки: access.log нагруженного сайта на десятки гигабайт (особенно если его долбят боты — привет, статья про fail2ban), journald, разросшийся до 4 ГБ (его лимит по умолчанию — до 10% диска), и какой-нибудь error.log приложения, куда со скоростью пулемёта сыплется одна и та же нотация, которую все привыкли игнорировать. Журналу срочную помощь оказываем сразу:

journalctl --vacuum-size=500M

Подозреваемый №2: Docker. Если на сервере есть Docker — почти наверняка часть диска у него, и немалая. Docker ничего не удаляет сам: старые образы после каждого обновления, остановленные контейнеры, «висячие» слои от пересборок, кэш сборки, тома от давно удалённых проектов. Сводка:

docker system df
TYPE            TOTAL   ACTIVE  SIZE      RECLAIMABLE
Images          24      3       11.2GB    9.4GB (84%)
Containers      5       3       820MB     790MB
Local Volumes   12      2       6.1GB     4.8GB (78%)
Build Cache     117     0       5.5GB     5.5GB

Двадцать гигабайт, из которых нужны три, — обычное дело. Уборка:

docker system prune -a
Перенос бесплатно
Хостинг стал тесным — переезжайте на свой VPS
Бесплатно поможем перенести проект без простоя и потери данных. Больше ресурсов, полный root-доступ и никаких тесных рамок.
Переехать на Siteko

С одной оговоркой на всю жизнь: prune с флагом --volumes удаляет и неиспользуемые тома — а в томах живут данные, в том числе базы остановленных проектов. Прежде чем добавлять --volumes, посмотрите docker volume ls и убедитесь, что там нет ничего живого. И отдельная тихая дыра — логи самих контейнеров: по умолчанию Docker пишет stdout каждого контейнера в json-файл без ограничения размера. Проверьте du -sh /var/lib/docker/containers/*/*-json.log — и если там гигабайты, пропишите лимит в /etc/docker/daemon.json:

{ "log-driver": "json-file", "log-opts": { "max-size": "20m", "max-file": "3" } }

Подозреваемый №3: старые ядра и кэш apt. Каждое обновление ядра оставляет предыдущее «на всякий случай», по 200–400 МБ штука, плюс кэш скачанных пакетов. За год набегает пара гигабайт. Лечится штатно и безопасно:

apt autoremove --purge
apt clean

(Про то, почему обновления вообще накапливаются и как обновляться без страха, — следующая, шестая статья.)

Подозреваемый №4: вы сами. Самый обидный пункт галереи. Дамп базы «на минутку» перед рискованной операцией — dump_final_2(1).sql на 4 ГБ в домашнем каталоге. Архив backup_old.tar.gz, который переехал на новый сервер вместе со старым сервером. Каталог site_backup_before_redesign/. Найдём всё крупное и забытое одним махом:

find / -xdev -type f -size +500M -exec ls -lh {} \; 2>/dev/null

Каждый найденный файл — маленькое свидание с собой прошлым. И заодно повод напомнить: бэкапы должны жить не на том же сервереподробно об этом мы уже писали. Дамп на своём же диске — это не бэкап, это пожиратель места с ложным чувством безопасности в комплекте.

Два спецслучая, о которых не расскажет df -h

Спецслучай первый: место есть, а «No space left on device». Файловая система хранит не только байты, но и inodes — записи о самих файлах, — и их количество конечно. Миллионы мелких файлов (кэш сессий PHP, который никто не чистит; очередь писем; node_modules в пяти копиях) способны исчерпать inodes при полупустом диске. Проверка:

df -i
от 0 ₽ на старте
Свой VPS дешевле, чем кажется
Прозрачные тарифы без сюрпризов в чеке. Платите за то, что реально используете, и масштабируйтесь, когда вырастете.
Смотреть тарифы

Если IUse% — 100%, а df -h показывает свободное место, — вот ваша разгадка. Найти рассадник мелочи: du --inodes --max-depth=1 /var | sort -rn | head — и дальше вниз по дереву, как обычно.

Спецслучай второй: файл удалили, а место не вернулось. Классика: нашли лог на 15 ГБ, сделали rm, а df -h не изменился. Почему: в Linux файл живёт, пока его держит хоть один процесс. Вы удалили имя, но nginx продолжает писать в открытый файл-призрак — место занято, а найти его теперь нельзя даже через ncdu. Проверка:

lsof +L1 | grep deleted

Лечение — перезапустить (или послать сигнал переоткрыть логи) процессу-владельцу: systemctl reload nginx. И правило на будущее: растущие логи не удаляют, а обнуляютtruncate -s 0 файл.log освобождает место мгновенно и без призраков. А ещё лучше — чтобы их обрезал не человек по ночам, а автоматика по расписанию. Переходим к главному.

Logrotate: уборка по расписанию

Всё, что мы делали выше, — разовая уборка. Повторится ли «внезапно» — зависит от того, налажена ли регулярная. За ротацию текстовых логов в Debian/Ubuntu отвечает logrotate: он уже установлен, уже крутится ежедневно и уже обслуживает системные логи и nginx — конфиги лежат в /etc/logrotate.d/. Проблема обычно не в его отсутствии, а в двух вещах: настройки по умолчанию слишком щедрые, и ваши собственные логи (приложения, скриптов, cron-задач) им не охвачены вообще.

Разберём конфиг на примере — заведём ротацию для логов гипотетического приложения, /etc/logrotate.d/myapp:

/var/www/myapp/storage/logs/*.log {
    daily              # проверять ежедневно
    rotate 14          # хранить 14 архивов, старше — удалять
    maxsize 100M       # или ротировать раньше, если файл раздулся
    compress           # архивы сжимать (текстовые логи жмутся в ~10 раз)
    delaycompress      # кроме самого свежего — вдруг он ещё дописывается
    missingok          # нет файла — не ошибка
    notifempty         # пустой — не трогать
    copytruncate       # скопировать и обнулить на месте (см. ниже)
}

Про copytruncate — важная развилка, прямо связанная с файлами-призраками выше. Стандартная схема ротации — переименовать файл и попросить сервис открыть новый (postrotate со systemctl reload ...). Она аккуратнее, но требует, чтобы приложение умело переоткрывать лог. Если не умеет (а самописные скрипты и многие приложения — не умеют), оно продолжит писать в переименованный файл вечно. copytruncate решает это в лоб: копирует содержимое и обнуляет оригинал на месте — приложение ничего не замечает. Цена — теоретическая потеря пары строк в момент копирования; для логов приложения это приемлемо.

от 0 ₽ на старте
Свой VPS дешевле, чем кажется
Прозрачные тарифы без сюрпризов в чеке. Платите за то, что реально используете, и масштабируйтесь, когда вырастете.
Смотреть тарифы

Проверить конфиг, не дожидаясь ночи:

logrotate -d /etc/logrotate.d/myapp    # репетиция, без изменений
logrotate -f /etc/logrotate.d/myapp    # принудительная ротация

И вторая половина уборки — журнал systemd, который logrotate не трогает. Ставим ему постоянный потолок в /etc/systemd/journald.conf:

SystemMaxUse=500M

и systemctl restart systemd-journald. Всё, оба мира логов под контролем.

Чек-лист: диск под контролем

Что Разовая уборка Постоянная профилактика
Текстовые логи truncate -s 0 жирным logrotate с maxsize на все свои логи
Журнал systemd journalctl --vacuum-size=500M SystemMaxUse=500M в journald.conf
Docker docker system prune -a лимит логов в daemon.json + prune по расписанию
Ядра и apt apt autoremove --purge && apt clean раз в месяц вместе с обновлениями
Забытые дампы find / -xdev -size +500M бэкапы — на другой сервер, автоматически
Ранняя тревога алерт Netdata на 85–90% (статья 4)

Пункты «по расписанию» из правой колонки напрашиваются на автоматизацию — и мы её сделаем в двенадцатой статье, когда будем разбирать cron и systemd-таймеры: одна задача в неделю на prune, одна на autoremove, и ручной уборки в вашей жизни больше нет.

Главная мысль на вынос даже не техническая. Диск не кончается внезапно — он кончается месяцами, просто без свидетелей. «Внезапность» — это разница между сервером, на который смотрят (алерт на 90%, ротация настроена, prune по расписанию), и сервером, на который смотрят только когда он уже лёг. Первый вариант дешевле во всех валютах, включая сон.

В следующей статье — вторая по популярности причина «ничего не трогал, само сломалось», только с обратным знаком: обновления. Разберёмся, почему apt upgrade пугает сильнее, чем должен, что такое unattended-upgrades, чем на проде опасен dist-upgrade — и когда переезд на новый релиз Ubuntu действительно нужен, а когда это чистый зуд перфекциониста.

1 месяц бесплатно
Первый сервер? Пусть будет нестрашным
Понятная панель, готовые шаблоны и поддержка, которая объясняет по-человечески. Поднимите проект, даже если SSH видите впервые.
Попробовать бесплатно
Хостинг Siteko

VDS с первого дня

Свой сервер Siteko под полным контролем

NVMe-диски, root-доступ и честные ресурсы без «соседей». Поднимите VPN, бота или прод-проект и масштабируйтесь без тесных рамок.

  • Root-доступ полная свобода настройки под ваш стек.
  • NVMe и свежее железо скорость заметна с первого запроса.
  • Перенос бесплатно поможем переехать без простоя.
Выбрать VDS