Что дают лимиты cgroups и systemd
Лимиты cgroups позволяют ограничить службе процессор, память, ввод-вывод и число процессов, не меняя её код: нужные директивы пишутся в юнит systemd, а ядро следит за их выполнением. Так одна сорвавшаяся служба не заберёт ресурсы у остальных. Создание самой службы описано в статье как создать службу systemd.
| Директива | Что ограничивает | Значения |
|---|---|---|
| CPUQuota= | потолок времени процессора | проценты: 20% — не больше 20% времени одного ядра, больше 100% — время нескольких ядер |
| CPUWeight= | долю процессора при конкуренции | от 1 до 10000, по умолчанию 100 |
| MemoryHigh= | мягкий порог памяти | байты с суффиксами K, M, G, T, процент или infinity |
| MemoryMax= | жёсткий предел памяти | те же единицы |
| MemorySwapMax= | предел использования swap | байты, процент от размера swap или infinity |
| IOWeight= | долю блочного ввода-вывода | от 1 до 10000, по умолчанию 100 |
| TasksMax= | число задач в юните | число, процент или infinity |
Значения — по man systemd.resource-control. Версию systemd проверьте командой systemctl --version: страница man описывает последнюю версию.
Как проверить, что у вас cgroup v2
Проверка одна: если существует файл /sys/fs/cgroup/cgroup.controllers, система использует cgroup v2. По документации Kubernetes и проекта rootlesscontaine.rs v2 включена по умолчанию в Debian 11 и новее, Ubuntu 21.10 и новее (рекомендуется 22.04 LTS и новее), RHEL 9 и новее, Fedora 31 и новее.
Процессор: CPUQuota и CPUWeight
CPUQuota= ставит жёсткий потолок: 20% значит, что процессы службы никогда не получат больше 20% времени одного ядра, а значение больше 100% даёт время нескольких ядер, например 200% — двух. CPUWeight= потолка не ставит и работает только при конкуренции: свободный процессор служба получит целиком.
CPUWeight= принимает значения от 1 до 10000, по умолчанию 100, и делит процессор между службами одного слайса пропорционально весам.
Память: MemoryHigh, MemoryMax и swap
MemoryMax= — жёсткий предел: если уложиться не удаётся, OOM-killer вызывается внутри юнита. MemoryHigh= — мягкий порог: процессы сильно замедляются и у них отбирают память, но OOM-killer не вызывается. Документация systemd советует основным механизмом делать MemoryHigh=, а MemoryMax= держать как последнюю линию обороны.
На практике это значит: слишком низкий MemoryHigh= проявится как медленная служба, слишком низкий MemoryMax= — как убитая. Как OOM-killer выбирает жертву, разобрано в статье OOM-killer в Linux.
MemorySwapMax= задаёт абсолютный предел использования swap службой: значение 0 запрещает ей swap совсем. Про swap — в статье настройка swap в Linux.
Ввод-вывод и число процессов
IOWeight= делит пропускную способность блочного ввода-вывода между службами одного слайса пропорционально весу, от 1 до 10000, по умолчанию 100. TasksMax= ограничивает число задач в юните; значение по умолчанию берётся из DefaultTasksMax= в systemd-system.conf. Эффект IOWeight= зависит от устройства и настроек ядра, проверяйте его измерением.
Как применить лимиты: drop-in по шагам
Лучший способ — drop-in: небольшой файл в каталоге /etc/systemd/system/<служба>.service.d/, который дополняет юнит и не трогает оригинал. Команда systemctl edit создаёт его сама, а systemctl edit --full открывает полную копию юнита. Значения ниже примерные: подберите их под свою службу и проверьте результат на тестовом экземпляре.
- Создайте файл limits.conf в каталоге drop-in или выполните
sudo systemctl edit myapp.service. - Впишите директивы в секцию [Service], как в примере ниже.
- Выполните
sudo systemctl daemon-reload. - Перезапустите службу:
sudo systemctl restart myapp.service.
# /etc/systemd/system/myapp.service.d/limits.conf
[Service]
CPUQuota=50%
CPUWeight=50
MemoryAccounting=1
MemoryHigh=512M
MemoryMax=768M
MemorySwapMax=0
IOAccounting=1
IOWeight=50
TasksMax=200MemoryAccounting=1 и IOAccounting=1 включают учёт для systemd-cgtop. MemoryHigh= стоит ниже MemoryMax=, чтобы сначала сработало замедление, и только потом убийство.
Как посмотреть результат
Действующие значения показывает systemctl show, а нагрузку по группам — systemd-cgtop: он упорядочивает cgroup по нагрузке на процессор, память или диск. Смотреть результат стоит сразу после перезапуска службы и ещё раз под нагрузкой. Данные неполны, пока для нужных служб не включены MemoryAccounting=1 и IOAccounting=1.
sudo systemctl daemon-reload
sudo systemctl restart myapp.service
systemctl show myapp.service -p MemoryHigh -p MemoryMax -p MemorySwapMax -p TasksMax
systemctl show myapp.service | grep -i cpuquota
systemd-cgtopИмя свойства квоты в systemctl show может отличаться от имени директивы, поэтому ищем по cpuquota. Если служба тормозит и после лимитов, переходите к профилированию: профилирование производительности Linux.
Лимиты для обычного пользователя
Для сеанса обычного пользователя, не root, по умолчанию делегируются только контроллеры memory и pids; cpu и io нужно делегировать отдельно. Это из документации проекта rootlesscontaine.rs, а не из man systemd, и умолчание зависит от дистрибутива: смотрите /lib/systemd/system/user@.service. Сначала проверьте, что уже делегировано в вашей системе.
# /etc/systemd/system/user@.service.d/delegate.conf
[Service]
Delegate=cpu cpuset io memory pidsПосле создания файла выполните systemctl daemon-reload и перезайдите в сеанс или перезагрузите сервер. Контроллер cpuset требует systemd 244 или новее. Для системных служб, которые запускает root, делегирование не нужно: хватает директив в юните.
Частые вопросы
CPUQuota=100% — это весь сервер?
Нет. 100% — это время одного ядра. Служба с CPUQuota=100% не займёт больше одного ядра, а чтобы дать ей два ядра, ставят 200%.
С чего начать, если я не знаю, сколько памяти нужно службе?
С измерения: включите учёт и понаблюдайте за службой в systemd-cgtop под обычной нагрузкой. Затем поставьте MemoryHigh= с запасом над типичным значением, а MemoryMax= — выше него.
Что делать клиентам ZevsHost?
На общем хостинге лимитами ресурсов управляет панель, настраивать ничего не нужно. На VPS и выделенном сервере службами управляете вы: файл drop-in с лимитами пишет сам владелец сервера.