Распределение подов

Общее устройство планировщика

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

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

Основные задачи планировщика:

  • Фильтрация (Filtering) — определение узлов, которые соответствуют требованиям пода.
  • Оценка (Scoring) — присвоение баллов узлам на основе различных критериев (чем выше балл, тем лучше узел).
  • Принятие решения (Binding) — назначение пода на лучший узел по итогам оценки.

Используемые плагины могут работать в одной или нескольких фазах. Например, один плагин может участвовать только в фильтрации, а другой — как в фильтрации, так и в оценке.

Примеры плагинов:

  • ImageLocality (фаза: Scoring) — отдает предпочтение узлам, на которых уже есть образы контейнеров, необходимые для пода. Это помогает сократить время развертывания, так как контейнеру не нужно загружать образ с удаленного репозитория.

  • TaintToleration (фазы: Filtering, Scoring) — реализует механизм taints и tolerations. Позволяет избежать назначения подов на узлы с нежелательными характеристиками или, наоборот, принудительно размещать их на специальных узлах (например, с выделенными ресурсами).

  • NodePorts (фаза: Filtering) — проверяет, есть ли у узла свободные порты, необходимые для работы пода. Это особенно важно для сервисов с фиксированными NodePort, так как на одном узле не может быть двух подов, использующих один и тот же порт.

С полным списком плагинов можно ознакомиться в документации Kubernetes.

Этапы планирования подов

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

На первой фазе Filtering — активируются плагины фильтрации (filter-плагины), которые из всех доступных узлов выбирают те, которые удовлетворяют определенным условиям фильтрации (например, taints, nodePorts, nodeName, unschedulable и другие).

Если узлы распределены по различным зонам (availability zones), DKP чередует выбор зон, чтобы избежать размещения всех подов в одной зоне. Например, если узлы расположены так:

Zone 1: Node 1, Node 2, Node 3, Node 4
Zone 2: Node 5, Node 6

То выбор будет происходить в таком порядке:

Node 1, Node 5, Node 2, Node 6, Node 3, Node 4

Важно отметить, что планировщик не проверяет все узлы в кластере, а лишь их часть, что оптимизирует процесс планирования. Количество проверяемых узлов зависит от размера кластера:

  • В небольших кластерах (≤50 узлов) проверяются все узлы;
  • В средних кластерах (100 узлов) — 50% узлов;
  • В очень крупных кластерах (5000 узлов) — 10% узлов;
  • Минимальный порог — 5% узлов, если их количество превышает 5000.

После завершения этапа фильтрации начинается фаза оценки (Scoring). На этом этапе каждый узел из отфильтрованного списка получает баллы от различных scoring-плагинов, которые анализируют его характеристики и соответствие требованиям пода.

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

Основные факторы, которые учитываются при оценке:

  • Загрузка узла (pod capacity) – учитывается доступное количество CPU и памяти. Узлы с большим количеством свободных ресурсов получают более высокий балл.
  • Локальность образов контейнеров (ImageLocality) – узлы, на которых уже загружены необходимые образы контейнеров, получают преимущество.
  • Учет affinity и anti-affinity – если под должен размещаться рядом с определенными подами или, наоборот, быть от них изолирован, узлы, соответствующие этим требованиям, получают более высокий приоритет.
  • Доступность хранилища (volume provisioning) – узлы, на которых доступны необходимые PersistentVolumes, оцениваются выше.

После суммирования всех оценок узел с наибольшим итоговым баллом выбирается для запуска пода.

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

Как изменить или расширить логику работы планировщика

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

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

  • Использование TLS – соединение с плагином должно быть защищено с использованием TLS.
  • Доступность – плагин должен быть развернут как сервис внутри кластера и доступен через HTTP(S).
  • Поддержка стандартных оепраций Verbs, filterVerb = filter – участвует в фильтрации узлов перед выбором места для пода, prioritizeVerb = prioritize – участвует в оценке узлов на этапе Scoring.
  • Кэширование информации об узлах – предполагается, что все подключаемые плагины могут кэшировать информацию об узле (параметр nodeCacheCapable: true) для повышения производительности.

