根据是否编写代码,我们可以把自定义调度器的方式分为两种:
- 不写代码,调整组合已有的默认插件,从而定义新的调度器
- 实现接口代码,自定义开发调度器
本文将会描述第二种方式,编写一个 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
}
所以只要实现了 Name
、Score
、ScoreExtensions
这三个方法的类型就是一个 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