Kubernetes Scheduler в Деталях: Ключевые Концепции. Часть 2

План Очередь планировщика. Кто последний в очереди? Scheduler framework и его компоненты. Informer

2024-03-09 13:30:39 - ERneSt⚡️os

Продолжение предыдущей части

Когда я начал погружаться в k8s и успешно сдал k8s CKA, я подумал что k8s scheduler работает как описано в части 1 и больше мне знать не обязательно, чтобы понимать как действительно работает k8s и почему поды планируются именно таким образом. Оказалось, что я упустил важный пласт знаний, который скрывается отчасти в документации, отчасти разбросан по англоязычным статьям в www. Сегодня мы наверстаем упущенное вместе.

Итоги предыдущей части:

k8s Scheduler непрерывно следит за появлением новых подов и нод, и если есть поды(в специальной очереди), которые не закреплены за нодой, то scheduler пытается закрепить их за правильно подобранной нодой.

Тезисно, k8s нужен для:

Scheduler framework и его роль в процессе планирования

Kubernetes в корне своей идеологии использует модульную архитектуру для увелечения гибкости, поддерживаемости и уменьшения сложности кода. k8s Scheduler - не исключение.

Kubernetes использует в своей имплементации Scheduler framework(был придуман как раз для эффективной имплементации k8s планировщика). Это расширяемая архитектура, представленная в Kubernetes, которая позволяет разработчикам настраивать планировщик через написание и включение собственных плагинов. Этот фреймворк состоит из набора точек расширения (extension points), которые позволяют вмешиваться в процесс планирования на различных этапах:

Схема Scheduler framework с официального сайта:

На схеме видно, что зеленым цветом отмечены блоки, которые можно самостоятельно расширять и менять. Желтым цветом отмечены элементы, которые недоступны для модификации. В официальной документации k8s очень не плохо расписано, за что отвечает каждый элемент, однако, чтобы раскрыть все взаимосвязи между объектами, одной документации недостаточно. Именно по этой причине я подготовил расширенную схему ниже и эту статью.

PreEnqueue

Это первый плагин планировщика, который вызывается в начале каждого цикла планирования. Его целью является фильтрация подов, которые должны быть запланированы. Например, если у POD'а не указаны требования к ресурсам, то этот POD не будет запланирован. Дальнейшее планирование POD'а возможно, только при условии, что PreEnqueue вернет success в результате своих проверок. 

Комментарий

Это некоторая оптимизация, которая позволяет проводить базовые проверки для подов перед помещением объекта POD'а в основную активную очередь. Эта оптимизация нужна для экономии ресурсов, т.е. если POD не удовлетворяет базовым проверкам, то и в очереди ему делать нечего.

Sort(QueueSort)

QueueSort — это функция (или точка расширения в планировщике Kubernetes), которая определяет, как поды должны быть упорядочены в ActiveQ. Сортировка очереди важна, потому что порядок, в котором поды рассматриваются для планирования, может повлиять на общую производительность и распределение ресурсов в кластере. Например, вы можете захотеть, чтобы более критичные поды или поды, ожидающие больше всего времени, были рассмотрены для планирования в первую очередь.

Комментарий

QueueSort не хранит поды; он лишь определяет логику их сортировки в рамках очереди. Очереди подов хранятся в ActiveQ.

Scheduler Queue

В Kubernetes планировщик (kube-scheduler) использует несколько очередей для управления подами, ожидающими назначения на узел. Каждая очередь представляет собой список подов, ожидающих назначения на узел. Всего имеется 3 типа очереди: ActiveQ, UnschedulableQ и BackoffQ. Подробнее тут.

Active Queue

Это активная очередь планирования, в которой хранятся все поды, ожидающие назначения на ноду. Поды в ActiveQ ожидают обработки планировщиком, чтобы быть проверенными по соответствию требованиям, ресурсам нод и так далее, перед тем как будут назначены на подходящую ноду для запуска.

Комментарий

Поды в очереди ActiveQ могут быть как новые, так и перепланированные. Перепланированные(не смог подобрать более подходящего слова для это сущности) поды - это поды, которые уже были запланированы, но по каким-либо причинам были перепланированы. Например, если нода, на которой был запланирован под, упала, то этот POD будет перепланирован на другую здоровую ноду. Кроме этого, QueueSort

UnschedulableQueue (unschedulableQ)

UnschedulableQueue предназначена для хранения подов, ожидающих каких-либо событий, в результате которых эти поды могли бы перейти в activeQ.

