K8S二次开发调度器系列之四:自定义开发score调度器


根据是否编写代码,我们可以把自定义调度器的方式分为两种:

  • 不写代码,调整组合已有的默认插件,从而定义新的调度器
  • 实现接口代码,自定义开发调度器

本文将会描述第二种方式,编写一个 score 类型的调度器。

在 Score 类型插件运行时,每个节点经过所有 Score 插件后获得得分会累加,最后总得分就是该节点得分。

我们看下 Score 类型插件的定义:

type ScorePlugin interface {
    Plugin
    Score(ctx context.Context, state *CycleState, p *v1.Pod, nodeName string) (int64, *Status)
    ScoreExtensions() ScoreExtensions
}

type Plugin interface {
    Name() string
}

所以只要实现了 NameScoreScoreExtensions 这三个方法的类型就是一个 Score 类型的插件。

其中 Score 方法是关键,这个方法会对传入的 Pod 和 Node 计算这个 Node 在这个插件的得分,所以我们开发一个 Score 类型的插件主要就是实现 Score 方法。

Score 方法有两个返回值,第一个是一个 int64 类型的值,表示这个节点在这个插件的得分,如果插件运行失败,返回值为 0;第二个值为 Status 类型的值,如果插件运行成功这个返回值为 nil,否则返回非 nil Status 值,这个值里面包含了插件失败原因。

另外每个插件都会有一个权重(weight)的配置,但是权重这个配置对于像 PreFilter、Filter 等插件没有作用,所以就不会去配置,它对 Score 类型的插件起作用,如果你开发的插件没指定权重,那么 scheduler 框架会默认设置为1,而且如果你把权重设置为0,scheduler 框架也会把它修正到1(如果是 0 意味着这个插件被关闭不起作用)。

那么这个权重有什么作用呢?

比如在我们开发的插件中,满足某个条件可以得一分,如果加了这个权重,那么可以将得分乘以权重来提升得分(如果权重大于一,默认为1),这将意味着如果节点满足这个插件的条件将得到更多的分。

下面我们编写一个评分器,如果节点含有 label node=hello 则不得分,否则得 1 分。

编写自定义调度器

创建一个空白目录 scheduler/node-score-label:

mkdir scheduler/node-score-label

创建目录 manifests 用于存放 KubeSchedulerConfiguration ,创建目录 plugins 存放插件代码,最终目录结构如下:

tree scheduler/
scheduler/
├── go.mod
├── go.sum
├── node-filter-label
│   ├── main.go
│   ├── manifests
│   │   └── nodelabelfilter.yaml
│   └── plugins
│       └── node_filter_label.go
└── node-score-label
    ├── main.go
    ├── manifests
    │   └── nodelabelfilter.yaml
    └── plugins
        └── node_score_label.go

编写 node_score_label.go

package plugins

import (
	"context"
	"fmt"

	v1 "k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/klog/v2"
	"k8s.io/kubernetes/pkg/scheduler/framework"
	"k8s.io/kubernetes/pkg/scheduler/framework/plugins/helper"
)

const SchedulerName = "NodeScoreLabel"

type NodeAge struct {
	handle framework.Handle
}

func (pl *NodeAge) Name() string {
	return SchedulerName
}

func (pl *NodeAge) 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.AsStatus(fmt.Errorf("getting node %s from Snapshot error: %w", nodeName, err))
	}
	score := 0
	found := false
	for k, v := range nodeInfo.Node().ObjectMeta.Labels {
		if k == "node" && v == "hello" {
			found = true
			break
		}
	}
	if found {
		klog.Infof("node=%s has label node=hello so it can't get score, pod_name=%s", nodeInfo.Node().Name, pod.Name)
	} else {
		klog.Infof("node=%s does'nt has label node=hello so it get score, pod_name=%s", nodeInfo.Node().Name, pod.Name)
		score += 1
	}
	return int64(score), nil
}

func (pl *NodeAge) ScoreExtensions() framework.ScoreExtensions {
	return pl
}

func (pl *NodeAge) NormalizeScore(ctx context.Context, state *framework.CycleState, pod *v1.Pod, scores framework.NodeScoreList) *framework.Status {
	return helper.DefaultNormalizeScore(framework.MaxNodeScore, false, scores)
}

func New(_ context.Context, _ runtime.Object, h framework.Handle) (framework.Plugin, error) {
	return &NodeAge{
		handle: h,
	}, nil
}

编写 main.go

package main

import (
	"os"

	"myscheduler/node-score-label/plugins"

	"k8s.io/component-base/cli"
	"k8s.io/component-base/logs"
	_ "k8s.io/component-base/metrics/prometheus/clientgo"
	_ "k8s.io/component-base/metrics/prometheus/version" // for version metric registration
	"k8s.io/kubernetes/cmd/kube-scheduler/app"
)

func main() {
	command := app.NewSchedulerCommand(app.WithPlugin(plugins.SchedulerName, plugins.New))
	logs.InitLogs()
	defer logs.FlushLogs()

	code := cli.Run(command)
	os.Exit(code)
}

编写 KubeSchedulerConfiguration

apiVersion: kubescheduler.config.k8s.io/v1
kind: KubeSchedulerConfiguration
clientConnection:
  kubeconfig: "/etc/kubernetes/scheduler.conf"
profiles:
- schedulerName: nodelabelscore
  plugins:
    score:
      enabled:
      - name: NodeScoreLabel
      disabled:
      - name: "*"

编译运行

go build -o nodelabelscore main.go
./nodelabelscore --leader-elect=false --config nodelabelscore.yaml 
I1117 12:09:37.156920   67619 serving.go:380] Generated self-signed cert in-memory
W1117 12:09:37.348447   67619 authentication.go:339] No authentication-kubeconfig provided in order to lookup client-ca-file in configmap/extension-apiserver-authentication in kube-system, so client certificate authentication won't work.
W1117 12:09:37.349091   67619 authentication.go:363] No authentication-kubeconfig provided in order to lookup requestheader-client-ca-file in configmap/extension-apiserver-authentication in kube-system, so request-header client certificate authentication won't work.
W1117 12:09:37.349439   67619 authorization.go:193] No authorization-kubeconfig provided, so SubjectAccessReview of authorization tokens won't work.
I1117 12:09:37.369622   67619 server.go:154] "Starting Kubernetes Scheduler" version="v0.0.0-master+$Format:%H$"
I1117 12:09:37.370145   67619 server.go:156] "Golang settings" GOGC="" GOMAXPROCS="" GOTRACEBACK=""
I1117 12:09:37.371931   67619 secure_serving.go:213] Serving securely on [::]:10259
I1117 12:09:37.372061   67619 tlsconfig.go:240] "Starting DynamicServingCertificateController"

测试

创建一个 Pod,观察调度情况:

apiVersion: v1
kind: Pod
metadata:
  name: nginx-labelscore
spec:
  schedulerName: nodelabelscore
  containers:
  - image: registry.cn-beijing.aliyuncs.com/fpf_devops/nginx:1.24
    name: nginx

查看自定义插件的日志:

I1117 12:58:27.982897   78682 node_score_label.go:40] node=k8s-node1 does'nt has label node=hello so it get score, pod_name=nginx-labelscore
I1117 12:58:27.982909   78682 node_score_label.go:38] node=k8s-node2 has label node=hello so it can't get score, pod_name=nginx-labelscore

文章作者: 洪宇轩
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 洪宇轩 !
评论
  目录