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

Планирование подов: requests, taints, Pending

Планирование и ресурсы

На моём узле - так называют одну машину кластера - доступно 2 ядра и 4009860 килобайт памяти. Создал под (запущенный экземпляр приложения), который просит 64 гигабайта и 32 ядра. Он честно застрял в состоянии ожидания, а в событиях появилось «0/1 nodes are available: 1 Insufficient cpu, 1 Insufficient memory». Планировщик не пытается втиснуть невозможное - он просто не находит места и ждёт.

Стержень: планировщик раскладывает поды по ЗАПРОСАМ, а не по фактическому потреблению; лимиты же работают уже после запуска и по-разному для памяти и процессора.

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

Запрос и лимит - разные вещи

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

Лимит - это потолок во время работы, и ведёт он себя для двух ресурсов по-разному. Я проверил оба. Под с лимитом памяти 64 мегабайта, который ест память, был убит: статус OOMKilled, код завершения 137. Под с лимитом процессора в одну десятую ядра, который считает в полную силу, продолжил работать - его просто ЗАМЕДЛИЛИ.

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

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

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

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

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

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

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

Куда именно поставить под

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

Обратный механизм - пометки на узлах, которые ОТТАЛКИВАЮТ поды. Узел с такой пометкой принимает только те поды, которые явно согласны на неё. Так выделяют узлы под особые задачи и так же кластер помечает больные узлы, чтобы на них ничего не ставили.

// И самое практичное правило - разнесение. Без него все реплики одного сервиса могут оказаться на одном узле, и его падение унесёт весь сервис разом. Правило равномерного распределения по узлам или по зонам - отдельным площадкам дата-центра - решает это одной настройкой в описании. Проверять полезно не по документации, а глазами: посмотреть, на каких узлах реально стоят поды.

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

Как отвечать: «Под висит в состоянии ожидания. Что смотреть?»

Смотрю события пода - там прямым текстом написана причина. Самая частая: ни один узел не подходит по ресурсам. Я это воспроизводил: попросил 64 гигабайта и 32 ядра на узле с двумя ядрами и получил «0 из 1 узлов доступны: недостаточно процессора и памяти». Причём планировщик считает по ЗАПРОСАМ, а не по фактическому потреблению - узел может простаивать, но если запросы уже разобраны, новый под не встанет. Дальше по списку причин: правила размещения, которым не соответствует ни один узел; отталкивающие пометки на узлах, на которые под не согласен; отсутствие свободного тома нужной зоны; исчерпанная квота пространства. Все они видны в событиях, поэтому первое действие всегда одно - прочитать события, а не гадать.

Ответ начинается с источника правды, даёт главную причину с измеренным примером и перечисляет остальные. Уточнение про запросы против потребления - то, что путают чаще всего.

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

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

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

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

  1. #dvo_k8s_scheduling1 / 5
    Под висит в Pending. Куда смотреть и какие причины самые частые?
    A)В логи пода: планировщик пишет туда причину отказа
    B)В события пода: чаще всего не хватает ресурсов или мешают taints
    C)В статус ноды: Pending означает, что нода не прошла проверку здоровья
    D)В журнал kubelet: под уже назначен, но контейнер не создан
    показать ответ и разбор
    +B)В события пода: чаще всего не хватает ресурсов или мешают taints

    // разбор: Pending означает, что под ещё не назначен на ноду (или назначен, но образ не скачан). Причину пишет планировщик в события: kubectl describe pod покажет строки вида Insufficient cpu/memory, node(s) had untolerated taint, no nodes match node selector, а также ожидание тома. Логов у пода в этом состоянии нет — контейнер не стартовал.

  2. #dvo_k8s_scheduling2 / 5
    Нужно, чтобы поды с GPU шли на ноды с ускорителями, а обычная нагрузка туда не попадала. Чем это делается?
    A)Хватит nodeSelector по метке gpu на подах с ускорением
    B)Хватит taint на нодах: он и притянет нужные поды
    C)Taint отгоняет чужих, affinity притягивает своих
    D)Приоритетный класс: поды с GPU вытеснят обычные с этих нод
    показать ответ и разбор
    +C)Taint отгоняет чужих, affinity притягивает своих

    // разбор: Это две разные механики. Taint на ноде отталкивает всё, у чего нет соответствующего toleration, — так дорогие ноды защищают от случайной нагрузки. Toleration лишь снимает запрет, но не притягивает: чтобы под гарантированно поехал на нужное железо, добавляют nodeAffinity или nodeSelector по метке. В связке получается и защита ноды, и адресное размещение.

  3. #dvo_k8s_scheduling3 / 5
    Нужно, чтобы поды приложения шли только на ноды с SSD (у них метка disk=ssd). Простейший способ?
    A)Проставить нужным нодам taint disk=ssd
    B)nodeSelector: disk=ssd в спеке пода
    C)Дать нодам без SSD taint, а поду прописать nodeName
    D)Выставить подам priorityClass с именем ssd
    показать ответ и разбор
    +B)nodeSelector: disk=ssd в спеке пода

    // разбор: nodeSelector — простейшая нода-аффинити: под планируется только на ноды с совпадающими метками (disk=ssd). taint делает обратное (отгоняет поды, если нет toleration), nodeName жёстко прибивает к одной конкретной ноде минуя планировщик, priorityClass — про вытеснение, а не размещение. Для сложных правил берут nodeAffinity (In/NotIn, preferred/required).

  4. #dvo_k8s_scheduling4 / 5
    Три реплики сервиса встали на одну ноду; она упала — сервис лёг целиком. Чем заставить их разъехаться по нодам?
    A)podAntiAffinity или topologySpreadConstraints — разносить реплики
    B)Просто поднять число реплик, тогда часть уедет на другие ноды
    C)Дать поду nodeSelector на конкретную вторую ноду
    D)Навесить на ноду taint, чтобы лишние реплики её избегали
    показать ответ и разбор
    +A)podAntiAffinity или topologySpreadConstraints — разносить реплики

    // разбор: Чтобы разнести реплики по нодам/зонам (чтобы отказ одной ноды не унёс все), берут podAntiAffinity (не класть рядом поды с той же меткой) или topologySpreadConstraints (равномерный разброс по ключу топологии — hostname/zone). Больше реплик само по себе разброс не гарантирует; nodeSelector прибивает к ноде; taint отгоняет без гарантии равномерности.

  5. #dvo_k8s_scheduling5 / 5
    Критичный под висит в Pending: ресурсов нет, а вокруг крутятся некритичные. Как заставить кластер потеснить их ради него?
    A)Поднять поду requests, тогда планировщик выделит ему место
    B)Навесить на некритичные поды taint, чтобы они ушли с ноды
    C)Высокий PriorityClass — планировщик вытеснит слабых
    D)Увеличить число реплик критичного пода — один точно влезет
    показать ответ и разбор
    +C)Высокий PriorityClass — планировщик вытеснит слабых

    // разбор: PriorityClass + preemption: под с более высоким приоритетом в Pending может вытеснить поды с меньшим приоритетом, освобождая ресурсы под себя. Назначь критичной нагрузке высокий PriorityClass — планировщик выселит менее приоритетные, если иначе разместить не может. Больший requests размещение усложняет; taint сам по себе запущенные поды не выселяет (пока не добавить NoExecute); лишние реплики нехватку ресурсов не лечат.

дальше

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

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