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

Prodan Labs 等级 345 0 0

Kubernetes自定义调度器 — 初识调度框架 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将返回队列并重试。

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

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的插件。

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

// Name is the name of the plugin used in the plugin registry and configurations.
const Name = "sample"

// Sort is a plugin that implements QoS class based sorting.
type sample struct{}

var _ framework.FilterPlugin = &sample{}
var _ framework.PreScorePlugin = &sample{}

// New initializes a new plugin and returns it.
func New(_ runtime.Object, _ framework.FrameworkHandle) (framework.Plugin, error) {
  return &sample{}, nil
}

实现对应插件的接口:


// Name returns name of the plugin.
func (pl *sample) Name() string {
  return Name
}
func (pl *sample) Filter(ctx context.Context, state *framework.CycleState, pod *v1.Pod, nodeInfo *framework.NodeInfo) *framework.Status {
  log.Printf("filter pod: %v, node: %v", pod.Name, nodeInfo)
  log.Println(state)
  // 排除没有cpu=true标签的节点
  if nodeInfo.Node().Labels["cpu"] != "true" {
    return framework.NewStatus(framework.Unschedulable, "Node: "+nodeInfo.Node().Name)
  }
  return framework.NewStatus(framework.Success, "Node: "+nodeInfo.Node().Name)
}
func (pl *sample)PreScore(ctx context.Context, state *framework.CycleState, pod *v1.Pod, nodes []*v1.Node) *framework.Status  {
  log.Println(nodes)
  return framework.NewStatus(framework.Success, "Node: "+pod.Name)
}

插件注册:

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

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

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

在kubernetes安装插件

kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: sample-scheduler-clusterrole
rules:
- apiGroups:
    - ""
  resources:
    - endpoints
    - events
  verbs:
    - create
    - get
    - update
- apiGroups:
    - ""
  resources:
    - nodes
  verbs:
    - get
    - list
    - watch
- apiGroups:
    - ""
  resources:
    - pods
  verbs:
    - delete
    - get
    - list
    - watch
    - update
- apiGroups:
    - ""
  resources:
    - bindings
    - pods/binding
  verbs:
    - create
- apiGroups:
    - ""
  resources:
    - pods/status
  verbs:
    - patch
    - update
- apiGroups:
    - ""
  resources:
    - replicationcontrollers
    - services
  verbs:
    - get
    - list
    - watch
- apiGroups:
    - apps
    - extensions
  resources:
    - replicasets
  verbs:
    - get
    - list
    - watch
- apiGroups:
    - apps
  resources:
    - statefulsets
  verbs:
    - get
    - list
    - watch
- apiGroups:
    - policy
  resources:
    - poddisruptionbudgets
  verbs:
    - get
    - list
    - watch
- apiGroups:
    - ""
  resources:
    - persistentvolumeclaims
    - persistentvolumes
  verbs:
    - get
    - list
    - watch
- apiGroups:
    - ""
  resources:
    - configmaps
  verbs:
    - get
    - list
    - watch
- apiGroups:
    - "storage.k8s.io"
  resources:
    - storageclasses
    - csinodes
  verbs:
    - get
    - list
    - watch
- apiGroups:
    - "coordination.k8s.io"
  resources:
    - leases
  verbs:
    - create
    - get
    - list
    - update
- apiGroups:
    - "events.k8s.io"
  resources:
    - events
  verbs:
    - create
    - patch
    - update
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: sample-scheduler-sa
namespace: kube-system
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: sample-scheduler-clusterrolebinding
namespace: kube-system
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: sample-scheduler-clusterrole
subjects:
- kind: ServiceAccount
name: sample-scheduler-sa
namespace: kube-system
---
apiVersion: v1
kind: ConfigMap
metadata:
name: scheduler-config
namespace: kube-system
data:
scheduler-config.yaml: |
  apiVersion: kubescheduler.config.k8s.io/v1beta1
  kind: KubeSchedulerConfiguration
  leaderElection:
    leaderElect: false
  clientConnection:
    kubeconfig: /kube-scheduler.kubeconfig
  profiles:
  - schedulerName: sample-scheduler
    plugins:
      filter:
        enabled:
        - name: sample
      preScore:
        enabled:
          - name: sample
        disabled:
          - name: "*"
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: sample-scheduler
namespace: kube-system
labels:
  component: sample-scheduler
spec:
replicas: 1
selector:
  matchLabels:
    component: sample-scheduler
template:
  metadata:
    labels:
      component: sample-scheduler
  spec:
    serviceAccount: sample-scheduler-sa
    priorityClassName: system-cluster-critical
    volumes:
      - name: scheduler-config
        configMap:
          name: scheduler-config
    containers:
      - name: scheduler-ctrl
        image: sample-scheduler:stretch-slim
        imagePullPolicy: IfNotPresent
        args:
          - --config=/etc/kubernetes/scheduler-config.yaml
          - --v=3
        resources:
          requests:
            cpu: "50m"
        volumeMounts:
          - name: scheduler-config
            mountPath: /etc/kubernetes

