Kubernetes kube-scheduler调度器

APIServer职责主要是认证,鉴权,准入,它判断一个请求是谁发起的,发起人有没有相应的权限,这个请求是不是合法的,以及从apiserver这端觉得要对原始请求变更一些属性,那么它就可以在这里面去做。

apserver经过这些环节之后,这些检查都通过了,请求也合法,那么它会将请求存在etcd里面。

apiserver本身是k8s集群当中唯一和etcd数据库通信的这样一个组件,其他所有组件都需要和apiserver去通信,去获取数据的变更信息。

kubelet分为自己的框架代码,以及包含下面的接口抽象,它将运行时抽象为cri,将网络抽象为cni,将存储抽象为csi。

kube-scheduler


kube-scheduler负责分配调度Pod到集群内的节点上,它监听kube-apiserver,查询还未分配Node 的 Pod,然后根据调度策略为这些Pod分配节点(更新 Pod 的 NodeName 字段)。调度器需要充分考虑诸多的因素∶

  • 公平调度(当接受到很多请求的时候,确保我们能够公平的去处理请求,大家都是平等的,本着先到先服务的原则去做调度,这是公平性。还有一些不公平的因素,比如调度的时候有调度优先级,我的一个应用特别重要,希望插队放到前面去,所以在这k8s提供了完备的支持,对于同一个调度优先级,我是公平的原则,不同的优先级会插队放在前面,优先级越高的越优先调度)
  • 资源高效利用(找到最合适的节点调度过去)
  • QoS
  • affinity和anti-affinity
  • 数据本地化(data locality)
  • 内部负载干扰(inter-workload interference)
  • deadlines。

调度器会去监听集群里面所有计算节点的信息,它要知道当前集群里面有多少个计算节点,这些节点的健康状态如何,它们的资源使用情况如何,有多少资源使用了,有多少可分配。

每个计算节点都会将自己的信息上报给apiserver,我们的调度器就会去watch apiserver,获取这些节点的信息,那么调度器就有一个集群的全局视图。

一方面它有集群所有节点的计算资源的全局视图,另外一方面它能够接受到用户的pod,对它来说就是调度请求,来寻找一个最佳节点,找到最佳节点就完成了pod和节点的绑定关系。本质上就是将pod的nodename字段填充了。

用户建立pod的时候是不去填nodename的,因为不知道pod会被调度到哪,调度器会去看一个pod的nodename为空,那就说明你要去调度的,所以它就会去做调度,找到合适的节点,将nodename填上去。

调度器


kube-scheduler调度分为两个阶段,predicate和priority∶

predicate∶过滤不符合条件的节点;filter

priority∶优先级排序,选择优先级最高的节点。score

filter:有100台机器,你有一个pod请求,我得先去看看有哪些节点不满足你的需求,先将不满足需求的节点过滤掉。

过滤之后可能还剩下10台能够满足你的需求,那么就需要排序了,排序就是按照各种因素去打分了,

Predicates策略


因为调度的时候,需要考虑诸多因素,每个因素对于调度器来说都是一个插件。做predicate的过程相对于遍历这些predict插件,然后一个个去执行。

PodFitsResources:检查Node的资源是否充足,包括允许的Pod数量、CPU、内存、GPU个数以及其他的OpaquelntResources。(先看看哪些节点是不满足pod资源的,没有合适资源机器全部刷掉)

PodFitsHostPorts:检查是否有Host Ports冲突。

PodFitsPorts: 同PodFitsHostPorts。(有些pod希望占用主机端口,那我去调度的时候要去看这个端口还是不是空余的,如果这个端口被占用了,说明这个节点就不能安置这个pod了)

HostName∶ 检查pod.Spec.NodeName是否与候选节点一致。

MatchNodeSelector∶ 检查候选节点的pod.Spec.NodeSelector是否匹配。(只会调度到这些节点)

NoVolumeZoneConflict∶ 检查volume zone 是否冲突。

MatchlnterPodAffinity∶检查是否匹配Pod的亲和性要求。

NoDiskConflict∶ 检查是否存在Volume冲突,仅限于GCEPD、AWS EBS、Ceph RBD 以及 iSCSI。

PodToleratesNodeTaints∶ 检查Pod是否容忍Node Taints。

CheckNodeMemoryPressure∶ 检查Pod是否可以调度到MemoryPressure的节点上。

CheckNodeDiskPressure∶ 检查Pod是否可以调度到DiskPressure的节点上。

NoVolumeNodeConflict∶ 检查节点是否满足Pod所引用的Volume的条件。

还有很多其他策略,你也可以编写自己的策略。

Predicates plugin 工作原理


当去做pod调度的时候,就会一个个去遍历predicate的plugin,我就一个一个plugin去跑,经过每一个plugin,我都会过滤一批机器,经过每一个plugin都会过滤掉一批机器,最后就剩下符合调度需求的机器。

Priorities策略


对于priority来说也有很多的插件,针对每个插件,他也是去遍历每个插件去算分,最后会给每个节点打分汇总,最终将得分最高的节点排在前面。

SelectorSpreadPriority∶优先减少节点上属于同一个Service或Replication Controller的Pod数量。

InterPodAffinityPriority∶优先将Pod调度到相同的拓扑上(如同一个节点、Rack、Zone等)。

LeastRequestedPriority∶优先调度到请求资源少的节点上。

BalancedResourceAllocation∶优先平衡各节点的资源使用。

NodePreferAvoidPodsPriority∶ alpha.kubernetes.io/preferAvoidPods字段判断,权重为10000,避免其他优先级策略的影响。

资源需求


CPU

    requests

       Kubernetes 调度 Pod 时,会判断当前节点正在运行的 Pod 的 CPU Request 的总和,再加上当前调度Pod的CPU request,计算其是否超过节点的CPU的可分配资源。

   limits

      配置cgroup以限制资源上限。

内存

    requests

      判断节点的剩余内存是否满足Pod的内存请求量,以确定是否可以将Pod调度到该节点。

 limits

      配置cgroup以限制资源上限。

磁盘资源需求


容器临时存储(ephemeral storage)包含日志和可写层数据,可以通过定义 Pod Spec 中的limits.ephemeral-storage和requests.ephemeral-storage来申请。

Pod 调度完成后,计算节点对临时存储的限制不是基于 cgroup 的,而是由 kubelet 定时获取容器的日志和容器可写层的磁盘使用情况,如果超过限制,则会对Pod进行驱逐。

Init Container的资源需求


在一个pod里面除了主容器,还有init container,做些初始化的工作,istio就有initcontainer,它起来之后会去配置本地的iptables规则,配置完之后就退出了。

比如说应用要通过jwt token去访问其他的应用,应用和应用之间需要做认证鉴权的,我们就会使用初始化容器,因为这个token是一次性获取的,我们就会使用初始化的容器去获取这个token,这个token获取完毕就存在本地硬盘,然后硬盘通过volume mount到一个主容器,和主容器mount到同一个路径,那么就可以被读取了。

initcontainer很多时候是主容器预先加载资源的时候,加载配置的时候就可以让其去做。

  • 当 kube-scheduler调度带有多个init 容器的 Pod 时,只计算 cpu.request 最多的 init 容器,而不是计算所有的init容器总和。(也可以设置request limit)
  • 由于多个init 容器按顺序执行,并且执行完成立即退出,所以申请最多的资源init 容器中的所需资源,即可满足所有init容器需求。
  • kube-scheduler在计算该节点被占用的资源时,init 容器的资源依然会被纳入计算。因为init容器在特定情况下可能会被再次执行,比如由于更换镜像而引起Sandbox重建时。

把Pod调度到指定Node上


可以通过 nodeSelector、nodeAffinity、podAffinity以及Taints和tolerations等来将Pod调度到需要的Node上。

也可以通过设置 nodeName 参数,将Pod 调度到指定Node节点上。

比如,使用nodeSelector,首先给Node加上标签∶kubectl label nodes <your-node-name>disktype=ssd

接着,指定该Pod只想运行在带有disktype=ssd 标签的Node上。

nodeAffinity 


nodeAffinity其实是上面nodeselector的扩展,硬亲和其实在predict阶段去做,在predicate阶段去看看节点满不满足我亲和性需求,如果满足才能作为备选。

软亲和是在priorities阶段去做,满足需求的排在前面,不满足需求的排在后面,就是参与打分。

nodeselector太单一了,后面就被亲和性和反亲和性替换掉了,可以认为节点的亲和性是nodeselector的一个演进。

nodeAffinity 目前支持两种∶ requiredDuringSchedulinglgnoredDuringExecution 和preferredDuringSchedulinglgnoredDuringExecution,分别代表必须满足条件和优选条件。

比如下面的例子代表调度到包含标签Kubernetes.io/e2e-az-name并且值为e2e-az1或e2e-az2的Node上,并且优选还带有标签 another-node-label-key=another-node-label-value 的 Node。

 prefer其实是用来打分的,它有个属性叫weight,就是它打分的一个权重,你可以有很多prefer的规则,每个规则具体多少占比就是通过这个weight。

podAffinity


podAffinity基于Pod的标签来选择Node,仅调度到满足条件Pod所在的Node上,支持podAffinity和podAntiAffinity。这个功能比较绕,以下面的例子为例∶

如果一个"Node所在Zone中包含至少一个带有security=S1标签且运行中的Pod",那么可以调度到该Node,不调度到"包含至少一个带有security=S2标签且运行中Pod"的Node上。

Taints和Tolerations


Taints和Tolerations用于保证Pod不被调度到不合适的Node上,其中Taint应用于Node上,而

Toleration则应用于Pod上。

目前支持的Taint类型∶

  • NoSchedule∶新的Pod不调度到该Node上,不影响正在运行的Pod
  • PreferNoSchedule∶soft版的NoSchedule,尽量不调度到该Node上
  • NoExecute∶新的Pod不调度到该Node上,并且删除(evict)已在运行的Pod。Pod可以增加一个时间(tolerationSeconds)。

k8s也在用taint,当一个节点不响应或者出现问题的时候,k8s就会将这批节点打上unhealthy这样的taint。这个taint的effect是NoExecute,然后tolerate second是600s,也就是在600s之后这个节点上面所有的pod都会被驱逐掉。 

然而,当Pod的Tolerations匹配Node的所有Taints的时候可以调度到该Node上,当Pod是已经运行的时候,也不会被删除(evicted)。

另外对于NoExecute,如果Pod增加了一个tolerationSeconds,则会在该时间之后才删除Pod。

多租户Kubernetes 集群-计算资源隔离


特别是在公有云用户,有些场景是同一个控制平面,管理多个节点,这些不同的节点为不同的公有云租户服务的。那这些用户有些诉求,比如应用不能和其他人部署在一起,我希望完全隔离,那么就可以将不同的计算节点打上不同的taint。

k8s也在用taint,当一个节点不响应或者出现问题的时候,k8s就会将这批节点打上unhealthy这样的taint。这个taint的effect是NoExecute,然后tolerate second是600s,也就是在600s之后这个节点上面所有的pod都会被驱逐掉。这样其实保证了节点出现故障,那么这上面的pod是需要被排空的,这是k8s自身使用taint的一个场景。

Kubernetes 集群一般是通用集群,可被所有用户共享,用户无需关心计算节点细节。但往往某些自带计算资源的客户要求∶

  • 带着计算资源加入Kubernetes 集群
  • 要求资源隔离。

实现方案∶

  • 将要隔离的计算节点打上Taints
  • 在用户创建创建Pod 时,定义 tolerations 来指定要调度到 node taints。

猜你喜欢

转载自blog.csdn.net/qq_34556414/article/details/125879601