Лимиты CPU, памяти и I/O: настройка cgroups v2 в Linux

Пиковая нагрузка на одном узле роняет соседние службы, и простой бьёт по выручке. Мы настраиваем cgroups v2 под ключ: изолируем службы, задаём лимиты и резервы, чтобы один процесс не забирал общие ресурсы. Расходы на инфраструктуру перестают расти вслепую, а разбор инцидентов ускоряется в разы.
Часто задаваемые вопросы

cgroups v2 и ограничение ресурсов: где ломается прод

В контейнерной среде без ограничений ресурсов узел живёт до первого всплеска. Служба, у которой нет потолка по памяти, при утечке растёт до предела ядра, вытесняет соседей в своп, и диспетчер начинает тратить время на освобождение страниц вместо полезной работы. Деградация под нагрузкой распространяется на весь узел — включая процессы, которые к виновнику отношения не имеют. Это классический шумный сосед: формально виноват один сервис, фактически перестаёт отвечать весь узел.

Перегрузка процессора выглядит иначе, но приводит туда же. Один поток с жадным циклом выбирает выделенную квоту, очереди планировщика растут, и время отклика API-шлюза уезжает за пределы разумного. Внешние системы по истечении ожидания начинают считать узел недоступным, хотя железо исправно, а сеть в норме. Диагностика при этом запаздывает: метрики уровня узла показывают общий рост загрузки, но не указывают на конкретного виновника.

cgroups v2 закрывает это единым деревом иерархии. cpu.max задаёт верхнюю границу времени процессора, cpu.weight — долю под конкуренцией, memory.max — жёсткий предел с завершением процесса, memory.high — мягкий порог с торможением вместо аварии, io.max — полосу на дисковом устройстве, memory.swap.max — запрет свопа для служб, которым он противопоказан. Дополняет картину PSI: он показывает, сколько времени задачи простаивали в ожидании ресурса, то есть сигнал приходит раньше, чем отказ.

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

Измеримый эффект: задержки, простой, изоляция служб

Пока у служб общий пул ресурсов, один процесс с утечкой памяти или всплеском ввода-вывода забирает узел целиком: соседи теряют отклик, ядро запускает аварийное освобождение памяти, деградирует уже всё. С cgroups v2 каждый модуль живёт в своей ветке иерархии и не может выйти за отведённую долю — каскадный отказ как класс исчезает.

Время отклика под нагрузкой перестаёт быть лотереей. Вес планировщика (cpu.weight) и потолок на процессорное время дают службе гарантированную долю даже тогда, когда сосед загружен, — ей не нужно ждать освобождения ядра. Это видно по хвосту распределения: самые медленные запросы перестают отставать от медианы в разы, а не в десятки раз.

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

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

Режимы ограничения: жёсткие лимиты, резервы, веса

Жёсткий потолок и вес решают разные задачи, и путать их — источник аварий. Жёсткий лимит (cpu.max, memory.max) задаёт предел, за который служба не выйдет даже при свободных ресурсах хоста: превышение по памяти приводит к срабатыванию механизма вытеснения, а не к деградации соседей. Гарантированный резерв (memory.min, memory.low) работает наоборот — он защищает процессы от вытеснения, когда на узле начинается давление памяти.

Приоритет по весам (cpu.weight, io.weight) не ограничивает ничего: он включается только при конкуренции и лишь перераспределяет освободившиеся ресурсы. Поэтому веса — правильный выбор для пакетных задач, которые должны уступать онлайн-нагрузке, но могут занять простой. Отдельная группа — ограничение числа процессов (pids.max) и потолок ввода-вывода (io.max): их часто пропускают, а именно они удерживают узел, когда служба исчерпала лимит файловых дескрипторов или устроила лавину процессов.

Вариант Когда подходит Ограничения
Жёсткий лимит по процессору и памяти Сервисы с предсказуемым профилем потребления При неверном расчёте — периодические перезапуски; пиковый трафик не уложится
Гарантированный резерв Критичные службы рядом с шумными соседями Резервирует память даже простаивающим процессам, снижая общую утилизацию узла
Приоритет по весам Пакетная обработка, фоновые задания Не даёт гарантий отклика: при полной загрузке узла служба ждёт
Ограничение числа процессов Публичные и пользовательские сервисы Требует подбора по реальному числу воркеров, иначе отказ вместо деградации
Потолок ввода-вывода Базы данных, репликация, выгрузки Ограничение на уровне устройства, нужен расчёт по полосе диска

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

