сеньорчикОткрыть в Telegram
← вся теориятеория к собесу · Kubernetes

Ресурсы и QoS в Kubernetes

Ресурсы: запросы, лимиты, классы

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

Стержень: память - жёсткий предел, за него убивают; процессор - мягкий, за него тормозят. И оба они не то же самое, что запрос.

// Формулировки: «чем requests отличается от limits?», «что такое QoS-классы?», «почему под убили по памяти?»

Запрос - это про место, лимит - про потолок

Запрос читается планировщиком, когда он выбирает узел (узел - это одна машина кластера). Он складывает запросы всех подов на узле и смотрит, влезет ли новый. Фактическое потребление при этом не учитывается: узел может простаивать, но если запросы разобраны, новый под туда не встанет и будет ждать.

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

// Отсюда практика. Лимит памяти ставят обязательно: без него один текущий под съедает узел и роняет соседей. Лимит процессора ставят осторожно и с запасом: слишком низкий незаметно душит приложение, и выглядит это как «стало тормозить», а не как отказ. А запрос ставят близко к реальному среднему потреблению, иначе либо узлы простаивают наполовину, либо на них набивается больше подов, чем они тянут.

запрос
бронь для планировщика; по ней решают, влезет ли под на узел
лимит памяти
жёсткая граница: превышение убивает процесс с кодом 137
лимит процессора
мягкая граница: превышение замедляет, но не убивает

Классы качества

Кластер сам присваивает поду класс по соотношению запросов и лимитов. Я проверил на трёх подах сразу. Запрос равен лимиту по обоим ресурсам - класс Guaranteed. Запрос есть, лимит выше - Burstable. Ничего не указано - BestEffort.

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

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

класс качества
присваивается автоматически по запросам и лимитам; задаёт очередь на выселение
пределы по умолчанию
значения, которые получает под, если явно ничего не указано

Как подобрать числа

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

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

// Отдельный случай - приложения на средах выполнения со сборщиком мусора. Многие из них по умолчанию смотрят на память ВСЕЙ машины, а не на лимит контейнера: я мерил это отдельно, контейнер с лимитом 256 мегабайт видел в системных счётчиках почти четыре гигабайта. Такому приложению размер кучи задают явно, иначе оно спокойно вырастет за лимит и будет убито.

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

Как отвечать: «Под убили по памяти. Что это значит и что делать?»

Значит, контейнер превысил свой лимит памяти, и ядро его убило: в описании пода стоит причина OOMKilled и код завершения 137 - я это специально воспроизводил на стенде. Главное отличие от процессора: память жёсткая, за превышение убивают, а процессор мягкий, за превышение просто замедляют, и под продолжает работать. Дальше разбираюсь по порядку. Смотрю фактическое потребление: если оно ровно растёт до лимита - это утечка, и лимит её только обнажил. Если потребление обычное, а всплеск редкий - лимит стоит впритык к пику и его надо поднять. И отдельно проверяю, знает ли приложение про свой лимит: среды выполнения со сборщиком мусора по умолчанию смотрят на память всей машины, я мерил - контейнер с лимитом 256 мегабайт видит в системных счётчиках почти четыре гигабайта, поэтому размер кучи ему задают явно.

Ответ читает признаки, отделяет утечку от заниженного лимита и добавляет причину, которую ищут дольше всего. Про невидимость лимита знают только те, кто это ловил.

На чём валятся

  • Не ставят лимит памяти: один под съедает узел и роняет соседей.
  • Ставят низкий лимит процессора и получают незаметное замедление вместо отказа.
  • Ждут, что превышение лимита процессора убьёт под. Убивает только память.
  • Оставляют поды без запросов: такие выселяются первыми.
  • Не говорят приложению про лимит памяти, и оно ориентируется на память всей машины.

Проверьте себя

