LOADING

加载过慢请开启缓存 浏览器默认开启

Kubernetes二次开发——使用extender扩展调度器

k8s 自定义调度器逻辑之 Scheduler Extender

实现一个读取Metrics-Server指标打分的简单Scheduler Extender。

功能如下:

  • 1)过滤阶段:仅调度到带有 Label filter.xy.com 的节点上
  • 2)打分阶段:读取Metrics-Server指标,CPU利用率最低的节点为优

Scheduler Extender 规范

Scheduler Extender 通过 HTTP 请求的方式,将调度框架阶段中的调度决策委托给外部的调度器,然后将调度结果返回给调度框架。

我们只需要实现一个 HTTP 服务,然后通过配置文件将其注册到调度器中,就可以实现自定义调度器。

通过 Scheduler Extender 扩展原有调度器一般分为以下两步:

  • 1)创建一个 HTTP 服务,实现对应接口
  • 2)修改调度器配置 KubeSchedulerConfiguration,增加 extenders 相关配置

外置调度器可以影响到三个阶段:

  • Filter:调度框架将调用 Filter 函数,过滤掉不适合被调度的节点。

  • Priority:调度框架将调用 Priority 函数,为每个节点计算一个优先级,优先级越高,节点越适合被调度。

  • Bind:调度框架将调用 Bind 函数,将 Pod 绑定到一个节点上。

Extender是外部服务,支持Filter、Preempt、Prioritize和Bind的扩展,scheduler运行到相应阶段时,通过调用Extender注册的webhook来运行扩展的逻辑,影响调度流程中各阶段的决策结果。
分别对应四个 POST-HTTP 接口,各自的请求、响应结构定义在这里:#kubernetes/kube-scheduler/extender/v1/types.go

在这个 HTTP 服务中,我们可以实现上述阶段中的任意一个或多个阶段的接口,来定制我们的调度需求。

代码实现

main.go

  • 使用go-restful框架搭建一个http服务,绑定路由及端口
func main() {
	klog.Infof("regist pprof handler")
	server.RegistPPROF()

	klog.Infof("regist schedule extender handler")
	ws := &restful.WebService{}
	ws.Path(apiPrefix)
	// 预选过滤接口
	ws.Route(ws.POST(filterPath).To(h.Filter))
	// 优选打分接口
	ws.Route(ws.POST(prioritizePath).To(h.Prioritize))
	ws.Route(ws.POST(preemptionPath).To(h.Preempt))
	// 当核心 scheduler 调度器确定 Node 与 Pod 时的回调接口,
	// 核心调度器会把即将绑定的一对 Pod 与 Node 发送到这个接口.
	ws.Route(ws.POST(bindPath).To(h.Bind))
	restful.Add(ws)

	klog.Infof("start listening")
	err := http.ListenAndServe(":8001", nil)
	if err != nil {
		return
	}
}

handler.go

  • 处理来自 Kubernetes 调度器扩展器的 HTTP 请求,类似于Java的Controller。具体功能如下:
  • Preempt:处理抢占请求,解析请求体并调用扩展器的 Preempt 方法。
  • Filter:处理过滤请求,解析请求体并调用扩展器的 Filter 方法。
  • Prioritize:处理优先级请求,解析请求体并调用扩展器的 Prioritize 方法。
  • Bind:处理绑定请求,解析请求体并调用扩展器的 Bind 方法。

extender.go

  • 用于创建与 Kubernetes API 服务器的连接,并初始化扩展器。具体功能如下:
  • 常量和结构体: Label:定义了一个标签键,用于后续过滤节点。 Extender:包含一个 kubernetes.Interface 类型的字段 ClientSet,用于与 Kubernetes API 交互。
  • NewExtender 方法:
    创建一个新的 Extender 实例。
    调用 NewClient 方法来获取 Kubernetes 客户端集(ClientSet),并将其赋值给 Extender 的 ClientSet 字段。
    如果创建客户端失败,则记录错误信息。
  • NewClient 方法:
    尝试从集群内部配置或外部配置文件中加载 Kubernetes API 服务器的配置。
    优先使用环境变量 KUBECONFIG 指定的路径,如果未设置,则默认使用用户主目录下的 .kube/config 文件。
    使用 rest.InClusterConfig 尝试从集群内部获取配置,如果失败则使用 clientcmd.BuildConfigFromFlags 从外部配置文件构建配置。
    使用配置创建 Kubernetes 客户端集并返回。

filter.go

  • 用于过滤掉不满足条件的节点,并返回满足条件的节点列表。具体功能如下:
  • 接收 ExtenderArgs 参数,包含待过滤的节点和 Pod。
  • 遍历所有节点,检查每个节点是否包含指定标签(Label),排除不带该标签的节点。
  • 如果没有节点满足条件,则返回错误信息。
  • 返回过滤后的节点列表,根据 NodeCacheCapable 的配置,使用 Nodes 或 NodeNames 字段。