Подключение расширенного (extender) плагина осуществляется через ресурс KubeSchedulerWebhookConfiguration – ресурс определяет конфигурацию внешнего вебхука планировщика (kube-scheduler) и позволяет учитывать более сложные условия при планировании нагрузки в кластере, например:

  • Размещение подов приложений организации хранилища данных ближе к самим данным,
  • Приоритизация узлов в зависимости от их состояния (сетевой нагрузки, состояния подсистемы хранения и т.д.),
  • Разделение узлов на зоны, и т.п.

Пример подключения внешнего плагина планировщика через вебхук:

apiVersion: deckhouse.io/v1alpha1
kind: KubeSchedulerWebhookConfiguration
metadata:
  name: sds-replicated-volume
webhooks:
- weight: 5
  failurePolicy: Ignore
  clientConfig:
    service:
      name: scheduler
      namespace: d8-sds-replicated-volume
      port: 8080
      path: /scheduler
    caBundle: ABCD=
  timeoutSeconds: 5

Если используется параметр failurePolicy: Fail, то при сбое работы вебхука планировщик полностью прекратит свою работу, и новые поды не смогут быть запущены. Рекомендуется тщательно тестировать работу расширяемых плагинов перед развертыванием в продуктивной среде.

Дополнительные механизмы управления размещением подов

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

Основные способы управления размещением подов:

  • Использование меток узлов (NodeGroup.spec.nodeTemplate.labels). Позволяет явно указать, на каких узлах должны запускаться определенные поды, с помощью spec.nodeSelector или spec.affinity.nodeAffinity.
  • Настройка taints и tolerations. Позволяет ограничить запуск подов только на определенных узлах, предотвращая их размещение на неподходящих хостах.
  • Использование пользовательских toleration-ключей (settings.modules.placement.customTolerationKeys). Позволяет управлять запуском критически важных компонентов Deckhouse (например, CNI и CSI) на выделенных узлах.
  1. Использование nodeSelector — позволяет явно указывать узлы, на которых должны запускаться поды. Это делается путем назначения лейблов узлам и последующего использования spec.nodeSelector в манифесте пода. Пример:

    Допустим, у нас есть узел kube-system-1, предназначенный для сервисов мониторинга. Добавим на него метку:

    d8 k label node kube-system-1 node-role/monitoring=""
    

    Теперь, чтобы развернуть поды только на этом узле, в Deployment добавляется nodeSelector:

    nodeSelector:
     node-role/monitoring: ""
    
  2. Использование taints и tolerations — в отличие от nodeSelector, taints позволяют запретить запуск подов на узлах, если у них нет соответствующего toleration. Это гарантирует, что на узле будут запускаться только определенные сервисы. Пример:

    Например, есть узел kube-frontend-1, который предназначен только для Ingress-контроллеров. Добавим на него taint:

    d8 k taint node kube-frontend-1 node-role/frontend="":NoExecute
    

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

    tolerations:
    - effect: NoExecute
      key: node-role/frontend
    

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

  3. DKP поддерживает механизм customTolerationKeys, который позволяет явно определять допустимые toleration-ключи. Это полезно, если требуется разместить системные сервисы (CNI, CSI и другие) на определенных узлах. Пример:

    customTolerationKeys:
    - dedicated.example.com
    - node-dedicated.example.com/master
    

    Этот механизм позволяет:

    • Разделять узлы на зоны, выделяя одни под Ingress-контроллеры, другие под системные сервисы (Prometheus, VPN, CoreDNS).
    • Изолировать критически важные приложения от системных компонентов, избегая конкуренции за ресурсы.

Профили планировщика

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

  • default-scheduler — стандартный профиль, который старается равномерно распределять поды по узлам, отдавая предпочтение менее загруженным.
  • high-node-utilization — профиль, который размещает поды на более загруженных узлах. Это может быть полезно в сценариях, когда необходимо уплотнить нагрузку и освободить неиспользуемые узлы для дальнейшего выключения или перераспределения ресурсов.

Для выбора профиля планировщика укажите его в параметре spec.schedulerName манифеста пода. Пример:

apiVersion: v1
kind: Pod
metadata:
  name: scheduler-example
  labels:
    name: scheduler-example
spec:
  schedulerName: high-node-utilization
  containers:
  - name: example-pod
    image: registry.k8s.io/pause:2.0  

