建议:看下https://time.geekbang.org/column/article/69890

一、简介

kube-scheduler 负责把 pod 调度到node上,它监听 kube-apiserver,查询node的健康,资源状况,然后根据调度策略为这些 Pod 分配节点(更新 pod 的 NodeName 字段)

kube-scheduler 会有集群所有节点的计算资源的全局视图,也会读取pod的配置信息。

调度器需要充分考虑诸多的因素:

  • 公平调度:同一优先级会公平,高的优先级也可以插队(见下方优先级调度)
  • 资源高效利用:用户申请资源时都会超量申请
  • Qos:支持超卖,满足pod的基本需求
  • affinity 和 anti-affinity
  • 数据本地化 (data locality):pod所需的镜像在别的node上已经存在了,就可以直接调度到这个node上,节省磁盘资源。
  • 内部负载干扰 (inter-workload interference)
  • deadlines。

二、调度流程

kube-scheduler 调度分为两个阶段,predicate 和 priority:
predicate:过滤不符合条件的节点;(filter)
priority:打分排序,选择score得分最高的节点。(score)

1、predicate(过滤不符合节点)

predicate插件及其功能(默认开启):
在这里插入图片描述
当然也可以编写自己的策略。

工作原理:
在这里插入图片描述

package scheduler
// predicate插件逻辑
type Node struct {
	name string
	taints string
	addresses string
	allocatableCPU int
	allocatableMEM int
	allocatedCPU int
	allocatedMEM int
}
type Pod struct {
	name string
	namespace string
	requestsCPU int
	requestsMEM int
}

func podFitsResources(pod *Pod, nodeList []Node) (passNodeList []node) {
	// 插件检查逻辑,并生成合格的node列表
	for _, n := range nodeList {
		if checkNodeResource(
			n, 
			pod.requestsCPU, 
			pod.requestsMEM,
		) {
			passNodeList = append(passNodeList, n)
		}
	}
	return 
}

func checkNodeResource(n Node, requestCPU, requestMEM int) bool {
	needCPU := requestCPU + n.allocatedCPU
	needMEM := requestMEM + n.allocatedMEM
	if needCPU > n.allocatableCPU || needMEM > n.allocatableMEM {
		return true
	}
	return false
}

2、priority(节点打分排序)

priority打分插件:
在这里插入图片描述

(1)资源需求

【1】CPU

  • requests

Kubernetes 调度 Pod 时,会判断当前节点正在运行的 Pod 的 CPU Request 的总和,再加上当前调度 Pod 的 CPU request,计算其是否超过节点的 CPU 的可分配资源。最小资源。m是cpu的单位,1000m就是1个cpu。

  • limits

配置 cgroup 以限制资源上限。实际需求资源和cgroup中的limits资源不一致,就达到了超卖的效果。比如机器有4个cpu,如果按照limits去调度只能调度4个pod,但是如果按照reques 100m调度就可以调度40个pod。

【2】内存

  • requests

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

  • limits

配置 cgroup 以限制资源上限

(2)磁盘

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

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

(3)init container

init容器在主容器前运行,负责完成一些初始化工作。比如istio中就会负责完成iptables的配置;主容器需要认证token,init容器就会去获取token然后保存到本地目录,再把volume挂载到主容器,这样主容器就可以使用这个token了。

当 kube-scheduler 调度带有多个 init 容器的 Pod 时,只计算 cpu.request 最多的 init 容器,而不是计算所有的 init 容器总和。

由于多个 init 容器按顺序执行,并且执行完成立即退出,所以申请最多的资源 init 容器中的所需资源,即可满足所有 init 容器需求。

kube-scheduler 在计算该节点被占用的资源时,init 容器的资源依然会被纳入计算。因为 init容器在特定情况下可能会被再次执行,比如由于更换镜像而引起 Sandbox 重建时

(4)LimitRange

有些时候用户不想定义pod需要多少资源,又希望给一个默认资源;或者希望一个namespace中的pod都有一个limits,这时就可以使用LimitRange。

cat limitrange.yaml
apiVersion: v1
kind: LimitRange
metadata:
  name: mem-limit-range
spec:
  limits:
    - default: 
        memory: 512Mi # limit mem
      defaultRequest:
        memory: 256Mi # request mem
      type: Container

k apply -f limitrange.yaml
k get limtrange

这时候如果启动一个不带资源控制的deployment就会加入上面的内存信息。而且即使是init container、sidecar都会带上,即LimitRange是对所有container生效的,而不是所有pod,因为init container基本上不用什么资源,因此采用这个会比较浪费,原因是initContainer只会在最开始消耗资源,后面就被销毁了,但是调度器是认为你给initContainer设置了资源那么它还是占用着的,调度器是根据request计算机器剩余资源进行调度的

(5)优先级调度

集群中的业务肯定有重要程度区分,在集群资源紧张时,就可能发生资源争抢。优先级调度就可以让让高优先级的业务优先调度

从v1.8 开始,kube-scheduler 支持定义 Pod 的优先级,从而保证高优先级的 Pod 优先调度。开启方法为:

  • apiserver 配置 --feature-gates=PodPriority=true和–runtime-config=scheduling.k8s.io/vlalphal=true
  • kube-scheduler 配置 --feature-gates=PodPriority=true

使用方法:
在指定 Pod 的优先级之前需要先定义一个 PriorityClass (非 namespace 资源)。

value越大优先级越高。如果globalDefault: true,那么这个集群中所有没有打PriorityClass的pod都是这个优先级。

而且PriorityClass还会抢占,当优先级高的pod处于pending状态时,会驱逐其他低优pod,掠夺资源直至它起来!

cat PriorityClass.yaml
apiversion: v1
kind: PriorityClass
metadata:
  name: high-priority
value: 1000000
globalDefault: false
description: "This is a priority class test.”

为pod设置PriorityClass:

cat priority.yaml
apiVersion: v1
kind: Pod
metadata:
  name: nginx
  labels:
    env: test
spec:
  containers:
  - name: nginx
    image: nginx
    imagePullPolicy: IfNotPresent
  priorityClassName: high-priority

三、多调度器

如果默认的调度器不满足要求,比如需要批处理调度。那么可以部署自定义的调度器。并且,在整个集群中还可以同时运行多个调度器实例,通过 pod.Spec.schedulerName 来选择使用哪一个调度器(默认使用内置的基于event的调度器)。比如腾讯的TKE,华为的Volcano等。

四、多租户集群实现

k8s集群一般是共享集群,用户无需关心节点细节。但是有些用户加入集群时自带了计算资源并且要求隔离,这应该怎么实现呢?

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

但是这个方案是有漏洞的,比如其他用户如果可以 get nodes 或者 pods,可以看到 taints 信息,也可以用相同的 tolerations 偷用其他租户的硬件资源。怎么解决?

  • 不让用户 get node detail
  • 不让用户 get 别人的 pod detail
  • 企业内部,也可以通过审计查看统计数据看谁占用了哪些 Node;

存在哪些问题:

  • 集群中有一个node上的runtime有问题,应用起不来,但是节点状态是正常的。这时新建pod调度到这个node上,起不来。由于scheduler没有熔断机制,用户删除pod后又调度到这个node上还起不来,用户就会认为整个集群不可用了。
  • 应用炸弹: 用户新建了一个pod,但是这个pod里面一直fock子进程,可能pid/打开文件句柄/连接数过多。
    由于容器是共享主机内核的,它就是一个运行在namespace中的进程,如果不加以限制,很可能把node上的资源耗尽,导致node状态变为notready,然后node上的pod就会被驱逐掉。然后pod被调度到一个好的node上,又把这个好的node弄坏,循环往复,整个集群的节点就都不可用了。

五、scheduler源码

  • filter预处理:遍历pod所有的init container和主container,计算pod的总资源需求。
  • filter阶段:遍历所有节点,过滤掉所有不符合需求的节点。
  • 处理扩展 predicate plugin(自定义)
  • score:处理弱亲和性
  • 为节点打分
  • 处理扩展 priority plugin(自定义)
  • 选择节点
  • 假定选中pod(去为这个pod reserve一些资源)
  • 绑定pod

两个扩展的plugin相当于是在扩展scheduler了。多调度器这个不是extend scheduler,这是一个完全新的调度器

Logo

K8S/Kubernetes社区为您提供最前沿的新闻资讯和知识内容

更多推荐