Ван Цинкан (Ли Фань) и Чжан Кай

Предисловие

Обладая многолетним опытом поддержки продуктов и клиентов Kubernetes, команда Alibaba Cloud Container Service for Kubernetes значительно оптимизировала и расширила Kube-scheduler для стабильного и эффективного планирования различных сложных рабочих нагрузок в различных сценариях. В этой серии статей под названием Расцветающая система планирования Kubernetes представлен исчерпывающий обзор нашего опыта, технического мышления и конкретных методов реализации для пользователей и разработчиков Kubernetes. Мы надеемся, что статьи помогут вам лучше понять мощные возможности и будущие тенденции системы планирования Kubernetes.

Управление контейнерами с помощью Kube-scheduler

Kubernetes стал де-факто стандартной платформой для управления контейнерными кластерами. Он автоматизирует развертывание, эксплуатацию и техническое обслуживание и планирование ресурсов на протяжении всего жизненного цикла контейнерных приложений. После более чем трех лет быстрой разработки Kubernetes добился значительного прогресса в плане стабильности, масштабируемости и масштабируемости. В частности, совершенствуются основные компоненты плоскости управления Kubernetes. Как планировщик, который определяет, может ли контейнер работать в кластере. Kube-scheduler долгое время обеспечивает стабильную производительность и может удовлетворить требования большинства сценариев планирования подов. В результате разработчики почти не думают о Kube-scheduler.

Поскольку Kubernetes широко используется в общедоступных облаках и корпоративных ИТ-системах, все больше разработчиков пытаются использовать Kubernetes для запуска и управления рабочими нагрузками вне веб-приложений и микросервисов. Типичные сценарии включают учебные задания для машинного обучения и глубокого обучения, высокопроизводительные вычислительные задания, рабочие процессы генетических вычислений и обычные задания по обработке больших данных. Кластеры Kubernetes могут управлять широким спектром ресурсов, в том числе графическими процессорами (GPU), тензорными процессорами (TPU), программируемыми пользователем вентильными массивами (FPGA) и высокопроизводительными сетями удаленного прямого доступа к памяти (RDMA). Кластеры Kubernetes также могут управлять различными пользовательскими ускорителями для задач в различных областях, таких как чипы искусственного интеллекта (ИИ), нейронные процессоры (NPU) и видеокодеки. Разработчики хотят использовать различные разнородные устройства в кластерах Kubernetes простым способом, так же, как они используют ЦП и память.

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

Обладая многолетним опытом поддержки продуктов и клиентов Kubernetes, команда Alibaba Cloud Container Service for Kubernetes значительно оптимизировала и расширила Kube-scheduler для стабильного и эффективного планирования различных сложных рабочих нагрузок в различных сценариях.

В этой серии статей под названием «Расцветающая система планирования Kubernetes» представлен исчерпывающий обзор нашего опыта, технического мышления и конкретных методов реализации для пользователей и разработчиков Kubernetes. Мы надеемся, что статьи помогут вам лучше понять мощные возможности и будущие тенденции системы планирования Kubernetes.

Ранние решения

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

Kube-scheduler был первым, кто столкнулся с проблемой унифицированного управления и планирования разнородных ресурсов и других типов сложных рабочих нагрузок. Сообщество Kubernetes постоянно обсуждает, как улучшить масштабируемость планировщиков. Вывод, сделанный sig-планированием, заключается в том, что если в планировщик добавить больше функций, в планировщике будет определен больший объем кода, и логика планировщика станет более сложной. В результате обслуживание планировщика, а также поиск и обработка ошибок становятся более сложными. Кроме того, пользователям, использующим настраиваемое расписание, трудно успевать за каждым обновлением функций планировщика.

Пользователи Alibaba Cloud сталкиваются с теми же проблемами. Собственный планировщик для Kubernetes зацикливается на фиксированной логике одного контейнера pod. В результате он не может своевременно и просто удовлетворить требования пользователей в различных сценариях. Чтобы решить эту проблему, мы расширяем наши политики планирования для конкретных сценариев на основе родного планировщика Kube.

Первоначально мы расширяем Kube-scheduler, используя расширитель планировщика или несколько планировщиков. В следующем разделе мы опишем и сравним эти два метода.

Расширитель планировщика

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

Давайте используем фазу фильтра в качестве примера. Процесс фильтрации зависит от результата выполнения:

  1. Планировщик сначала выполняет политику фильтрации по умолчанию. Если выполнение не удается, планировщик помечает процесс планирования pod как неудавшийся.
  2. Если выполняется политика фильтрации по умолчанию, планировщик отправляет HTTP-запрос для вызова веб-перехватчика, зарегистрированного с помощью расширителя, отправляет расширителю информацию о модуле и узле, необходимую для планирования, и использует возвращенный результат фильтрации в качестве окончательного результата.

