Введение / Зачем это нужно
Control Groups (cgroups) — это механизм ядра Linux, который позволяет изолировать и ограничивать использование ресурсов (CPU, памяти, дискового ввода-вывода, сети) для групп процессов. Systemd, являющийся инициализатором и менеджером системных служб в большинстве современных дистрибутивов, предоставляет удобный и декларативный интерфейс для работы с cgroups.
Этот гайд покажет, как использовать встроенные возможности systemd для управления ресурсами без необходимости вручную работать с cgcreate или редактировать файлы в /sys/fs/cgroup. Вы сможете:
- Ограничить объём оперативной памяти и файлового подкачивания (swap) для службы.
- Установить лимит на использование CPU (в процентах или в ядрах).
- Настроить приоритет дискового I/O.
- Создавать логические группы процессов (срезы — slices) для единого управления ресурсами.
После выполнения этого руководства вы получите полный контроль над расходованием ресурсов вашими сервисами, что особенно важно для хостинга, контейнеризации и стабильной работы многопользовательских систем.
Требования / Подготовка
Перед началом убедитесь, что:
- У вас есть права root или пользователь с правами через
sudo. - Установлен systemd версии 235 или новее. Проверить:
systemctl --version. - Ядро Linux поддерживает cgroups v2 (рекомендуется) или v1. Проверить тип иерархии:
mount | grep cgroup.- Идеальный вариант:
cgroup2смонтирован в/sys/fs/cgroup. - Если смонтированы старые контроллеры (
memory,cpuи т.д.), systemd всё равно сможет управлять ими через единую иерархию.
- Идеальный вариант:
- Вы знаете имя службы systemd, для которой хотите задать ограничения (например,
nginx.service,docker.service). Список активных служб:systemctl list-units --type=service --state=running.
Шаг 1: Проверьте версию systemd и поддержку cgroups v2
Сначала давайте убедимся в готовности системы. Выполните в терминале:
# Проверка версии systemd (требуется 235+)
systemctl --version | head -n1
# Проверка, что systemd использует cgroups v2 (предпочтительно)
# Если в выводе есть "cgroup2", значит, используется единственный unified hierarchy.
mount | grep -E 'cgroup|cgroup2'
# Пример ожидаемого вывода для cgroups v2:
# cgroup2 on /sys/fs/cgroup type cgroup2 (rw,nosuid,nodev,noexec,relatime)
# Если вы видите отдельные монтирования для memory, cpu, blkio и т.д. — это cgroups v1.
# Systemd с ним тоже работает, но синтаксис некоторых директив может незначительно отличаться.
💡 Совет: На большинстве дистрибутивов, выпущенных после 2020 года (Ubuntu 20.04+, Fedora 31+, Debian 11+), по умолчанию используется cgroups v2.
Шаг 2: Создайте кастомный slice (необязательно, но рекомендуется)
Slice — это своего рода папка в иерархии cgroups. Все процессы, запущенные внутри определённого слота, будут наследовать его ограничения ресурсов. Это удобно для группировки связанных служб (например, всех сервисов веб-приложения).
- Создайте конфигурационный файл для нового слота. Назовём его
myapp.slice(заменитеmyappна осмысленное имя):sudo nano /etc/systemd/system/myapp.slice - Добавьте базовый контент. Мы зададим слоту описание и укажем, что он должен находиться внутри системного слота
system.slice(стандартная практика):[Slice] # Описание слота Description=Slice для группировки сервисов моего приложения # Устанавливаем лимиты ВСЕМ юнитам внутри этого слота по умолчанию. # Эти значения можно переопределить в конфиге конкретного сервиса. MemoryMax=2G CPUQuota=50% - Сохраните файл (
Ctrl+O,Enter,Ctrl+X). - Перезагрузите конфигурацию systemd:
sudo systemctl daemon-reload - Активируйте слот (хотя он и не имеет состояния
active, его нужно "загрузить" в менеджер):sudo systemctl start myapp.slice
Теперь любой сервис, который вы добавите в этот слот (через Slice=myapp.slice в его конфиге), автоматически получит указанные выше ограничения, если не переопределит их самостоятельно.
Шаг 3: Настройте ограничения ресурсов для конкретного юнита (сервиса)
Давайте добавим ограничения в конфигурационный файл вашего сервиса. Допустим, это myapp.service.
- Создайте или отредактируйте файл юнита:
sudo nano /etc/systemd/system/myapp.service - В секцию
[Service]добавьте нужные директивы. Полный список смотрите вman systemd.resource-control. Пример конфигурации:[Unit] Description=Моё важное приложение # Указываем, что сервис должен работать внутри нашего кастомного слота # (если вы создали слот на шаге 2). Если нет — пропустите эту строку.
Slice=myapp.slice
[Service]
Type=simple
ExecStart=/usr/local/bin/myapp
# --- Ограничения ресурсов (переопределяют или дополняют слот) ---
# Ограничение по оперативной памяти (включая кэш). При превышении процесс получит OOM.
MemoryMax=1G
# Ограничение по файловому подкачиванию (swap). Установите 0, чтобы запретить.
MemorySwapMax=512M
# Лимит на использование CPU. 50% = половина одного ядра.
CPUQuota=50%
# Дополнительно: можно задать приоритет по CPU (от 1 до 10000, чем выше, тем приоритетнее)
CPUWeight=500
# Ограничение по дисковому I/O (в IOPS). 1000 по умолчанию.
IOWeight=300
# Лимит на операции чтения/записи в секунду (требует cgroups v2).
# IOReadBandwidthMax=/dev/sda 10M
# IOWriteBandwidthMax=/dev/sda 5M
[Install]
WantedBy=multi-user.target
```
3. Сохраните файл.
- Критически важный шаг: Примените изменения конфигурации:
sudo systemctl daemon-reload - Перезапустите сервис, чтобы новые лимиты вступили в силу:
sudo systemctl restart myapp.service
Шаг 4: Управление свойствами во время выполнения (временные изменения)
Иногда нужно быстро ограничить уже запущенный процесс без редактирования файлов. Используйте systemctl set-property.
- Для постоянного изменения (сохранится после перезагрузки сервиса):
sudo systemctl set-property myapp.service MemoryMax=500M sudo systemctl restart myapp.service # Перезапуск обязателен! - Для временного, "сырого" (runtime) изменения (дествует до перезагрузки systemd или сервиса):
sudo systemctl set-property --runtime myapp.service CPUQuota=80%
Это удобно для тестирования.
Шаг 5: Проверка результата
После настройки и перезапуска сервиса проверьте, что лимиты применены.
- Просмотр статуса сервиса и его cgroup-свойств:
systemctl status myapp.service
В выводе ищите строки, начинающиеся сMemory,CPU,Tasks. Они покажут текущие лимиты и потребление. - Использование
systemd-cgtop(аналогtopдля cgroups):# Обновление каждые 2 секунды systemd-cgtop -n 2
Вы увидите иерархию всех cgroups systemd и потребление ими ресурсов (MEM, CPU, IO). Ваш сервис должен отображаться в веткеsystem.sliceили вашего кастомногоmyapp.slice. - Прямой просмотр файлов в виртуальной файловой системе cgroups:
# Перейдите в каталог вашего сервиса или слота cd /sys/fs/cgroup/$(systemctl show -p ControlGroup myapp.service | cut -d= -f2) # Посмотрите установленные лимиты (значения в байтах или процентах) cat memory.max cat cpu.max
Для cgroups v1 пути и имена файлов будут другими (например,memory.limit_in_bytes). - Проверка изнутри контейнера/процесса: Если приложение само может отдавать статистику (например, через
/proc/self/status), убедитесь, чтоVmRSSне превышаетMemoryMax.
Возможные проблемы
Проблема: Сервис не стартует или падает сразу после daemon-reload
Причина: Синтаксическая ошибка в конфигурационном файле .service или .slice.
Решение: Проверьте корректность INI-файла. Используйте sudo systemctl status myapp.service — в выводе будет указана строка с ошибкой разбора. Также можно проверить синтаксис командой sudo systemd-analyze verify /etc/systemd/system/myapp.service.
Проблема: Ограничения MemoryMax игнорируются, процесс использует больше памяти
Причина 1: Включён MemorySwapMax, и система активно использует swap. Ограничение MemoryMax работает только по RAM, а общий лимит (RAM+Swap) задаётся MemorySwapMax.
Решение: Установите MemorySwapMax=0, чтобы запретить использование swap для этого сервиса, или увеличьте оба лимита.
Причина 2: Процесс запущен не от systemd (например, вручную из shell), а вы настраиваете юнит.
Решение: Убедитесь, что процесс является дочерним по отношению к юниту systemd. Используйте pstree -p | grep myapp или systemctl status myapp.service — там должен быть список PID процессов.
Проблема: CPUQuota=50% не ограничивает процесс до 50% одного ядра
Причина: На многопроцессорной системе (например, 8 ядер) 50% от всех ядер — это 4 ядра. Если вы хотите ограничить процесс половиной одного ядра, нужно указать CPUQuota=50% и убедиться, что AllowedCPUs не настроен. Если нужно именно "поляра", то это 100% от одного ядра. Проценты считаются от суммарной мощности всех доступных ядер.
Решение: Пересчитайте нужное значение. Для строгого ограничения одним ядром используйте CPUQuota=100% и AllowedCPUs=0-0 (если нужно только ядро 0).
Проблема: Ошибка "Failed to set property: Permission denied" при set-property
Причина: Вы пытаетесь изменить свойство, которое может быть установлено только в конфигурационном файле (некоторые свойства "immutable" после старта службы), или у вас недостаточно прав.
Решение: Используйте sudo. Если свойство действительно нельзя менять "на лету", внесите изменение в файл юнита и выполните daemon-reload + restart.
Продвинутый пример: Создание слота для группировки контейнеров
Допустим, вы запускаете несколько Docker-контейнеров, но хотите, чтобы они не "съедали" все ресурсы сервера. Docker по умолчанию использует свой собственный cgroup-драйвер, но если вы настроили Docker на использование systemd (через --exec-opt native.cgroupdriver=systemd), то каждый контейнер станет дочерним system.slice или docker.slice. Вы можете создать отдельный слот для группы контейнеров:
- Создайте
/etc/systemd/system/containers.slice:[Slice] Description=Slice для изоляции контейнеров # Ограничим всю группу контейнеров 4 ядрами и 8 ГБ RAM CPUQuota=400% MemoryMax=8G - Перезагрузите systemd:
sudo systemctl daemon-reload. - При запуске контейнера через
docker runукажите, в какой cgroup его поместить (это поддерживается Docker с systemd-драйвером):docker run -d --name my_container --cgroup-parent=containers.slice nginx:alpine
Теперь все процессы в этом контейнере будут вcontainers.slice, и их суммарное потребление не превысит заданных лимитов.
Заключение (не добавлять как заголовок, просто завершить текст)
Вы освоили ключевые механизмы управления ресурсами через systemd и cgroups. Этот подход интегрирован в ОС и не требует установки дополнительного ПО. Помните, что слишком жёсткие лимиты могут привести к падению служб (OOM-киллер, троттлинг CPU), поэтому тестируйте конфигурации в staging-среде. Для более сложных сценариев (например, сетевые лимиты) изучайте дополнительные директивы в man systemd.resource-control и документацию ядра по cgroups v2.