Kubernetes自定义调度器 — 初识调度框架

Description Kubernetes 已经成为容器编排(Orchestration)平台的事实标准,它为容器化应用提供了简单且高效部署的方式、大规模可伸缩、资源调度等生命周期管理功能。kube-scheduler作为kubernetes的核心组件,它负责整个集群资源的调度功能,根据特定的调度算法或调度策略,将Pod调度到最优的Node节点,使集群的资源得到合理且充分的利用。

一般情况下,kube-scheduler提供的默认算法或编排的策略能够满足我们绝大多数的要求,但是在实际的项目中,需要考虑的问题就有很多了,我们会比kubernetes更加了解我们自己的应用,比如kubernetes无法对GPU资源调度的,这就需要我们来扩展调度器功能。

自定义调度器


纵观Kubernetes的发展历程,经过近几年的快速发展,在稳定性、扩展性和规模化方面都有了长足进步,核心组件日臻成熟。但实际上社区对于kube-scheduler的整体扩展规划设计是近两年才开始的。早期提升调度器扩展能力的方式主要有两种,一种是调度器扩展(Scheduler Extender), 另外一种是多调度器(Multiple schedulers)。

不管是Scheduler Extender还是Multiple schedulers,在性能和灵活性方面都有很大的问题,为了解决该困境,社区从Kubernetes 1.15版本开始, 作为alpha功能加入了新的调度框架 Kubernetes Scheduling Framework机制,可插拔架构使Kube-scheduler 扩展性更好、代码更简洁。

在进入Framework之前,我们再来了解下Kubernetes调度程序大概过程:

  1. Kube-Controller-Manager将spec.nodeName为空的Pod加入待调度的Pod列表
  2. Kube-scheduler watch Kube-apiserver,从调度队列中Pop出一个Pod,开始一个标准的调度周期
  3. 经过过滤阶段,根据Pod的属性(如:CPU/内存,nodeSelector/nodeAffinity),在该阶段计算出满足要求的节点候选列表
  4. 然后打分阶段,为每个候选节点给出一个分数,并挑选出得分最高的节点
  5. 最后kube-scheduler向kube-apiserver发送绑定调用,设置Pod的spec.nodeName属性以表示将该Pod调度到的节点
  6. kubelet通过kube-apiserver监听到kube-scheduler产生的绑定信息,获得Pod列表,下载Image并启动容器,然后由kubelet负责拉起Pod

Scheduling Framework调度框架


Scheduling Framework在原有的调度流程中, 定义了丰富扩展点接口,用户可以实现扩展点定义的接口来定义自己的调度逻辑,并将扩展注册到扩展点上,调度框架在执行调度工作流时,运行到相应的扩展点时,会调用用户注册的插件。

调度框架定义了一些扩展点,有些扩展点上的扩展可以更改调度程序的决策,有些扩展点上的扩展只是发送一个通知。每次调度一个Pod的过程分为两个阶段:调度周期(Scheduling Cycle)和绑定周期(Binding Cycle)。

调度周期为Pod选择一个节点,绑定周期将该决定应用于集群。调度周期和绑定周期一起被称为"调度上下文"(scheduling context)。调度过程和绑定过程遇到该Pod不可调度或存在内部错误,则中止调度或绑定周期,该Pod将返回队列并重试。

下图展示了调度框架中的调度上下文及其中的扩展点,一个扩展可以注册多个扩展点,以便可以执行更复杂的有状态的任务 Description

scheduling cycle 是同步执行的,同一个时间只有一个 scheduling cycle,是线程安全的; binding cycle 是异步执行的,同一个时间中可能会有多个 binding cycle在运行,是线程不安全的。

调度周期(Scheduling Cycle)

QueueSort 对调度队列中的Pod进行排序,以决定先调度哪个Pod,本质上提供了一个Less(Pod1, Pod2)功能用于比较两个Pod谁更优先获得调度,同一时间点只能有一个QueueSort插件生效。

