Ресурсы и QoS в Kubernetes
Поставил два пода с разными лимитами. Первому дал лимит памяти 64 мегабайта и заставил её есть: он умер, статус OOMKilled, код завершения 137. Второму дал лимит в одну десятую ядра и заставил считать в полную силу: он продолжил работать, просто медленно. Один и тот же вид ограничения, два принципиально разных исхода.
Стержень: память - жёсткий предел, за него убивают; процессор - мягкий, за него тормозят. И оба они не то же самое, что запрос.
// Формулировки: «чем requests отличается от limits?», «что такое QoS-классы?», «почему под убили по памяти?»
Запрос - это про место, лимит - про потолок
Запрос читается планировщиком, когда он выбирает узел (узел - это одна машина кластера). Он складывает запросы всех подов на узле и смотрит, влезет ли новый. Фактическое потребление при этом не учитывается: узел может простаивать, но если запросы разобраны, новый под туда не встанет и будет ждать.
Лимит работает уже после запуска и ограничивает фактическое потребление. Для памяти это жёсткая граница ядра: превысил - процесс убит, я получил ровно это, с кодом 137. Для процессора граница мягкая: превысить нельзя, но и не убивают - процессу просто выдают меньше времени, и он работает медленнее.
// Отсюда практика. Лимит памяти ставят обязательно: без него один текущий под съедает узел и роняет соседей. Лимит процессора ставят осторожно и с запасом: слишком низкий незаметно душит приложение, и выглядит это как «стало тормозить», а не как отказ. А запрос ставят близко к реальному среднему потреблению, иначе либо узлы простаивают наполовину, либо на них набивается больше подов, чем они тянут.
- запрос
- бронь для планировщика; по ней решают, влезет ли под на узел
- лимит памяти
- жёсткая граница: превышение убивает процесс с кодом 137
- лимит процессора
- мягкая граница: превышение замедляет, но не убивает
Классы качества
Кластер сам присваивает поду класс по соотношению запросов и лимитов. Я проверил на трёх подах сразу. Запрос равен лимиту по обоим ресурсам - класс Guaranteed. Запрос есть, лимит выше - Burstable. Ничего не указано - BestEffort.
Класс определяет очередь на выселение с узла, когда там кончается память. Первыми уходят поды без запросов, затем те, кто потребляет больше своего запроса, последними - те, у кого запрос равен лимиту. То есть указанные ресурсы работают как страховка: они говорят кластеру, сколько вам действительно нужно.
// Отсюда прямой вывод: под без запросов и лимитов - худший вариант. Он размещается куда попало, вылетает первым и мешает соседям. Лечится это пределами по умолчанию на уровне отсека кластера: под без явных значений получает их автоматически. Плюс квота на отсек, чтобы одна команда не заняла весь кластер.
- класс качества
- присваивается автоматически по запросам и лимитам; задаёт очередь на выселение
- пределы по умолчанию
- значения, которые получает под, если явно ничего не указано
Как подобрать числа
Наугад не подбирают - смотрят на фактическое потребление под настоящей нагрузкой. Порядок такой: снять потребление за неделю, взять для запроса значение около обычного уровня, а для лимита памяти - заметно выше пика, потому что за пик по памяти убивают.
Три типовые ошибки. Первая: запрос равен пику - тогда узлы простаивают, а платите вы за них полностью. Вторая: запрос сильно ниже реального потребления - на узел набивается слишком много подов, и при всплеске они начинают выселять друг друга. Третья: лимит памяти впритык к пику - под живёт до первого редкого всплеска, а потом умирает по ночам с кодом 137, и это выглядит мистикой.
// Отдельный случай - приложения на средах выполнения со сборщиком мусора. Многие из них по умолчанию смотрят на память ВСЕЙ машины, а не на лимит контейнера: я мерил это отдельно, контейнер с лимитом 256 мегабайт видел в системных счётчиках почти четыре гигабайта. Такому приложению размер кучи задают явно, иначе оно спокойно вырастет за лимит и будет убито.
- подбор по факту
- запрос около обычного потребления, лимит памяти заметно выше пика
- невидимость лимита
- приложение видит память всей машины, если ему не сказать про лимит явно
Как отвечать: «Под убили по памяти. Что это значит и что делать?»
Значит, контейнер превысил свой лимит памяти, и ядро его убило: в описании пода стоит причина OOMKilled и код завершения 137 - я это специально воспроизводил на стенде. Главное отличие от процессора: память жёсткая, за превышение убивают, а процессор мягкий, за превышение просто замедляют, и под продолжает работать. Дальше разбираюсь по порядку. Смотрю фактическое потребление: если оно ровно растёт до лимита - это утечка, и лимит её только обнажил. Если потребление обычное, а всплеск редкий - лимит стоит впритык к пику и его надо поднять. И отдельно проверяю, знает ли приложение про свой лимит: среды выполнения со сборщиком мусора по умолчанию смотрят на память всей машины, я мерил - контейнер с лимитом 256 мегабайт видит в системных счётчиках почти четыре гигабайта, поэтому размер кучи ему задают явно.
Ответ читает признаки, отделяет утечку от заниженного лимита и добавляет причину, которую ищут дольше всего. Про невидимость лимита знают только те, кто это ловил.
На чём валятся
- −Не ставят лимит памяти: один под съедает узел и роняет соседей.
- −Ставят низкий лимит процессора и получают незаметное замедление вместо отказа.
- −Ждут, что превышение лимита процессора убьёт под. Убивает только память.
- −Оставляют поды без запросов: такие выселяются первыми.
- −Не говорят приложению про лимит памяти, и оно ориентируется на память всей машины.
Проверьте себя
Пять вопросов из банка по этой подтеме. Всего их 6, остальные разбираются в тренажёре.
- Как получить для пода класс QoS Guaranteed?A)Во всех контейнерах requests равны limitsB)Задать limits по памяти и оставить CPU без ограниченийC)Указать приоритетный класс с высоким значениемD)Задать requests хотя бы одному контейнеру пода
показать ответ и разбор
+A)Во всех контейнерах requests равны limits// разбор: Guaranteed выдаётся, когда у каждого контейнера пода заданы и совпадают requests и limits по обоим ресурсам. Достаточно одного контейнера с неполными настройками — и под становится Burstable. Полное отсутствие requests и limits даёт BestEffort. Класс важен при нехватке памяти на ноде: kubelet вытесняет сначала BestEffort, потом Burstable, превысивших свои запросы.
- Сервис отвечает медленно, но средний CPU пода — 40% от лимита. Что стоит проверить?A)Нехватку памяти: своп в кластере отключёнB)Сетевые политики: они добавляют задержку на каждый запросC)Троттлинг по квоте CPU: всплески упираются в лимит внутри окнаD)Раскладку по нодам: поды могли уехать на одну машину
показать ответ и разбор
+C)Троттлинг по квоте CPU: всплески упираются в лимит внутри окна// разбор: Квота CPU выдаётся окнами по сто миллисекунд. Приложение с короткими всплесками успевает выбрать лимит за первые миллисекунды окна и стоит до следующего — на графике минутного среднего это выглядит как спокойные 40%, а на деле запросы ждут. Смотрят счётчики троттлинга (throttled periods и время) и либо поднимают лимит, либо убирают его вовсе для латентно-чувствительных сервисов.
- Контейнер периодически перезапускается, в 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 (нехватка ресурсов на ноде) и от обычного краха приложения.
- На ноде кончается память. В каком порядке 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.
- У пода не задан memory limit. Под пиковой нагрузкой он съел память ноды, и пострадали соседние поды. Что упустили?A)Надо было убрать и requests, тогда планировщик разнёс бы подыB)Проблема в отсутствии CPU-лимита, а не памятиC)Без лимита один под может выесть память ноды — это шумный соседD)Достаточно поднять приоритет соседних подов
показать ответ и разбор
+C)Без лимита один под может выесть память ноды — это шумный сосед// разбор: memory limit — потолок, который cgroup не даст перешагнуть: без него под способен занять всю память ноды и спровоцировать node-OOM/эвикшн соседей (noisy neighbor). Requests нужны планировщику для вместимости, limits — рантайму для изоляции. Практика: задавать разумный memory limit (и следить, чтобы не занизить — иначе OOMKilled самого пода). CPU-лимит — отдельная история про троттлинг.
дальше
Теорию прочитали. Навык ставится повторением
В Сеньорчике эта подтема идёт в ежедневных сессиях: движок возвращает её, пока ответы не станут уверенными, и ведёт прогресс отдельно по каждой подтеме. Теория внутри тоже бесплатна, лимит только на количество вопросов в день.