Как мы внедряем лимиты: от аудита до передачи

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

  1. Аудит потребления. Смотрим, где процессы живут в дереве cgroups v2, что попадает в корневой срез и кто из сервисов на одной машине фактически съедает ресурсы.
  2. Профили нагрузки. Снимаем суточный и недельный профиль: рабочее множество памяти, пиковые окна, число потоков и дисковых операций, метрики давления при деградации соседей.
  3. Проектирование политики. Разводим мягкий и жёсткий порог по памяти, задаём вес и потолок процессорного времени, лимиты ввода-вывода с запасом на всплеск, выстраиваем иерархию срезов.
  4. Настройка. Переносим значения в конфигурацию срезов, отдельно обрабатываем системные и прикладные сервисы, проверяем, что запуск и фоновые задачи не упираются в потолок.
  5. Проверка под нагрузкой. Повторяем пиковый сценарий: троттлинг не режет отклик, защита памяти срабатывает предсказуемо, соседние сервисы не деградируют.
  6. План откату. Фиксируем, какие параметры и в каком порядке возвращаются, где точка невозврата и как быстро снять лимит на конкретном сервисе.
  7. Передача в эксплуатацию. Отдаём регламент: значения, метрики и алерты на приближение к потолку, порядок изменения лимитов и ответственных за пересмотр профилей.

Что остаётся на руки: конфигурации, схемы, регламенты

Итог работ — не запущенный сервер, а комплект, по которому ваша команда сопровождения ведёт систему без нас и без устных пояснений. Всё, что настроено в cgroups v2, лежит в репозитории: лимиты описаны декларативно, изменения проходят проверку, история правок сохраняется и откатывается одной командой.

  • Файлы конфигурации с лимитами на каждый сервис: cpu.max, memory.max, memory.high, io.max — со значением и комментарием, из каких замеров оно получено.
  • Схема распределения ресурсов: иерархия групп, что вложено во что, где зарезервировано под системные процессы, где оставлен запас на пиковую нагрузку.
  • Наборы значений для контуров: отдельные профили для промышленного, предпродуктивного окружения и локальной разработки, чтобы тест не повторял боевые ограничения.
  • Регламент обслуживания: периодичность проверок, пороги срабатывания memory.high, реакция на устойчивый рост потребления, порядок снятия статистики по группам.
  • Инструкция для дежурных: пошаговые сценарии на типовые события — группа упирается в лимит, процесс снят по нехватке памяти, дисковая подсистема тормозит из-за конкуренции.
  • Порядок изменения лимитов: кто согласует правку, как замеряется реальная потребность, каким образом значение доезжает до узлов и за какое время откатывается.
  • Карта мониторинга: какие показатели групп выведены на панели и на какие оповещения завязаны ранние признаки приближения к границе.

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

Кейс: узел перестал падать под пиковой нагрузкой

До: отказ одного сервиса останавливал узел

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

Что изменили

Перевели узел на cgroups v2 с единой иерархией вместо разрозненных контроллеров. Задали жёсткие пределы по памяти и числу процессов, относительные веса по процессорному времени и квоты на дисковый ввод-вывод для каждого сервиса. Ключевое — групповое завершение: при перерасходе памяти гасится один проблемный процесс, а не весь узел. Поведение держится и когда соседи одновременно выходят на пик.

Результат

Частота отказов узла снизилась с нескольких раз в неделю до нуля за наблюдаемый период, время восстановления — с часов до минут: проблемный сервис перезапускается по политике рестарта, остальные не замечают. Метрики снимали в Prometheus — при пиках потребление упирается в заданный предел, а не растёт до исчерпания. Класс отказов «утечка одного сервиса роняет соседей» закрыт, а пределы живут в конфигурации и пересматриваются без пересборки образов.

Что делать, если лимиты уже настроены в прежней версии cgroups?

Перенести политики «как есть» не получится: в прежней ветке каждый контроллер живёт в своей иерархии, а в новой дерево одно и параметры в нём объединены.

