前言

在近期的工作中,咱们发现 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.cpuspec.containers[].resources.limits.memoryspec.containers[].resources.requests.cpuspec.containers[].resources.requests.memory

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

Kubernetes 的服务质量

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

  • Guaranteed:当 Pod 里的每一个 Container 都同时设置了 requests 和 limits,并且 requests 和 limits 值相等的时候,这个 Pod 就属于 Guaranteed 类别 。
  • Burstable:而当 Pod 不满足 Guaranteed 的条件,但至多有一个 Container 设置了 requests。那么这个 Pod 就会被划分到 Burstable 类别。
  • BestEffort:而如果一个 Pod 既没有设置 requests,也没有设置 limits,那么它的 QoS 类别就是 BestEffort。

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

memory.available<100Minodefs.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 Pod0      => 其余过程    02~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 做调度抉择蕴含三个步骤:

  • 过滤:调用一组叫作 Predicate 的调度算法,将所有满足 Pod 调度需要的 Node 选出来;
  • 打分:调用一组叫作 Priority 的调度算法,给每一个可调度 Node 进行打分;
  • 绑定:调度器将 Pod 对象的 nodeName 字段的值,批改为得分最高的 Node。

    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 参数来开启调度打分日志。

自定义配置

如果官网默认的过滤和打分策略,无奈满足理论业务,咱们能够自定义配置:

  • 调度策略:容许你批改默认的过滤 断言(Predicates) 和打分 优先级(Priorities) 。
  • 调度配置:容许你实现不同调度阶段的插件, 包含:QueueSort, Filter, Score, Bind, Reserve, Permit 等等。 你也能够配置 kube-scheduler 运行不同的配置文件。

解决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。

  • Kubernetes Metrics Server:是 kubernetes 监控体系中的外围组件之一,它负责从 kubelet 收集资源指标,而后对这些指标监控数据进行聚合(依赖kube-aggregator),并在 Kubernetes Apiserver 中通过 Metrics API( /apis/metrics.k8s.io/) 公开裸露它们;
  • Prometheus Server: 是一款基于时序数据库的开源监控告警零碎,非常适合 Kubernetes 集群的监控。基本原理是通过 Http 协定周期性抓取被监控组件的状态,任意组件只有提供对应的 Http 接口就能够接入监控。不须要任何 SDK 或者其余的集成过程。这样做非常适合做虚拟化环境监控零碎,比方 VM、Docker、Kubernetes 等。官网地址:https://prometheus.io/
  • SignalFx:是一家基础设施及利用实时云监控服务商,它采纳了一个低提早、可扩大的流式剖析引擎,以监督微服务(涣散耦合、独立部署的利用组件汇合)和协调的容器环境(如Kubernetes和Docker)。官网地址:https://www.signalfx.com/

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已移至其余节点;
  • 原始调度决策不再实用,因为在节点中增加或删除了污点或标签,不再满足 pod/node 亲和性要求。

当然咱们能够去手动做一些集群的均衡,比方手动去删掉某些 Pod,触发从新调度就能够了,然而显然这是一个繁琐的过程,也不是解决问题的形式。为了解决理论运行中集群资源无奈充分利用或节约的问题,能够应用 descheduler 组件对集群的 Pod 进行调度优化,descheduler 能够依据一些规定和配置策略来帮忙咱们从新均衡集群状态,其外围原理是依据其策略配置找到能够被移除的 Pod 并驱赶它们,其自身并不会进行调度被驱赶的 Pod,而是依附默认的调度器来实现,descheduler 重均衡原理可参见官网。

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

参考资料

  • kubernetes官网:https://kubernetes.io/zh/
  • 极客工夫《深刻分析 Kubernetes》专栏(40~44章节)
  • k8s 调度不平均问题解决:https://blog.csdn.net/trntaken/article/details/122377896
  • 最全的k8s调度策略:https://cloud.tencent.com/developer/article/1644857
  • k8s之QoS:https://blog.csdn.net/zenglingmin8/article/details/121152679
  • 当一个 Pod 被调度时,k8s外部产生了什么?https://www.bbsmax.com/A/n2d9Neo0zD/
  • k8s学习笔记-调度介绍:https://www.cnblogs.com/centos-python/articles/10884738.html
  • Kubernetes 调度均衡器 Descheduler 应用:https://zhuanlan.zhihu.com/p/475102379

本文由mdnice多平台公布