k8s学习笔记-调度介绍

一:概述

一个容器平台的主要功能就是为容器分配运行时所需要的计算,存储和网络资源。容器调度系统负责选择在最合适的主机上启动容器,并且将它们关联起来。

它必须能够自动的处理容器故障并且能够在更多的主机上自动启动更多的容器来应对更多的应用访问。

目前三大主流的容器平台Swarm, Mesos和Kubernetes具有不同的容器调度系统。

1.Swarm的特点是直接调度Docker容器,并且提供和标准Docker API一致的API。

2.Mesos针对不同的运行框架采用相对独立的调度系统,其中Marathon框架提供了Docker容器的原生支持。

3.Kubernetes则采用了Pod和Label这样的概念把容器组合成一个个的互相存在依赖关系的逻辑单元。相关容器被组合成Pod后被共同部署和调度,形成服务(Service)。

这个是Kubernetes和Swarm,Mesos的主要区别。

相对来说,Kubernetes采用这样的方式简化了集群范围内相关容器被共同调度管理的复杂性。换一种角度来看,Kubernetes采用这种方式能够相对容易的支持更强大,更复杂的容器调度算法。

二:K8S 调度工作方式

Kubernetes调度器作为集群的大脑,在如何提高集群的资源利用率、保证集群中服务的稳定运行中也会变得越来越重要

Kubernetes的资源分为两种属性。

1.可压缩资源(例如CPU循环,Disk I/O带宽)都是可以被限制和被回收的,对于一个Pod来说可以降低这些资源的使用量而不去杀掉Pod。

2.不可压缩资源(例如内存、硬盘空间)一般来说不杀掉Pod就没法回收。未来Kubernetes会加入更多资源,如网络带宽,存储IOPS的支持。

Kubernets调度器

Scheduler调度器做为Kubernetes三大核心组件之一, 承载着整个集群资源的调度功能,其根据特定调度算法和策略,将Pod调度到最优工作节点上,从而更合理与充分的利用集群计算资源。

其作用是根据特定的调度算法和策略将Pod调度到指定的计算节点(Node)上,其做为单独的程序运行,启动之后会一直监听API Server,获取PodSpec.NodeName为空的Pod,对每个Pod都会创建一个绑定。

默认情况下,k8s的调度器采用扩散策略,将同一集群内部的pod对象调度到不同的Node节点,以保证资源的均衡利用。

 

首先用户通过 Kubernetes 客户端 Kubectl 提交创建 Pod 的 Yaml 的文件,向Kubernetes 系统发起资源请求,该资源请求被提交到
Kubernetes 系统中,用户通过命令行工具 Kubectl 向 Kubernetes 集群即 APIServer 用 的方式发送“POST”请求,即创建 Pod 的请求。
APIServer 接收到请求后把创建 Pod 的信息存储到 Etcd 中,从集群运行那一刻起,资源调度系统 Scheduler 就会定时去监控 APIServer
通过 APIServer 得到创建 Pod 的信息,Scheduler 采用 watch 机制,一旦 Etcd 存储 Pod 信息成功便会立即通知APIServer,
APIServer会立即把Pod创建的消息通知Scheduler,Scheduler发现 Pod 的属性中 Dest Node 为空时(Dest Node=””)便会立即触发调度流程进行调度。
而这一个创建Pod对象,在调度的过程当中有3个阶段:节点预选、节点优选、节点选定,从而筛选出最佳的节点

  • 节点预选:基于一系列的预选规则对每个节点进行检查,将那些不符合条件的节点过滤,从而完成节点的预选
  • 节点优选:对预选出的节点进行优先级排序,以便选出最合适运行Pod对象的节点
  • 节点选定:从优先级排序结果中挑选出优先级最高的节点运行Pod,当这类节点多于1个时,则进行随机选择

Kubernetes调度器使用Predicates和Priorites来决定一个Pod应该运行在哪一个节点上。

Predicates是强制性规则,用来形容主机匹配Pod所需要的资源,如果没有任何主机满足该Predicates, 则该Pod会被挂起,直到有主机能够满足。

 

 

1.预选环节:

源码参考:https://github.com/kubernetes/kubernetes/blob/281023790fd27eec7bfaa7e26ff1efd45a95fb09/pkg/scheduler/framework/plugins/legacy_registry.go

