56

Kubernetes 调度器浅析

 5 years ago
source link: http://dockone.io/article/8653?amp%3Butm_medium=referral
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

一、概述

Kubernetes 是 Google 开源的容器集群管理系统(谷歌内部:Borg),而今天要介绍的 kube-scheduler 是 k8s 系统的核心组件之一,其主要职责就是通过自身的调度算法,为新创建的 Pod 寻找一个最合适的 Node。

主要包含如下几个步骤:

  • 通过一组叫做谓词 predicates 的过滤算法,先挑出满足条件的 Node;
  • 通过一组叫做优先级 priorities 的打分算法,来给上一步符合条件的每个 Node 进行打分排名;
  • 最终选择得分最高的节点,当然如果得分一样就随机一个节点,填回 Pod 的 spec.nodeName 字段。

官方流程图如下:

For given pod:



+---------------------------------------------+

|               Schedulable nodes:            |

|                                             |

| +--------+    +--------+      +--------+    |

| | node 1 |    | node 2 |      | node 3 |    |

| +--------+    +--------+      +--------+    |

|                                             |

+-------------------+-------------------------+

                    |

                    |

                    v

+-------------------+-------------------------+



Pred. filters: node 3 doesn't have enough resource



+-------------------+-------------------------+

                    |

                    |

                    v

+-------------------+-------------------------+

|             remaining nodes:                |

|   +--------+                 +--------+     |

|   | node 1 |                 | node 2 |     |

|   +--------+                 +--------+     |

|                                             |

+-------------------+-------------------------+

                    |

                    |

                    v

+-------------------+-------------------------+



Priority function:    node 1: p=2

                      node 2: p=5



+-------------------+-------------------------+

                    |

                    |

                    v

        select max{node priority} = node 2

scheduler 的工作看似很简单,但其实不然。考虑的问题非常多,比如要保证每个节点被公平调度,提高资源利用率,提高 pod 调度效率,提升调度器扩展能力等等。

可涉及的内容非常多,接下来会围绕两个核心步骤对 k8s 的 默认调度策略 深入了解。

参考 Kubernetes 版本: v1.12

二、Predicates

Predicates 在调度过程中的作用就是先进行 过滤 ,过滤掉所有不符合条件的节点后,剩下的所有节点就都是可以运行带调度 Pod。

Predicates 的可以分为如下四类:

  • GeneralPredicates :负责最基础的调度策略,比如 PodFitsResources 计算宿主机资源是否够用。
  • 与 Volume 相关的过滤规则 :负责与容器持久化 Volume 相关的调度策略。
  • 与宿主机相关的过滤规则 :负责考察待调度 Pod 是否满足 Node 本身的一些条件。
  • 与已运行 Pod 相关的过滤规则 :负责检查待调度 Pod 与 Node 上已有 Pod 之间的亲和性关系。

具体的 Predicates 默认策略,可以参考: 默认调度策略

当开始调度一个 Pod 的时候,调度器会同时开启多个协程并发的进行 Node Predicates 过滤,最后返回一个可以运行 Pod 的节点列表。每个协程都是按照固定的顺序进行计算过滤的。

接下来,我们看下四大类具体运行的调度策略内容。

1. GeneralPredicates

看字面意思就知道 GeneralPredicates 负责的是最基础的调度策略,其包含的具体策略如下:

  • PodFitsResources : 计算宿主机的 CPU、内存、扩展资源(如 GPU)等是否够用。
  • PodFitsHost : 检查宿主机的名字是否跟 Pod 的 spec.nodeName 匹配。
  • PodFitsHostPorts : 检查 Pod 申请的宿主机端口有没有冲突。
  • PodMatchNodeSelector : 检查节点是否能匹配 Pod 的 nodeSelector 和 nodeAffinity。

因为 GeneralPredicates 是最基础的调度策略,所以该接口也会被别的组件直接调用,比如 kubelet、daemonSet controller。kubelet 在启动 pod 之前,还会再执行一遍 GeneralPredicates,用于二次确认。

2. 与 Volume 相关的过滤规则

不废话就直接列举具体的策略了:

  • NoDiskConflict :检查该节点上所有的 Pods 是否与待调度的 Pod 的 Volume 有冲突,比如 AWS、GCE 的 Volume 是不允许被两个 Pod 同时使用的。
  • VolumeZonePredicate :检查 Pod Volume 的 zone 标签是否与节点的 zone 标签匹配。如果 Node 没有 zone 标签则认定为匹配。
  • MaxPDVolumeCountPredicate :检查节点上某种类型的 Volume 是否已经超过指定数目。
  • CSIMaxVolumeLimitPredicate :检查 csi volume 相关的限制
  • VolumeBindingPredicate :检查 Pod 对应的 Local PV 的 nodeAffinity 字段,是否跟某个节点的标签相匹配。如果该 Pod PVC 还没有绑定 PV 的话,则调度器还要负责检查所有待绑定的 PV,且该 PV 的 nodeAffinity 是否与节点标签匹配。