运行日志: Kubernetes自定义调度器 — 初识调度框架

验证插件


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

apiVersion: apps/v1
kind: Deployment
metadata:
name: test-scheduler
spec:
replicas: 1
selector:
  matchLabels:
    app: test-scheduler
template:
  metadata:
    labels:
      app: test-scheduler
  spec:
    schedulerName: sample-scheduler
    containers:
    - image: docker.io/library/nginx:1.19.2-alpine
      imagePullPolicy: IfNotPresent
      name: nginx
      ports:
      - containerPort: 80

创建测试pod


root@ubuntu:~# kubectl create -f  test-deploy.yaml
deployment.apps/test-scheduler created
root@ubuntu:~# kubectl get po
NAME                             READY   STATUS    RESTARTS   AGE
test-scheduler-6645fd9f6-pfzkm   0/1     Pending   0          4s
root@ubuntu:~# kubectl describe pod test-scheduler-6645fd9f6-pfzkm 
Name:           test-scheduler-6645fd9f6-pfzkm
Namespace:      default
Priority:       0
Node:           <none>
Labels:         app=test-scheduler
                pod-template-hash=6645fd9f6
Annotations:    <none>
Status:         Pending
IP:             
IPs:            <none>
Controlled By:  ReplicaSet/test-scheduler-6645fd9f6
Containers:
  nginx:
    Image:        docker.io/library/nginx:1.19.2-alpine
    Port:         80/TCP
    Host Port:    0/TCP
    Environment:  <none>
    Mounts:
      /var/run/secrets/kubernetes.io/serviceaccount from default-token-jbdqf (ro)
Conditions:
  Type           Status
  PodScheduled   False 
Volumes:
  default-token-jbdqf:
    Type:        Secret (a volume populated by a Secret)
    SecretName:  default-token-jbdqf
    Optional:    false
QoS Class:       BestEffort
Node-Selectors:  <none>
Tolerations:     node.kubernetes.io/not-ready:NoExecute op=Exists for 360s
                 node.kubernetes.io/unreachable:NoExecute op=Exists for 360s
Events:
  Type     Reason            Age   From  Message
  ----     ------            ----  ----  -------
  Warning  FailedScheduling  36s         0/1 nodes are available: 1 Node: kube-master.
  Warning  FailedScheduling  36s         0/1 nodes are available: 1 Node: kube-master.

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

I1011 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."

给节点增加标签


root@ubuntu:~# kubectl label nodes kube-master cpu=true
node/kube-master labeled
root@ubuntu:~# kubectl get nodes --show-labels 
NAME          STATUS   ROLES    AGE   VERSION   LABELS
kube-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
root@ubuntu:~# kubectl get po
NAME                             READY   STATUS    RESTARTS   AGE
test-scheduler-6645fd9f6-pfzkm   1/1     Running   0          3m16s

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

I1011 15:17:45.635502     466 default_binder.go:51] Attempting to bind default/test-scheduler-6645fd9f6-pfzkm to kube-master
I1011 15:17:45.643123     466 eventhandlers.go:205] delete event for unscheduled pod default/test-scheduler-6645fd9f6-pfzkm
I1011 15:17:45.643204     466 eventhandlers.go:225] add event for scheduled pod default/test-scheduler-6645fd9f6-pfzkm 
I1011 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框架,发现还是挺有意思的,后续有时间再更新吧。


感兴趣的读者可以关注下微信号 Kubernetes自定义调度器 — 初识调度框架

收藏
评论区

相关推荐

