Kubemetes Scheduler 在整个系统中承担了承上启下的重要功能,承上是指它负责接收Controller Manager 创建的新Pod,为其安排一个落脚的家(目标 Node);启下是指安置工作完成后,目标 Node 上的 kubelet 服务进程接管后继工作,负责 Pod 生命周期中的下半生。
具体来说,Kubernetes Scheduler的作用是将待调度的Pod按照特定的调度算法和调度策略绑定到集群的某个合适的Node上,并将绑定信息存储到etcd中。
在整个调度过程中设计三个调度对象,分别是:带调度的Pod列表、可用的Node列表、调度算法策略。
简单地说,就是通过调度算法策略为待调度Pod列表中每个Pod 从Node 列表中选择一个最适合的 Node 。随后节点上的kubelet通过API Server监听到Kubernetes scheduler产生的Pod绑定事件,获取对应的Pod清单,下载image镜像,并启动容器。
流程图如下:
我们已经弄清楚kube-scheduler在做调度这件事。那问题来了,
如果只是单个节点,我认为操作系统自身的调度机制能够帮助我们解决了资源分配的问题。但是大的环境下我们采用了分布式服务,将进程部署在多台节点上组成一个集群提供服务。这时候资源如何进行分配就成了一个问题。这是一个大的背景。那我们从技术角度上去看,你无非就是要将进程部署到不同节点嘛,随机分配下不就好了吗,还需要用个调度吗?我们知道不同的业务功能在微服务的情况下可能对应不同的进程,不同的业务逻辑对于进程的资源要求也会不一样。如何我们随机分配进程到节点上,很容易就出现各个节点资源不平均,最终导致整个系统的不稳定。
一般情况下,我们可能会考虑,根据资源的要求去判断节点资源是否满足,如果满足则指定为该节点。咋一看好像问题不大,那实际上pod的调度却要考虑到方方面面
controller-manager创建了deployment对应的pod资源,而这个pod实际上却不知道应该去哪个节点上。那kube-scheduler实际要做的可以看成三个步骤。
重点是第二个步骤,调度算法是如何选择一个合适的node呢?
整个调度的一个过程:
预选阶段,优选阶段和抢占机制是整个调度的重要实现逻辑,我们可以具体看看这几个机制具体是怎样的。
预选阶段
过滤条件包含如下:
PodFitsHostPorts:检查Pod容器所需的HostPort是否已被节点上其它容器或服务占用。如果已被占用,则禁止Pod调度到该节点。
PodFitsHost:检查Pod指定的NodeName是否匹配当前节点。
PodFitsResources:检查节点是否有足够空闲资源(例如CPU和内存)来满足Pod的要求。
PodMatchNodeSelector:检查Pod的节点选择器(nodeSelector)是否与节点(Node)的标签匹配
NoVolumeZoneConflict:对于给定的某块区域,判断如果在此区域的节点上部署Pod是否存在卷冲突。
NoDiskConflict:根据节点请求的卷和已经挂载的卷,评估Pod是否适合该节点。
MaxCSIVolumeCount:决定应该附加多少CSI卷,以及该卷是否超过配置的限制。
CheckNodeMemoryPressure:如果节点报告内存压力,并且没有配置异常,那么将不会往那里调度Pod。
CheckNodePIDPressure:如果节点报告进程id稀缺,并且没有配置异常,那么将不会往那里调度Pod。
CheckNodeDiskPressure:如果节点报告存储压力(文件系统已满或接近满),并且没有配置异常,那么将不会往那里调度Pod。
CheckNodeCondition:节点可以报告它们有一个完全完整的文件系统,然而网络不可用,或者kubelet没有准备好运行Pods。如果为节点设置了这样的条件,并且没有配置异常,那么将不会往那里调度Pod。
PodToleratesNodeTaints:检查Pod的容忍度是否能容忍节点的污点。
CheckVolumeBinding:评估Pod是否适合它所请求的容量。这适用于约束和非约束PVC。
优选阶段
过滤条件包含如下:
SelectorSpreadPriority:对于属于同一服务、有状态集或副本集(Service,StatefulSet or ReplicaSet)的Pods,会将Pods尽量分散到不同主机上。
InterPodAffinityPriority:策略有podAffinity和podAntiAffinity两种配置方式。简单来说,就说根据Node上运行的Pod的Label来进行调度匹配的规则,匹配的表达式有:In, NotIn, Exists, DoesNotExist,通过该策略,可以更灵活地对Pod进行调度。
LeastRequestedPriority:偏向使用较少请求资源的节点。换句话说,放置在节点上的Pod越多,这些Pod使用的资源越多,此策略给出的排名就越低。
MostRequestedPriority:偏向具有最多请求资源的节点。这个策略将把计划的Pods放到整个工作负载集所需的最小节点上运行。
RequestedToCapacityRatioPriority:使用默认的资源评分函数模型创建基于ResourceAllocationPriority的requestedToCapacity。
BalancedResourceAllocation:偏向具有平衡资源使用的节点。
NodePreferAvoidPodsPriority:根据节点注释scheduler.alpha.kubernet .io/preferAvoidPods为节点划分优先级。可以使用它来示意两个不同的Pod不应在同一Node上运行。
NodeAffinityPriority:根据preferredduringschedulingignoredingexecution中所示的节点关联调度偏好来对节点排序。
TaintTolerationPriority:根据节点上无法忍受的污点数量,为所有节点准备优先级列表。此策略将考虑该列表调整节点的排名。
ImageLocalityPriority:偏向已经拥有本地缓存Pod容器镜像的节点。
ServiceSpreadingPriority:对于给定的服务,此策略旨在确保Service的Pods运行在不同的节点上。总的结果是,Service对单个节点故障变得更有弹性。
EqualPriority:赋予所有节点相同的权值1。
EvenPodsSpreadPriority:实现择优 pod的拓扑扩展约束
抢占 (Preemption) 指的是终止低优先级的 Pods 以便高优先级的 Pods 可以 调度运行的过程。
Pod 被创建后会进入队列等待调度。 调度器从队列中挑选一个 Pod 并尝试将它调度到某个节点上。 如果没有找到满足 Pod 的所指定的所有要求的节点,则触发对悬决 Pod 的抢占逻辑。 让我们将悬决 Pod 称为 P。抢占逻辑试图找到一个节点, 在该节点中删除一个或多个优先级低于 P 的 Pod,则可以将 P 调度到该节点上。 如果找到这样的节点,一个或多个优先级较低的 Pod 会被从节点中驱逐。 被驱逐的 Pod 消失后,P 可以被调度到该节点上。
Scheduler 的调度策略启动配置目前支持三种方式,配置文件 / 命令行参数 / ConfigMap。调度策略可以配置指定调度主流程中要用哪些过滤器 (Predicates)、打分器 (Priorities) 、外部扩展的调度器 (Extenders),以及最新支持的 SchedulerFramwork 的自定义扩展点 (Plugins)。
Scheduler 在启动的时候通过 K8s 的 informer 机制以 List+Watch 从 kube-apiserver 获取调度需要的数据例如:Pods、Nodes、Persistant Volume(PV), Persistant Volume Claim(PVC) 等等,并将这些数据做一定的预处理作为调度器的的 Cache。
通过 Informer 将需要调度的 Pod 插入 Queue 中,Pipeline 会循环从 Queue Pop 等待调度的 Pod 放入 Pipeline 执行。
调度流水线 (Schedule Pipeline) 主要有三个阶段:Scheduler Thread,Wait Thread,Bind Thread。
Filter 阶段用于选择符合 Pod Spec 描述的 Nodes;Score 阶段用于从 Filter 过后的 Nodes 进行打分和排序;Reserve 阶段将 Pod 跟排序后的最优 Node 的 NodeCache 中,表示这个 Pod 已经分配到这个 Node 上, 让下一个等待调度的 Pod 对这个 Node 进行 Filter 和 Score 的时候能看到刚才分配的 Pod。
整个调度流水线只有在 Scheduler Thread 阶段是串行的一个 Pod 一个 Pod 的进行调度,在 Wait 和 Bind 阶段 Pod 都是异步并行执行。