3. 与宿主机相关的过滤规则

这些规则主要考察待调度的 Pod 是否满足 Node 本身的一些条件。

具体的策略如下:

  • NodeConditionPredicate :检查 Node 是否还未准备好或者处于NodeOutOfDisk、NodeNetworkUnavailable 状态,又或者 Node spec.Unschedulable 设置为 true,那该节点都将无法被调度。
  • PodToleratesNodeTaints :检查 Node 的 taint(污点)机制。只有当 Pod 的 Toleration 与 Node 的 Taint 匹配时,Pod 才能调度到该节点上。
  • NodeMemoryPressurePredicate :检查当前节点的内存是否已经不够使用。
  • NodeDiskPressurePredicate :检查当前节点的磁盘是否已经不够使用。
  • NodePIDPressurePredicate :检查当前节点的 PID 是否已经不够使用。

4. 与已运行 Pod 相关的过滤规则

该规则主要就是 PodAffinityPredicate,用于检查待调度 Pod 与 Node 上已有的 Pod 之间的亲和性和反亲和性关系。

具体的亲和性相关的调度,后面会单独拿一篇文章进行介绍。

三、Priorities

完成了前一个阶段的节点 “过滤” 之后,便需要通过 Priorities 为这些节点打分,选择得分最高的节点,作为调度对象。

打分函数很多,总得分可以参考:

总分 = (权重1 * 打分函数1) + (权重2 * 打分函数2) + … + (权重n * 打分函数n)

每一次打分的范围是 0 — 10 分。10 表示非常合适,0 表示非常不合适。

并且每个打分函数都可以配置对应的权重值,下面介绍 调度器策略配置 时,也会涉及权重值的配置。默认权重值是 1,如果觉得某个打分函数特别重要,便可以加大该权重值。

具体的 Priorities 默认策略可以参考: defaultPriorities

Priorities 最常用到的一个打分规则是 LeastRequestedPriority , 该算法用于选出空闲资源(cpu & memory)最多的宿主机。

还有一个常见的是 BalancedResourceAllocation ,该规则主要目的是资源平衡。在所有节点里选择各种资源分配最均衡的节点,避免出现某些节点 CPU 被大量分配,但是 Memory 大量剩余的情况。

此外,还有 InterPodAffinityPriorityNodeAffinityPriorityTaintTolerationPriority ,与亲和性与污点调度有关,后面会有单独的文章进行介绍。这里表示节点满足的规则越多,那得分就越高。

在 K8S v1.12 版本还引入了一个调度策略,即 ImageLocalityPriority 。该策略主要目的是优先选择那些已经存有 Pod 所需 image 的节点,可以避免实际运行 Pod 时,再去下载 image。

注意: pod 运行时是否会下载 image,还跟 Pod ImagePullPolicy 配置有关。

可以看到 k8s scheduler 完成一次调度所需的信息非常之多。所以在实际的调度过程中,大量的信息都事先已经缓存,提高了 Pod 的调度效率。

四、调度策略配置

Kubernetes 调度器有默认的调度策略,具体可以参考 default 。当然用户也可以修改调度策略,可以通过命令行参数 policy-config-file 指定一个 JSON 文件来描述哪些 predicates 和 priorities 在启动 k8s 时被使用, 通过这个参数调度就能使用管理者定义的策略了。

示例如下:

{

"kind" : "Policy",

"apiVersion" : "v1",

"predicates" : [

{"name" : "PodFitsHostPorts"},

{"name" : "PodFitsResources"},

{"name" : "NoDiskConflict"},

{"name" : "NoVolumeZoneConflict"},

{"name" : "MatchNodeSelector"},

{"name" : "HostName"}

],

"priorities" : [

{"name" : "LeastRequestedPriority", "weight" : 1},

{"name" : "BalancedResourceAllocation", "weight" : 1},

{"name" : "ServiceSpreadingPriority", "weight" : 1},

{"name" : "EqualPriority", "weight" : 1}

],

"hardPodAffinitySymmetricWeight" : 10,

"alwaysCheckAllPredicates" : false

}

五、自定义调度器

前面提到了调度器的扩展能力,除了使用 k8s 自带的调度器,你也可以编写自己的调度器。通过修改 Pod 的 spec.schedulername 参数来指定调度器的名字。

参考资料

- The Kubernetes Scheduler

- Scheduler Algorithm in Kubernetes


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK