Top.Mail.Ru
OSEN-НИЙ SAAALEСкидка 50% на виртуальный хостинг и VDS
до 30.11.2025 Подробнее
Выберите продукт

Лимиты в systemd: MemoryMax, CPUQuota, TasksMax и защита от runaway‑процессов

Как ограничить сервисы systemd по памяти, CPU и числу потоков, чтобы на VDS не было «всё занято» из‑за утечек или форк‑бомбы. Разбираем MemoryMax, CPUQuota, TasksMax, мягкие и жёсткие лимиты, per‑unit и per‑slice пресеты, мониторинг и тесты.
Лимиты в systemd: MemoryMax, CPUQuota, TasksMax и защита от runaway‑процессов

В любой инфраструктуре, особенно на VDS, неприятная истина проста: один неудачный релиз, бесконтрольный воркер или форк‑бомба могут превратить сервер в кирпич за минуты. Даже аккуратно настроенный веб‑стек не застрахован от runaway‑процессов — потери памяти при пиковой нагрузке, лавинообразного роста потоков или 100% CPU, «съеденного» одной задачей. Современный systemd даёт в руки удобные и предсказуемые лимиты поверх cgroups v2: MemoryMax, CPUQuota, TasksMax и сопутствующие директивы. Ниже — практический разбор с примерами, где и как ставить пределы, чтобы сервисы не мешали друг другу и сервер оставался управляемым.

Как systemd лимитирует ресурсы

systemd управляет ресурсами через cgroups v2. Каждый unit (чаще всего .service) получает собственную контрольную группу, в которой ядро считает память, CPU‑время и количество задач (процессов/потоков). Лимиты задаются в unit‑файле или через drop‑ins. В отличие от классических ulimit, лимиты systemd работают на уровне cgroup, то есть охватывают всю цепочку дочерних процессов сервиса.

Важные понятия:

  • Жёсткие лимиты (например, MemoryMax) — при превышении процесс выбивается OOM‑килером на уровне cgroup.
  • Мягкие лимиты (например, MemoryHigh) — ядро начнёт агрессивнее отбирать страницу кеша и давить память, но сервис ещё живёт.
  • Квоты CPU (CPUQuota) — ограничение доли CPU, чтобы один сервис не доминировал при контеншне.
  • Лимит задач (TasksMax) — верхний предел процессов и потоков в unit.

Если вы всё ещё опираетесь только на ulimit, переведите критичные сервисы на лимиты systemd. Они надёжнее и проще контролируются: процессы не выскальзывают за пределы unit.

MemoryMax: жёсткий предел памяти

MemoryMax задаёт верхнюю границу использования памяти сервисом и всеми его дочерними процессами. В cgroup v2 это фактически memory.max: при достижении лимита начинается отбрасывание страниц, а затем срабатывает OOM‑килер, убивая самый «тяжёлый» процесс внутри unit. Это именно то, что спасает сервер от глобального OOM и фризов.

Дополнительно доступны:

  • MemoryHigh — мягкий предел (pressure), помогает сгладить пики без немедленного убийства процессов.
  • MemorySwapMax — ограничение свопа для unit, если своп включён. Удобно, когда не хотите «спасаться свопом», растягивая агонию.
  • MemoryAccounting=yes — включает учёт памяти; обычно в новых дистрибутивах включен глобально, но лучше указывать явно в критичных unit.

Пример drop‑in для сервиса приложения: лимит 1.5 ГБ RAM и 256 МБ свопа, мягкая граница на 1.2 ГБ:

# /etc/systemd/system/myapp.service.d/limits.conf
[Service]
MemoryAccounting=yes
MemoryHigh=1200M
MemoryMax=1500M
MemorySwapMax=256M
OOMPolicy=kill

Пояснения:

  • OOMPolicy=kill указывает systemd, что делать при OOM внутри unit: убить весь сервис. Это лучше, чем оставлять часть воркеров «зомби».
  • Реальные значения выбирайте с запасом: сложите рабочую память воркеров, буферы, плюс 20–30% на всплески.
  • Профессиональный подход — сначала включить мониторинг (MemoryAccounting) и посмотреть MemoryCurrent под реальной нагрузкой, затем выставить MemoryHigh, потом MemoryMax.

