关于程序员:解决k8s调度不均衡问题

2次阅读

共计 8562 个字符,预计需要花费 22 分钟才能阅读完成。

前言

在近期的工作中,咱们发现 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 服务质量一共有三个级别:

  • 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<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 做调度抉择蕴含三个步骤:

  • 过滤:调用一组叫作 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 查看节点是否可调度;
GeneralPred 2 是一组联结查看,蕴含了:HostNamePred、PodFitsResourcesPred、PodFitsHostPortsPred、MatchNodeSelectorPred 4 个查看
HostNamePred 3 查看 Pod 指定的 Node 名称是否和 Node 名称雷同;
PodFitsHostPortsPred 4 查看 Pod 申请的端口(网络协议类型)在节点上是否可用;
MatchNodeSelectorPred 5 查看是否匹配 NodeSelector 节点选择器的设置;
PodFitsResourcesPred 6 查看节点的闲暇资源(例如,CPU 和内存)是否满足 Pod 的要求;
NoDiskConflictPred 7 依据 Pod 申请的卷是否在节点上曾经挂载,评估 Pod 和节点是否匹配;
PodToleratesNodeTaintsPred 强制 8 查看 Pod 的容忍是否能容忍节点的污点;
CheckNodeLabelPresencePred 9 检测 NodeLabel 是否存在;
CheckServiceAffinityPred 10 检测服务的亲和;
MaxEBSVolumeCountPred 11 已废除,检测 Volume 数量是否超过云服务商 AWS 的存储服务的配置限度;
MaxGCEPDVolumeCountPred 12 已废除,检测 Volume 数量是否超过云服务商 Google Cloud 的存储服务的配置限度;
MaxCSIVolumeCountPred 13 Pod 附加 CSI 卷的数量,判断是否超过配置的限度;
MaxAzureDiskVolumeCountPred 14 已废除,检测 Volume 数量是否超过云服务商 Azure 的存储服务的配置限度;
MaxCinderVolumeCountPred 15 已废除,检测 Volume 数量是否超过云服务商 OpenStack 的存储服务的配置限度;
CheckVolumeBindingPred 16 基于 Pod 的卷申请,评估 Pod 是否适宜节点,这里的卷包含绑定的和未绑定的 PVC 都实用;
NoVolumeZoneConflictPred 17 给定该存储的故障区域限度,评估 Pod 申请的卷在节点上是否可用;
EvenPodsSpreadPred 18 检测 Node 是否满足拓扑流传限度;
MatchInterPodAffinityPred 19 检测是否匹配 Pod 的亲和与反亲和的设置;

能够看出,Kubernetes 正在逐渐移除某个具体云服务商的服务的相干代码,而应用接口(Interface)来扩大性能。

打分(Priority)

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

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

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

算法名称 默认 权重 具体阐明
EqualPriority 给予所有节点相等的权重;
MostRequestedPriority 反对最多申请资源的节点。该策略将 Pod 调度到整体工作负载所需的起码的一组节点上;
RequestedToCapacityRatioPriority 应用默认的打分办法模型,创立基于 ResourceAllocationPriority 的 requestedToCapacity;
SelectorSpreadPriority 1 属于同一 Service、StatefulSet 或 ReplicaSet 的 Pod,尽可能地跨 Node 部署(鸡蛋不要只放在一个篮子里,扩散危险,进步可用性);
ServiceSpreadingPriority 对于给定的 Service,此策略旨在确保该 Service 关联的 Pod 在不同的节点上运行。它偏差把 Pod 调度到没有该服务的节点。整体来看,Service 对于单个节点故障变得更具弹性;
InterPodAffinityPriority 1 实现了 Pod 间亲和性与反亲和性的优先级;
LeastRequestedPriority 1 偏差起码申请资源的节点。换句话说,节点上的 Pod 越多,应用的资源就越多,此策略给出的排名就越低;
BalancedResourceAllocation 1 CPU 和内存使用率越靠近的节点权重越高,该策略不能独自应用,必须和 LeastRequestedPriority 组合应用,尽量抉择在部署 Pod 后各项资源更平衡的机器。
NodePreferAvoidPodsPriority 10000 依据节点的注解 scheduler.alpha.kubernetes.io/preferAvoidPods 对节点进行优先级排序。你能够应用它来暗示两个不同的 Pod 不应在同一节点上运行;
NodeAffinityPriority 1 依据节点亲和中 PreferredDuringSchedulingIgnoredDuringExecution 字段对节点进行优先级排序;
TaintTolerationPriority 1 依据节点上无法忍受的污点数量,给所有节点进行优先级排序。此策略会依据排序后果调整节点的等级;
ImageLocalityPriority 1 如果 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 多平台公布

正文完
 0