• Kubernetes控制平面组件:Scheduler调度器



    建议:看下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
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40

    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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    这时候如果启动一个不带资源控制的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.”
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    为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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    三、多调度器

    如果默认的调度器不满足要求,比如需要批处理调度。那么可以部署自定义的调度器。并且,在整个集群中还可以同时运行多个调度器实例,通过 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,这是一个完全新的调度器

  • 相关阅读:
    HTTP Get 和 Post 的区别
    如何指定JVM target 为1.8
    地球系统模式(CESM)实践技术
    Android简易音乐重构MVVM Java版-新增首页最近播放歌曲展示及底部音乐bar+日推功能(十六)
    现代C++(Modern C++)基本用法实践:五、智能指针
    MongoDB聚合运算符:$bottom
    Spring 源码阅——忽略感知接口对应成员变量的自动装配
    Java数组的定义与使用
    高校/中职大数据实验室建设方案
    抽象工厂模式-C++实现
  • 原文地址:https://blog.csdn.net/weixin_44571270/article/details/126680852