Например, эта очередь используется для подов которые планировщик не смог разместить на доступных узлах кластера из-за различных ограничений, таких как недостаток ресурсов, требования к affinity, taints и tolerations и другие пользовательские ограничения. Поды в этой очереди считаются "unschedulable" (не готовые к процессу планирования или нераспределяемыми).

Функциональность:

Комментарий

UnschedulableQueue - это ещё 1 способ для оптимизации процесса планирования, который позволяет избежать ненужных циклов планирования подов в условиях, когда их невозможно распределить, и одновременно обеспечивает механизм для их переоценки при изменении условий кластера.



PodBackoffQueue (podBackoffQ)

PodBackoffQueue - для обработки и управления повторными попытками планирования подов, которые не удалось запланировать в предыдущих попытках.

Эта очередь имеет механизм экспоненциальной задержки между повторными попытками, чтобы предотвратить чрезмерную нагрузку на планировщик и ресурсы кластера.

Функциональность:

Комментарий про экспоненциальную задержку

flushUnschedulableQLeftover и flushBackoffQCompleted: Механизмы отвечающие за перемещение подов обратно в активную очередь

Эти механизмы помогают убедиться, что поды, которые по тем или иным причинам временно не могли быть запланированы, не остаются без внимания и имеют возможность быть запланированными, когда условия в кластере меняются.

flushUnschedulableQLeftover

Этот механизм отвечает за перемещение подов из UnschedulableQueue (очередь нераспределяемых подов) обратно в активную очередь (ActiveQ).

  1. Поды помещаются в UnschedulableQueue, когда планировщик определяет, что в текущий момент нет подходящего узла для их размещения, например, из-за недостатка ресурсов.
  2. Когда в кластере происходят изменения (например, освобождаются ресурсы на узлах или добавляются новые узлы), flushUnschedulableQLeftover активируется, чтобы переместить поды из UnschedulableQueue обратно в ActiveQ для повторной оценки и планирования.
flushBackoffQCompleted

Этот механизм управляет подами в BackoffQueue (очередь отката), которая содержит поды, временно отложенные из-за предыдущих неудачных попыток планирования.

  1. Поды попадают в BackoffQueue, когда они неоднократно не могут быть запланированы. Для этих подов применяется механизм экспоненциального отката, который увеличивает время ожидания перед каждой новой попыткой планирования.
  2. Когда время ожидания в BackoffQueue истекает, flushBackoffQCompleted переносит поды обратно в ActiveQ, чтобы они могли быть повторно рассмотрены для планирования.
Schedule pipeline

Schedule Pipeline - представляет собой центральную логику планировщика Kubernetes. Schedule Pipeline - это цепочка шагов и проверок, после которых pod должен быть назначен на одну из нод. Schedule Pipeline разделен на 3 потока. Про потоки писал ранее тут.

PreFilter

Этот этап процесса планирования выполняется до основных этапов фильтрации и выбора узла и служит для выполнения быстрых предварительных проверок, чтобы определить, соответствует ли POD базовым критериям для планирования.

Основная функция PreFilter

В случае неудачи pod помещается в UnschedulableQueue, где он ожидает, пока не изменится состояние кластера, что может позволить ему быть запланированным.

Filter

Filter является ключевой точкой расширения (extension point) в Scheduling Framework. Он отвечает за фильтрацию узлов, которые не могут принять pod, и выбор узла, который может принять pod.

Процесс фильтрации

Комментарий о расширяемости

т.к. этот плагин (extension point) помечен зеленым(как и другие подобные на схеме), то разработчики разработчики могут создавать и внедрять свои собственные фильтры в рамках Scheduling Framework. Это позволяет адаптировать процесс планирования к специфическим требованиям и условиям кластера.

PreScore

Этот этап предназначен для подготовки необходимых данных или выполнения предварительных расчетов, которые будут использоваться на этапе ранжирования узлов.

Это еще 1 плагин призванный оптимизировать процесс, его назначение очень похоже на PreFilter по сути. 

Пояснение

Score

Цель этапа Score заключается в оценке и ранжировании узлов, которые успешно прошли этап фильтрации, чтобы определить, какой из них лучше всего подходит для размещения данного POD'а.

Каждому узлу присваивается оценка на основе различных критериев, таких как доступные ресурсы, близость к необходимым сервисам, требования к affinity/anti-affinity и другие факторы. Эти оценки затем используются для ранжирования узлов, чтобы определить, какой из них лучше всего подходит для размещения POD'а.

Пояснение о метриках и оценках

Reserve

