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): их часто пропускают, а именно они удерживают узел, когда служба исчерпала лимит файловых дескрипторов или устроила лавину процессов.
| Вариант | Когда подходит | Ограничения |
|---|---|---|
| Жёсткий лимит по процессору и памяти | Сервисы с предсказуемым профилем потребления | При неверном расчёте — периодические перезапуски; пиковый трафик не уложится |
| Гарантированный резерв | Критичные службы рядом с шумными соседями | Резервирует память даже простаивающим процессам, снижая общую утилизацию узла |
| Приоритет по весам | Пакетная обработка, фоновые задания | Не даёт гарантий отклика: при полной загрузке узла служба ждёт |
| Ограничение числа процессов | Публичные и пользовательские сервисы | Требует подбора по реальному числу воркеров, иначе отказ вместо деградации |
| Потолок ввода-вывода | Базы данных, репликация, выгрузки | Ограничение на уровне устройства, нужен расчёт по полосе диска |
На практике режимы комбинируют: критичной службе ставят резерв и потолок одновременно, а фоновой — только веса, чтобы она не конкурировала за ресурс в часы нагрузки.
Как мы внедряем лимиты: от аудита до передачи
Лимиты, выставленные по документации, а не по факту, либо душат сервис, либо не срабатывают: рабочий набор памяти оказывается втрое больше заявленного, а старт уходит в троттлинг. Поэтому мы не меняем настройки, пока не сняли реальные профили потребления.
- Аудит потребления. Смотрим, где процессы живут в дереве cgroups v2, что попадает в корневой срез и кто из сервисов на одной машине фактически съедает ресурсы.
- Профили нагрузки. Снимаем суточный и недельный профиль: рабочее множество памяти, пиковые окна, число потоков и дисковых операций, метрики давления при деградации соседей.
- Проектирование политики. Разводим мягкий и жёсткий порог по памяти, задаём вес и потолок процессорного времени, лимиты ввода-вывода с запасом на всплеск, выстраиваем иерархию срезов.
- Настройка. Переносим значения в конфигурацию срезов, отдельно обрабатываем системные и прикладные сервисы, проверяем, что запуск и фоновые задачи не упираются в потолок.
- Проверка под нагрузкой. Повторяем пиковый сценарий: троттлинг не режет отклик, защита памяти срабатывает предсказуемо, соседние сервисы не деградируют.
- План откату. Фиксируем, какие параметры и в каком порядке возвращаются, где точка невозврата и как быстро снять лимит на конкретном сервисе.
- Передача в эксплуатацию. Отдаём регламент: значения, метрики и алерты на приближение к потолку, порядок изменения лимитов и ответственных за пересмотр профилей.
Что остаётся на руки: конфигурации, схемы, регламенты
Итог работ — не запущенный сервер, а комплект, по которому ваша команда сопровождения ведёт систему без нас и без устных пояснений. Всё, что настроено в 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 или оркестратор.
- Ограничения: окна обслуживания, требования к непрерывности, кто выдаёт доступы к узлам.
- Кто с вашей стороны принимает решения и проверяет результат после изменений.
Дальше порядок простой: обсуждаем задачу, фиксируем целевые показатели по процессору, памяти и дисковым операциям и согласуем план работ по этапам с точками проверки. Первый следующий шаг — собрать профили групп на тестовом узле; затем выставляем лимиты и прогоняем конфигурацию под нагрузкой. Если метрики не выдержаны, возвращаем предыдущий набор — откат остаётся в ваших руках.
Формат сотрудничества выбираете вы: разовые работы по узкому участку или постоянное сопровождение с обновлением профилей под рост нагрузки.







