Kubernetes Scheduler 的作用是将待调度的 Pod 按照一定的调度算法和策略绑定到集群中一个合适的 Worker Node(以下简称 Node) 上,并将绑定信息写入到 etcd 中,之后目标 Node 中 kubelet 服务通过 API Server 监听到 Scheduler 产生的 Pod 绑定事件获取 Pod 信息,然后下载镜像启动容器,调度流程如图所示:
Scheduler 提供的调度流程分为预选 (Predicates) 和优选 (Priorities) 两个步骤:
1. 预选,K8S会遍历当前集群中的所有 Node,筛选出其中符合要求的 Node 作为候选
2. 优选,K8S将对候选的 Node 进行打分
Scheduler的任务是选择一个placement(位置)。一个placement是一个部分的,非内射的Pod集合到节点集合的分配。
Scheduler的调度逻辑是:将 Possible(可能)的 Node 节点, 通过预选过程变成 Feasible(可用) Node 列表,再通过优选(打分)的过程来确定最终运行 Pod 的Viable(可行)的Node 的过程。
Scheduler是一个保证局部最优解的多步调度器,而不是一个保证全局最优解的单步调度器。
不过看到这里虽然说的很简单,但是我带过了一个非常重要的知识点,就是 Kubernetes Scheduler 在多任务情况下,他是如何工作的? Queue,队列方式工作的,也就是说,即便有多个调度任务同时在 etcd 中等待调度, Scheduler 每次只会调度一个任务(和 Master 的数量无关)。那我稍微多往下说点内部逻辑:
1. Scheduler 内部维护一个调度的pods队列podQueue, 并监听APIServer。
2. 当我们创建Pod时,首先通过 API Server 往 etcd 写入 pod 元数据。
3. 调度器通过 Informer 监听 pods 状态,当有新增 pod 时,将 pod 加入到 PodQueue中。
4. 调度器中的主进程,会不断的从podQueue取出的pod,并将pod进入调度分配节点环节
5. 调度环节分为两个步奏, Filter过滤满足条件的节点 、 Prioritize根据pod配置,例如资源使用率,亲和性等指标,给这些节点打分,最终选出分数最高的节点。
6. 分配节点成功, 调用apiServer的binding pod 接口, 将pod.Spec.NodeName设置为所分配的那个节点。
7. 节点上的kubelet同样监听ApiServer,如果发现有新的pod被调度到所在节点,调用本地的dockerDaemon 运行容器。
假如调度器尝试调度 Pod 不成功,如果开启了优先级和抢占功能,会尝试做一次抢占,将节点中优先级较低的pod删掉,并将待调度的pod调度到节点上。 如果未开启,或者抢占失败,会记录日志,并将pod加入 PodQueue 队尾(这意味着,如果调度队列很长,你只有多等待了)。
经过预选筛选和优选打分之后,K8S选择分数最高的 Node 来运行 Pod,如果最终有多个 Node 的分数最高,那么 Scheduler 将从当中随机选择一个 Node 来运行 Pod。
在 Scheduler 中,可选的预选策略包括:
如果开启了 TaintNodesByCondition(从 1.12 开始为 beta级别,默认开启) 特性,则 CheckNodeCondition、CheckNodeMemoryPressure、CheckNodeDiskPressure、CheckNodePIDPressure 预选策略则会被禁用,PodToleratesNodeNoExecuteTaints、CheckNodeUnschedulable 则会启用。
在 Scheduler 中,可选的优选策略包括:
如果开启了 ResourceLimitsPriorityFunction(默认不开启) 特性,则 ResourceLimitsPriority 会被启用。
如何扩展 Kubernetes Scheduler
Scheduler 内置的策略在大多数场景下可以满足要求,但是在一些特殊场景下,不能满足复杂的调度需求,我们可以通过扩展程序对 Scheduler 进行扩展。扩展后的 Scheduler 会在调用内置预选策略和优选策略之后通过 HTTP 协议调用扩展程序再次进行预选和优选,最后选择一个合适的 Node 进行 Pod 的调度。调度流程如下:
写在最后,就不去贴如何开发一个自定义的 Scheduler 了,代码长度不小。。。 不过说实在,学会了原理,在看看文档,百度百度。。。 一个简单的 Scheduler 不难写。。