func (ex *Extender) Filter(args extenderv1.ExtenderArgs) *extenderv1.ExtenderFilterResult {
// 过滤掉不满足条件的节点
nodes := make([]v1.Node, 0)
nodeNames := make([]string, 0)

	for _, node := range args.Nodes.Items {
		_, ok := node.Labels[Label]
		klog.Infof("Node[ %s ] Special Label Value: %t", node.Name, ok)
		if !ok { // 排除掉不带指定标签的节点
			continue
		}
		nodes = append(nodes, node)
		nodeNames = append(nodeNames, node.Name)
	}

	// 没有满足条件的节点就报错
	if len(nodes) == 0 {
		return &extenderv1.ExtenderFilterResult{Error: fmt.Errorf("all node do not have label %s", Label).Error()}
	}

	args.Nodes.Items = nodes
	return &extenderv1.ExtenderFilterResult{
		Nodes:     args.Nodes, // 当 NodeCacheCapable 设置为 false 时会使用这个值
		NodeNames: &nodeNames, // 当 NodeCacheCapable 设置为 true 时会使用这个值
	}

prioritize.go

  • 用于为每个节点上的 Pod 打分。具体功能如下:

  • 初始化 Kubernetes 客户端

  • 调用 HandlerMetricsNodeUsage 方法获取所有节点的 CPU 使用情况。

  • 计算最低 CPU 使用量:
    遍历所有节点,找到 CPU 使用量最低的值 minCpuUsage,作为打分的基础。

  • 计算每个节点的得分:
    再次遍历所有节点,根据每个节点的 CPU 使用量计算得分。得分规则是:CPU 使用量越低,得分越高(score = minCpuUsage - cpuUsage + 1)。

  • 返回包含所有节点及其得分的 HostPriorityList。

其他代码没有特殊逻辑,不做赘述

编译部署

192.168.112.150:5001为本地Harbor仓库

make build-image
#或者直接docker build -t 192.168.112.150:5001/library/kube-metrics-scheduler-extender:v0.0.1 . 

部署到集群

kubectl apply -f deploy/manifest.yaml

确认服务正常运行

[root@master scheduler-dev]# kubectl -n kube-system get po|grep scheduler-extender
kube-metrics-scheduler-extender-7b7cf4f9c8-7rk9s   2/2     Running       0               116s

查看extender日志

kubectl logs -f --tail 500 kube-metrics-scheduler-extender-7b7cf4f9c8-jg8zs -c kube-metrics-scheduler-extender -n kube-system

测试

启动测试 Pod

创建一个 Deployment 并指定使用上一步中部署的 Scheduler,然后测试会调度到哪个节点上。

root@k8s-master:/home/xiaoyao/kube/xy-github/kube-metrics-scheduler-extender/deploy# kubectl apply -f deploy-test.yaml 
deployment.apps/test created
root@k8s-master:/home/xiaoyao/kube/xy-github/kube-metrics-scheduler-extender/deploy# kubectl get po
NAME                    READY   STATUS    RESTARTS   AGE
test-8666bdf76f-ktd8g   0/1     Pending   0          6s
# 查看pod Events,没有符合带有label的节点,pod将不被调度
Events:
  Type     Reason            Age    From                             Message
  ----     ------            ----   ----                             -------
  Warning  FailedScheduling  6m19s  kube-metrics-scheduler-extender  Post "http://kube-metrics-scheduler-extender.kube-system.svc:8001/scheduler/filter": EOF
  Warning  FailedScheduling  76s    kube-metrics-scheduler-extender  Post "http://kube-metrics-scheduler-extender.kube-system.svc:8001/scheduler/filter": EOF
  Warning  FailedScheduling  51s    kube-metrics-scheduler-extender  Post "http://kube-metrics-scheduler-extender.kube-system.svc:8001/scheduler/filter": context deadline exceeded (Client.Timeout exceeded while awaiting headers)
  Warning  FailedScheduling  50s    kube-metrics-scheduler-extender  all node do not have label filter.xy.com
# 给节点添加标签,pod将能成功调度,因集群节点只有一个节点满足filter,故不走prioritize
kubectl  label node k8s-node1 filter.xy.com=true
# 若给其他节点也打上标签 会走prioritize
kubectl  label node k8s-node2 filter.xy.com=true
# 此时会发现虽然可以正常调度,但extender会报错,因为集群没有安装
E0214 06:46:25.480282       1 client.go:61] the server could not find the requested resource (get nodes.metrics.k8s.io)
E0214 06:46:25.480355       1 prioritize.go:18] Failed to get node metrics: the server could not find the requested resource (get nodes.metrics.k8s.io)
# 通过以下命令安装metrics server,具体请参考文档https://blog.csdn.net/u011837804/article/details/128487211
kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml
# 删除pod后重新调度,查看extender日志,发现成功获取metrics指标
I0214 07:29:54.650718       1 prioritize.go:35] Node [ k8s-master ] Cpu Usage Value: 264, Score: -196
I0214 07:29:54.650725       1 prioritize.go:35] Node [ k8s-node1 ] Cpu Usage Value: 87, Score: -19
I0214 07:29:54.650728       1 prioritize.go:35] Node [ k8s-node2 ] Cpu Usage Value: 67, Score: 1

完整代码