- Обзор
- конфигурация планирования kubernetes
- Конфигурация планировщика [1] The
- Использование kubeSchedulerConfiguration
- планировщик плагин планирования [3]
- Как расширить kube-scheduler [4]
- Определение входа
- Реализация плагина
- Определите структуру плагина
- Реализуйте соответствующую точку расширения
- Эксперимент: составление расписания на основе сетевого трафика [7]
- Экспериментальная среда
- Начало эксперимента
- Обработка ошибок
- Определение API плагина
- Определение расширений
- Определите параметры, которые будут переданы в
- Настройка параметров планировщика
- Развертывание проекта
- Проверка результатов
Обзор
В этой статье будет дано подробное объяснение того, как масштабировать планировщик 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