Kubernetes 调度均衡器 Descheduler 使用

Posted CNCF

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Kubernetes 调度均衡器 Descheduler 使用相关的知识,希望对你有一定的参考价值。

从 kube-scheduler 的角度来看,它是通过一系列算法计算出最佳节点运行 Pod,当出现新的 Pod 进行调度时,调度程序会根据其当时对 Kubernetes 集群的资源描述做出最佳调度决定,但是 Kubernetes 集群是非常动态的,由于整个集群范围内的变化,比如一个节点为了维护,我们先执行了驱逐操作,这个节点上的所有 Pod 会被驱逐到其他节点去,但是当我们维护完成后,之前的 Pod 并不会自动回到该节点上来,因为 Pod 一旦被绑定了节点是不会触发重新调度的,由于这些变化,Kubernetes 集群在一段时间内就可能会出现不均衡的状态,所以需要均衡器来重新平衡集群。

当然我们可以去手动做一些集群的平衡,比如手动去删掉某些 Pod,触发重新调度就可以了,但是显然这是一个繁琐的过程,也不是解决问题的方式。为了解决实际运行中集群资源无法充分利用或浪费的问题,可以使用 descheduler 组件对集群的 Pod 进行调度优化,descheduler 可以根据一些规则和配置策略来帮助我们重新平衡集群状态,其核心原理是根据其策略配置找到可以被移除的 Pod 并驱逐它们,其本身并不会进行调度被驱逐的 Pod,而是依靠默认的调度器来实现,目前支持的策略有:

  • RemoveDuplicates
  • LowNodeUtilization
  • RemovePodsViolatingInterPodAntiAffinity
  • RemovePodsViolatingNodeAffinity
  • RemovePodsViolatingNodeTaints
  • RemovePodsViolatingTopologySpreadConstraint
  • RemovePodsHavingTooManyRestarts
  • PodLifeTime
  • 这些策略都是可以启用或者禁用的,作为策略的一部分,也可以配置与策略相关的一些参数,默认情况下,所有策略都是启用的。另外,还有一些通用配置,如下:

  • nodeSelector:限制要处理的节点
  • evictLocalStoragePods: 驱逐使用 LocalStorage 的 Pods
  • ignorePvcPods: 是否忽略配置 PVC 的 Pods,默认是 False
  • maxNoOfPodsToEvictPerNode:节点允许的最大驱逐 Pods 数
  • 我们可以通过如下所示的 DeschedulerPolicy 来配置:

    可以以 JobCronJob 或者 Deployment 的形式运行在 k8s 集群内,同样我们可以使用 Helm Chart 来安装 descheduler

    CronJob 或者 Deployment 方式运行,默认情况下 descheduler 会以一个 critical pod 运行,以避免被自己或者 kubelet 驱逐了,需要确保集群中有 system-cluster-critical 这个 Priorityclass:

    的形式运行,执行周期为 schedule: "*/2 * * * *",这样每隔两分钟会执行一次 descheduler 任务,默认的配置策略如下所示:

    strategies,可以指定 descheduler 的执行策略,这些策略都是可以启用或禁用的,下面我们会详细介绍,这里我们使用默认策略即可,使用如下命令直接安装即可:

    资源对象来平衡集群状态:

    任务,我们可以通过查看日志可以了解做了哪些平衡操作:

    会将 Pod 驱逐进行重调度,但是如果一个服务的所有副本都被驱逐的话,则可能导致该服务不可用。如果服务本身存在单点故障,驱逐的时候肯定就会造成服务不可用了,这种情况我们强烈建议使用反亲和性和多副本来避免单点故障,但是如果服务本身就被打散在多个节点上,这些 Pod 都被驱逐的话,这个时候也会造成服务不可用了,这种情况下我们可以通过配置 PDB(PodDisruptionBudget) 对象来避免所有副本同时被删除,比如我们可以设置在驱逐的时候某应用最多只有一个副本不可用,则创建如下所示的资源清单即可:

    来重新平衡集群状态,那么我们强烈建议给应用创建一个对应的 PodDisruptionBudget 对象进行保护。

    更旧的 Pods,可以通过 podStatusPhases 来配置哪类状态的 Pods 会被驱逐,建议为每个应用程序创建一个 PDB,以确保应用程序的可用性,比如我们可以配置如下所示的策略来驱逐运行超过7天的 Pod:

    用于排除类型,这些类型下的 Pod 不会被驱逐:

    进行配置。

    节点的利用率不足可以通过配置 thresholds 阈值参数来确定,可以通过 CPU、内存和 Pods 数量的百分比进行配置。如果节点的使用率均低于所有阈值,则认为该节点未充分利用。

    此外,还有一个可配置的阈值 targetThresholds,用于计算可能驱逐 Pods 的潜在节点,该参数也可以配置 CPU、内存以及 Pods 数量的百分比进行配置。thresholdstargetThresholds 可以根据你的集群需求进行动态调整,如下所示示例:

    targetThresholds 必须配置相同的类型
  • 参数值的访问是0-100(百分制)
  • 相同的资源类型,thresholds 的配置不能高于 targetThresholds 的配置
  • 如果未指定任何资源类型,则默认是100%,以避免节点从未充分利用变为过度利用。和 LowNodeUtilization 策略关联的另一个参数是 numberOfNodes,只有当未充分利用的节点数大于该配置值的时候,才可以配置该参数来激活该策略,该参数对于大型集群非常有用,其中有一些节点可能会频繁使用或短期使用不足,默认情况下,numberOfNodes 为0。

    污点的 Pod,比如有一个名为 podA 的 Pod,通过配置容忍 key=value:NoSchedule 允许被调度到有该污点配置的节点上,如果节点的污点随后被更新或者删除了,则污点将不再被 Pods 的容忍满足,然后将被驱逐:

    内所需的最小 Pod 数,不过该策略需要 k8s 版本高于1.18才能使用。

    默认情况下,此策略仅处理硬约束,如果将参数 includeSoftConstraints 设置为 True,也将支持软约束。

    提供了两种主要的方式进行过滤:命名空间过滤和优先级过滤。

    参数进行配置,如下所示:

    参数配置,如下所示:

    这个 PriorityClass 类的值。

    比如使用 thresholdPriority

    进行过滤:

    thresholdPriorityClassName,如果指定的优先级类不存在,则 descheduler 不会创建它,并且会引发错误。

    设置为 system-cluster-criticalsystem-node-critical 的 Pod
  • 不属于 RS、Deployment 或 Job 管理的 Pods 不会被驱逐
  • DaemonSet 创建的 Pods 不会被驱逐
  • 使用 LocalStorage 的 Pod 不会被驱逐,除非设置 evictLocalStoragePods: true
  • 具有 PVC 的 Pods 不会被驱逐,除非设置 ignorePvcPods: true
  • LowNodeUtilizationRemovePodsViolatingInterPodAntiAffinity 策略下,Pods 按优先级从低到高进行驱逐,如果优先级相同,Besteffort 类型的 Pod 要先于 BurstableGuaranteed 类型被驱逐
  • annotations 中带有 descheduler.alpha.kubernetes.io/evict 字段的 Pod 都可以被驱逐,该注释用于覆盖阻止驱逐的检查,用户可以选择驱逐哪个Pods
  • 如果 Pods 驱逐失败,可以设置 --v=4descheduler 日志中查找原因,如果驱逐违反 PDB 约束,则不会驱逐这类 Pods

  • 文章转载自k8s技术圈点击这里阅读原文了解更多

    CNCF概况(幻灯片)

    扫描二维码联系我们!



    CNCF (Cloud Native Computing Foundation)成立于2015年12月,隶属于Linux  Foundation,是非营利性组织。 

    CNCF云原生计算基金会)致力于培育和维护一个厂商中立的开源生态系统,来推广云原生技术。我们通过将最前沿的模式民主化,让这些创新为大众所用。请长按以下二维码进行关注。

    解决k8s调度不均衡问题

    前言

    在近期的工作中,我们发现 k8s 集群中有些节点资源使用率很高,有些节点资源使用率很低,我们尝试重新部署应用和驱逐 Pod,发现并不能有效解决负载不均衡问题。在学习了 Kubernetes 调度原理之后,重新调整了 Request 配置,引入了调度插件,才最终解决问题。这篇就来跟大家分享 Kubernetes 资源和调度相关知识,以及如何解决k8s调度不均衡问题。

    Kubernetes 的资源模型

    在 Kubernetes 里,Pod 是最小的原子调度单位。这也就意味着,所有跟调度和资源管理相关的属性都应该是属于 Pod 对象的字段。而这其中最重要的部分,就是 Pod 的 CPU 和内存配置。
    像 CPU 这样的资源被称作“可压缩资源”(compressible resources)。它的典型特点是,当可压缩资源不足时,Pod 只会“饥饿”,但不会退出。
    而像内存这样的资源,则被称作“不可压缩资源(incompressible resources)。当不可压缩资源不足时,Pod 就会因为 OOM(Out-Of-Memory)被内核杀掉。
    Pod 可以由多个 Container 组成,所以 CPU 和内存资源的限额,是要配置在每个 Container 的定义上的。这样,Pod 整体的资源配置,就由这些 Container 的配置值累加得到。
    Kubernetes 里 Pod 的 CPU 和内存资源,实际上还要分为 limits 和 requests 两种情况:

    spec.containers[].resources.limits.cpu
    spec.containers[].resources.limits.memory
    spec.containers[].resources.requests.cpu
    spec.containers[].resources.requests.memory
    

    这两者的区别其实非常简单:在调度的时候,kube-scheduler 只会按照 requests 的值进行调度。而在真正设置 Cgroups 限制的时候,kubelet 则会按照 limits 的值来进行设置。
    这是因为在实际场景中,大多数作业使用到的资源其实远小于它所请求的资源限额,这种策略能有效的提高整体资源的利用率。

    Kubernetes 的服务质量

    服务质量 QoS 的英文全称为 Quality of Service。在 Kubernetes 中,每个 Pod 都有个 QoS 标记,通过这个 Qos 标记来对 Pod 进行服务质量管理,它确定 Pod 的调度和驱逐优先级。在 Kubernetes 中,Pod 的 QoS 服务质量一共有三个级别:

    具体地说,当 Kubernetes 所管理的宿主机上不可压缩资源短缺时,就有可能触发 Eviction 驱逐。目前,Kubernetes 为你设置的 Eviction 的默认阈值如下所示:

    memory.available<100Mi
    nodefs.available<10%
    nodefs.inodesFree<5%
    imagefs.available<15%
    

    当宿主机的 Eviction 阈值达到后,就会进入 MemoryPressure 或者 DiskPressure 状态,从而避免新的 Pod 被调度到这台宿主机上,然后 kubelet 会根据 QoS 的级别来挑选 Pod 进行驱逐,具体驱逐优先级是:BestEffort -> Burstable -> Guaranteed。
    QoS 的级别是通过 Linux 内核 OOM 分数值来实现的,OOM 分数值取值范围在-1000 ~1000之间。在 Kubernetes 中,常用服务的 OOM 的分值如下:

    -1000  => sshd等进程	
    -999   => Kubernetes 管理进程
    -998   => Guaranteed Pod
    0      => 其他进程	0
    2~999  => Burstable Pod 	
    1000   => BestEffort Pod 	
    

    OOM 分数越高,就代表这个 Pod 的优先级越低,在出现资源竞争的时候,就越早被杀掉,分数为-999和-1000的进程永远不会因为 OOM 而被杀掉。

    划重点:如果期望 Pod 尽可能的不被驱逐,就应当把 Pod 里的每一个 Container 的 requests 和 limits 都设置齐全,并且 requests 和 limits 值要相等。

    Kubernetes 的调度策略

    kube-scheduler 是 Kubernetes 集群的默认调度器,它的主要职责是为一个新创建出来的 Pod,寻找一个最合适的 Node。kube-scheduler 给一个 Pod 做调度选择包含三个步骤:

    Kubernetes 官方过滤和打分编排源码如下:
    https://github.com/kubernetes/kubernetes/blob/281023790fd27eec7bfaa7e26ff1efd45a95fb09/pkg/scheduler/framework/plugins/legacy_registry.go

    过滤(Predicate)

    过滤阶段,首先遍历全部节点,过滤掉不满足条件的节点,属于强制性规则,这一阶段输出的所有满足要求的 Node 将被记录并作为第二阶段的输入,如果所有的节点都不满足条件,那么 Pod 将会一直处于 Pending 状态,直到有节点满足条件,在这期间调度器会不断的重试。
    调度器会根据限制条件和复杂性依次进行以下过滤检查,检查顺序存储在一个名为 PredicateOrdering() 的函数中,具体如下表格:

    算法名称默认顺序详细说明
    CheckNodeUnschedulablePred强制1检查节点是否可调度;
    GeneralPred2是一组联合检查,包含了:HostNamePred、PodFitsResourcesPred、PodFitsHostPortsPred、MatchNodeSelectorPred 4个检查
    HostNamePred3检查 Pod 指定的 Node 名称是否和 Node 名称相同;
    PodFitsHostPortsPred4检查 Pod 请求的端口(网络协议类型)在节点上是否可用;
    MatchNodeSelectorPred5检查是否匹配 NodeSelector 节点选择器的设置;
    PodFitsResourcesPred6检查节点的空闲资源(例如,CPU 和内存)是否满足 Pod 的要求;
    NoDiskConflictPred7根据 Pod 请求的卷是否在节点上已经挂载,评估 Pod 和节点是否匹配;
    PodToleratesNodeTaintsPred强制8检查 Pod 的容忍是否能容忍节点的污点;
    CheckNodeLabelPresencePred9检测 NodeLabel 是否存在;
    CheckServiceAffinityPred10检测服务的亲和;
    MaxEBSVolumeCountPred11已废弃,检测 Volume 数量是否超过云服务商 AWS 的存储服务的配置限制;
    MaxGCEPDVolumeCountPred12已废弃,检测 Volume 数量是否超过云服务商 Google Cloud 的存储服务的配置限制;
    MaxCSIVolumeCountPred13Pod 附加 CSI 卷的数量,判断是否超过配置的限制;
    MaxAzureDiskVolumeCountPred14已废弃,检测 Volume 数量是否超过云服务商 Azure 的存储服务的配置限制;
    MaxCinderVolumeCountPred15已废弃,检测 Volume 数量是否超过云服务商 OpenStack 的存储服务的配置限制;
    CheckVolumeBindingPred16基于 Pod 的卷请求,评估 Pod 是否适合节点,这里的卷包括绑定的和未绑定的 PVC 都适用;
    NoVolumeZoneConflictPred17给定该存储的故障区域限制, 评估 Pod 请求的卷在节点上是否可用;
    EvenPodsSpreadPred18检测 Node 是否满足拓扑传播限制;
    MatchInterPodAffinityPred19检测是否匹配 Pod 的亲和与反亲和的设置;

    可以看出,Kubernetes 正在逐步移除某个具体云服务商的服务的相关代码,而使用接口(Interface)来扩展功能。

    打分(Priority)

    打分阶段,通过 Priority 策略对可用节点进行评分,最终选出最优节点。具体是用一组打分函数处理每一个可用节点,每一个打分函数会返回一个 0~100 的分数,分数越高表示节点越优, 同时每一个函数也会对应一个权重值。将每个打分函数的计算得分乘以权重,然后再将所有打分函数的得分相加,从而得出节点的最终优先级分值。权重可以让管理员定义优选函数倾向性的能力,其计算优先级的得分公式如下:

    finalScoreNode = (weight1 * priorityFunc1) + (weight2 * priorityFunc2) ++ (weightn * priorityFuncn)
    

    全部打分函数如下表格所示:

    算法名称默认权重详细说明
    EqualPriority-给予所有节点相等的权重;
    MostRequestedPriority-支持最多请求资源的节点。 该策略将 Pod 调度到整体工作负载所需的最少的一组节点上;
    RequestedToCapacityRatioPriority-使用默认的打分方法模型,创建基于 ResourceAllocationPriority 的 requestedToCapacity;
    SelectorSpreadPriority1属于同一 Service、 StatefulSet 或 ReplicaSet 的 Pod,尽可能地跨 Node 部署(鸡蛋不要只放在一个篮子里,分散风险,提高可用性);
    ServiceSpreadingPriority-对于给定的 Service,此策略旨在确保该 Service 关联的 Pod 在不同的节点上运行。 它偏向把 Pod 调度到没有该服务的节点。 整体来看,Service 对于单个节点故障变得更具弹性;
    InterPodAffinityPriority1实现了 Pod 间亲和性与反亲和性的优先级;
    LeastRequestedPriority1偏向最少请求资源的节点。 换句话说,节点上的 Pod 越多,使用的资源就越多,此策略给出的排名就越低;
    BalancedResourceAllocation1CPU和内存使用率越接近的节点权重越高,该策略不能单独使用,必须和 LeastRequestedPriority 组合使用,尽量选择在部署Pod后各项资源更均衡的机器。
    NodePreferAvoidPodsPriority10000根据节点的注解 scheduler.alpha.kubernetes.io/preferAvoidPods 对节点进行优先级排序。 你可以使用它来暗示两个不同的 Pod 不应在同一节点上运行;
    NodeAffinityPriority1根据节点亲和中 PreferredDuringSchedulingIgnoredDuringExecution 字段对节点进行优先级排序;
    TaintTolerationPriority1根据节点上无法忍受的污点数量,给所有节点进行优先级排序。 此策略会根据排序结果调整节点的等级;
    ImageLocalityPriority1如果Node上存在Pod容器部分所需镜像,则根据这些镜像的大小来决定分值,镜像越大,分值就越高;
    EvenPodsSpreadPriority
    2实现了 Pod 拓扑扩展约束的优先级排序;

    我自己遇到的是“多节点调度资源不均衡问题”,所以跟节点资源相关的打分算法是我关注的重点。
    1、BalancedResourceAllocation(默认开启),它的计算公式如下所示:

    score = 10 - variance(cpuFraction,memoryFraction,volumeFraction)*10
    

    其中,每种资源的 Fraction 的定义是 :Pod 的 request 资源 / 节点上的可用资源。而 variance 算法的作用,则是计算每两种资源 Fraction 之间的“距离”。而最后选择的,则是资源 Fraction 差距最小的节点。
    所以说,BalancedResourceAllocation 选择的,其实是调度完成后,所有节点里各种资源分配最均衡的那个节点,从而避免一个节点上 CPU 被大量分配、而 Memory 大量剩余的情况。
    2、LeastRequestedPriority(默认开启),它的计算公式如下所示:

    score = (cpu((capacity-sum(requested))10/capacity) + memory((capacity-sum(requested))10/capacity))/2
    

    可以看到,这个算法实际上是根据 request 来计算出空闲资源(CPU 和 Memory)最多的宿主机。
    3、MostRequestedPriority(默认不开启),它的计算公式如下所示:

    score = (cpu(10 sum(requested) / capacity) + memory(10 sum(requested) / capacity)) / 2
    

    在 ClusterAutoscalerProvider 中替换 LeastRequestedPriority,给使用多资源的节点更高的优先级。

    你可以修改 /etc/kubernetes/manifests/kube-scheduler.yaml 配置,新增 v=10 参数来开启调度打分日志。

    自定义配置

    如果官方默认的过滤和打分策略,无法满足实际业务,我们可以自定义配置:

    解决k8s调度不均衡问题

    一、按实际用量配置 Pod 的 requeste

    从上面的调度策略可以得知,资源相关的打分算法 LeastRequestedPriority 和 MostRequestedPriority 都是基于 request 来进行评分,而不是按 Node 当前资源水位进行调度(在没有安装 Prometheus 等资源监控相关组件之前,kube-scheduler 也无法实时统计 Node 当前的资源情况),所以可以动态采 Pod 过去一段时间的资源使用率,据此来设置 Pod 的Request,才能契合 kube-scheduler 默认打分算法,让 Pod 的调度更均衡。

    二、为资源占用较高的 Pod 设置反亲和

    对一些资源使用率较高的 Pod ,进行反亲和,防止这些项目同时调度到同一个 Node,导致 Node 负载激增。

    三、引入实时资源打分插件 Trimaran

    但在实际项目中,并不是所有情况都能较为准确的估算出 Pod 资源用量,所以依赖 request 配置来保障 Pod 调度的均衡性是不准确的。那有没有一种通过 Node 当前实时资源进行打分调度的方案呢?Kubernetes 官方社区 SIG 小组提供的调度插件 Trimaran 就具备这样的能力。

    Trimaran 官网地址:https://github.com/kubernetes-sigs/scheduler-plugins/tree/master/pkg/trimaran

    Trimaran 是一个实时负载感知调度插件,它利用 load-watcher 获取程序资源利用率数据。目前,load-watcher支持三种度量工具:Metrics Server、Prometheus 和 SignalFx。

    Trimaran 的架构如下:

    可以看到在 kube-scheduler 打分的过程中,Trimaran 会通过 load-watcher 获取当前 node 的实时资源水位,然后据此打分从而干预调度结果。

    Trimaran 打分原理:https://github.com/kubernetes-sigs/scheduler-plugins/tree/master/kep/61-Trimaran-real-load-aware-scheduling

    四、引入重平衡工具 descheduler

    从 kube-scheduler 的角度来看,调度程序会根据其当时对 Kubernetes 集群的资源描述做出最佳调度决定,但调度是静态的,Pod 一旦被绑定了节点是不会触发重新调度的。虽然打分插件可以有效的解决调度时的资源不均衡问题,但每个 Pod 在长期的运行中所占用的资源也是会有变化的(通常内存会增加)。假如一个应用在启动的时候只占 2G 内存,但运行一段时间之后就会占用 4G 内存,如果这样的应用比较多的话,Kubernetes 集群在运行一段时间后就可能会出现不均衡的状态,所以需要重新平衡集群。
    除此之外,也还有一些其他的场景需要重平衡:

    当然我们可以去手动做一些集群的平衡,比如手动去删掉某些 Pod,触发重新调度就可以了,但是显然这是一个繁琐的过程,也不是解决问题的方式。为了解决实际运行中集群资源无法充分利用或浪费的问题,可以使用 descheduler 组件对集群的 Pod 进行调度优化,descheduler 可以根据一些规则和配置策略来帮助我们重新平衡集群状态,其核心原理是根据其策略配置找到可以被移除的 Pod 并驱逐它们,其本身并不会进行调度被驱逐的 Pod,而是依靠默认的调度器来实现,descheduler 重平衡原理可参见官网。

    descheduler 官网地址:https://github.com/kubernetes-sigs/descheduler

    参考资料

    本文由mdnice多平台发布

    以上是关于Kubernetes 调度均衡器 Descheduler 使用的主要内容,如果未能解决你的问题,请参考以下文章

    解决k8s调度不均衡问题

    解决k8s调度不均衡问题

    01-Kubernetes介绍,基础组件,原理,架构。

    Kubernetes架构全图

    使用 kubeadm 部署 Kubernetes 集群

    Docker集群管理工具-Kubernetes部署记录