原文出处:改造 Kubernetes 自定义调度器 | Jayden's Blog (jaydenchang.top)

Overview

Kubernetes 默认调度器在调度 Pod 时并不关心特殊资源例如磁盘、GPU 等,因此突发奇想来改造调度器,在翻阅官方调度器框架[1]、调度器配置[2]和参考大佬的文章[3]后,自己也来尝试改写一下。

环境配置

相关软件版本:

  • Kubernetes 版本:v1.19.0
  • Docker 版本:v26.1.2
  • Prometheus 版本:v2.49
  • Node Exporter 版本:v1.7.0

集群内有 1 个 master 和 3 个 node。

实验部分

项目总览

项目结构如下:

.
├── Dockerfile
├── deployment.yaml
├── go.mod
├── go.sum
├── main.go
├── pkg
│   ├── cpu
│   │   └── cputraffic.go
│   ├── disk
│   │   └── disktraffic.go
│   ├── diskspace
│   │   └── diskspacetraffic.go
│   ├── memory
│   │   └── memorytraffic.go
│   ├── network
│   │   └── networktraffic.go
│   └── prometheus.go
├── scheduler
├── scheduler.conf
└── scheduler.yaml

插件部分

下面以构建内存插件为例。

定义插件名称、变量和结构体

const MemoryPlugin = "MemoryTraffic"
var _ = framework.ScorePlugin(&MemoryTraffic{}) type MemoryTraffic struct {
prometheus *pkg.PrometheusHandle
handle framework.FrameworkHandle
}

下面来实现 framework.FrameworkHandle 的接口。

先定义插件初始化入口

func New(plArgs runtime.Object, h framework.FrameworkHandle) (framework.Plugin, error) {
args := &MemoryTrafficArgs{}
if err := fruntime.DecodeInto(plArgs, args); err != nil {
return nil, err
} klog.Infof("[MemoryTraffic] args received. Device: %s; TimeRange: %d, Address: %s", args.DeviceName, args.TimeRange, args.IP) return &MemoryTraffic{
handle: h,
prometheus: pkg.NewProme(args.IP, args.DeviceName, time.Minute*time.Duration(args.TimeRange)),
}, nil
}

实现 Score 接口,Score 进行初步打分

func (n *MemoryTraffic) Score(ctx context.Context, state *framework.CycleState, p *corev1.Pod, nodeName string) (int64, *framework.Status) {
nodeBandwidth, err := n.prometheus.MemoryGetGauge(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("[MemoryTraffic] node '%s' bandwidth: %v", nodeName, bandWidth)
return bandWidth, nil
}

实现 NormalizeScore,对上一步 Score 的打分进行修正

func (n *MemoryTraffic) 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 = node.Score * 100 / higherScore
klog.Infof("[MemoryTraffic] Nodes final score: %v", scores[i].Score)
} klog.Infof("[MemoryTraffic] Nodes final score: %v", scores)
return nil
}

配置插件名称和返回 ScoreExtension

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

Prometheus 部分

首先来编写查询内存可用率的 PromQL

const memoryMeasureQueryTemplate = ` (avg_over_time(node_memory_MemAvailable_bytes[30m]) / avg_over_time(node_memory_MemTotal_bytes[30m])) * 100 * on(instance) group_left(nodename) (node_uname_info{nodename="%s"})`

然后来声明 PrometheusHandle

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

另外在插件部分也要声明查询 Prometheus 的参数结构体

type MemoryTrafficArgs struct {
IP string `json:"ip"`
DeviceName string `json:"deviceName"`
TimeRange int `json:"timeRange"`
}

编写初始化 Prometheus 插件入口

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

编写通用查询接口,可供其他类型资源查询

func (p *PrometheusHandle) query(promQL string) (model.Value, error) {
results, warnings, err := p.client.Query(context.Background(), promQL, time.Now())
if len(warnings) > 0 {
klog.Warningf("[Prometheus Query Plugin] Warnings: %v\n", warnings)
} return results, err
}