Перераспределение подов

DKP каждые 15 минут анализирует состояние кластера и выполняется вытеснение (eviction) подов, которые соответствуют условиям, описанным в активных стратегиях планирования. Вытеснённые поды вновь проходят стандартный процесс планирования с учётом текущего состояния кластера. Это позволяет перераспределить рабочие нагрузки в соответствии с выбранной стратегией и, при необходимости, освободить ресурсы некоторых узлов.

Учет класса приоритета пода

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

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

Как DKP учитывает класс приоритета

Deckhouse Kubernetes Platform использует механизм приоритетов подов для определения порядка их вытеснения при нехватке ресурсов. Чем выше приоритет пода, тем меньшая вероятность его удаления в процессе перераспределения нагрузки.

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

Порог можно указать двумя способами:

  • По названию класса: если указать priorityClassThreshold.name, то вытеснению будут подлежать только поды с приоритетом ниже указанного класса приоритета.
  • По числовому значению: если указать priorityClassThreshold.value, то вытеснению подлежат поды с приоритетом ниже заданного значения (целочисленное значение).

Пример:

apiVersion: deckhouse.io/v1alpha2
kind: Descheduler
metadata:
  name: custom
spec:
  priorityClassThreshold:
    name: high-priority

Какие поды не вытесняются

  • DKP не вытесняет под в следующих случаях:

    • Под находится в пространстве имен d8-* или kube-system;
    • Под имеет priorityClassName system-cluster-critical или system-node-critical;
    • Под связан с локальным хранилищем;
    • Под связан с DaemonSet;
    • Вытеснение пода нарушит Pod Disruption Budget (PDB);
    • Нет доступных узлов для запуска вытесненного пода.

Если несколько подов подходят под критерии вытеснения, DKP применяет дополнительную логику:

  1. Сначала вытесняются поды с наименьшим приоритетом (BestEffort);
  2. Затем — поды с приоритетом Burstable;
  3. В последнюю очередь вытесняются Guaranteed поды, если это возможно.

DKP позволяет точно настроить, на какие поды и узлы будет распространяться вытеснение:

  • spec.podLabelSelector — ограничивает поды по меткам;
  • spec.namespaceLabelSelector — фильтрует пространства имен, из которых поды будут рассматриваться для вытеснения;
  • spec.nodeLabelSelector — выбирает узлы по меткам.

Каждый из этих полей содержит стандартные поля matchExpressions и matchLabels, в которых можно указывать операторы In, NotIn, Exists, DoesNotExist и нужные значения меток.

Как включить или отключить перераспределение

Для включения функции перераспределения подов необходимо включить модуль descheduler.

Это можно сделать следующими способами:

  1. Через ресурс ModuleConfig (например, ModuleConfig/descheduler). Установите параметр spec.enabled в значение true или false:

    apiVersion: deckhouse.io/v1alpha1
    kind: ModuleConfig
    metadata:
      name: descheduler
    spec:
      enabled: true    # или false для отключения
    
  2. Через команду d8 (в поде d8-system/deckhouse):

    d8 platform module enable descheduler
    # или, чтобы отключить:
    d8 platform module disable descheduler
    
  3. Через веб-интерфейс Deckhouse:

    • Перейдите в раздел «Deckhouse - «Модули»;
    • Найдите модуль descheduler и нажмите на него;
    • Включите тумблер «Модуль включен».

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

Стратегии перераспределения

В параметре spec.strategies перечисляются стратегии, которые вы хотите включить или настроить. Каждая стратегия имеет флаг enabled (по умолчанию false).

Ниже приведён список основных стратегий, доступных в DKP.

HighNodeUtilization — концентрирует нагрузку на меньшем числе узлов, вытесняя поды с недостаточно нагруженных узлов, чтобы они перезапустились на других узлах. Требуется:

  • Специальная настройка модуля descheduler — MostAllocated;
  • (Опционально) включенный автоскейлинг кластера — чтобы неиспользуемые узлы могли быть выключены.

Стратегия включается параметром spec.strategies.highNodeUtilization.enabled.

Параметр thresholds задаёт порог для признания узла «недостаточно нагруженным». Если ресурс (CPU, память и т.д.) ниже всех пороговых значений, узел считается недогруженным, и DKP попытется вытеснить с него поды.