Цель этапа Reserve заключается в резервировании необходимых ресурсов на узле для POD'а, который планируется к запуску. Это может включать, например, выделение определенного объема памяти или CPU. Этап Reserve гарантирует, что все ресурсы, необходимые для POD'а, будут доступны на выбранном узле перед его окончательным назначением.

Это необходимо для того, чтобы другие поды не могли занять эти ресурсы(предотвращение ситуации race condition). Этот плагин реализует так же метод UnReserve.

UnReserve

Если на каком-либо позднем этапе планирования (например, на этапе Permit или Bind) возникают проблемы и POD не может быть успешно размещен на узле, UnReserve используется для отката действий, выполненных на этапе Reserve. UnReserve освобождение ресурсы, делает отмену изменений, сделанных на узле в процессе резервирования, что позволяет этим ресурсам быть доступными для других подов.

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

Это метод часть плагина Reserve. Этот метод может быть вызван не зависимо от очереди выполнения из любых других плагинов(после этапа Reserve).

Permit

Этот этап позволяет планировщику синхронизировать свои действия с внешними системами, прежде чем разместить pod на узле.

Основная задача: решить, следует ли разрешить или отложить размещение пода (pod) на узле (node), основываясь на текущем состоянии кластера или внешних факторах.

Этап Permit работает в своем отдельном потоке, что означает, что он выполняется параллельно с основным(main) потоком планировщика. Это подчеркивает асинхронный и неблокирующий характер этой фазы.

Permit может сделать одну из 3-х вещей:

  1. approve - Все предыдущие плагины подтвердили, что POD может быть запущен на ноде. Значит финальное решение для POD'а - approve.
  2. deny - Один из предыдущих плагинов вернул не положительный результат. Значит финальное решение для POD'а - deny.
  3. wait - Если плагин permit возвращает “wait”, то POD остается в фазе permit, пока POD не получит approve или deny статус. Если происходит тайм-аут, “wait” становится “deny”, и POD возвращается в очередь планирования, активируя метод Un-reserve в фазе Reserve.

Пояснение:

PreBind

Все семейство плагинов Bind работает в отдельном потоке, как изображено на схеме.

Простой пример - PreBind может убедиться в доступности внешнего сетевого хранилища перед привязкой.

Bind

На этом этапе планировщик фактически привязывает POD к узлу. Это может включать в себя различные действия, такие как резервирование ресурсов, настройка сети, запуск контейнеров и т.д.

Процесс привязки:

  1. API-запрос на привязку: Планировщик отправляет запрос в API сервер Kubernetes, чтобы привязать pod к выбранному узлу. Это обычно включает обновление объекта Pod (в etcd конечно) с информацией о том, к какому узлу он привязан.
  2. Запуск POD'а (этот пункт вне k8s scheduler, указан тут для наглядности всего процесса): После успешной привязки в работу вступает kubelet на соответствующем узле и начинает процесс запуска POD'а.
Важные замечания
  1. Pod удаляется из активной очереди (ActiveQ) после успешного выполнения этапа Bind, но до начала этапа PostBind. Этот процесс нужен, чтобы показать, что под, который уже был успешно назначен на узел, больше не рассматривается для дальнейшего планирования.
  2. Взаимодействие Scheduling Pipeline и Кэша
  3. Сбор Информации: В начале Scheduling Pipeline, планировщик собирает информацию о всех доступных узлах и подах в кластере. Эта информация обычно извлекается из кэша планировщика, который поддерживает актуальное состояние кластера.
  4. Обновление Кэша: Кэш постоянно обновляется по мере изменения состояния узлов и подов в кластере. Эти обновления могут поступать от различных компонентов Kubernetes, таких как kubelet или контроллеры. Обновленная информация критически важна для точного и эффективного планирования.
Informer

Informer в Kubernetes представляет собой мощный инструмент для наблюдения за ресурсами, кэширования их состояния и уведомления об их изменениях, что критически важно для реактивности и эффективности работы различных компонентов системы.

При запуске планировщик Kubernetes извлекает данные, необходимые для планирования, с сервера API Kubernetes через informer с помощью API List и Watch. Эти данные содержат информацию о модулях, узлах, PV, PVC, etc...

Роль и функционал Informer'а

P.S.: Статья вышла больше, чем я ожидал, поэтому сюда не включен разбор конкретных плагинов и пример написания собственного планировщика (если будет запрос в комментариях, то эта статья появится на хабре).

Пожалуйста, не стесняйтесь указывать на неточности и ошибки в комментариях, я буду исправлять и дополнять статью. K8S достаточно сложная тема, информацию о ней пришлось собирать из разных источников.

Полезные ссылки:


More Posts