CPUQuota: не дать одному сервису «съесть» весь процессор

CPUQuota позволяет ограничить долю CPU, доступную unit. Значение в процентах, где 100% — одно полное ядро. На 4 vCPU сервис с CPUQuota=200% может использовать до двух ядер. Это удобно для фоновых задач и «шумных соседей» внутри одного сервера.

Сопутствующие опции:

  • CPUAccounting=yes — включает учёт CPU, чтобы видеть реальное потребление (CPUUsageNSec).
  • CPUWeight=1..10000 — относительный приоритет при конкуренции (cfs.weight). Не ограничивает потолок, только влияет на справедливое распределение.
  • CPUQuotaPeriodSec= — период квоты; по умолчанию обычно 100 мс. Иногда имеет смысл увеличивать для задач с короткими пиками.

Минимальный пример для надежного фона:

# /etc/systemd/system/worker.service.d/cpu.conf
[Service]
CPUAccounting=yes
CPUQuota=50%
CPUWeight=100

Такой воркер никогда не заблокирует фронт приложения и Nginx: при конфликте ему достанется не более половины одного ядра.

TasksMax: защита от форк‑ и тред‑бомб

TasksMax ограничивает общее количество потоков и процессов в unit. Это защита на случай утечки потоков, неправильной конфигурации пулов или намеренного форк‑спама. При достижении лимита новые fork/clone получают ошибку, что сохраняет управляемость системы.

Рекомендации по выбору значения:

  • Для веб‑сервера (Nginx) обычно достаточно 512–1024 задач.
  • Для PHP‑FPM или другого пула — кратно числу воркеров + запас на вспомогательные процессы, часто 1024–2048.
  • Для шины фоновых задач — исходя из максимального числа конкурирующих джоб и потоков в каждой.

Пример:

# /etc/systemd/system/php-fpm.service.d/tasks.conf
[Service]
TasksAccounting=yes
TasksMax=1500

Важно не ставить слишком низко: приложениям на JVM, .NET или с активным нативным пулом потоков может понадобиться больше, чем кажется. Ориентируйтесь на замеры в пике.

Пример unit‑файла systemd с лимитами ресурсов

Слои изоляции: service, slice и шаблоны

Кроме настройки каждого .service, удобно создавать свои .slice и размещать в них родственные сервисы. Лимиты в slice задают общий потолок и наследуются сервисами, а уже в самих сервисах можно уточнять.

# /etc/systemd/system/tenant-a.slice
[Unit]
Description=Slice for tenant A

[Slice]
MemoryMax=4G
CPUQuota=250%
TasksMax=8000

Затем привяжите сервисы:

# /etc/systemd/system/myapp.service.d/slice.conf
[Service]
Slice=tenant-a.slice

Так вы ограничите суммарное потребление группы сервисов (например, фронт, воркеры и крон одного проекта), сохраняя индивидуальные лимиты внутри. Если вы управляете воркерами через systemd‑шаблоны, дополнительно загляните в материал о практиках для очередей и воркеров — он про шаблоны, рестарты и троттлинг управления воркерами через systemd.

FastFox VDS
Облачный VDS-сервер в России
Аренда виртуальных серверов с моментальным развертыванием инфраструктуры от 195₽ / мес

Политика перезапуска и «самолечение»

Лимиты — это только половина решения. Вторая — корректная политика рестартов. Если сервис падает по OOM, не нужно оставлять его мёртвым без шанса на восстановление, но и перезапускать в бесконечном цикле тоже опасно.

# /etc/systemd/system/myapp.service.d/restart.conf
[Service]
Restart=on-failure
RestartSec=5s
StartLimitIntervalSec=60
StartLimitBurst=5

Так вы ограничите «тупой» перезапуск, но дадите сервису шанс вернуться после разовой аномалии. В логах удобно отслеживать NRestarts= и причины падений.

Глобальные настройки по умолчанию

Чтобы не повторять директивы во всех unit, можно включить учёт ресурсов глобально и задать дефолтный потолок на задачи:

# /etc/systemd/system.conf
[Manager]
DefaultCPUAccounting=yes
DefaultMemoryAccounting=yes
DefaultTasksAccounting=yes
DefaultTasksMax=10000

После изменений выполните:

systemctl daemon-reexec

Используйте разумные дефолты: достаточно высокие, чтобы не мешать обычной работе, но предотвращающие неконтролируемый рост.

Диагностика и наблюдение

Несколько полезных приёмов, когда нужно понять, «упирается» ли сервис в лимит:

  • systemctl status myapp — быстрый взгляд на состояние и последние строки журнала.
  • systemctl show -p MemoryCurrent,MemoryHigh,MemoryMax myapp — факты по памяти.
  • systemctl show -p CPUUsageNSec,CPUQuota,CPUWeight myapp — факты по CPU.
  • systemctl show -p TasksCurrent,TasksMax myapp — количество задач и потолок.
  • systemd-cgtop — «top» по cgroups: видно, кто ест CPU и RAM.
  • journalctl -u myapp -g OOM — поиск сообщений об OOM.

Для точечной проверки квот и лимитов удобно запускать нагрузку внутри временной cgroup:

systemd-run --unit=bench --scope -p CPUQuota=50% -p MemoryMax=512M stress-ng --vm 1 --vm-bytes 700M --timeout 30s

Команда покажет, как ядро ограничит процесс по памяти и CPU. Это безопасный способ проверить будущие значения на тестовом сервере. Если параллельно усиливаете изоляцию (ProtectSystem, RestrictNamespaces и т.п.), пригодится дополнительный разбор по усилению unit’ов — смотрите харднинг сервисов systemd.

Мониторинг cgroups через systemd-cgtop

Практичные пресеты для веб‑стека

Nginx

Обычно у Nginx небольшой RSS, но важна защита от лавины воркеров при проблемной конфигурации:

# /etc/systemd/system/nginx.service.d/limits.conf
[Service]
MemoryAccounting=yes
MemoryHigh=300M
MemoryMax=500M
TasksAccounting=yes
TasksMax=1024
CPUAccounting=yes
CPUQuota=100%

Квота в 100% одного ядра не помешает сервить статику на многоядерной машине (ядро распределит нагрузку между воркерами), но не позволит Nginx забрать все ядра при сбое.

PHP‑FPM

Здесь латентные риски — крупные чанки под запросы и рост числа воркеров. Кроме unit‑лимитов, обязательно проверьте pm.max_children и pm.max_requests в пуле.

# /etc/systemd/system/php-fpm.service.d/limits.conf
[Service]
MemoryAccounting=yes
MemoryHigh=1.5G
MemoryMax=2G
MemorySwapMax=512M
CPUAccounting=yes
CPUQuota=200%
TasksAccounting=yes
TasksMax=2048

Если пул и так ограничен по числу воркеров, TasksMax остаётся страховкой на случай дополнительных потоков из расширений.

Очереди и воркеры

Воркеры для задач (например, обработка изображений, экспорт, рассылки) склонны «захватывать» CPU. Дайте им потолок и мягкую память:

# /etc/systemd/system/queue-worker@.service
[Unit]
Description=Queue worker %i
After=network.target

[Service]
Type=simple
ExecStart=/usr/local/bin/worker --queue=%i
Restart=on-failure
RestartSec=3s
CPUAccounting=yes
CPUQuota=150%
CPUWeight=200
MemoryAccounting=yes
MemoryHigh=800M
MemoryMax=1G
TasksAccounting=yes
TasksMax=1024

[Install]
WantedBy=multi-user.target

Шаблон с инстансами позволяет масштабировать горизонтально, сохраняя те же лимиты. Если нужно разное поведение для очередей, используйте drop‑ins на конкретные инстансы.

Тонкости и подводные камни

  • cgroups v2. На современных дистрибутивах они включены по умолчанию. Если используете устаревшие ядра или cgroups v1, часть поведения будет отличаться, а некоторые директивы станут недоступны.
  • Page cache тоже считается. Учёт памяти включает анонимные страницы и часть кеша страниц, закреплённого за cgroup. Это нормально, просто учитывайте это в расчётах.
  • Высокие пики компиляции или GC. Для JVM/.NET/Node пиковые всплески вполне нормальны. Сначала посмотрите MemoryCurrent под профилированной нагрузкой, и только потом ставьте MemoryMax.
  • CPUQuota и латентность. Слишком агрессивная квота может увеличить задержки в пиковых сценариях. Играйте CPUQuotaPeriodSec, если видите «дёрганую» производительность.
  • TasksMax и баги библиотек. Когда библиотека внезапно порождает десятки тысяч потоков, лимит спасёт сервер, но приложение начнёт падать на EAGAIN. Это ожидаемо и правильно — ищите причину и чините.

