Расширение планировщика kubernetes с помощью Prometheus


Обзор

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

конфигурация планирования kubernetes

В кластере kubernetes может работать несколько различных планировщиков, и различные планировщики также могут быть назначены для планирования в Pods. Это не упоминается в общих руководствах по планированию Kubernetes, что означает, что функция планирования kubernetes на самом деле не полностью используется для политик affinity, taint и других, а некоторые плагины планирования, упомянутые в предыдущих статьях, такие как планирование на основе занятости портов NodePorts и другие политики, как правило, не используются. Этот раздел посвящен этой части, которая также используется в качестве основы для расширения планировщика.

Конфигурация планировщика [1] The

kube-scheduler предоставляет ресурс конфигурационного файла в качестве файла конфигурации для kube-scheduler, который указывается при запуске через -config=. Конфигурация KubeSchedulerConfiguration, используемая в настоящее время в различных версиях kubernetes, имеет следующий вид

  • В версиях до 1.21 используется v1beta1.
  • Версии 1.22 использовали v1beta2, но сохранили v1beta1.
  • Версии 1.23, 1.24, 1.25 используют v1beta3, но сохраняют v1beta2 и удаляют v1beta1.

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

apiVersion: kubescheduler.config.k8s.io/v1beta1
kind: KubeSchedulerConfiguration
clientConnection:
  kubeconfig: /etc/srv/kubernetes/kube-scheduler/kubeconfig
Войдите в полноэкранный режим Выход из полноэкранного режима

Примечания: -kubeconfig и -config не могут быть указаны одновременно, если -config указан, остальные параметры становятся недействительными. [2]

Использование kubeSchedulerConfiguration

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

Следующая конфигурация является примером раздела о настройке точек расширения, который можно найти в разделе контекста планирования официальной документации kubernetes

apiVersion: kubescheduler.config.k8s.io/v1beta1
kind: KubeSchedulerConfiguration
profiles:
  - plugins:
      score:
        disabled:
        - name: PodTopologySpread
        enabled:
        - name: MyCustomPluginA
          weight: 2
        - name: MyCustomPluginB
          weight: 1
Войдите в полноэкранный режим Выход из полноэкранного режима

Примечания: Если name=»*», это отключит/включит все плагины для точки расширения.

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

apiVersion: kubescheduler.config.k8s.io/v1beta2
kind: KubeSchedulerConfiguration
profiles:
  - schedulerName: default-scheduler
    plugins:
      preScore:
        disabled:
        - name: '*'
      score:
        disabled:
        - name: '*'
  - schedulerName: no-scoring-scheduler
    plugins:
      preScore:
        disabled:
        - name: '*'
      score:
        disabled:
        - name: '*'
Войдите в полноэкранный режим Выход из полноэкранного режима

планировщик плагин планирования [3]

kube-scheduler предоставляет ряд плагинов в качестве методов планирования, которые по умолчанию включены без настройки, например.

  • ImageLocality: планирование будет более предвзято к узлам, где для узла существует образ контейнера. Очки расширения: score.
  • TaintToleration: Включает функциональность защиты от искажений и толерантности. Точки расширения: filter, preScore, score.
  • NodeName: Реализация NodeName, самого простого метода планирования в политике планирования. Точки расширения: filter.
  • NodePorts: планирование будет проверять, занят ли порт узла. Точки расширения: preFilter, filter.
  • NodeAffinity: обеспечивает функциональность, связанную с аффинити узла. Расширения: filter, score.
  • PodTopologySpread: реализует топологические домены Pod. Точки расширения: preFilter, filter, preScore, score.
  • NodeResourcesFit: Этот плагин будет проверять, есть ли у узла все ресурсы, запрошенные Pod. Используется одна из следующих трех политик: LeastAllocated (по умолчанию) MostAllocated и RequestedToCapacityRatio. Точки расширения: preFilter, filter, score.
  • VolumeBinding: проверка наличия или возможности связывания запрашиваемого тома. Точки расширения: preFilter, filter, reserve, preBind, score.
  • VolumeRestrictions: проверяет, соответствуют ли тома, установленные в узле, ограничениям, характерным для поставщика томов. Точка расширения: filter.
  • VolumeZone: проверяет, что запрашиваемые тома соответствуют любым требованиям к зонам, которые они могут иметь. Точка расширения: filter.
  • InterPodAffinity: реализует функции межподового сродства и антисродства. Точки расширения: preFilter, filter, preScore, score.
  • PrioritySort: обеспечивает сортировку на основе приоритета по умолчанию. Точка расширения: queueSort.

