Планирование подов: 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, остальные разбираются в тренажёре.
- Под висит в Pending. Куда смотреть и какие причины самые частые?A)В логи пода: планировщик пишет туда причину отказаB)В события пода: чаще всего не хватает ресурсов или мешают taintsC)В статус ноды: Pending означает, что нода не прошла проверку здоровьяD)В журнал kubelet: под уже назначен, но контейнер не создан
показать ответ и разбор
+B)В события пода: чаще всего не хватает ресурсов или мешают taints// разбор: Pending означает, что под ещё не назначен на ноду (или назначен, но образ не скачан). Причину пишет планировщик в события: kubectl describe pod покажет строки вида Insufficient cpu/memory, node(s) had untolerated taint, no nodes match node selector, а также ожидание тома. Логов у пода в этом состоянии нет — контейнер не стартовал.
- Нужно, чтобы поды с GPU шли на ноды с ускорителями, а обычная нагрузка туда не попадала. Чем это делается?A)Хватит nodeSelector по метке gpu на подах с ускорениемB)Хватит taint на нодах: он и притянет нужные подыC)Taint отгоняет чужих, affinity притягивает своихD)Приоритетный класс: поды с GPU вытеснят обычные с этих нод
показать ответ и разбор
+C)Taint отгоняет чужих, affinity притягивает своих// разбор: Это две разные механики. Taint на ноде отталкивает всё, у чего нет соответствующего toleration, — так дорогие ноды защищают от случайной нагрузки. Toleration лишь снимает запрет, но не притягивает: чтобы под гарантированно поехал на нужное железо, добавляют nodeAffinity или nodeSelector по метке. В связке получается и защита ноды, и адресное размещение.
- Нужно, чтобы поды приложения шли только на ноды с SSD (у них метка disk=ssd). Простейший способ?A)Проставить нужным нодам taint disk=ssdB)nodeSelector: disk=ssd в спеке подаC)Дать нодам без SSD taint, а поду прописать nodeNameD)Выставить подам priorityClass с именем ssd
показать ответ и разбор
+B)nodeSelector: disk=ssd в спеке пода// разбор: nodeSelector — простейшая нода-аффинити: под планируется только на ноды с совпадающими метками (disk=ssd). taint делает обратное (отгоняет поды, если нет toleration), nodeName жёстко прибивает к одной конкретной ноде минуя планировщик, priorityClass — про вытеснение, а не размещение. Для сложных правил берут nodeAffinity (In/NotIn, preferred/required).
- Три реплики сервиса встали на одну ноду; она упала — сервис лёг целиком. Чем заставить их разъехаться по нодам?A)podAntiAffinity или topologySpreadConstraints — разносить репликиB)Просто поднять число реплик, тогда часть уедет на другие нодыC)Дать поду nodeSelector на конкретную вторую нодуD)Навесить на ноду taint, чтобы лишние реплики её избегали
показать ответ и разбор
+A)podAntiAffinity или topologySpreadConstraints — разносить реплики// разбор: Чтобы разнести реплики по нодам/зонам (чтобы отказ одной ноды не унёс все), берут podAntiAffinity (не класть рядом поды с той же меткой) или topologySpreadConstraints (равномерный разброс по ключу топологии — hostname/zone). Больше реплик само по себе разброс не гарантирует; nodeSelector прибивает к ноде; taint отгоняет без гарантии равномерности.
- Критичный под висит в Pending: ресурсов нет, а вокруг крутятся некритичные. Как заставить кластер потеснить их ради него?A)Поднять поду requests, тогда планировщик выделит ему местоB)Навесить на некритичные поды taint, чтобы они ушли с нодыC)Высокий PriorityClass — планировщик вытеснит слабыхD)Увеличить число реплик критичного пода — один точно влезет
показать ответ и разбор
+C)Высокий PriorityClass — планировщик вытеснит слабых// разбор: PriorityClass + preemption: под с более высоким приоритетом в Pending может вытеснить поды с меньшим приоритетом, освобождая ресурсы под себя. Назначь критичной нагрузке высокий PriorityClass — планировщик выселит менее приоритетные, если иначе разместить не может. Больший requests размещение усложняет; taint сам по себе запущенные поды не выселяет (пока не добавить NoExecute); лишние реплики нехватку ресурсов не лечат.
дальше
Теорию прочитали. Навык ставится повторением
В Сеньорчике эта подтема идёт в ежедневных сессиях: движок возвращает её, пока ответы не станут уверенными, и ведёт прогресс отдельно по каждой подтеме. Теория внутри тоже бесплатна, лимит только на количество вопросов в день.