Дисковые квоты для пользователей и контейнеров
Один пользователь с логами, которые забыли ротировать, или один контейнер с утечкой в volume — и вот уже диск сервера забит на 100%, а вместе с ним падают чужие сайты, базы данных и почта, которые к виновнику вообще не имеют отношения. Дисковые квоты решают эту проблему на уровне ОС: они не дают одному пользователю или контейнеру занять больше выделенной ему доли, независимо от того, случайность это или чей-то злой умысел. Разберём, как настроить квоты на классических файловых системах, отдельно — для Docker-контейнеров, и как не проспать момент, когда лимит вот-вот будет исчерпан.
Содержание
Зачем квоты, если места вроде хватает
На сервере с одним владельцем и одним приложением дисковые квоты обычно не нужны — там один процесс отвечает за всё, и если диск заполнился, разбираться приходится в одном месте. Квоты становятся насущной задачей там, где на одном диске живёт несколько независимых сущностей: несколько пользователей на shared-хостинге, несколько клиентов на одном VPS с отдельными аккаунтами, несколько Docker-контейнеров с разными командами или проектами.
Без квот у любой из этих сущностей есть техническая возможность занять весь диск целиком. Причины бывают безобидные: забытый лог без ротации, аварийный дамп базы, временные файлы конвертации видео, которые никто не подчистил. Бывают и не очень безобидные: один арендатор VPS сознательно льёт данные, чтобы вытеснить соседей, или скомпрометированный контейнер пишет мусор до отказа диска как часть атаки.
Итог один: когда диск заполняется на 100%, перестают писать не только виновник, а вообще все процессы, которым нужно что-то сохранить на этот раздел — от логов systemd до записи в базу данных. Квота ограничивает ущерб рамками одного пользователя или контейнера: у него просто перестаёт получаться писать данные сверх лимита, а у соседей всё продолжает работать штатно.
Классические квоты Linux: quotacheck, edquota, setquota
На ext4 и большинстве других традиционных файловых систем Linux квоты реализованы через отдельную подсистему ядра, которая считает, сколько места (в блоках) и сколько inode использует каждый пользователь или группа на конкретной файловой системе. Настройка состоит из нескольких обязательных шагов.
Сначала ставим утилиты:
# Debian/Ubuntu
apt install quota
# RHEL/AlmaLinux/Rocky
dnf install quota
Далее нужно включить поддержку квот прямо на файловой системе — это делается через опции монтирования в /etc/fstab. Пример для отдельного раздела /dev/sdb1, смонтированного в /data:
/dev/sdb1 /data ext4 defaults,usrquota,grpquota 0 2
Опция usrquota включает учёт по пользователям, grpquota — по группам. После правки fstab перемонтируем раздел без перезагрузки:
mount -o remount /data
Дальше нужно создать служебные файлы учёта квот и просканировать файловую систему на предмет текущего использования — это делает quotacheck:
quotacheck -cum /data # создать файл учёта по пользователям (aquota.user)
quotacheck -cgm /data # то же самое для групп (aquota.group)
Флаг -c создаёт файлы заново, -u/-g — по пользователям/группам, -m — не перемонтировать в read-only во время проверки (на «живом» проде это важно). После первого создания файлов включаем квоты:
quotaon /data
Теперь можно назначить лимит конкретному пользователю. Это делается интерактивно через edquota (откроется редактор с текущими значениями):
edquota -u ivan
Внутри будет строка вида:
Disk quotas for user ivan (uid 1001):
Filesystem blocks soft hard inodes soft hard
/dev/sdb1 102400 5000000 6000000 12000 0 0
Значения blocks/soft/hard указаны в килобайтах. Для того же результата без интерактивного редактора есть setquota — удобнее для скриптов и автоматизации:
setquota -u ivan 5000000 6000000 0 0 /data
Здесь по порядку: soft-лимит блоков (5 000 000 КБ ≈ 4.7 ГБ), hard-лимит блоков (6 000 000 КБ ≈ 5.7 ГБ), soft и hard лимиты по inode (0 0 означает «без ограничения по числу файлов»). Проверить текущее состояние пользователя:
quota -u ivan
А общий отчёт по всем пользователям файловой системы даёт repquota:
repquota -a
На XFS механизм концептуально тот же, но инструментарий другой: вместо edquota/setquota используется xfs_quota, а вместо usrquota/grpquota в fstab — опции uquota, gquota, pquota (project quota, отдельный третий вид квот, полезный для ограничения по каталогу вне привязки к владельцу файлов). Если вы выбираете файловую систему заранее, у нас есть отдельный разбор — ext4, XFS, ZFS, Btrfs — как выбрать.
Нужен сервер под эту задачу?
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Арендовать серверSoft и hard лимиты: в чём разница
Классическая система квот Linux всегда оперирует парой значений, и путаница между ними — частая причина, почему квота «как будто не работает».
Hard-лимит — это абсолютный потолок. Как только пользователь или группа достигают этого значения по объёму данных или по числу inode, ядро физически запрещает дальнейшую запись: приложение получит ошибку Disk quota exceeded (обычно это EDQUOT на уровне системного вызова), и никакого способа обойти это без вмешательства администратора нет. Это защитный предел, который нельзя превысить ни при каких обстоятельствах.
Soft-лимит — это предупреждение, а не запрет. Как только использование превышает soft-значение, пользователь по-прежнему может писать данные — но система засекает время. В течение так называемого grace-периода (льготного срока, по умолчанию 7 дней) превышение soft-лимита допускается. Если за это время использование не опустится обратно ниже soft-порога, soft-лимит начинает вести себя как hard: запись блокируется, даже если формально hard-лимит ещё не достигнут.
Grace-период настраивается отдельно, тоже через edquota, но с флагом -t:
edquota -t
Там задаются сроки в днях или часах отдельно для блоков и для inode. Логика такого разделения простая: soft-лимит — это ожидаемая «нормальная» квота, которую можно кратковременно превысить (пользователь залил больше файлов, чем обычно, разово), а hard-лимит — это жёсткая гарантия того, что как бы долго ни длилось превышение, дальше определённой границы диск не пострадает. На практике многие ставят soft-лимит немного ниже hard (например, soft на 10-15% меньше), чтобы у пользователя было время заметить предупреждение и убрать лишнее, прежде чем запись реально остановится.
Квоты для Docker: другой механизм
Классическая quota-подсистема Linux считает использование диска по владельцу файла (UID/GID) на конкретной файловой системе. Docker-контейнер по умолчанию не заводит отдельного «пользователя файловой системы» для своего слоя хранения — все контейнеры на хосте обычно пишут через один и тот же storage driver (чаще всего overlay2) в общий каталог /var/lib/docker, поэтому обычные usrquota/grpquota тут работают плохо: они не различают, какие мегабайты в общем каталоге принадлежат какому контейнеру.
Есть два рабочих подхода.
Первый — лимит на уровне storage-опции overlay2. Если базовая файловая система, на которой лежит /var/lib/docker, — XFS с включённой project-квотой (pquota), Docker умеет ограничивать размер writable-слоя каждого контейнера через --storage-opt:
docker run --storage-opt size=10G my-image
Ограничение: это работает только с overlay2 поверх XFS с pquota. На ext4 эта опция не сработает вовсе — Docker вернёт ошибку, что storage driver не поддерживает per-container size limit.
Второй, более универсальный подход — отдельный volume с явным ограничением размера. Вместо того чтобы ограничивать сам контейнер, создают отдельный раздел или loopback-файл фиксированного размера, монтируют его как volume, и контейнер физически не может писать в него больше, чем позволяет размер этого раздела — вне зависимости от storage driver:
# создать файл-образ на 10 ГБ и отформатировать под ext4
fallocate -l 10G /var/lib/docker-volumes/app-data.img
mkfs.ext4 /var/lib/docker-volumes/app-data.img
# смонтировать его через loop-устройство
mount -o loop /var/lib/docker-volumes/app-data.img /mnt/app-data
# передать в контейнер как bind-mount
docker run -v /mnt/app-data:/data my-image
Этот способ не зависит от файловой системы хоста и от версии Docker — ограничение работает на уровне блочного устройства, а не на уровне метаданных storage driver. Он же удобен, если данных для одного сервиса ожидается много и стабильно — тогда логичнее сразу выделить под него отдельный LVM-том или ZFS-датасет фиксированного размера, а не общий overlay2-слой. Про то, какие типы volume вообще есть в Docker и когда какой уместен, у нас есть отдельная статья — Docker volumes: типы и когда какой использовать.
Важный нюанс: лимит на диск для контейнера — это защита от переполнения общего диска хоста, а не защита данных внутри контейнера друг от друга. Если внутри одного контейнера работает несколько процессов от разных пользователей ОС и вы хотите разграничить их между собой — там снова пригодится классическая quota-подсистема, но уже внутри файловой системы, примонтированной в контейнер.
ZFS и Btrfs: квоты без quotacheck и edquota
Если под данные используется ZFS или Btrfs, можно вообще не связываться с классической quota-подсистемой — у обеих файловых систем есть встроенный, более простой механизм ограничения места на уровне датасета (ZFS) или подтома (Btrfs).
В ZFS квота задаётся одной командой на весь датасет, без предварительного quotacheck и без отдельного включения флагов монтирования:
zfs set quota=10G tank/clients/ivan
Разница между quota и refquota в ZFS принципиальна: quota считает всё, что относится к датасету, включая место, занятое снапшотами и клонами. refquota считает только «живые» данные, видимые в файловой системе прямо сейчас, без учёта снапшотов:
zfs set refquota=10G tank/clients/ivan
Если вы активно используете снапшоты для бэкапов (см. ZFS за 15 минут: пул, датасет, снапшот), обычно осмысленнее ограничивать именно refquota — иначе накопленные снапшоты сами могут упереться в лимит и начать блокировать запись новых данных, хотя видимых файлов в датасете стало даже меньше.
У ZFS есть и вариант квоты по пользователю или группе прямо внутри одного общего датасета, без создания отдельного датасета на каждого:
zfs set userquota@ivan=5G tank/shared
zfs set groupquota@developers=20G tank/shared
Посмотреть текущее использование места по датасетам:
zfs list -o name,used,avail,refer,quota tank/clients/ivan
В Btrfs механизм называется quota groups (qgroups), и его сначала нужно явно включить на уровне подтома:
btrfs quota enable /mnt/data
btrfs qgroup limit 10G /mnt/data/subv-ivan
Посмотреть текущее состояние qgroup:
btrfs qgroup show -pcre /mnt/data
У Btrfs qgroups есть репутация менее предсказуемого механизма при большом количестве снапшотов — расчёт использования пересчитывается рекурсивно по дереву группы и может заметно нагружать диск на файловых системах с активной историей снапшотов. Если это ваш случай, стоит свериться с текущим состоянием Btrfs перед тем, как закладывать qgroups в продакшен-сценарий с частыми снапшотами.
В обоих случаях (ZFS и Btrfs) не нужно ничего мыслить в терминах soft/hard и grace-периода — там либо лимит есть и место просто заканчивается при его достижении (запись возвращает ошибку), либо лимита нет вовсе. Это проще в настройке, но и грубее: собственного «мягкого» предупреждения с отсрочкой, как в классической quota-подсистеме, тут из коробки нет — эту роль должен взять на себя мониторинг.
Мониторинг: квота без алертов — это отложенный сюрприз
Квота сама по себе не решает проблему полностью — она превращает «диск сервера переполнен и легло всё» в «конкретный пользователь или контейнер внезапно не может писать данные». Это уже лучше, но для пользователя или сервиса, который упёрся в лимит, это всё ещё неприятный сюрприз: приложение может начать возвращать ошибки 500, задание — падать с непонятным исключением, а разбираться в причине придётся постфактум, когда что-то уже не сработало.
Правильный процесс — следить за приближением к порогу заранее, а не реагировать на отказ записи по факту. Практический ориентир — алерт на 80% использования как первое предупреждение и на 90-95% как срочное. Дальше нужно решить: увеличить квоту, почистить данные или расширить сам диск, если это применимо.
Для классических quota-лимитов текущее состояние легко вытащить скриптом на основе repquota:
#!/bin/bash
THRESHOLD=80
repquota -a | awk -v t=$THRESHOLD 'NR>5 {
if ($3 > 0) {
used=$3; soft=$4;
if (soft > 0) {
pct = used * 100 / soft;
if (pct >= t) print $1, pct"%"
}
}
}'
Для ZFS проще опираться на zfs list с полями used и quota и парсить процент в cron-скрипте, либо использовать готовый Zabbix/Prometheus-экспортёр, который уже умеет отдавать использование датасета относительно квоты напрямую.
Для Docker-контейнеров с ограничением через volume мониторить нужно обычное использование смонтированного раздела через df — контейнер тут прозрачен, интересен именно раздел или loop-устройство под ним. Если у вас уже поднят мониторинг диска в целом на сервере, квоты логично добавить туда же дополнительными правилами — а не заводить для них отдельный независимый механизм алертов. Как настроить базовый мониторинг диска с нуля, разобрано в статье мониторинг диска на VPS: установка и настройка.
Отдельно стоит убедиться, что алерт долетает туда, где его реально увидят вовремя — почта легко теряется в потоке других писем, а вот уведомление в Telegram обычно замечают в течение минут, а не часов.
Нужен сервер под эту задачу?
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Арендовать серверНужны сами нейросети для контента?
Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.
Частые вопросы
Что произойдёт с процессом, который упёрся в hard-лимит квоты?
Системный вызов записи вернёт ошибку EDQUOT («Disk quota exceeded»). Как именно это отобразится пользователю, зависит от приложения — где-то это явное сообщение об ошибке, где-то — падение процесса или незаписанные данные без внятного лога, поэтому важно проверить обработку этой ошибки в конкретном приложении заранее, а не полагаться на то, что оно красиво её покажет.
Можно ли включить квоты на уже работающем сервере без остановки сервисов?
Да, quotacheck умеет работать в режиме -m без перевода файловой системы в read-only, но кратковременная нагрузка на диск во время первого сканирования будет — лучше делать это в окно с низкой активностью, а не в пик нагрузки.
Работают ли квоты для root?
По умолчанию root не подчиняется пользовательским квотам — это осознанное поведение ядра для операций восстановления. Если нужно ограничить именно root, смотрите на project-квоты (XFS pquota) или на изоляцию через отдельный volume фиксированного размера, описанную выше.
Что лучше: квота на всю файловую систему или отдельный раздел под каждого клиента?
Отдельный раздел (или ZFS-датасет / LVM-том) даёт более чистую изоляцию — сбой файловой системы одного клиента не задевает остальных, и его легче перенести или расширить отдельно. Общая файловая система с квотами по UID проще в администрировании при множестве мелких аккаунтов, но её fsck или повреждение затронет всех сразу.
Нужно ли настраивать квоты, если сервер один и на нём один проект?
Обычно нет практического смысла — весь смысл квот в разграничении между независимыми сущностями. Если это один сервис с одним владельцем, вместо квот полезнее просто мониторинг общего заполнения диска и ротация логов, чтобы место не уходило незаметно.
Обсудить статью, задать вопрос или начать новую тему
Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.
Перейти в сообщество →