获取内存可用率接口

func (p *PrometheusHandle) MemoryGetGauge(node string) (*model.Sample, error) {
value, err := p.query(fmt.Sprintf(memoryMeasureQueryTemplate, node))
fmt.Println(fmt.Sprintf(memoryMeasureQueryTemplate, node))
if err != nil {
return nil, fmt.Errorf("[MemoryTraffic Plugin] Error querying prometheus: %w", err)
} nodeMeasure := value.(model.Vector)
if len(nodeMeasure) != 1 {
return nil, fmt.Errorf("[MemoryTraffic Plugin] Invalid response, expected 1 value, got %d", len(nodeMeasure))
}
return nodeMeasure[0], nil }

然后在程序入口里启用插件并执行

func main() {
rand.Seed(time.Now().UnixNano())
command := app.NewSchedulerCommand(
app.WithPlugin(network.NetworkPlugin, network.New),
app.WithPlugin(disk.DiskPlugin, disk.New),
app.WithPlugin(diskspace.DiskSpacePlugin, diskspace.New),
app.WithPlugin(cpu.CPUPlugin, cpu.New),
app.WithPlugin(memory.MemoryPlugin, memory.New),
)
// 对于外部注册一个plugin
// command := app.NewSchedulerCommand(
// app.WithPlugin("example-plugin1", ExamplePlugin1.New)) if err := command.Execute(); err != nil {
fmt.Fprintf(os.Stderr, "%v\n", err)
os.Exit(1)
}
}

配置部分

为方便观察,这里使用二进制方式运行,准备运行时的配置文件

apiVersion: kubescheduler.config.k8s.io/v1beta1
kind: KubeSchedulerConfiguration
clientConnection:
kubeconfig: /etc/kubernetes/scheduler.conf
profiles:
- schedulerName: custom-scheduler
plugins:
score:
enabled:
- name: "CPUTraffic"
weight: 3
- name: "MemoryTraffic"
weight: 4
- name: "DiskSpaceTraffic"
weight: 3
- name: "NetworkTraffic"
weight: 2
disabled:
- name: "*"
pluginConfig:
- name: "NetworkTraffic"
args:
ip: "http://172.19.32.140:9090"
deviceName: "eth0"
timeRange: 60
- name: "CPUTraffic"
args:
ip: "http://172.19.32.140:9090"
deviceName: "eth0"
timeRange: 0
- name: "MemoryTraffic"
args:
ip: "http://172.19.32.140:9090"
deviceName: "eth0"
timeRange: 0
- name: "DiskSpaceTraffic"
args:
ip: "http://172.19.32.140:9090"
deviceName: "eth0"
timeRange: 0

kubeconfig 处为 master 节点的 scheduler.conf,以实际路径为准,内包含集群的证书哈希,ip 为部署 Prometheus 节点的 ip,端口为 Promenade 配置中对外暴露的端口。

将二进制文件和 scheduler.yaml 放至 master 同一目录下运行:

./scheduler --logtostderr=true \
--address=127.0.0.1 \
--v=6 \
--config=`pwd`/scheduler.yaml \
--kubeconfig="/etc/kubernetes/scheduler.conf" \

验证结果

准备一个要部署的 Pod,使用指定的调度器名称

apiVersion: apps/v1
kind: Deployment
metadata:
name: gin
namespace: default
labels:
app: gin
spec:
replicas: 2
selector:
matchLabels:
app: gin
template:
metadata:
labels:
app: gin
spec:
schedulerName: my-custom-scheduler # 使用自定义调度器
containers:
- name: gin
image: jaydenchang/k8s_test:latest
imagePullPolicy: Always
command: ["./app"]
ports:
- containerPort: 9999
protocol: TCP

最后的可以查看日志,部分日志如下:

I0808 17:32:35.138289   27131 memorytraffic.go:83] [MemoryTraffic] node 'node1' bandwidth: %!s(int64=2680340)
I0808 17:32:35.138763 27131 memorytraffic.go:70] [MemoryTraffic] Nodes final score: [{node1 2680340} {node2 0}]
I0808 17:32:35.138851 27131 memorytraffic.go:70] [MemoryTraffic] Nodes final score: [{node1 71} {node2 0}]
I0808 17:32:35.138911 27131 memorytraffic.go:73] [MemoryTraffic] Nodes final score: [{node1 71} {node2 0}]
I0808 17:32:35.139565 27131 default_binder.go:51] Attempting to bind default/go-deployment-66878c4885-b4b7k to node1
I0808 17:32:35.141114 27131 eventhandlers.go:225] add event for scheduled pod default/go-deployment-66878c4885-b4b7k
I0808 17:32:35.141714 27131 eventhandlers.go:205] delete event for unscheduled pod default/go-deployment-66878c4885-b4b7k
I0808 17:32:35.143504 27131 scheduler.go:609] "Successfully bound pod to node" pod="default/go-deployment-66878c4885-b4b7k" node="no
de1" evaluatedNodes=2 feasibleNodes=2
I0808 17:32:35.104540 27131 scheduler.go:609] "Successfully bound pod to node" pod="default/go-deployment-66878c4885-b4b7k" node="no
de1" evaluatedNodes=2 feasibleNodes=2

参考链接


  1. Scheduling Framework | Kubernetes

  2. Scheduler Configuration | Kubernetes

  3. 基于Prometheus的Kubernetes网络调度器 | Cylon's Collection (oomkill.com)

改造 Kubernetes 自定义调度器的更多相关文章

  1. scrapy 基础组件专题(七):scrapy 调度器、调度器中间件、自定义调度器

    一.调度器 配置 SCHEDULER = 'scrapy.core.scheduler.Scheduler' #表示scrapy包下core文件夹scheduler文件Scheduler类# 可以通过 ...

  2. 第十四章 kubernetes 核心技术-调度器

    一.概述 一个容器平台的主要功能就是为容器分配运行时所需要的计算,存储和网络资源.容器调 度系统负责选择在最合适的主机上启动容器,并且将它们关联起来.它必须能够自动的处 理容器故障并且能够在更多的主机 ...

  3. TKE 用户故事 | 作业帮 Kubernetes 原生调度器优化实践

    作者 吕亚霖,2019年加入作业帮,作业帮架构研发负责人,在作业帮期间主导了云原生架构演进.推动实施容器化改造.服务治理.GO微服务框架.DevOps的落地实践. 简介 调度系统的本质是为计算服务/任 ...

  4. Kubernetes增强型调度器Volcano算法分析

    [摘要] Volcano 是基于 Kubernetes 的批处理系统,源自于华为云开源出来的.Volcano 方便 AI.大数据.基因.渲染等诸多行业通用计算框架接入,提供高性能任务调度引擎,高性能异 ...

  5. Kubernetes增强型调度器Volcano算法分析【华为云技术分享】

    [摘要] Volcano 是基于 Kubernetes 的批处理系统,源自于华为云开源出来的.Volcano 方便 AI.大数据.基因.渲染等诸多行业通用计算框架接入,提供高性能任务调度引擎,高性能异 ...

  6. Kubernetes之调度器和调度过程

    scheduler 当Scheduler通过API server 的watch接口监听到新建Pod副本的信息后,它会检查所有符合该Pod要求的Node列表,开始执行Pod调度逻辑.调度成功后将Pod绑 ...

  7. Kubernetes集群调度器原理剖析及思考

    简述 云环境或者计算仓库级别(将整个数据中心当做单个计算池)的集群管理系统通常会定义出工作负载的规范,并使用调度器将工作负载放置到集群恰当的位置.好的调度器可以让集群的工作处理更高效,同时提高资源利用 ...

  8. 第十五章 Kubernetes调度器

    一.简介 Scheduler 是 kubernetes 的调度器,主要的任务是把定义的 pod 分配到集群的节点上.听起来非常简单,但有很多要考虑的问题: ① 公平:如何保证每个节点都能被分配资源 ② ...

  9. Kubernetes K8S之调度器kube-scheduler详解

    Kubernetes K8S之调度器kube-scheduler概述与详解 kube-scheduler调度概述 在 Kubernetes 中,调度是指将 Pod 放置到合适的 Node 节点上,然后 ...

  10. 巧用Prometheus来扩展kubernetes调度器

    Overview 本文将深入讲解 如何扩展 Kubernetes scheduler 中各个扩展点如何使用,与扩展scheduler的原理,这些是作为扩展 scheduler 的所需的知识点.最后会完 ...