30min搭建kubernetes
master and node 操作如下 yum源修改 // 备份本地yum源 mv /etc/yum.repos.d/CentOSBase.repo /etc/yum.repos.d/CentOSBase.repo_bak // 获取阿里yum源配置 wget O /etc/yum.repos.d/CentOSBase.repo http:
Kubernetes(k8s)中文文档 Kubernetes概述
简介 Kubernetes(https://www.kubernetes.org.cn/)是一个开源的,用于管理云平台中多个主机上的容器化的应用,Kubernetes的目标是让部署容器化的应用简单并且高效(powerful),Kubernetes提供了应用部署,规划,更新,维护的一种机制。 Kubernetes一个核心的特点就是能够自主的管理容
k8s超详细总结
一个目标:容器操作;两地三中心;四层服务发现;五种Pod共享资源;六个CNI常用插件;七层负载均衡;八种隔离维度;九个网络模型原则;十类IP地址;百级产品线;千级物理机;万级容器;相如无亿,K8s有亿:亿级日服务人次。 一个目标:容器操作 Kubernetes(k8s)是自动化容器操作的开源平台。这些容
Kubernetes + GitLab 微服务应用自动化部署
Kubernetes GitLab 微服务应用自动化部署 Docker 简化了单个服务的部署,Kubernetes 通过强大的容器编排能力使得运维人员可以轻松管理成千上万的容器,这些容器归属于多个服务,而这些服务又组合形成了多个应用。从代码到运行中的应用,需要经历构建、打包、测试和部署等阶段,如果每个步骤都手动执行,无疑会消耗研发人员的大量时间。本文讲解
Kubernetes Ingress — NGINX
在 Kubernetes 中,Service 是一种抽象的概念,它定义了每一组 Pod 的逻辑集合和访问方式,并提供一个统一的入口,将请求进行负载分发到后端的各个 Pod 上。Service 默认类型是 ClusterIP,集群内部的应用服务可以相互访问,但集群外部的应用服务无法访问。为此 Kubernetes 提供了 NodePorts,LoadBalan
Kubernetes Ingress — Kong
Kong 是由 Mashape 公司开源的一个高性能、高可用、易扩展的 API Gateway 项目,基于OpenResty(Nginx Lua模块),并提供了插件实现 API 的 AOP 功能。可以通过负载均衡、插件扩展等方式,来处理网络请求。 Kong 主要的概念Servi
Kubernetes部署高可用PostgreSQL集群
PostgreSQL 是一个功能强大的开源关系数据库,它使用和扩展了 SQL 语言,并结合了许多安全存储和扩展最复杂数据工作负载的功能。PostgreSQL 的起源可以追溯到 1986 年,作为加利福尼亚大学伯克利分校 POSTGRES 项目的一部分,与 Linux 内核相似,PostgreSQL 是由社区驱动的开源项目,由全世界贡献者所维护。Postgre
Kubernetes Pod 自动扩容 — HPA
Kubernetes 增强了应用服务的横向扩容能力,在应对线上应用服务的资源使用率在高峰和低谷的时候,我们需要能够自动去感知应用的负载变化去调整 Pod 的副本数量,削峰填谷,提高集群的整体资源利用率和应用的服务质量。为此,Kubernetes 1.2 版本中引入 Horizontal Pod Autoscaling (HPA), 它与 kubectl sc
Kubernetes自定义调度器 — 初识调度框架
Kubernetes 已经成为容器编排(Orchestration)平台的事实标准,它为容器化应用提供了简单且高效部署的方式、大规模可伸缩、资源调度等生命周期管理功能。kubescheduler作为kubernetes的核心组件,它负责整个集群资源的调度功能,根据特定的调度算法或调度策略,将Pod调度到最优的Node节点,使集群的资源得到合理且充分的利用。
Kubernetes自定义调度器 — 初窥门径
通过上一篇文章对schedulerframework调度框架已经有了大致了解,根据我们的实际生产的一些问题(如计算服务没有被调度到实际CPU最优的节点)和需求,来实现一个简单的基于CPU指标的自定义调度器。自定义调度器通过kubernetes资源指标服务metricsserver来获取各节点的当前的资源情况,并进行打分,然后把Pod调度到分数最高的节
Prometheus架构与实践分享
Prometheus已经被广泛应用于数据中心监控,尤其是和Kubernetes结合的容器监控。本文主要从架构分析到落地实践,详细介绍Prometheus原理和使用。对比Prometheus与其他监控工具(Zabbix、OpenFalcon)的特点与使用场景。然后介绍Prometheus与Kubernetes集成,主要从监控和自动伸缩两个方面。最后通过企业案
基于k8s的DevOps实践之路
原标题:基于k8s的DevOps实践之路很多快速发展的公司都面临着一个巨大挑战:在需求不断动态横向扩容的同时继续保持系统的高可用性。如何有效解决这一问题,Kubernetes(k8s)应运而生。k8s以运行可扩展工作负载而闻名,它可以根据资源使用情况调整工作负载。白山科技云分发团队基于多年的DevOps实践经验,在白山会运维日第三期与Thoughtworks
SRE和DevOps值得关注的十大开源项目
构建可扩展且高度可靠的软件系统是每个SRE的最终目标。在SRE/DevOps领域中,有大量出色的开源项目,每个项目都有新颖而激动人心的解决方案。在本文中,我们将会介绍一些在监视,部署和维护领域最受欢迎的开源项目。 1\. Cloudprober可以主动跟踪和监视应用程序,并帮助你提前发现故障。它使用“活动(active)”监视模型来检查你的组件是否按预
2021年值得关注的五个云原生安全问题
云原生正变得越来越流行,在2020年,统计发现,其中83%的组织也在生产环境中使用kubernetes。由于,团队可以轻松地构建和使用,,,以及管理服务,而不必担心底层的服务器。但是,云原生安全性仍然是一个真正的挑战。奇怪的是,云原生安全性的最大问题与所有新技术无关。首席安全官表示,如果组织希望以更好地开始新的一年,则应着手解决五个主要的云原生安全性问题。有
2021年云原生技术趋势
2020年3月下旬的几周时间里,COVID19使全球业务停滞不前,因此每个人都在试图找出应对这种疫情的最佳方法。那时候,没有人知道会发生什么,因此会议被取消,合同被搁置,项目被推迟。一切都停止了。但是,到2020年4月初,我们见证了IT社区的工作重新焕发活力。尽管其他行业还不能恢复正常,但很快我们就明白,仍然需要IT来进行经济和社会活动。你甚至可以得出这样