Как видите, у расширителя есть следующие проблемы:

  1. Расширитель вызывается путем отправки HTTP-запроса. На HTTP-запросы влияет сетевое окружение, и производительность HTTP-запросов намного ниже, чем у локальных вызовов функций. Кроме того, во время каждого вызова информация о модулях и узлах должна быть маршалирована и демаршалирована, что еще больше снижает производительность.
  2. Вы можете делать определенные расширения только в фиксированных местах, поэтому вы не можете гибко расширять планировщик. Например, пользователи могут вызывать веб-перехватчик только после выполнения политики фильтрации по умолчанию.

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

Несколько планировщиков

В кластерах Kubernetes планировщик похож на специальный контроллер. Он прослушивает информацию о модуле и узле, выбирает лучший узел для модуля и обновляет информацию spec.NodeName модуля, чтобы синхронизировать результат планирования с узлом. Поэтому, чтобы удовлетворить особые требования определенных пользователей к планированию, разработчики разработали настраиваемый планировщик для выполнения предыдущего процесса, а затем развертывания как пользовательского планировщика, так и планировщика по умолчанию.

Пользовательский планировщик имеет следующие проблемы:

  1. Если настраиваемый планировщик развертывается вместе с планировщиком по умолчанию, планировщики могут планировать разные модули на одном и том же ресурсе узла одновременно во время принятия решения о планировании, поскольку представление ресурсов, отображаемое каждому планировщику, является глобальным представлением ресурсов. В результате может возникнуть конфликт ресурсов узла.
  2. Некоторые пользователи разделили ресурсы, которые могут быть запланированы планировщиком, на разные пулы по меткам, чтобы избежать конфликтов ресурсов. Однако общее использование ресурсов кластера снизилось.
  3. Некоторые пользователи заменили планировщик по умолчанию на собственный планировщик. В этом случае потребуются высокие затраты на исследования и разработки, а после обновления Kubernetes могут возникнуть проблемы совместимости.

В заключение, расширитель планировщика требует низких затрат на обслуживание, но имеет низкую производительность, в то время как пользовательский планировщик имеет высокую производительность, но влечет за собой чрезмерно высокие затраты на разработку и обслуживание. Это ставит разработчиков перед дилеммой. В это время появилась Kubernetes Scheduling Framework V2, дающая разработчикам лучшее из обоих миров.

Анализ структуры планирования нового поколения

Сообщество постепенно заметило трудности, с которыми столкнулись разработчики. Чтобы решить предыдущие проблемы и сделать Kube-scheduler более масштабируемым и лаконичным, сообщество создало Kubernetes Scheduling Framework после выпуска Kubernetes 1.16.

Kubernetes Scheduling Framework определяет различные API для точек расширения на основе исходного процесса планирования. Разработчики могут реализовывать подключаемые модули, внедряя API, определенные для точек расширения, и регистрируя подключаемые модули в точках расширения. Когда Scheduling Framework запускается в соответствующей точке расширения во время планирования, Scheduling Framework вызывает зарегистрированный подключаемый модуль, чтобы изменить решение о планировании. Таким образом, ваша логика планирования интегрируется в структуру планирования.

Процесс планирования в Scheduling Framework разделен на две фазы: цикл планирования и цикл привязки. Циклы планирования выполняются синхронно, и одновременно выполняется только один цикл планирования, что является потокобезопасным. Циклы привязки выполняются асинхронно. Несколько циклов привязки могут выполняться одновременно, что небезопасно для потоков.

Цикл планирования

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

Сортировка по очереди

// QueueSortPlugin is an interface that must be implemented by "QueueSort" plugins.
// These plugins are used to sort pods in the scheduling queue. Only one queue sort
// plugin may be enabled at a time.
type QueueSortPlugin interface {
    Plugin
    // Less are used to sort pods in the scheduling queue.
    Less(*PodInfo, *PodInfo) bool
}

Приоритетная очередь планировщика реализована с использованием кучи. Вы можете определить функцию сравнения для кучи в подключаемом модуле QueueSort, чтобы определить сортировку в очереди с приоритетом. Примечание. В каждый момент времени существует только одна функция сравнения для кучи. Поэтому одновременно может быть включен только один подключаемый модуль QueueSort. Если вы включите два подключаемых модуля QueueSort, планировщик сообщит об ошибке и завершит работу при запуске. Следующий код показывает функцию сравнения по умолчанию для справки:

