Linux

Управление ресурсами Linux через systemd cgroups: полное руководство

Этот гайд подробно объясняет, как использовать встроенные возможности systemd для создания и управления cgroups (control groups). Вы научитесь ограничивать ресурсы (CPU, память, дисковый I/O) для отдельных сервисов или групп процессов, что критично для стабильности сервера и изоляции рабочих нагрузок.

Обновлено 16 февраля 2026 г.
20-30 мин
Средняя
FixPedia Team
Применимо к:systemd 235+Linux ядро 4.5+Ubuntu 18.04+CentOS 8+

Введение / Зачем это нужно

Control Groups (cgroups) — это механизм ядра Linux, который позволяет изолировать и ограничивать использование ресурсов (CPU, памяти, дискового ввода-вывода, сети) для групп процессов. Systemd, являющийся инициализатором и менеджером системных служб в большинстве современных дистрибутивов, предоставляет удобный и декларативный интерфейс для работы с cgroups.

Этот гайд покажет, как использовать встроенные возможности systemd для управления ресурсами без необходимости вручную работать с cgcreate или редактировать файлы в /sys/fs/cgroup. Вы сможете:

  • Ограничить объём оперативной памяти и файлового подкачивания (swap) для службы.
  • Установить лимит на использование CPU (в процентах или в ядрах).
  • Настроить приоритет дискового I/O.
  • Создавать логические группы процессов (срезы — slices) для единого управления ресурсами.

После выполнения этого руководства вы получите полный контроль над расходованием ресурсов вашими сервисами, что особенно важно для хостинга, контейнеризации и стабильной работы многопользовательских систем.

Требования / Подготовка

Перед началом убедитесь, что:

  1. У вас есть права root или пользователь с правами через sudo.
  2. Установлен systemd версии 235 или новее. Проверить: systemctl --version.
  3. Ядро Linux поддерживает cgroups v2 (рекомендуется) или v1. Проверить тип иерархии: mount | grep cgroup.
    • Идеальный вариант: cgroup2 смонтирован в /sys/fs/cgroup.
    • Если смонтированы старые контроллеры (memory, cpu и т.д.), systemd всё равно сможет управлять ими через единую иерархию.
  4. Вы знаете имя службы 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. Все процессы, запущенные внутри определённого слота, будут наследовать его ограничения ресурсов. Это удобно для группировки связанных служб (например, всех сервисов веб-приложения).

  1. Создайте конфигурационный файл для нового слота. Назовём его myapp.slice (замените myapp на осмысленное имя):
    sudo nano /etc/systemd/system/myapp.slice
    
  2. Добавьте базовый контент. Мы зададим слоту описание и укажем, что он должен находиться внутри системного слота system.slice (стандартная практика):
    [Slice]
    # Описание слота
    Description=Slice для группировки сервисов моего приложения
    # Устанавливаем лимиты ВСЕМ юнитам внутри этого слота по умолчанию.
    # Эти значения можно переопределить в конфиге конкретного сервиса.
    MemoryMax=2G
    CPUQuota=50%
    
  3. Сохраните файл (Ctrl+O, Enter, Ctrl+X).
  4. Перезагрузите конфигурацию systemd:
    sudo systemctl daemon-reload
    
  5. Активируйте слот (хотя он и не имеет состояния active, его нужно "загрузить" в менеджер):
    sudo systemctl start myapp.slice
    

Теперь любой сервис, который вы добавите в этот слот (через Slice=myapp.slice в его конфиге), автоматически получит указанные выше ограничения, если не переопределит их самостоятельно.

Шаг 3: Настройте ограничения ресурсов для конкретного юнита (сервиса)

Давайте добавим ограничения в конфигурационный файл вашего сервиса. Допустим, это myapp.service.

  1. Создайте или отредактируйте файл юнита:
    sudo nano /etc/systemd/system/myapp.service
    
  2. В секцию [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. Сохраните файл.

  1. Критически важный шаг: Примените изменения конфигурации:
    sudo systemctl daemon-reload
    
  2. Перезапустите сервис, чтобы новые лимиты вступили в силу:
    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: Проверка результата

После настройки и перезапуска сервиса проверьте, что лимиты применены.

  1. Просмотр статуса сервиса и его cgroup-свойств:
    systemctl status myapp.service
    

    В выводе ищите строки, начинающиеся с Memory, CPU, Tasks. Они покажут текущие лимиты и потребление.
  2. Использование systemd-cgtop (аналог top для cgroups):
    # Обновление каждые 2 секунды
    systemd-cgtop -n 2
    

    Вы увидите иерархию всех cgroups systemd и потребление ими ресурсов (MEM, CPU, IO). Ваш сервис должен отображаться в ветке system.slice или вашего кастомного myapp.slice.
  3. Прямой просмотр файлов в виртуальной файловой системе 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).
  4. Проверка изнутри контейнера/процесса: Если приложение само может отдавать статистику (например, через /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. Вы можете создать отдельный слот для группы контейнеров:

  1. Создайте /etc/systemd/system/containers.slice:
    [Slice]
    Description=Slice для изоляции контейнеров
    # Ограничим всю группу контейнеров 4 ядрами и 8 ГБ RAM
    CPUQuota=400%
    MemoryMax=8G
    
  2. Перезагрузите systemd: sudo systemctl daemon-reload.
  3. При запуске контейнера через 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.

Часто задаваемые вопросы

Чем cgroups в systemd отличаются от классических cgroups v1?
Почему мои настройки MemoryMax не применяются?
Можно ли управлять cgroups для уже запущенного процесса?
Что такое slice и зачем он нужен?

Полезное

Проверьте версию systemd и поддержку cgroups v2
Создайте кастомный slice для группировки сервисов
Настройте ограничения ресурсов для юнита (сервиса)
Перезагрузите конфигурацию systemd и примените настройки
Убедитесь, что ограничения работают