随机推荐

  1. 成为一名 BI数据分析师,这些能力不能少

    近些年来,随着数据技能的日益普及和数据工具的不断简化,大数据技术的迅速发展催生了很多新生职业,BI数据分析师就是其中一个岗位. 说到BI数据分析,我们首先要说的是 BI,它的全称是 Business ...

  2. 学习 XSLT:XML文档转换的关键

    XSL(eXtensible Stylesheet Language)是一种用于 XML 的样式语言. XSL(T) 语言 XSLT 是一种用于转换 XML 文档的语言. XPath 是一种用于在 X ...

  3. 容器开发运维人员的 Linux 操作机配置优化建议

    "工欲善其事必先利其器", 作为一个PAAS平台架构师, 容器相关技术(docker, k8s等)是必不可少的. 本文简单介绍下我自己的Linux操作机配置. 提升工作效率, 提高 ...

  4. 微信小程序为什么引入 rpx

    前言 众所周知,px 是一个叫做像素的东西,pixel. 像素是指由图像的小方格组成的,这些小方块都有一个明确的位置和被分配的色彩数值,小方格颜色和位置就决定该图像所呈现出来的样子. 可以将像素视为整 ...

  5. 重启React Native老项目的奇幻之旅:填坑实录与解决方案分享

    这两天为了重启五年前基于 React Native(版本 0.59.9)开发的老项目,经过各种填坑查询等操作,最终把它成功地运行起来了. 在这篇文章中,我将详述那些遭遇的挑战以及对应的解决方案,以期为 ...

  6. 【Oracle】Oracle数据库,第N大数据取值

    Oracle数据库,第N大数据取值 没想到力扣还有数据库的练习,正好本菜鸡跑过来练手 要显示第二大的数据可以使用order by进行排序,然后用limit对显示的数据进行限制,limit1,1,以此来 ...

  7. 入选 SIGMOD2021 的时间序列多周期检测通用框架 RobustPeriod 如何支撑阿里业务场景?

    简介: 本文除了介绍RobustPeriod的核心技术亮点,还将重点解释如何将它构筑成服务来解决阿里云的业务痛点. 近日,由阿里云计算平台和阿里云达摩院合作的时序多周期检测相关论文RobustPeri ...

  8. OpenKruise v0.9.0 版本发布:新增 Pod 重启、删除防护等重磅功能

    简介: OpenKruise 是阿里云开源的云原生应用自动化管理套件,也是当前托管在 Cloud Native Computing Foundation (CNCF) 下的 Sandbox 项目.它来 ...

  9. [Unreal] 虚拟现实理论与最佳实践

    优秀的虚拟现实场景所需要具备的特点: 真实感.沉浸感.舒适性.流畅性. VR的这些特点上有其它媒体所不具备的优势,主要通过硬件来实现. VR 的沉浸感让用户置身于周围世界,也引发了一些独特的症状: 眼 ...

  10. [ML] 可视化编写运行 Python 脚本的工具 Jupyter

    Jupyter 提供了可视化的编写和运行 python 程序的 Web 界面. https://jupyter.org/install 使用只需要两步: $ pip install jupyterla ...