// Less is the function used by the activeQ heap algorithm to sort pods.
// It sorts pods based on their priority. When priorities are equal, it uses
// PodQueueInfo.timestamp.
func (pl *PrioritySort) Less(pInfo1, pInfo2 *framework.QueuedPodInfo) bool {
    p1 := pod.GetPodPriority(pInfo1.Pod)
    p2 := pod.GetPodPriority(pInfo2.Pod)
    return (p1 > p2) || (p1 == p2 && pInfo1.Timestamp.Before(pInfo2.Timestamp))
}

Предварительный фильтр

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

Фильтр

Подключаемый модуль Filter — это логика предиката в Scheduler V1. Он используется для фильтрации узлов, которые не могут запланировать модуль. Для повышения эффективности можно настроить порядок выполнения подключаемых модулей фильтра. Вы можете установить приоритет политик фильтрации, которые могут отфильтровывать большое количество узлов, сводя к минимуму количество политик фильтрации, которые будут выполняться впоследствии. Например, вы можете установить приоритет политик фильтрации для NodeSelector, чтобы отфильтровать большое количество узлов. Узел одновременно выполняет политики фильтрации, поэтому подключаемые модули фильтра вызываются несколько раз в цикле планирования.

Постфильтр

Новый API PostFilter будет выпущен в Kubernetes 1.19. Этот API в основном используется для обработки операций, выполняемых после сбоя модуля на этапе фильтрации, таких как вытеснение и запуск автоматического масштабирования.

Предварительная оценка

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

Оценка

Точка расширения Scoring — это логика приоритета в Scheduler V1. После того как узлы отфильтрованы с помощью подключаемых модулей фильтра, планировщик выбирает оптимальный узел из оставшихся узлов на этом этапе на основе политики, определенной точкой расширения оценки.

Операции в точке расширения Scoring разделены на два этапа:

  1. Оценка: оценивает оставшиеся узлы, вызывая настроенную политику оценки.
  2. Нормализация: нормализует оцениваемую структуру от 0 до 100.

Резерв

Резервная точка расширения — это предполагаемая операция в Scheduler V1. На этом этапе планировщик кэширует результаты планирования. Если на какой-либо последующей фазе возникает ошибка или сбой, планировщик переходит к фазе Unreserve для отката данных.

Разрешить

Точка расширения Permit — это новая функция, представленная в Scheduler Framework V2. Разработчики могут определить свои политики для перехвата модуля в точке расширения разрешения после того, как планировщик зарезервирует ресурсы для модуля на этапе резервирования и до того, как планировщик выполнит операцию привязки. Затем, в зависимости от условий, разработчики могут выполнять операции утверждения, отклонения и ожидания над модулями, прошедшими фазу разрешения. В частности, «одобрить» означает, что поду разрешено пройти фазу «Разрешение». «Запретить» означает, что модуль отклонен и не проходит фазу разрешения, поэтому модуль не может быть запланирован. «Подождите» указывает, что модуль находится в состоянии ожидания. Разработчики могут установить период ожидания для операций ожидания.

Цикл связывания

Цикл привязки включает в себя вызов операций API, предоставляемых Kube-apiserver, и поэтому занимает много времени. Чтобы повысить эффективность планирования, вам необходимо запускать циклы привязки асинхронно. Таким образом, эта фаза небезопасна для потоков.

Привязать

Точка расширения Bind — это операция Bind в Scheduler V1. Он вызывает операции API, предоставляемые Kube-apiserver, для привязки модуля к соответствующему узлу.

PreBind и PostBind

Разработчики могут выполнять PreBind и PostBind до и после операции Bind соответственно. Вы можете получать и обновлять информацию о данных на этих двух этапах.

Отменить резервирование

Точка расширения UnReserve используется для очистки кэшей, созданных на этапе резервирования, и отката данных до исходного состояния. UnReserve и Reserve определены отдельно в текущей версии. В будущем мы унифицируем их и потребуем от разработчиков определить как Reserve, так и UnReserve, чтобы гарантировать, что данные можно очистить, не оставляя грязных данных.

Внедрите свои плагины планирования

Планировщик-Плагины

Чтобы упростить управление различными плагинами, связанными с планированием, команда sig-scheduling, отвечающая за Kube-scheduler в Kubernetes, создала проект под названием scheduler-plugins. Вы можете определить свои плагины на основе этого проекта. Далее, давайте используем подключаемый модуль QoS в качестве примера, чтобы продемонстрировать, как разрабатывать ваши подключаемые модули.

