Гайд: Ограничение ресурсов для сервисов через systemd cgroups v2 (MemoryMax, CPUQuota, IOWeight)
Архитектура единой иерархии Control Groups v2 (Unified cgroups v2)
Подсистема ядра Linux cgroups v2 (Control Groups) в сочетании с systemd обеспечивает изоляцию и квотирование аппаратных ресурсов сервера (CPU, RAM, Disk I/O, Network, PIDs) между процессами и службами. В отличие от устаревшей раздельной модели cgroups v1, вторая версия реализует единую древовидную иерархию (Unified Hierarchy) с контроллерами memory.max, cpu.max, io.weight, исключая конфликты распределения ресурсов. Если для ресурсоемкого сервиса не настроены лимиты, возникают критические сбои:
- Эффект «Шумного соседа» (Noisy Neighbor): Фоновый воркер или скрипт аналитики утилизирует 100% ядер процессора, блокируя обслуживание клиентов основным веб-сервером;
- Неконтролируемый OOM-Kill: Утечка памяти в одном приложении приводит к тому, что ядро Linux уничтожает СУБД (PostgreSQL/MySQL), работающую на этом же хосте;
- Дисковое голодание (I/O Starvation): Неприоритетный бэкап забивает очередь дискового ввода-вывода.
Бизнес-риски
Непредсказуемые отказы продуктовых сервисов, каскадная деградация производительности кластера, перерасход ресурсов инфраструктуры.
Сравнение ключевых директив cgroups v2 в systemd
| Директива systemd | Контроллер ядра cgroups v2 | Поведение при достижении лимита |
|---|---|---|
MemoryMax= | memory.max | Жесткий лимит. При попытке превысить — мгновенный OOM-Kill внутри группы. |
MemoryHigh= | memory.high | Мягкий троттлинг. Ядро агрессивно сбрасывает кэш и замедляет процессы группы без их убийства. |
CPUQuota= | cpu.max (CFS Quota) | Ограничение процессорного времени (например, 200% = максимум 2 ядра CPU). |
CPUWeight= | cpu.weight (CFS Shares) | Относительный вес приоритета процессора при конкуренции (от 1 до 10000). |
IOWeight= | io.weight (BFQ) | Относительный приоритет дискового ввода-вывода (от 1 до 10000). |
TasksMax= | pids.max | Защита от Fork-бомб (максимальное количество потоков/процессов в юните). |
Регламент настройки квот и изоляции ресурсов сервисов
Сценарий 1: Проверка активации cgroups v2 в ядре
# Проверка типа смонтированной файловой системы cgroups:
mount -t cgroup2
# Ожидаемый вывод: cgroup2 on /sys/fs/cgroup type cgroup2 (rw,nosuid,nodev,noexec,relatime)
# Если используется cgroups v1 -> добавьте в /etc/default/grub параметр:
# GRUB_CMDLINE_LINUX="systemd.unified_cgroup_hierarchy=1"
# и выполните sudo update-grub && sudo rebootСценарий 2: Применение ограничений через Drop-In переопределения (systemctl edit)
Настройте безопасные лимиты для сервиса без прямого редактирования основного файла пакета:
# 1. Открытие редактора переопределений для сервиса (например, my-worker.service):
sudo systemctl edit my-worker.service
# 2. Внесите конфигурацию ограничений в секцию [Service]:
[Service]
# Ограничение памяти: мягкий троттлинг на 2 ГБ, жесткий стоп на 2.5 ГБ:
MemoryHigh=2G
MemoryMax=2.5G
# Ограничение процессора: не более 1.5 ядер CPU (150%):
CPUQuota=150%
# Пониженный приоритет CPU при конкуренции с веб-сервером:
CPUWeight=50
# Пониженный приоритет дискового ввода-вывода:
IOWeight=20
# Ограничение максимального количества процессов:
TasksMax=500
# Настройка защиты от убийства системным OOM (значение от -1000 до 1000, где -1000 = запрет убийства):
OOMScoreAdjust=500 # Делаем этот рабочий процесс первой жертвой при нехватке памяти в ОС
# 3. Примените изменения:
sudo systemctl daemon-reload
sudo systemctl restart my-worker.serviceСценарий 3: Динамическое изменение лимитов на лету (systemctl set-property)
Изменение квот без перезапуска работающего продуктового процесса:
# Установить лимит CPUQuota = 50% прямо сейчас и сохранить на постоянной основе:
sudo systemctl set-property my-worker.service CPUQuota=50% MemoryMax=1GСценарий 4: Мониторинг потребления ресурсов группами через systemd-cgtop
# Интерактивный мониторинг потребления CPU, RAM и I/O по cgroups в реальном времени:
systemd-cgtop -mТиповые ошибки администраторов
- Установка MemoryMax без MemoryHigh: При резких скачках нагрузки процесс мгновенно уничтожается OOM-киллером вместо мягкого освобождения кэшей.
- Использование CPUQuota=50% на многопоточном сервисе: 50% означает половину времени ОДНОГО ядра, что может критически замедлить многопоточное Java/Go-приложение. Для 4-ядерного выделения указывайте
CPUQuota=400%.
Инженеры ITSTM настроят единую модель квотирования cgroups v2, выделят гарантированные ресурсы под критические СУБД и изолируют вспомогательные микросервисы.
Частые вопросы (FAQ)
Как посмотреть реальное потребление памяти конкретной cgroup из ядра?
Проверьте псевдофайл: cat /sys/fs/cgroup/system.slice/my-worker.service/memory.current.
В чем разница между MemoryMax и MemorySwapMax?
MemoryMax ограничивает физическую оперативную память (RAM), а MemorySwapMax задает максимальный объем swap-пространства, который разрешено использовать процессам данной группы.
Как защитить базу данных MySQL / PostgreSQL от OOM-Killer через systemd?
В секции [Service] unit-файла базы данных укажите OOMScoreAdjust=-900. Ядро будет уничтожать любые другие процессы, защищая демон СУБД до последнего.
Что произойдет, если процесс внутри cgroup превысит TasksMax?
Попытки вызова fork() или pthread_create() начнут возвращать ошибку EAGAIN (Resource temporarily unavailable), предотвращая исчерпание системной таблицы PID.