cpu.shares уступает место cpu.weight с другим диапазоном значений, квота и период сливаются в cpu.max, дисковые ограничения — в io.max. Значит, перенос — это пересчёт значений и явное включение нужных контроллеров в cgroup.subtree_control, а не замена имён по шаблону.

Обратную совместимость даёт смешанный режим: часть контроллеров остаётся в старой иерархии, часть уходит в новую. Так узел переживает перезагрузку в удобное окно, но как постоянная схема это не годится — процесс начинает учитываться в двух деревьях, а отданный контроллер в старую иерархию уже не возвращается.

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

Проверка после перехода — не только «служба запустилась». Сверяют, что контроллеры активны и лимиты применились к процессам, снимают метрики давления memory.pressure, cpu.pressure, io.pressure — в прежней ветке их просто нет. Затем нагрузочный профиль, близкий к рабочему, подтверждает, что ограничения не режут полезную работу.

Частые технические сомнения и возражения

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

Не задушим ли мы службу жёстким лимитом?

Лимит процессорного времени не выключает процесс, а ограничивает его долю: всплеск обрабатывается медленнее, но не падает. Опасен именно жёсткий предел памяти. Поэтому сначала снимаем профиль потребления под реальной нагрузкой, ставим лимит с запасом и включаем мягкий порог раньше жёсткого — служба отдаёт кэш вместо аварии.

Настройки обратимы, если что-то пойдёт не так?

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

Как понять, что узкое место не в лимитах?

До правок снимаем метрики: время принудительного ограничения, события памяти, статистику ввода-вывода, показатели давления на ресурсы. Если ограничение не растёт, а задержки есть — причина в диске, сети или самом коде, и лимиты её не создали.

Совместимо ли это с контейнерами?

Контейнерные среды и оркестраторы работают с cgroups v2 через системный драйвер, поэтому наши правила и лимиты из манифестов действуют согласованно. Конфликтов нет: применяется более строгое ограничение на уровне службы.

Что происходит после сдачи работ?

Остаёмся на связи по согласованному регламенту: разбираем инциденты, связанные с лимитами, пересчитываем параметры при росте нагрузки, обновляем документацию. Передаём панели мониторинга, оповещения и пошаговую инструкцию по откату.

Обсудим задачу: с чего начнём и что нужно от вас

Чтобы вернуться с оценкой и планом, а не с уточняющими вопросами, нам достаточно нескольких вводных. Чем точнее опишете текущее состояние, тем меньше времени уйдёт на разведку и тем быстрее начнутся работы.

  • Что размещено на узле: сколько служб, какие критичны по задержке, а какие терпят очередь.
  • Как сейчас заданы ограничения: жёсткие лимиты, весовые коэффициенты или процессы живут без границ.
  • Симптомы: всплески задержек, аварийное завершение процессов, рост очереди дисковых операций.
  • Платформа: ветка ядра, дистрибутив, есть ли единое управление группами через systemd или оркестратор.
  • Ограничения: окна обслуживания, требования к непрерывности, кто выдаёт доступы к узлам.
  • Кто с вашей стороны принимает решения и проверяет результат после изменений.

Дальше порядок простой: обсуждаем задачу, фиксируем целевые показатели по процессору, памяти и дисковым операциям и согласуем план работ по этапам с точками проверки. Первый следующий шаг — собрать профили групп на тестовом узле; затем выставляем лимиты и прогоняем конфигурацию под нагрузкой. Если метрики не выдержаны, возвращаем предыдущий набор — откат остаётся в ваших руках.

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

Наши компетенции:

Последние работы

  • Разработка сайта компании B2B ADVANCE
    Разработка сайта компании B2B ADVANCE
    1542
  • Разработка веб-приложения для компании FEEDME
    Разработка веб-приложения для компании FEEDME
    1393
  • Разработка веб-сайта для компании БЕЛФИНГРУПП
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    1081
  • Разработка интернет магазина для компании FURNORO
    Разработка интернет магазина для компании FURNORO
    1368
  • Разработка логотипа компании B2B Advance
    Разработка логотипа компании B2B Advance
    815
  • Разработка веб-приложения для компании Enviok
    Разработка веб-приложения для компании Enviok
    1139