Защита от runaway‑процессов: полный чек‑лист

Соберём вместе практику, которую стоит применить к ключевым сервисам на VDS:

  1. Включить учёт ресурсов глобально: DefaultCPUAccounting, DefaultMemoryAccounting, DefaultTasksAccounting.
  2. Для каждой роли сервиса задать разумные MemoryHigh, MemoryMax, CPUQuota, TasksMax.
  3. Выделить логические .slice для проектов/тенантов. Лимитировать с запасом на будущий рост.
  4. Настроить рестарты: Restart, RestartSec, StartLimit*, OOMPolicy.
  5. Мониторить: собирать MemoryCurrent, CPUUsageNSec, TasksCurrent, NRestarts в метрики и алерты.
  6. Тестировать под нагрузкой: systemd-run и инструментами нагрузки проверять, как сервис ведёт себя у потолка.

Главная цель — не «урезать всем всё», а сделать так, чтобы сбой одного компонента не сносил весь сервер и не мешал остальным сервисам выполнять свою работу.

Отладка инцидентов: быстрый сценарий

Если сервер начал тормозить или один из сервисов постоянно перезапускается, выполните:

  • systemd-cgtop — кто лидер по CPU и RSS прямо сейчас.
  • systemctl show -p MemoryCurrent,MemoryMax,TasksCurrent,TasksMax culprit.service — уткнулся ли сервис в лимит.
  • journalctl -u culprit.service --since "-1h" — содержит ли журнал OOM/ошибки аллокаций.
  • Временно поднимите MemoryHigh drop‑in’ом, если видите давление памяти без OOM, и наблюдайте эффект.

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

Роль systemd‑oomd

На современных системах дополнительную защиту даёт systemd-oomd — пользовательский демон, который реагирует на давление памяти и может предварительно завершать «прожорливые» cgroups до глобального OOM. Для него критически важно, чтобы был включён учёт ресурсов и разумно выставлены MemoryHigh/MemoryMax. Это не замена лимитам, а ещё один инструмент сделать отказ более контролируемым.

Итого

Лимиты systemd — это инструмент «страхования» производительности. MemoryMax обрывает утечки и не даёт вытащить сервер в глобальный OOM, CPUQuota сохраняет отзывчивость системы под пиками, TasksMax спасает от форк‑ и тред‑бомб. Добавьте к этому здравую политику рестартов, слои через .slice и наблюдаемость — и ваши сервисы на VDS будут предсказуемы даже в стрессовых сценариях. Начните с включения учёта, замерьте реальную нагрузку, задайте мягкие лимиты, а затем постепенно приближайте жёсткие — это лучший путь к стабильности без случайных даунтаймов.

Поделиться статьей

Вам будет интересно

Безопасные миграции MySQL: pt‑online‑schema‑change и gh‑ost без простоя OpenAI Статья написана AI Fastfox

Безопасные миграции MySQL: pt‑online‑schema‑change и gh‑ost без простоя

Если таблицы уже на десятках гигабайт, обычный ALTER грозит блокировками и простоями. Разбираем онлайн‑миграции MySQL с pt‑online‑ ...
journald или rsyslog: настраиваем персистентность, rate‑limit и форвардинг OpenAI Статья написана AI Fastfox

journald или rsyslog: настраиваем персистентность, rate‑limit и форвардинг

Как выбрать между journald и rsyslog, включить хранение на диске, настроить rate‑limit и надёжный форвардинг на коллектор? В матер ...
Packer + cloud-init: собираем золотой образ для быстрых развёртываний на VDS OpenAI Статья написана AI Fastfox

Packer + cloud-init: собираем золотой образ для быстрых развёртываний на VDS

Надо запускать новые VDS за минуты и без ручных правок? Разберём Packer + cloud-init: архитектуру пайплайна, минимальные HCL/YAML, ...