Плагин QoS реализован в основном на основе класса качества обслуживания (QoS) модулей. Он используется для определения порядка планирования модулей с одинаковыми приоритетами в процессе планирования. Порядок планирования указан ниже:

  1. Гарантировано (запросы == лимиты)
  2. Burstable (запросы ‹ лимиты)
  3. BestEffort (запросы и лимиты не установлены)

Создание подключаемого модуля

Сначала определите объект и конструктор для вашего подключаемого модуля.

// QoSSort is a plugin that implements QoS class based sorting.
type Sort struct{}
// New initializes a new plugin and returns it.
func New(_ *runtime.Unknown, _ framework.FrameworkHandle) (framework.Plugin, error) {
    return &Sort{}, nil
}

Затем внедрите соответствующий API на основе точки расширения, соответствующей подключаемому модулю. QoS является частью подключаемого модуля QueueSort. Поэтому вам необходимо реализовать функцию для API плагина QueueSort. Как показано в следующем коде, API подключаемого модуля QueueSort определяет только функцию Less. Поэтому можно только реализовать эту функцию.

// QueueSortPlugin is an interface that must be implemented by "QueueSort" plugins.
// These plugins are used to sort pods in the scheduling queue. Only one queue sort
// plugin may be enabled at a time.
type QueueSortPlugin interface {
    Plugin
    // Less are used to sort pods in the scheduling queue.
    Less(*PodInfo, *PodInfo) bool
}

В следующем коде показана реализованная функция. По умолчанию подключаемый модуль QueueSort по умолчанию сначала сравнивает приоритеты модулей, и если приоритеты некоторых модулей совпадают, подключаемый модуль сравнивает временные метки модулей. Мы переопределили функцию Less для определения приоритетов путем сравнения значений QoS в случаях, когда приоритеты равны.

// Less is the function used by the activeQ heap algorithm to sort pods.
// It sorts pods based on their priority. When priorities are equal, it uses
// PodInfo.timestamp.
func (*Sort) Less(pInfo1, pInfo2 *framework.PodInfo) bool {
    p1 := pod.GetPodPriority(pInfo1.Pod)
    p2 := pod.GetPodPriority(pInfo2.Pod)
    return (p1 > p2) || (p1 == p2 && compQOS(pInfo1.Pod, pInfo2.Pod))
}
func compQOS(p1, p2 *v1.Pod) bool {
    p1QOS, p2QOS := v1qos.GetPodQOS(p1), v1qos.GetPodQOS(p2)
    if p1QOS == v1.PodQOSGuaranteed {
        return true
    } else if p1QOS == v1.PodQOSBurstable {
        return p2QOS ! = v1.PodQOSGuaranteed
    } else {
        return p2QOS == v1.PodQOSBestEffort
    }
}

Зарегистрируйте подключаемый модуль

Зарегистрируйте определенный подключаемый модуль и соответствующий конструктор в основной функции.

// cmd/main.go
func main() {
    rand.Seed(time.Now().UnixNano())
    command := app.NewSchedulerCommand(
        app.WithPlugin(qos.Name, qos.New),
    )
    if err := command.Execute(); err ! = nil {
        os.Exit(1)
    }
}

Скомпилируйте код

$ make

Запустите планировщик

Запустите kube-планировщик. Во время запуска настройте путь kubeconfig в файле ./manifests/qos/scheduler-config.yaml и импортируйте файл kubeconfig кластера и профиль подключаемого модуля.

$ bin/kube-scheduler --kubeconfig=scheduler.conf --config=./manifests/qos/scheduler-config.yaml

Теперь вы должны хорошо понимать архитектуру и метод разработки Kubernetes Scheduling Framework на основе предыдущего введения и примеров.

Идти вперед

Как новая архитектура для планировщиков, Kubernetes Scheduling Framework значительно продвинулась в плане масштабируемости и настройки. Это позволит Kubernetes постепенно поддерживать больше типов нагрузок приложений и превратиться в единую платформу с одной ИТ-архитектурой и стеком технологий. Чтобы лучше поддерживать миграцию заданий по обработке данных на платформу Kubernetes, мы также прилагаем усилия для интеграции следующих общих функций в области вычислений данных в собственный планировщик Kube с помощью системы подключаемых модулей Scheduling Framework: совместное планирование, групповое планирование. планирование, планирование емкости, доминирующая справедливость ресурсов и управление несколькими очередями.

В дальнейшем эта серия статей будет посвящена ИИ, обработке больших данных и кластерам высокопроизводительных вычислений (HPC), а также описанию того, как мы разрабатываем соответствующие плагины планировщика. Оставайтесь с нами, чтобы узнать больше!

Оригинальный источник: