Новинка Виртуальный VDS сервер в Финляндии от 390р
Выберите продукт

Лимиты в 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-сервер
Виртуальные серверы с быстрым запуском и гибкой конфигурацией от 390₽ / мес
Доступные локации
Россия Нидерланды Финляндия

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

Лимиты — это только половина решения. Вторая — корректная политика рестартов. Если сервис падает по 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 будут предсказуемы даже в стрессовых сценариях. Начните с включения учёта, замерьте реальную нагрузку, задайте мягкие лимиты, а затем постепенно приближайте жёсткие — это лучший путь к стабильности без случайных даунтаймов.

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

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

Как безопасно выполнить bootstrap MariaDB Galera после полного останова

Как безопасно выполнить bootstrap MariaDB Galera после полного останова

После одновременной остановки всех узлов Galera обычного запуска MariaDB недостаточно. Сначала найдите сервер с последней транзакц ...
CoreDNS в Kubernetes: исправляем CrashLoopBackOff, SERVFAIL и DNS-петлю

CoreDNS в Kubernetes: исправляем CrashLoopBackOff, SERVFAIL и DNS-петлю

Разберём, почему CoreDNS перезапускается или отвечает SERVFAIL, как обнаружить замкнутую пересылку DNS-запросов, проверить доступн ...
Apache Kafka KRaft на VDS: отдельные controller и broker

Apache Kafka KRaft на VDS: отдельные controller и broker

Соберём новый Kafka-кластер без ZooKeeper на шести Linux VDS: три узла controller образуют отказоустойчивый metadata quorum, а три ...