什么是 Kubernetes scheduling?

Kubernetes scheduling是 Kubernetes 集群中的调度机制,它负责将集群内部的资源按照一定的策略将其调度到不同的 Node 上运行。更准确一点,「集群内部的资源」说的其实就是 Pod—-Kubernetes 中最小的工作实体和可调度实体。

如何实现 Kubernetes scheduling?

Kubernetes scheduling一共由四个组件配合完成:

  1. API Server: 负责给出调度过程中需要的资源对象的信息
  2. kube-scheduler:Kubernetes 内置的一个 Controller,核心的调度逻辑在这个组件内实现
  3. Kubelet:Kubelet 存在于每一个 Node 上。它会根据 Pod.Spec 中的信息为其创建相应的容器,并负责维护这些容器的生命周期
  4. ControllerManager:虽然Kubernetes scheduling主要是对于 Pod 而言的。但是除了你直接创建一个 Pod 之外,大多数情况下我们还是通过创建一个具体的资源,如 Deployment,Statefulset 等,然后这些资源的控制器(Controller) 将会为我们调用 API Server 的 API 创建相应的 Pod。所以,和调度相关的工作一定有 ControllerManager 的参与

有了上述的四个组件,就可以实现一个完备的Kubernetes scheduling了。但是这里有一个小的细节需要读者考虑一下:各个组件看起来是通过「资源对象的信息」即 Event 来驱动整个调度过程运行的。那这些 Event 是如何在各个组件内进行传递的呢?答案是List-Watch 机制。

快速了解 Kubernetes scheduling 运行机制

1
2
3
4
5
6
7
8
for {
    pod := getNextPod()
    node, err := findFitNode(pod)
    if err == nil {
        pod.Spec.nodeName = node.Name
        updatePod(pod)
    }
}

上面一个较为简单的实例可以大致描述一个 Pod 被调度的过程:

  1. 从 Queue 中取出一个待调度的 Pod
  2. 按照一定的策略,为其找到合适的 Node
  3. 将 Pod 调度至相应的 Node

其中第三步是依靠更新 Pod 的信息来做到的。因为此时 Kubelet 会通过List-Watch机制监听 Pod 的相关Event。当它发现有预备调度的 Pod 时,就可以在相应的 Node 上按照 pod.Spec 上描述的信息为其创建 Containers。

扩展一下

真正的调度过程,要远比我们上面给出的那个例子要复杂的多。本节我们将在此基础之上继续了解调度过程内部的一些更细节的地方。

上图主要描述了findFitNode方法内部的一些细节。我们可以看到,它会根据一定的策略和优先级来为 Pod 挑选 Node,如果没有挑选到合适的呢?那只能将这个 Pod 继续塞回队尾,等待下一次处理。如果已经找到符合条件的,那自然就可以进行后面的调度操作了。

有些 Pod 在创建的时候,可能需要挂载一定量的存储资源,所以不可避免的需要和 PVC 和 PV 打交道。上图中给出的实例是针对「静态 PVC」 场景的,即 PV 和相应的物理资源已经处理好了,若此时有符合条件的 PVC 出现,那么直接将 PVC 和 PV 绑定即可。与之相反的是「动态 PVC」 场景,即集群内部没有与 PVC 匹配的 PV 资源,此时 PV Controller 将会通过List-Watch机制监听到 PVC 相关的信息,为其动态的创建一个符合要求的 PV 和相应的物理资源。

可能有的读者比较迷惑,为什么在完成了 Bind PVC 的工作之后又将 Pod 送回到了 Queue 中呢?这是因为 Scheduler 虽然已经了解了 Pod 对于 Node 的要求,并且也已经确认能够满足它。但是它想再将目前的场景送入「策略系统」,看是否能将 Pod 调度到里物理资源更近的机器上(也可能是有其他可以优化的地方)。

说到这里就不得不提及一下 Scheduler 这个组件的本质。无论是在 Kubernetes 中还是在 Mesos 中,Scheduler 组件的一个重要的职责就是通过一定的调度策略充分利用集群的资源,并且让使用者更高效的利用集群内的资源。当你对 Scheduler 这个组件有了这样一个认识之后,上面它所做的操作相信理解起来也不难了。

聊聊调度策略(从用户可控的角度)

Kubernetes 一个非常值得称赞的特性就是,它为用户在很多方面都提供了高度的扩展性,如 CRD,自定义 Controller,CNI,CRI 等。所以,对于「调度策略」这么贴近业务的一个功能也一定会有所支持。

在 Kubernetes 中,通过以下几种方式为用户提供稍微灵活一些的调度策略

  1. NodeSelector
  2. Anti-Affinity/Affinity
  3. Tolerations/Taints

它们有一个共同的特点:都是通过 Kubernetes 的 Label 和 LabelSelector 机制来实现的。更详细的描述可以通过这篇文章深入了解一下:Contrain Pod Scheduling - LittleDriver

除了上面我们说到的 Kubernetes 内置的可以影响调度策略的方式,还有一个更高阶的玩法:自己实现一个 Scheduler,替换掉 Kubernetes 中默认的。

对于替换 Scheduler 的一点思考

不得不说,Kubernetes 中可以替换 Scheduler 这种重要组件的行为着实让我吃了一惊。但是仔细回味一下我前几天看过的List-Watch机制原理以及它在调度过程中的应用,我一下子明白了 Kubernetes 设计者的良苦用心:由于 Kubernetes 集群内部各个组件基本都是依靠List-Watch这种机制来进行消息传递的,且 Kubernetes 本身各个核心组件也都是依靠 Event 驱动机制来执行一些操作,所以 Scheduler 组件并没有和集群内其他任何组件产生过度的耦合。他们之间的交互要么依赖 Event,要么依赖一些 HTTP 或者 gRPC 的接口。那么,如果我们自己将 Scheduler 的框架实现一下,然后在其中填充和自己业务紧密相关的逻辑,完全可以无缝替换掉默认的 Scheduler。