PreFilter 对Pod的信息进行预处理,或者检查一些集群或Pod必须满足的前提条件,只有当所有的PreFilter插件都返回success时,才能进入下一个阶段,如果返回了error,则调度过程终止。

Filter 用来过滤掉不满足Pod调度要求的节点,对于每一个节点,调度器将按顺序执行Filter扩展。为了提升效率,Filter的执行顺序可以被配置,以减少Filter策略执行的次数,如可以把NodeSelector的Filter放到第一个,从而过滤掉大量的节点。Node节点执行Filter策略是并发执行的,所以在同一调度周期中多次调用过滤器。

PostFilter 在1.19版本发布,主要是用于处理当找不到该Pod的可行节点时才调用,例如试图通过抢占其他Pod来使该Pod可调度。,Autoscale触发等行为。

PreScore 在1.19之前版本称为PostFilter,主要用于在Score之前进行一些信息生成即预评分,从而为Score插件使用提供可共享的状态。如果PreScore插件返回错误,则调度周期将中止。

Score 用于对已通过过滤阶段的节点进行打分,调度器将针对每一个节点调用Score扩展,评分结果是一个范围内的整数。在NormalizeScore阶段之后,调度器将会把每个Score扩展对具体某个节点的评分结果和该扩展的权重合并起来,作为最终评分结果。

NormalizeScore 用于对节点进行最终排序之前修改每个节点的评分分数,注册到该扩展点的扩展在被调用时,将获得同一个插件中的Score扩展的评分结果作为参数,调度框架每执行一次调度,都将调用所有插件中的一个NormalizeScore扩展一次。

Reserve 用于避免调度器在等待Pod与节点绑定的过程中调度新的Pod到节点上时,发生实际使用资源超出可用资源的情况,因为绑定Pod到节点上是异步发生的。实现Reserve扩展的插件有两种方法,分别是Reserve和Unreserve,每个Reserve插件的Reserve方法可能成功也可能失败;如果一个Reserve方法调用失败,则不会执行后续插件,并且认为Reserve阶段失败,触发Unreserve扩展。

Permit 用于阻止或者延迟Pod与节点的绑定。可以执行三种操作:

approve(批准):所有Permit插件批准Pod后,将其发送进行绑定。

deny(拒绝):如果任何Permit插件拒绝Pod,则将其返回到调度队列,触发Reserve插件中的Unreserve扩展。

wait(等待):如果一个permit扩展返回了wait,则Pod将保持在permit阶段,如超时,wait状态变成deny,Pod 将被放回到待调度队列,触发Reserve插件中的Unreserve扩展。

绑定周期(Binding Cycle)

PreBind 用于在Pod绑定之前执行某些逻辑,如将数据卷挂载到节点上,然后再允许Pod在此处运行。如插件返回错误,则Pod被拒绝并返回到调度队列。

Bind 用于将Pod绑定到节点。在所有PreBind插件完成之前,不会调用Bind插件。调度框架按照Bind扩展注册的顺序逐个调用Bind扩展,具体某个Bind扩展可以选择处理或者不处理该Pod,如果某个Bind扩展处理了该Pod与节点的绑定,余下的Bind扩展将被忽略。

PostBind 这是一个信息扩展点。成功绑定Pod后,将调用后PostBind插件。Bind周期到此结束,可以用来清理关联的资源。

实现自定义的调度插件


sig-scheduling小组为了更好的管理调度相关的Plugin,新建scheduler-plugins项目,用户可以直接基于这个项目来定义自己的插件。 https://github.com/kubernetes-sigs/scheduler-plugins 接下来我们以社区示例的Qos的插件来为例,简单开发一个Filter、PreScore的插件。

定义插件的对象和构造函数:

1// Name is the name of the plugin used in the plugin registry and configurations. 2const Name = "sample" 3 4// Sort is a plugin that implements QoS class based sorting. 5type sample struct{} 6 7var _ framework.FilterPlugin = &sample{} 8var _ framework.PreScorePlugin = &sample{} 9 10// New initializes a new plugin and returns it. 11func New(_ runtime.Object, _ framework.FrameworkHandle) (framework.Plugin, error) { 12 return &sample{}, nil 13}