Пять вопросов из банка по этой подтеме. Всего их 6, остальные разбираются в тренажёре.

  1. #dvo_k8s_resources1 / 5
    Как получить для пода класс QoS Guaranteed?
    A)Во всех контейнерах requests равны limits
    B)Задать limits по памяти и оставить CPU без ограничений
    C)Указать приоритетный класс с высоким значением
    D)Задать requests хотя бы одному контейнеру пода
    показать ответ и разбор
    +A)Во всех контейнерах requests равны limits

    // разбор: Guaranteed выдаётся, когда у каждого контейнера пода заданы и совпадают requests и limits по обоим ресурсам. Достаточно одного контейнера с неполными настройками — и под становится Burstable. Полное отсутствие requests и limits даёт BestEffort. Класс важен при нехватке памяти на ноде: kubelet вытесняет сначала BestEffort, потом Burstable, превысивших свои запросы.

  2. #dvo_k8s_resources2 / 5
    Сервис отвечает медленно, но средний CPU пода — 40% от лимита. Что стоит проверить?
    A)Нехватку памяти: своп в кластере отключён
    B)Сетевые политики: они добавляют задержку на каждый запрос
    C)Троттлинг по квоте CPU: всплески упираются в лимит внутри окна
    D)Раскладку по нодам: поды могли уехать на одну машину
    показать ответ и разбор
    +C)Троттлинг по квоте CPU: всплески упираются в лимит внутри окна

    // разбор: Квота CPU выдаётся окнами по сто миллисекунд. Приложение с короткими всплесками успевает выбрать лимит за первые миллисекунды окна и стоит до следующего — на графике минутного среднего это выглядит как спокойные 40%, а на деле запросы ждут. Смотрят счётчики троттлинга (throttled periods и время) и либо поднимают лимит, либо убирают его вовсе для латентно-чувствительных сервисов.

  3. #dvo_k8s_resources3 / 5
    Контейнер периодически перезапускается, в describe у него Last State: Terminated, Reason: OOMKilled, Exit Code 137. Что это?
    A)Приложение само упало с необработанным исключением
    B)Контейнер превысил свой memory limit — ядро его убило
    C)Kubelet перезапустил под по liveness-пробе
    D)Нода ушла в DiskPressure и вытеснила под
    показать ответ и разбор
    +B)Контейнер превысил свой memory limit — ядро его убило

    // разбор: OOMKilled с кодом 137 (128+9, SIGKILL) значит: контейнер вышел за свой memory limit, и cgroup-OOM его убил, после чего kubelet перезапустил по restartPolicy. Разбираются: реально ли лимит занижен (поднять requests/limits) или это утечка памяти в приложении (чинить приложение). Отличается от Evicted (нехватка ресурсов на ноде) и от обычного краха приложения.

  4. #dvo_k8s_resources4 / 5
    На ноде кончается память. В каком порядке kubelet вытесняет поды по классам QoS?
    A)BestEffort → Burstable → Guaranteed последними
    B)Сначала Guaranteed как самые тяжёлые по ресурсам
    C)В порядке возраста подов, QoS тут не участвует
    D)Случайно — какой под попался под давление первым
    показать ответ и разбор
    +A)BestEffort → Burstable → Guaranteed последними

    // разбор: При node-pressure eviction kubelet выселяет по классам QoS: первыми BestEffort (без requests/limits), затем Burstable (превысившие свои requests сильнее), Guaranteed (requests==limits во всех контейнерах) — последними. Отсюда практика: критичным сервисам задавать requests==limits (Guaranteed), чтобы их выселяли в последнюю очередь. Внутри класса учитывается, насколько под превысил свои requests.

  5. #dvo_k8s_resources5 / 5
    У пода не задан memory limit. Под пиковой нагрузкой он съел память ноды, и пострадали соседние поды. Что упустили?
    A)Надо было убрать и requests, тогда планировщик разнёс бы поды
    B)Проблема в отсутствии CPU-лимита, а не памяти
    C)Без лимита один под может выесть память ноды — это шумный сосед
    D)Достаточно поднять приоритет соседних подов
    показать ответ и разбор
    +C)Без лимита один под может выесть память ноды — это шумный сосед

    // разбор: memory limit — потолок, который cgroup не даст перешагнуть: без него под способен занять всю память ноды и спровоцировать node-OOM/эвикшн соседей (noisy neighbor). Requests нужны планировщику для вместимости, limits — рантайму для изоляции. Практика: задавать разумный memory limit (и следить, чтобы не занизить — иначе OOMKilled самого пода). CPU-лимит — отдельная история про троттлинг.

дальше

Теорию прочитали. Навык ставится повторением

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