АрхитектураОблако и инфраструктураИнженер платформенной инфраструктуры

На узле Kubernetes возникло давление по памяти: как определяется, какой под будет вытеснен первым?

На узле Kubernetes возникло давление по памяти: как определяется, какой под будет вытеснен первым?

Проходите собеседования с ИИ помощником Hintsage

Краткий ответ

При нехватке памяти kubelet инициирует вытеснение подов, оценивая, превышает ли их фактическое потребление заявленные requests, а затем учитывая приоритет и относительный объём потребления. Классы QoS косвенно влияют на порядок через requests и limits, но правило не сводится к простому принципу «сначала BestEffort».

Если память закончилась настолько быстро, что сработал уже OOM killer ядра, решение принимает операционная система, а не kubelet. Поэтому порядок завершения подов при штатном eviction и при системном OOM может различаться.

Исторический контекст

Контейнеры используют общие ресурсы узла, поэтому изоляция процессов сама по себе не гарантирует, что один workload не вытеснит остальные. Для управляемого поведения Kubernetes ввёл requests, limits, классы качества обслуживания и механизм реагирования на давление ресурсов.

Исходная проблема состояла в необходимости сохранить работоспособность узла и наиболее важных сервисов при дефиците памяти. Оркестратору нужно было не только обнаружить нехватку ресурса, но и выбрать жертву с минимальным ущербом для системы.

Постановка проблемы

Предположим, на узле работают три пода: один потребляет больше своего memory request, второй укладывается в request, а третий не имеет requests и limits. При росте нагрузки физическая память заканчивается, и простое удаление самого крупного пода может привести к потере критичного сервиса.

Ошибочная трактовка состоит в том, что Kubernetes всегда удаляет сначала BestEffort, затем Burstable, а Guaranteed никогда не затрагивает. На практике важны фактическое превышение requests, приоритет пода, настройки eviction и то, успел ли kubelet принять решение до срабатывания OOM killer.

Подробное решение

Kubelet отслеживает сигналы давления, включая доступную память и объём памяти, который может быть быстро освобождён. При достижении настроенного порога он переводит узел в состояние memory pressure и начинает вытеснять поды, чтобы восстановить запас ресурса.

В первую очередь оценивается, превышает ли фактическое потребление контейнеров их memory requests. Поды, потребляющие больше заявленного request, являются более вероятными кандидатами. Затем учитывается Priority пода: менее приоритетные workloads предпочтительнее для вытеснения. При прочих равных сравнивается относительное превышение потребления над request.

QoS-класс вычисляется по requests и limits всех контейнеров пода. Guaranteed обычно получают контейнеры, у которых requests и limits заданы для памяти и CPU и совпадают; Burstable имеет неполное или различающееся задание ресурсов; BestEffort не имеет requests и limits. Эти классы влияют на защиту подов, особенно при OOM, но не заменяют анализ фактического потребления и приоритета.

Вытеснение kubelet обычно приводит к корректному завершению пода: под получает сигнал завершения, может выполниться период graceful shutdown, после чего kubelet освобождает ресурсы. Контроллер, например Deployment, может создать замену, но при продолжающемся дефиците новый под также рискует быть вытеснен.

Нужно отличать этот процесс от OOM killer ядра Linux. Если память исчерпана внезапно или kubelet не успел освободить её, ядро выбирает процесс по своим OOM-оценкам. Для контейнеров эти оценки зависят от QoS, поэтому BestEffort обычно защищён слабее, а Guaranteed — сильнее, но это не абсолютная гарантия.

Requests не являются жёстким ограничителем потребления. Они используются планировщиком и механизмами принятия решений. Чтобы контейнер не мог превысить заданный объём памяти, нужен memory limit, однако слишком низкий limit может вызвать OOM внутри контейнера даже при свободной памяти на узле.

Практически следует задавать реалистичные requests, устанавливать приоритеты для критичных сервисов, контролировать запасы памяти и настраивать eviction thresholds. Завышенные requests снижают плотность размещения, заниженные повышают риск вытеснения и нестабильного поведения под нагрузкой.

Ситуация из практики

На узле размещались API, фоновый обработчик и вспомогательный агент. У API был высокий PriorityClass и request памяти, соответствующий обычной нагрузке; обработчик имел небольшой request, но периодически превышал его; у агента requests отсутствовали. Во время пакетной обработки память узла быстро закончилась.

Рассматривались три варианта. Увеличение памяти узла уменьшало вероятность инцидента, но не устраняло утечку и повышало стоимость. Простое снижение limits у обработчика защищало узел, но приводило к частым OOM внутри приложения. Установка реалистичных requests и limits, повышение приоритета API и исправление пикового потребления обработчика решали проблему на разных уровнях.

Выбрали третий вариант и дополнительно настроили наблюдение за memory pressure и событиями eviction. В результате API сохранялся приоритетным workload, обработчик ограничивался предсказуемо, а агент вытеснялся первым при реальном дефиците. Компромисс состоял в снижении максимальной пропускной способности фоновой обработки во время пиков.

Что кандидаты часто упускают

  1. Гарантирует ли класс Guaranteed, что под никогда не будет вытеснен?

Нет. Класс Guaranteed повышает защищённость, но не является абсолютной гарантией. Под может быть вытеснен из-за системного давления, локального эфемерного хранилища, нарушения условий размещения или приоритета других workloads; кроме того, при полном системном OOM решение принимает ядро.

  1. Чем memory request отличается от memory limit при выборе жертвы?

Request участвует в планировании и сравнении фактического потребления с обещанным объёмом ресурса. Превышение request делает под более вероятным кандидатом на eviction. Limit задаёт верхнюю границу для контейнера: при её превышении контейнер может получить OOM независимо от того, есть ли свободная память на узле.

  1. Почему под может быть завершён ядром до того, как Kubernetes зарегистрирует eviction?

Kubelet принимает решения с некоторым интервалом и реагирует на наблюдаемые сигналы. Если нагрузка резко исчерпала память, ядро Linux может немедленно запустить OOM killer. В этом случае Kubernetes позже зафиксирует уже произошедшее завершение, а порядок выбора будет определён OOM-механизмом ядра, а не полной логикой eviction kubelet.