通过组合多个函数可以完成一条可扩展的过滤器链。目前k8s中已注册的过滤器函数如下:

  • CheckNodeCondition:检查是否可以在节点报告磁盘、网络不可用或未准备好的情况下将Pod对象调度其上。
  • HostName:如果Pod对象拥有spec.hostname属性,则检查节点名称字符串是否和该属性值匹配。
  • PodFitsHostPorts:如果Pod对象定义了ports.hostPort属性,则检查Pod指定的端口是否已经被节点上的其他容器或服务占用。
  • MatchNodeSelector:如果Pod对象定义了spec.nodeSelector属性,则检查节点标签是否和该属性匹配。
  • NoDiskConflict:检查Pod对象请求的存储卷在该节点上可用。
  • PodFitsResources:检查节点上的资源(CPU、内存)可用性是否满足Pod对象的运行需求。
  • PodToleratesNodeTaints:如果Pod对象中定义了spec.tolerations属性,则需要检查该属性值是否可以接纳节点定义的污点(taints)。
  • PodToleratesNodeNoExecuteTaints:如果Pod对象定义了spec.tolerations属性,检查该属性是否接纳节点的NoExecute类型的污点。
  • CheckNodeLabelPresence:仅检查节点上指定的所有标签的存在性,要检查的标签以及其可否存在取决于用户的定义。
  • CheckServiceAffinity:根据当前Pod对象所属的Service已有其他Pod对象所运行的节点调度,目前是将相同的Service的Pod对象放在同一个或同一类节点上。
  • MaxEBSVolumeCount:检查节点上是否已挂载EBS存储卷数量是否超过了设置的最大值,默认值:39
  • MaxGCEPDVolumeCount:检查节点上已挂载的GCE PD存储卷是否超过了设置的最大值,默认值:16
  • MaxAzureDiskVolumeCount:检查节点上已挂载的Azure Disk存储卷数量是否超过了设置的最大值,默认值:16
  • CheckVolumeBinding:检查节点上已绑定和未绑定的PVC是否满足Pod对象的存储卷需求。
  • NoVolumeZoneConflct:在给定了区域限制的前提下,检查在该节点上部署Pod对象是否存在存储卷冲突。
  • CheckNodeMemoryPressure:在给定了节点已经上报了存在内存资源压力过大的状态,则需要检查该Pod是否可以调度到该节点上。
  • CheckNodePIDPressure:如果给定的节点已经报告了存在PID资源压力过大的状态,则需要检查该Pod是否可以调度到该节点上。
  • CheckNodeDiskPressure:如果给定的节点存在磁盘资源压力过大,则检查该Pod对象是否可以调度到该节点上。
  • MatchInterPodAffinity:检查给定的节点能否可以满足Pod对象的亲和性和反亲和性条件,用来实现Pod亲和性调度或反亲和性调度。

在上面的这些预选策略里面,CheckNodeLabelPressure和CheckServiceAffinity可以在预选过程中结合用户自定义调度逻辑,这些策略叫做可配置策略。其他不接受参数进行自定义配置的称为静态策略。

2.优先环节:

通过上面的预选如果调度器发现有多个主机满足条件,那么Priorities就用来判断哪一个主机最适合运行Pod。Priorities是一个键值对,key表示名称,value表示权重

通过某种策略进行可用节点的评分,最终选出最优节点

用一组优先级函数处理每一个通过预选的节点,每一个优先级函数会返回一个0-10的分数,分数越高表示节点越优, 同时每一个函数也会对应一个表示权重的值。

它首先将每个优选函数的计算得分乘以权重,然后再将所有优选函数的得分相加,从而得出节点的最终优先级分值。权重可以让管理员定义优选函数倾向性的能力,其计算优先级的得分公式如下:
finalScoreNode = (weight1 * priorityFunc1) + (weight2 * priorityFunc2) + … + (weightn * priorityFuncn)


示例:假设有个节点nodeA,有两个打分函数priorityFunc1、priorityFunc2(每个方法都能返回一个score),两个方法分别都有权重因子weight1、weight2。

则nodeA的总分为:finalScoreNodeA = (weight1 * priorityFunc1) + (weight2 * priorityFunc2)

下面是优先函数

源码位置:https://github.com/kubernetes/kubernetes/blob/281023790fd27eec7bfaa7e26ff1efd45a95fb09/pkg/scheduler/framework/plugins/legacy_registry.go

详细说明:

LeastRequestedPriority:

节点的优先级就由节点空闲资源与节点总容量的比值,即由(总容量-节点上Pod的容量总和-新Pod的容量)/总容量)来决定。
CPU和内存具有相同权重,资源空闲比越高的节点得分越高。
cpu((capacity – sum(requested)) * 10 / capacity) + memory((capacity – sum(requested)) * 10 / capacity) / 2
BalancedResourceAllocation:
CPU和内存使用率越接近的节点权重越高,该策略不能单独使用,必须和LeastRequestedPriority组合使用,尽量选择在部署Pod后各项资源更均衡的机器。
如果请求的资源(CPU或者内存)大于节点的capacity,那么该节点永远不会被调度到。
InterPodAffinityPriority:
通过迭代 weightedPodAffinityTerm 的元素计算和,并且如果对该节点满足相应的PodAffinityTerm,则将 “weight” 加到和中,具有最高和的节点是最优选的。 `
SelectorSpreadPriority:
为了更好的容灾,对同属于一个service、replication controller或者replica的多个Pod副本,尽量调度到多个不同的节点上。
如果指定了区域,调度器则会尽量把Pod分散在不同区域的不同节点上。当一个Pod的被调度时,会先查找Pod对于的service或者replication controller,
然后查找service或replication controller中已存在的Pod,运行Pod越少的节点的得分越高。
NodeAffinityPriority:
亲和性机制。Node Selectors(调度时将pod限定在指定节点上),
支持多种操作符(In, NotIn, Exists, DoesNotExist, Gt, Lt),而不限于对节点labels的精确匹配。
另外支持两种类型的选择器,一种是“hard(requiredDuringSchedulingIgnoredDuringExecution)”选择器,
它保证所选的主机必须满足所有Pod对主机的规则要求。
这种选择器更像是之前的nodeselector,在nodeselector的基础上增加了更合适的表现语法。
另一种是“soft(preferresDuringSchedulingIgnoredDuringExecution)”选择器,
它作为对调度器的提示,调度器会尽量但不保证满足NodeSelector的所有要求。
NodePreferAvoidPodsPriority(权重1W):
如果 节点的 Anotation 没有设置 key-value:scheduler. alpha.kubernetes.io/ preferAvoidPods = "...",则节点对该 policy 的得分就是10分,
加上权重10000,那么该node对该policy的得分至少10W分。如果Node的Anotation设置了,
scheduler.alpha.kubernetes.io/preferAvoidPods = "..." ,如果该 pod 对应的 Controller 是 ReplicationController 或 ReplicaSet,
则该 node 对该 policy 的得分就是0分。
TaintTolerationPriority :
使用 Pod 中 tolerationList 与 节点 Taint 进行匹配,配对成功的项越多,则得分越低。
ImageLocalityPriority:
根据Node上是否存在一个pod的容器运行所需镜像大小对优先级打分,分值为0-10。遍历全部Node,
如果某个Node上pod容器所需的镜像一个都不存在,分值为0;
如果Node上存在Pod容器部分所需镜像,则根据这些镜像的大小来决定分值,镜像越大,分值就越高;如果Node上存在pod所需全部镜像,分值为10。
EqualPriority :
是一个优先级函数,它给予所有节点相等权重。
MostRequestedPriority :
在 ClusterAutoscalerProvider 中,替换 LeastRequestedPriority,给使用多资源的节点,更高的优先级。
计算公式为:(cpu(10 sum(requested) / capacity) + memory(10 sum(requested) / capacity)) / 2

 进一步总结:

Predicates阶段回答“能不能”的问题:首先遍历全部节点,过滤掉不满足条件的节点,这一阶段输出的所有满足要求的Node将被记录并作为第二阶段的输入。
Priorities阶段是回答“哪个更适合的问题”:即再次对节点进行筛选,筛选出最适合运行Pod的节点。
如果在预选(Predicates)过程中,如果所有的节点都不满足条件,Pod 会一直处在Pending 状态,直到有节点满足条件,这期间调度器会不断的重试。经过节点过滤后,如多个节点满足条件,会按照节点优先级(priorities)大小对节点排序,最后选择优先级最高的节点部署Pod

调度过程的简单图示如下:

 

 要想获得所有节点最终的权重分值,就要先计算每个优先级函数对应该节点的分值,然后计算总和。因此不管过程如何,如果有 N 个节点,M 个优先级函数,一定会计算 M*N 个中间值,构成一个二维表格:

 

 最后,会把表格中按照节点把优先级函数的权重列表相加,得到最终节点的分值。上面代码就是这个过程,中间过程可以并发计算(下文图中的workQueue),以加快速度。

 参考文档:

https://www.kubernetes.org.cn/1613.html

https://www.infoq.cn/article/or7CRphTDlX1IVhsFNgk

http://dockone.io/article/2885

http://dockone.io/article/8854



 

posted @ 2019-05-18 09:51  屌丝的IT  阅读(4203)  评论(0编辑  收藏  举报