Дополнительные примеры использования конфигурационных файлов см. в официальной документации

Как расширить kube-scheduler [4]

Когда впервые задумываешься о написании планировщика, часто думается, что расширение kube-scheduler — это очень сложная задача. рамки.

Фреймворк призван сделать планировщик более масштабируемым за счет переопределения точек расширения, использования их в качестве плагинов и поддержки пользователей для регистрации out of tree расширений для kube-scheduler, и эти шаги анализируются ниже.

Определение входа

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

import (
    scheduler "k8s.io/kubernetes/cmd/kube-scheduler/app"
)

func main() {
    command := scheduler.NewSchedulerCommand(
            scheduler.WithPlugin("example-plugin1", ExamplePlugin1),
            scheduler.WithPlugin("example-plugin2", ExamplePlugin2))
    if err := command.Execute(); err != nil {
        fmt.Fprintf(os.Stderr, "%vn", err)
        os.Exit(1)
    }
}
Войдите в полноэкранный режим Выход из полноэкранного режима

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

// WithPlugin 用于注入out of tree plugins 因此scheduler代码中没有其引用。
func WithPlugin(name string, factory runtime.PluginFactory) Option {
    return func(registry runtime.Registry) error {
        return registry.Register(name, factory)
    }
}
Войдите в полноэкранный режим Выход из полноэкранного режима

Реализация плагина

Реализация подключаемого модуля — это просто вопрос реализации соответствующего интерфейса точки расширения. Ниже приведен анализ встроенных плагинов

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

Определите структуру плагина

FrameworkHandle — это тот, который обеспечивает связь между Kubernetes API и планировщиком. Структура показывает, что он содержит lister, informer и т.д. Этот параметр также должен быть реализован.

type NodeAffinity struct {
    handle framework.FrameworkHandle
}
Войдите в полноэкранный режим Выход из полноэкранного режима

Реализуйте соответствующую точку расширения

func (pl *NodeAffinity) Score(ctx context.Context, state *framework.CycleState, pod *v1.Pod, nodeName string) (int64, *framework.Status) {
    nodeInfo, err := pl.handle.SnapshotSharedLister().NodeInfos().Get(nodeName)
    if err != nil {
        return 0, framework.NewStatus(framework.Error, fmt.Sprintf("getting node %q from Snapshot: %v", nodeName, err))
    }

    node := nodeInfo.Node()
    if node == nil {
        return 0, framework.NewStatus(framework.Error, fmt.Sprintf("getting node %q from Snapshot: %v", nodeName, err))
    }

    affinity := pod.Spec.Affinity

    var count int64
    // A nil element of PreferredDuringSchedulingIgnoredDuringExecution matches no objects.
    // An element of PreferredDuringSchedulingIgnoredDuringExecution that refers to an
    // empty PreferredSchedulingTerm matches all objects.
    if affinity != nil && affinity.NodeAffinity != nil && affinity.NodeAffinity.PreferredDuringSchedulingIgnoredDuringExecution != nil {
        // Match PreferredDuringSchedulingIgnoredDuringExecution term by term.
        for i := range affinity.NodeAffinity.PreferredDuringSchedulingIgnoredDuringExecution {
            preferredSchedulingTerm := &affinity.NodeAffinity.PreferredDuringSchedulingIgnoredDuringExecution[i]
            if preferredSchedulingTerm.Weight == 0 {
                continue
            }

            // TODO: Avoid computing it for all nodes if this becomes a performance problem.
            nodeSelector, err := v1helper.NodeSelectorRequirementsAsSelector(preferredSchedulingTerm.Preference.MatchExpressions)
            if err != nil {
                return 0, framework.NewStatus(framework.Error, err.Error())
            }

            if nodeSelector.Matches(labels.Set(node.Labels)) {
                count += int64(preferredSchedulingTerm.Weight)
            }
        }
    }

    return count, nil
}
Войдите в полноэкранный режим Выход из полноэкранного режима

Наконец, реализована функция New, чтобы обеспечить способ регистрации этого расширения. Эта Новая функция может быть внедрена в планировщик как плагины вне дерева в main.go.

// New initializes a new plugin and returns it.
func New(_ runtime.Object, h framework.FrameworkHandle) (framework.Plugin, error) {
    return &NodeAffinity{handle: h}, nil
}
Войдите в полноэкранный режим Выход из полноэкранного режима

Эксперимент: составление расписания на основе сетевого трафика [7]

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

Экспериментальная среда

  • Кластер kubernetes, в котором гарантировано не менее двух узлов.
  • Предоставленный кластер kubernetes требует установки prometheus node_exporter либо внутри кластера, либо вне его, и тот, который используется здесь, является внешним по отношению к кластеру.
  • Знание promQL и client_golang

Эксперименты условно разделены на следующие этапы.

  • Определение API плагина
    • Назовите плагин NetworkTraffic.
  • Определение точек расширения
    • Здесь используется расширение Score и определяется алгоритм подсчета баллов
  • Определите, как получить оценку (получите соответствующие данные из метрики Prometheus)
  • Определите параметры, которые будут передаваться пользовательскому планировщику
  • Развертывание проекта в кластере (внутрикластерное и внекластерное развертывание)
  • Валидация результатов экспериментов

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

Код для всего эксперимента загружен на github.com/CylonChau/customScheduler

Начало эксперимента

Обработка ошибок

При инициализации проекта, go mod tidy и так далее, вы столкнетесь с большим количеством следующих ошибок

go: github.com/GoogleCloudPlatform/spark-on-k8s-operator@v0.0.0-20210307184338-1947244ce5f4 requires
        k8s.io/apiextensions-apiserver@v0.0.0: reading k8s.io/apiextensions-apiserver/go.mod at revision v0.0.0: unknown revision v0.0.0
Войдите в полноэкранный режим Выход из полноэкранного режима

проблема kubernetes #79384 [w1242231] Внизу страницы находится скрипт, который можно запустить напрямую, если вышеуказанная проблема не решена.

#!/bin/sh
set -euo pipefail

VERSION=${1#"v"}
if [ -z "$VERSION" ]; then
    echo "Must specify version!"
    exit 1
fi
MODS=($(
    curl -sS https://raw.githubusercontent.com/kubernetes/kubernetes/v${VERSION}/go.mod |
    sed -n 's|.*k8s.io/(.*) => ./staging/src/k8s.io/.*|k8s.io/1|p'
))
for MOD in "${MODS[@]}"; do
    V=$(
        go mod download -json "${MOD}@kubernetes-${VERSION}" |
        sed -n 's|.*"Version": "(.*)".*|1|p'
    )
    go mod edit "-replace=${MOD}=${MOD}@${V}"
done
go get "k8s.io/kubernetes@v${VERSION}"
Войдите в полноэкранный режим Выход из полноэкранного режима

Определение API плагина

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

Определите имя плагина и переменные

const Name = "NetworkTraffic"
var _ = framework.ScorePlugin(&NetworkTraffic{})
Войдите в полноэкранный режим Выход из полноэкранного режима

Определите структуру плагина

type NetworkTraffic struct {
    // 这个作为后面获取node网络流量使用
    prometheus *PrometheusHandle
    // FrameworkHandle 提供插件可以使用的数据和一些工具。
    // 它在插件初始化时传递给 plugin 工厂类。
    // plugin 必须存储和使用这个handle来调用framework函数。
    handle framework.FrameworkHandle
}
Войдите в полноэкранный режим Выход из полноэкранного режима

Определение расширений

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

func (n *NetworkTraffic) Score(ctx context.Context, state *framework.CycleState, p *corev1.Pod, nodeName string) (int64, *framework.Status) {
    // 通过promethes拿到一段时间的node的网络使用情况
    nodeBandwidth, err := n.prometheus.GetGauge(nodeName)
    if err != nil {
        return 0, framework.NewStatus(framework.Error, fmt.Sprintf("error getting node bandwidth measure: %s", err))
    }
    bandWidth := int64(nodeBandwidth.Value)
    klog.Infof("[NetworkTraffic] node '%s' bandwidth: %s", nodeName, bandWidth)
    return bandWidth, nil // 这里直接返回就行
}
Войдите в полноэкранный режим Выход из полноэкранного режима

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

// Run NormalizeScore method for each ScorePlugin in parallel.
parallelize.Until(ctx, len(f.scorePlugins), func(index int) {
    pl := f.scorePlugins[index]
    nodeScoreList := pluginToNodeScores[pl.Name()]
    if pl.ScoreExtensions() == nil {
        return
    }
    status := f.runScoreExtension(ctx, pl, state, pod, nodeScoreList)
    if !status.IsSuccess() {
        err := fmt.Errorf("normalize score plugin %q failed with error %v", pl.Name(), status.Message())
        errCh.SendErrorWithCancel(err, cancel)
        return
    }
})
Войдите в полноэкранный режим Выход из полноэкранного режима

Код выше показывает, что реализация Score требует, чтобы ScoreExtensions был реализован, или возвращен, если он не реализован. Пример в nodeaffinity показывает, что этот метод возвращает только само расширение, но фактическая нормализация, т.е. фактический подсчет баллов, осуществляется в NormalizeScore.

// NormalizeScore invoked after scoring all nodes.
func (pl *NodeAffinity) NormalizeScore(ctx context.Context, state *framework.CycleState, pod *v1.Pod, scores framework.NodeScoreList) *framework.Status {
    return pluginhelper.DefaultNormalizeScore(framework.MaxNodeScore, false, scores)
}

// ScoreExtensions of the Score plugin.
func (pl *NodeAffinity) ScoreExtensions() framework.ScoreExtensions {
    return pl
}
Войдите в полноэкранный режим Выход из полноэкранного режима

В системе планирования фактическая операция также выполняется NormalizeScore().

func (f *frameworkImpl) runScoreExtension(ctx context.Context, pl framework.ScorePlugin, state *framework.CycleState, pod *v1.Pod, nodeScoreList framework.NodeScoreList) *framework.Status {
    if !state.ShouldRecordPluginMetrics() {
        return pl.ScoreExtensions().NormalizeScore(ctx, state, pod, nodeScoreList)
    }
    startTime := time.Now()
    status := pl.ScoreExtensions().NormalizeScore(ctx, state, pod, nodeScoreList)
    f.metricsRecorder.observePluginDurationAsync(scoreExtensionNormalize, pl.Name(), status, metrics.SinceInSeconds(startTime))
    return status
}
Войдите в полноэкранный режим Выход из полноэкранного режима

Вот как реализовать соответствующий метод

В NormalizeScore необходимо реализовать алгоритм выбора конкретного узла, поскольку интервал для подсчета баллов узлов составляет $[0,100]$, поэтому алгоритм, реализованный здесь, будет $max — (текущая пропускная способность / максимальная пропускная способность * 100)$, что гарантирует, что чем выше использование пропускной способности машины, тем ниже балл.

Например, если максимальная пропускная способность составляет 200000, а текущая пропускная способность узла 140 000, то оценка узла будет такой: $max — frac{140000}{200000}times 100 = 100 — (0.7times100)=30$.

// 如果返回framework.ScoreExtensions 就需要实现framework.ScoreExtensions
func (n *NetworkTraffic) ScoreExtensions() framework.ScoreExtensions {
    return n
}

// NormalizeScore与ScoreExtensions是固定格式
func (n *NetworkTraffic) NormalizeScore(ctx context.Context, state *framework.CycleState, pod *corev1.Pod, scores framework.NodeScoreList) *framework.Status {
    var higherScore int64
    for _, node := range scores {
        if higherScore < node.Score {
            higherScore = node.Score
        }
    }
    // 计算公式为,满分 - (当前带宽 / 最高最高带宽 * 100)
    // 公式的计算结果为,带宽占用越大的机器,分数越低
    for i, node := range scores {
        scores[i].Score = framework.MaxNodeScore - (node.Score * 100 / higherScore)
        klog.Infof("[NetworkTraffic] Nodes final score: %v", scores)
    }

    klog.Infof("[NetworkTraffic] Nodes final score: %v", scores)
    return nil
}
Войдите в полноэкранный режим Выход из полноэкранного режима

Примечания: Учитывая, что максимальное количество узлов, поддерживаемых в kubernetes, составляет 5000, не отнимает ли цикл много производительности при получении максимального результата? Этот параметр определяет количество циклов в данном развертывании. Более подробную информацию можно найти в официальной документации к этому разделу [6]

Настройка имени плагина

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

// Name returns name of the plugin. It is used in logs, etc.
func (n *NetworkTraffic) Name() string {
    return Name
}
Войдите в полноэкранный режим Выход из полноэкранного режима

Определите параметры, которые будут переданы в

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

Сначала необходимо определить структуру PrometheusHandle

type PrometheusHandle struct {
    deviceName string // 网络接口名称
    timeRange  time.Duration // 抓取的时间段
    ip         string // prometheus server的连接地址
    client     v1.API // 操作prometheus的客户端
}
Войдите в полноэкранный режим Выход из полноэкранного режима

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

sum_over_time(node_network_receive_bytes_total{device="eth0"}[1s]) * on(instance) group_left(nodename) (node_uname_info{nodename="node01"})
Войдите в полноэкранный режим Выход из полноэкранного режима

Весь раздел «Прометей» выглядит следующим образом

type PrometheusHandle struct {
    deviceName string
    timeRange  time.Duration
    ip         string
    client     v1.API
}

func NewProme(ip, deviceName string, timeRace time.Duration) *PrometheusHandle {
    client, err := api.NewClient(api.Config{Address: ip})
    if err != nil {
        klog.Fatalf("[NetworkTraffic] FatalError creating prometheus client: %s", err.Error())
    }
    return &PrometheusHandle{
        deviceName: deviceName,
        ip:         ip,
        timeRange:  timeRace,
        client:     v1.NewAPI(client),
    }
}

func (p *PrometheusHandle) GetGauge(node string) (*model.Sample, error) {
    value, err := p.query(fmt.Sprintf(nodeMeasureQueryTemplate, node, p.deviceName, p.timeRange))
    fmt.Println(fmt.Sprintf(nodeMeasureQueryTemplate, p.deviceName, p.timeRange, node))
    if err != nil {
        return nil, fmt.Errorf("[NetworkTraffic] Error querying prometheus: %w", err)
    }

    nodeMeasure := value.(model.Vector)
    if len(nodeMeasure) != 1 {
        return nil, fmt.Errorf("[NetworkTraffic] Invalid response, expected 1 value, got %d", len(nodeMeasure))
    }
    return nodeMeasure[0], nil
}

func (p *PrometheusHandle) query(promQL string) (model.Value, error) {
    // 通过promQL查询并返回结果
    results, warnings, err := p.client.Query(context.Background(), promQL, time.Now())
    if len(warnings) > 0 {
        klog.Warningf("[NetworkTraffic Plugin] Warnings: %vn", warnings)
    }

    return results, err
}
Войдите в полноэкранный режим Выход из полноэкранного режима

Настройка параметров планировщика

Поскольку вам необходимо указать адрес прометея, имя сетевой карты и размер данных, которые нужно получить, вся структура выглядит следующим образом; кроме того, структура параметров должна соответствовать формату <Plugin Name>Args.

type NetworkTrafficArgs struct {
    IP         string `json:"ip"`
    DeviceName string `json:"deviceName"`
    TimeRange  int    `json:"timeRange"`
}
Войдите в полноэкранный режим Выход из полноэкранного режима

Для того чтобы сделать этот тип данных доступным в виде разбираемой структуры KubeSchedulerConfiguration, необходимо сделать еще один шаг — расширить соответствующий тип ресурса при расширении APIServer. Существует два способа расширения типа ресурса KubeSchedulerConfiguration в kubernetes.

Одна из них — старая функция framework.DecodeInto, которая делает следующее

func New(plArgs *runtime.Unknown, handle framework.FrameworkHandle) (framework.Plugin, error) {
    args := Args{}
    if err := framework.DecodeInto(plArgs, &args); err != nil {
        return nil, err
    }
    ...
}
Войдите в полноэкранный режим Выход из полноэкранного режима

Другой способ заключается в том, что должен быть реализован соответствующий метод глубокого копирования, например, в NodeLabel’s

// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object

// NodeLabelArgs holds arguments used to configure the NodeLabel plugin.
type NodeLabelArgs struct {
    metav1.TypeMeta

    // PresentLabels should be present for the node to be considered a fit for hosting the pod
    PresentLabels []string
    // AbsentLabels should be absent for the node to be considered a fit for hosting the pod
    AbsentLabels []string
    // Nodes that have labels in the list will get a higher score.
    PresentLabelsPreference []string
    // Nodes that don't have labels in the list will get a higher score.
    AbsentLabelsPreference []string
}
Войдите в полноэкранный режим Выход из полноэкранного режима

Наконец, зарегистрируйте его в register, все поведение аналогично поведению расширенного APIServer

// addKnownTypes registers known types to the given scheme
func addKnownTypes(scheme *runtime.Scheme) error {
    scheme.AddKnownTypes(SchemeGroupVersion,
        &KubeSchedulerConfiguration{},
        &Policy{},
        &InterPodAffinityArgs{},
        &NodeLabelArgs{},
        &NodeResourcesFitArgs{},
        &PodTopologySpreadArgs{},
        &RequestedToCapacityRatioArgs{},
        &ServiceAffinityArgs{},
        &VolumeBindingArgs{},
        &NodeResourcesLeastAllocatedArgs{},
        &NodeResourcesMostAllocatedArgs{},
    )
    scheme.AddKnownTypes(schema.GroupVersion{Group: "", Version: runtime.APIVersionInternal}, &Policy{})
    return nil
}
Войдите в полноэкранный режим Выход из полноэкранного режима

Примечания: Для генерации функций глубокого копирования и других файлов можно использовать скрипт kubernetes/hack/update-codegen.sh из репозитория kubernetes

Для удобства здесь используется метод framework.DecodeInto.

Развертывание проекта

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

apiVersion: kubescheduler.config.k8s.io/v1beta1
kind: KubeSchedulerConfiguration
clientConnection:
  kubeconfig: /mnt/d/src/go_work/customScheduler/scheduler.conf
profiles:
- schedulerName: custom-scheduler
  plugins:
    score:
      enabled:
      - name: "NetworkTraffic"
      disabled:
      - name: "*"
  pluginConfig:
    - name: "NetworkTraffic"
      args:
        ip: "http://10.0.0.4:9090"
        deviceName: "eth0"
        timeRange: 60
Войдите в полноэкранный режим Выход из полноэкранного режима

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

FROM golang:alpine AS builder
MAINTAINER cylon
WORKDIR /scheduler
COPY ./ /scheduler
ENV GOPROXY https://goproxy.cn,direct
RUN 
    sed -i 's/dl-cdn.alpinelinux.org/mirrors.ustc.edu.cn/g' /etc/apk/repositories && 
    apk add upx  && 
    GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -ldflags "-s -w" -o scheduler main.go && 
    upx -1 scheduler && 
    chmod +x scheduler

FROM alpine AS runner
WORKDIR /go/scheduler
COPY --from=builder /scheduler/scheduler .
COPY --from=builder /scheduler/scheduler.yaml /etc/
VOLUME ["./scheduler"]
Войдите в полноэкранный режим Выход из полноэкранного режима

Список ресурсов, необходимых для развертывания внутри кластера

apiVersion: v1
kind: ServiceAccount
metadata:
  name: scheduler-sa
  namespace: kube-system
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: scheduler
subjects:
  - kind: ServiceAccount
    name: scheduler-sa
    namespace: kube-system
roleRef:
  kind: ClusterRole
  name: system:kube-scheduler
  apiGroup: rbac.authorization.k8s.io
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: custom-scheduler
  namespace: kube-system
  labels:
    component: custom-scheduler
spec:
  selector:
    matchLabels:
      component: custom-scheduler
  template:
    metadata:
      labels:
        component: custom-scheduler
    spec:
      serviceAccountName: scheduler-sa
      priorityClassName: system-cluster-critical
      containers:
        - name: scheduler
          image: cylonchau/custom-scheduler:v0.0.1
          imagePullPolicy: IfNotPresent
          command:
            - ./scheduler
            - --config=/etc/scheduler.yaml
            - --v=3
          livenessProbe:
            httpGet:
              path: /healthz
              port: 10251
            initialDelaySeconds: 15
          readinessProbe:
            httpGet:
              path: /healthz
              port: 10251
Войдите в полноэкранный режим Выход из полноэкранного режима

Запустите пользовательский планировщик, здесь он запускается как простой двоичный файл, поэтому ему нужен файл аутентификации kubeconfig

./main --logtostderr=true 
    --address=127.0.0.1 
    --v=3 
    --config=`pwd`/scheduler.yaml 
    --kubeconfig=`pwd`/scheduler.conf
Войдите в полноэкранный режим Выход из полноэкранного режима

После запуска оригинальная служба kube-scheduler отключается для удобства, так как оригинальный kube-scheduler уже действует как master в HA, поэтому пользовательский планировщик не будет использоваться для вызова отложенного pod.

Проверка результатов

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

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
spec:
  selector:
    matchLabels:
      app: nginx
  replicas: 2 
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:1.14.2
        ports:
        - containerPort: 80
      schedulerName: custom-scheduler
Войдите в полноэкранный режим Выход из полноэкранного режима

Экспериментальная среда представляет собой 2-узловой кластер kubernetes, master и node01, поскольку master имеет больше сервисов, чем node01, и в этом случае планировщик всегда будет планироваться на node01, независимо ни от чего.

$ kubectl get pods -o wide
NAME                                READY   STATUS    RESTARTS   AGE   IP             NODE     NOMINATED NODE   READINESS GATES
nginx-deployment-69f76b454c-lpwbl   1/1     Running   0          43s   192.168.0.17   node01   <none>           <none>
nginx-deployment-69f76b454c-vsb7k   1/1     Running   0          43s   192.168.0.16   node01   <none>           <none>
Войдите в полноэкранный режим Выход из полноэкранного режима

Журнал планировщика выглядит следующим образом

I0808 01:56:31.098189   27131 networktraffic.go:83] [NetworkTraffic] node 'node01' bandwidth: %!s(int64=12541068340)
I0808 01:56:31.098461   27131 networktraffic.go:70] [NetworkTraffic] Nodes final score: [{master-machine 0} {node01 12541068340}]
I0808 01:56:31.098651   27131 networktraffic.go:70] [NetworkTraffic] Nodes final score: [{master-machine 0} {node01 71}]
I0808 01:56:31.098911   27131 networktraffic.go:73] [NetworkTraffic] Nodes final score: [{master-machine 0} {node01 71}]
I0808 01:56:31.099275   27131 default_binder.go:51] Attempting to bind default/nginx-deployment-69f76b454c-vsb7k to node01
I0808 01:56:31.101414   27131 eventhandlers.go:225] add event for scheduled pod default/nginx-deployment-69f76b454c-lpwbl
I0808 01:56:31.101414   27131 eventhandlers.go:205] delete event for unscheduled pod default/nginx-deployment-69f76b454c-lpwbl
I0808 01:56:31.103604   27131 scheduler.go:609] "Successfully bound pod to node" pod="default/nginx-deployment-69f76b454c-lpwbl" node="no
de01" evaluatedNodes=2 feasibleNodes=2
I0808 01:56:31.104540   27131 scheduler.go:609] "Successfully bound pod to node" pod="default/nginx-deployment-69f76b454c-vsb7k" node="no
de01" evaluatedNodes=2 feasibleNodes=2
Войдите в полноэкранный режим Выход из полноэкранного режима

Ссылка

[1] конфигурация планирования конфигурация планирования

[2] kube-scheduler kube-scheduler

[3] scheduling-plugins scheduling-plugins

[4] пользовательские плагины планировщика пользовательские плагины планировщика

[5] ssues #79384

[6] перфоманс-тюнинг планировщика

[7] создание плагина kube-scheduler

Оцените статью
devanswers.ru
Добавить комментарий