Пример:

---
apiVersion: deckhouse.io/v1alpha2
kind: Descheduler
metadata:
  name: high-node-utilization
spec:
  strategies:
    highNodeUtilization:
      enabled: true
      thresholds:
        cpu: 50
        memory: 50

В GKE (Google Kubernetes Engine) нельзя настроить MostAllocated по умолчанию, но можно использовать стратегию optimize-utilization.

LowNodeUtilization — более равномерно нагружает узлы. Стратегия выявляет недостаточно нагруженные узлы и вытесняет поды с других, избыточно нагруженных узлов. Стратегия предполагает, что пересоздание вытесненных подов произойдет на недостаточно нагруженных узлах.

Стратегия включается параметром spec.strategies.lowNodeUtilization.enabled.

Недостаточно нагруженный узел — узел, использование ресурсов которого меньше всех пороговых значений, заданных в секции параметров strategies.lowNodeUtilization.thresholds.

Избыточно нагруженный узел — узел, использование ресурсов которого больше хотя бы одного из пороговых значений, заданных в секции параметров strategies.lowNodeUtilization.targetThresholds.

Узлы с использованием ресурсов в диапазоне между thresholds и targetThresholds считаются оптимально используемыми. Поды на таких узлах вытесняться не будут.

Пример:

apiVersion: deckhouse.io/v1alpha2
kind: Descheduler
metadata:
  name: low-node-utilization
spec:
  strategies:
    lowNodeUtilization:
      enabled: true
      thresholds:
        cpu: 20
      targetThresholds:
        cpu: 50

RemoveDuplicates — не позволяет нескольким подам одного контроллера (ReplicaSet/ReplicationController/StatefulSet/Job) одновременно находиться на одном узле (исключая DaemonSet). Если по каким-то причинам на одном узле оказалось 2+ подов от одного контроллера, стратегия вытеснит «лишние» поды, чтобы они равномернее распределились по кластеру.

Ситуация может возникать после кратковременного выхода из строя узлов, когда поды «переехали», а узел вернулся в строй, но поды так и остались сконцентрированы на другом узле.

Стратегия включается параметром strategies.removeDuplicates.enabled.

Пример:

spec:
  strategies:
    removeDuplicates:
      enabled: true

RemovePodsViolatingInterPodAntiAffinity — вытесняет любые поды, нарушающие правила inter-pod affinity/anti-affinity. Например, если под2 и под3 имеют anti-affinity по отношению к под1, но все трое по каким-то причинам оказались на одном узле, модуль вытеснит под1, чтобы под2 и под3 могли продолжать работу в соответствии со своими правилами.

Стратегия включается параметром strategies.removePodsViolatingNodeAffinity.enabled.

Пример:

spec:
  strategies:
    removePodsViolatingInterPodAntiAffinity:
      enabled: true

RemovePodsViolatingNodeAffinity — эта стратегия отвечает за вытеснение подов, которые больше не соответствуют своим требованиям node affinity. Node affinity определяет, какие узлы подходят поду при размещении:

  1. requiredDuringSchedulingIgnoredDuringExecution:
    • Под с таким типом правила обязан запускаться только на узле, удовлетворяющем заданным условиям (например, наличию определённой метки).
    • Если со временем узел перестаёт удовлетворять этим условиям (например, метку убрали), а в кластере есть другой узел, который им по-прежнему соответствует, DKP вытеснит под, чтобы он заново запустился на подходящем узле.
  2. preferredDuringSchedulingIgnoredDuringExecution:
    • Под с таким типом правила может быть размещён на узле, который удовлетворяет предпочтению, но если подходящего узла не было, он запускается там, где мог.
    • Если позже в кластере появляется более соответствующий узел, DKP может вытеснить под со старого узла, чтобы он перезапустился уже оптимальных условиях.

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

Стратегия включается параметром spec.strategies.removePodsViolatingNodeAffinity.enabled.

Пример:

spec:
  strategies:
    removePodsViolatingNodeAffinity:
      enabled: true
      nodeAffinityType:
        - requiredDuringSchedulingIgnoredDuringExecution
        - preferredDuringSchedulingIgnoredDuringExecution