实现对应插件的接口:

1 2// Name returns name of the plugin. 3func (pl *sample) Name() string { 4 return Name 5} 6func (pl *sample) Filter(ctx context.Context, state *framework.CycleState, pod *v1.Pod, nodeInfo *framework.NodeInfo) *framework.Status { 7 log.Printf("filter pod: %v, node: %v", pod.Name, nodeInfo) 8 log.Println(state) 9 // 排除没有cpu=true标签的节点 10 if nodeInfo.Node().Labels["cpu"] != "true" { 11 return framework.NewStatus(framework.Unschedulable, "Node: "+nodeInfo.Node().Name) 12 } 13 return framework.NewStatus(framework.Success, "Node: "+nodeInfo.Node().Name) 14} 15func (pl *sample)PreScore(ctx context.Context, state *framework.CycleState, pod *v1.Pod, nodes []*v1.Node) *framework.Status { 16 log.Println(nodes) 17 return framework.NewStatus(framework.Success, "Node: "+pod.Name) 18}

插件注册:

1func main() { 2 rand.Seed(time.Now().UnixNano()) 3 // Register custom plugins to the scheduler framework. 4 // Later they can consist of scheduler profile(s) and hence 5 // used by various kinds of workloads. 6 command := app.NewSchedulerCommand( 7 app.WithPlugin(plugins.Name, plugins.New), 8 ) 9 if err := command.Execute(); err != nil { 10 os.Exit(1) 11 } 12}

把编译源码并打包成容器镜像

1FROM debian:stretch-slim 2WORKDIR / 3COPY app /usr/local/bin 4CMD ["app"]

在kubernetes安装插件

1kind: ClusterRole 2apiVersion: rbac.authorization.k8s.io/v1 3metadata: 4name: sample-scheduler-clusterrole 5rules: 6- apiGroups: 7 - "" 8 resources: 9 - endpoints 10 - events 11 verbs: 12 - create 13 - get 14 - update 15- apiGroups: 16 - "" 17 resources: 18 - nodes 19 verbs: 20 - get 21 - list 22 - watch 23- apiGroups: 24 - "" 25 resources: 26 - pods 27 verbs: 28 - delete 29 - get 30 - list 31 - watch 32 - update 33- apiGroups: 34 - "" 35 resources: 36 - bindings 37 - pods/binding 38 verbs: 39 - create 40- apiGroups: 41 - "" 42 resources: 43 - pods/status 44 verbs: 45 - patch 46 - update 47- apiGroups: 48 - "" 49 resources: 50 - replicationcontrollers 51 - services 52 verbs: 53 - get 54 - list 55 - watch 56- apiGroups: 57 - apps 58 - extensions 59 resources: 60 - replicasets 61 verbs: 62 - get 63 - list 64 - watch 65- apiGroups: 66 - apps 67 resources: 68 - statefulsets 69 verbs: 70 - get 71 - list 72 - watch 73- apiGroups: 74 - policy 75 resources: 76 - poddisruptionbudgets 77 verbs: 78 - get 79 - list 80 - watch 81- apiGroups: 82 - "" 83 resources: 84 - persistentvolumeclaims 85 - persistentvolumes 86 verbs: 87 - get 88 - list 89 - watch 90- apiGroups: 91 - "" 92 resources: 93 - configmaps 94 verbs: 95 - get 96 - list 97 - watch 98- apiGroups: 99 - "storage.k8s.io" 100 resources: 101 - storageclasses 102 - csinodes 103 verbs: 104 - get 105 - list 106 - watch 107- apiGroups: 108 - "coordination.k8s.io" 109 resources: 110 - leases 111 verbs: 112 - create 113 - get 114 - list 115 - update 116- apiGroups: 117 - "events.k8s.io" 118 resources: 119 - events 120 verbs: 121 - create 122 - patch 123 - update 124--- 125apiVersion: v1 126kind: ServiceAccount 127metadata: 128name: sample-scheduler-sa 129namespace: kube-system 130--- 131kind: ClusterRoleBinding 132apiVersion: rbac.authorization.k8s.io/v1 133metadata: 134name: sample-scheduler-clusterrolebinding 135namespace: kube-system 136roleRef: 137apiGroup: rbac.authorization.k8s.io 138kind: ClusterRole 139name: sample-scheduler-clusterrole 140subjects: 141- kind: ServiceAccount 142name: sample-scheduler-sa 143namespace: kube-system 144--- 145apiVersion: v1 146kind: ConfigMap 147metadata: 148name: scheduler-config 149namespace: kube-system 150data: 151scheduler-config.yaml: | 152 apiVersion: kubescheduler.config.k8s.io/v1beta1 153 kind: KubeSchedulerConfiguration 154 leaderElection: 155 leaderElect: false 156 clientConnection: 157 kubeconfig: /kube-scheduler.kubeconfig 158 profiles: 159 - schedulerName: sample-scheduler 160 plugins: 161 filter: 162 enabled: 163 - name: sample 164 preScore: 165 enabled: 166 - name: sample 167 disabled: 168 - name: "*" 169--- 170apiVersion: apps/v1 171kind: Deployment 172metadata: 173name: sample-scheduler 174namespace: kube-system 175labels: 176 component: sample-scheduler 177spec: 178replicas: 1 179selector: 180 matchLabels: 181 component: sample-scheduler 182template: 183 metadata: 184 labels: 185 component: sample-scheduler 186 spec: 187 serviceAccount: sample-scheduler-sa 188 priorityClassName: system-cluster-critical 189 volumes: 190 - name: scheduler-config 191 configMap: 192 name: scheduler-config 193 containers: 194 - name: scheduler-ctrl 195 image: sample-scheduler:stretch-slim 196 imagePullPolicy: IfNotPresent 197 args: 198 - --config=/etc/kubernetes/scheduler-config.yaml 199 - --v=3 200 resources: 201 requests: 202 cpu: "50m" 203 volumeMounts: 204 - name: scheduler-config 205 mountPath: /etc/kubernetes

运行日志: Description

验证插件


测试的pod,schedulerName字段指定自定义调度器

1apiVersion: apps/v1 2kind: Deployment 3metadata: 4name: test-scheduler 5spec: 6replicas: 1 7selector: 8 matchLabels: 9 app: test-scheduler 10template: 11 metadata: 12 labels: 13 app: test-scheduler 14 spec: 15 schedulerName: sample-scheduler 16 containers: 17 - image: docker.io/library/nginx:1.19.2-alpine 18 imagePullPolicy: IfNotPresent 19 name: nginx 20 ports: 21 - containerPort: 80

创建测试pod

1 2root@ubuntu:~# kubectl create -f test-deploy.yaml 3deployment.apps/test-scheduler created 4root@ubuntu:~# kubectl get po 5NAME READY STATUS RESTARTS AGE 6test-scheduler-6645fd9f6-pfzkm 0/1 Pending 0 4s 7root@ubuntu:~# kubectl describe pod test-scheduler-6645fd9f6-pfzkm 8Name: test-scheduler-6645fd9f6-pfzkm 9Namespace: default 10Priority: 0 11Node: <none> 12Labels: app=test-scheduler 13 pod-template-hash=6645fd9f6 14Annotations: <none> 15Status: Pending 16IP: 17IPs: <none> 18Controlled By: ReplicaSet/test-scheduler-6645fd9f6 19Containers: 20 nginx: 21 Image: docker.io/library/nginx:1.19.2-alpine 22 Port: 80/TCP 23 Host Port: 0/TCP 24 Environment: <none> 25 Mounts: 26 /var/run/secrets/kubernetes.io/serviceaccount from default-token-jbdqf (ro) 27Conditions: 28 Type Status 29 PodScheduled False 30Volumes: 31 default-token-jbdqf: 32 Type: Secret (a volume populated by a Secret) 33 SecretName: default-token-jbdqf 34 Optional: false 35QoS Class: BestEffort 36Node-Selectors: <none> 37Tolerations: node.kubernetes.io/not-ready:NoExecute op=Exists for 360s 38 node.kubernetes.io/unreachable:NoExecute op=Exists for 360s 39Events: 40 Type Reason Age From Message 41 ---- ------ ---- ---- ------- 42 Warning FailedScheduling 36s 0/1 nodes are available: 1 Node: kube-master. 43 Warning FailedScheduling 36s 0/1 nodes are available: 1 Node: kube-master.

因为排除没有 cpu=true 标签的节点,故pod处于Pending状态,日志也会打印信息

1I1011 15:14:35.015568 466 factory.go:445] "Unable to schedule pod; no fit; waiting" pod="default/test-scheduler-6645fd9f6-pfzkm" err="0/1 nodes are available: 1 Node: kube-master."

给节点增加标签

1 2root@ubuntu:~# kubectl label nodes kube-master cpu=true 3node/kube-master labeled 4root@ubuntu:~# kubectl get nodes --show-labels 5NAME STATUS ROLES AGE VERSION LABELS 6kube-master Ready <none> 30d v1.19.1 beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,cpu=true,kubernetes.io/arch=amd64,kubernetes.io/hostname=kube-master,kubernetes.io/os=linux 7root@ubuntu:~# kubectl get po 8NAME READY STATUS RESTARTS AGE 9test-scheduler-6645fd9f6-pfzkm 1/1 Running 0 3m16s

Pod已经正常调度,处于running状态,同时自定义插件也输出对应的日志

1I1011 15:17:45.635502 466 default_binder.go:51] Attempting to bind default/test-scheduler-6645fd9f6-pfzkm to kube-master 2I1011 15:17:45.643123 466 eventhandlers.go:205] delete event for unscheduled pod default/test-scheduler-6645fd9f6-pfzkm 3I1011 15:17:45.643204 466 eventhandlers.go:225] add event for scheduled pod default/test-scheduler-6645fd9f6-pfzkm 4I1011 15:17:45.645886 466 scheduler.go:597] "Successfully bound pod to node" pod="default/test-scheduler-6645fd9f6-pfzkm" node="kube-master" evaluatedNodes=1 feasibleNodes=1

小结


最近一直在研究Scheduling Framework框架,发现还是挺有意思的,后续有时间再更新吧。


感兴趣的读者可以关注下微信号 Description

点赞
收藏

评论区

加载中...

相关推荐

MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1

文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s

Oracle 分组与拼接字符串同时使用

SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(

手写Java HashMap源码

HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22

Kubernetes自定义调度器 — 初窥门径

通过上一篇文章对schedulerframework调度框架已经有了大致了解,根据我们的实际生产的一些问题(如计算服务没有被调度到实际CPU最优的节点)和需求,来实现一个简单的基于CPU指标的自定义调度器。自定义调度器通过kubernetes资源指标服务metricsserver来获取各节点的当前的资源情况,并进行打分,然后把Pod调度到分数最高的节

Minikube之Win10单机部署

Kubernetes(k8s)是自动化容器操作的开源平台,基于这个平台,你可以进行容器部署,资源调度和集群扩容等操作。如果你曾经用过Docker部署容器,那么可以将Docker看成Kubernetes底层使用的组件,Kubernetes是Docker的上层封装,通过它可以很方便的进行Docker集群的管理。今天我们使用minikube在单机上进行Kubern

Dubbo 3.0 前瞻之对接 Kubernetes 原生服务

Kubernetes是当前全球最流行的容器服务平台,在Kubernetes集群中,Dubbo应用的部署方式往往需要借助第三方注册中心实现服务发现。Dubbo与Kubernetes的调度体系的结合,可以让原本需要管理两套平台的运维成本大大减低,而且Dubbo适配了Kubernetes原生服务也可以让框架本身更加融入云原生体系。基于D