使用 ​Koordinator 实现资源负载感知(重)调度

科技   2024-10-21 09:54   四川  

Koordinator 是一个基于 QoS 的 Kubernetes 混合工作负载调度系统。它旨在提高对延迟敏感的工作负载和批处理作业的运行时效率和可靠性,简化与资源相关的配置调整的复杂性,并增加 Pod 部署密度以提高资源利用率。

混部:在同一套硬件资源上,同时运行多个不同类型的工作负载,例如:批处理作业、交互式任务、实时数据处理等。

Koordinator 是具有高性能、可扩展的, 在大规模生产环境中得到了验证的,可以构建支持企业生产环境的容器编排系统。

Koordinator 通过提供以下功能增强了在 Kubernetes 中管理工作负载的用户体验:

  • 精心设计的优先级和 QoS 机制,可将不同类型的工作负载混跑在集群中,并在单个节点上运行不同类型的 Pod 。
  • 允许资源超卖以实现高资源利用率,但仍通过利用应用程序分析机制来满足 QoS 保证。
  • 细粒度的资源协调和隔离机制,以提高延迟敏感的工作负载和批处理作业的效率。
  • 灵活的作业调度机制,支持特定领域的工作负载,例如大数据、人工智能、音频和视频。
  • 一整套用于监控、故障排除和操作的工具。

Koordinator QoS vs Kubernetes QoS

Kubernetes 提供三种类型的 QoS:Guaranteed/Burstable/BestEffort,其中 Guaranteed/Burstable 被广泛使用 BestEffort 很少使用。Koordinator 与 Kubernetes QoS 兼容,并且对每种类型都有许多增强功能。为了避免干扰原生 QoS 语义,Koordinator 引入了一个独立的字段 koordinator.sh/qosClass 来描述混部 QoS。该 QoS 描述了在混部场景中节点上运行的 Pod 的服务质量。它是混合系统最关键的语义。

Koordinator 与 Kubernetes QoS 兼容,并且对每种类型都有许多增强功能。

Koordinator scheduler vs kube-scheduler

Koordinator 调度器并非旨在取代 kube-scheduler,而是为了让混部的工作负载在 kubernetes 上运行得更好。

Koordinator 调度器是基于 schedule-framework 开发的,在原生调度能力之上增加了与混部和优先级抢占相关的调度插件。Koordinator 将致力于推动相关的增强进入 Kubernetes 的上游社区,推动混部技术的标准化。

架构

Koordinator 由两个控制面(Koordinator Scheduler/Koordinator Manager)和一个 DaemonSet 组件(Koordlet)组成。Koordinator 在 Kubernetes 原有的能力基础上增加了混部功能,并兼容了 Kubernetes 原有的工作负载。

组件

下面是 Koordinator 的核心相关组件:

Koord-Scheduler

Koord-Scheduler 以 Deployment 的形式部署在集群中,用于增强 Kubernetes 在 QoS-aware,差异化 SLO 以及任务调度场景的资源调度能力,具体包括:

  • QoS-aware 调度,包括负载感知调度让节点间负载更佳平衡,资源超卖的方式支持运行更多的低优先级工作负载。
  • 差异化 SLO,包括 CPU 精细化编排,为不同的工作负载提供不同的 QoS 隔离策略(cfs、LLC、memory 带宽、网络带宽、磁盘 io)。
  • 任务调度,包括弹性额度管理,Gang 调度,异构资源调度等,以支持更好的运行大数据和 AI 工作负载。

Gang Scheduling:将一组具有相同调度需求的 Pod 视为一个调度单元,一起调度,一起迁移,一起销毁。在 AI 场景中很多任务都需要使用 Gang scheduling,社区已经有很多相关实现,比如 Coscheduling、Vocalno。

为了更好的支持不同类型的工作负载,Koord-scheduler 还包括了一些通用性的能力增强:

  • Reservation,支持为特定的 Pod 或者工作负载预留节点资源。资源预留特性广泛应用于重调度,资源抢占以及节点碎片整理等相关优化过程。
  • Node Reservation,支持为 kubernetes 之外的工作负载预留节点资源,一般应用于节点上运行着非容器化的负载场景。

Koord-Decheduler

Koord-Decheduler 以 Deployment 的形式部署在集群中,它是 kubernetes 上游社区的增强版本,当前包含:

  • 重调度框架, Koord-Decheduler 重新设计了全新重调度框架,在可扩展性、资源确定性以及安全性上增加了诸多的加强,更多的细节.
  • 负载感知重调度,基于新框架实现的一个负载感知重调度插件,支持用户配置节点的安全水位,以驱动重调度器持续优化集群编排,从而规避集群中出现局部节点热点.

水位:节点上资源使用的一个阈值,当资源使用达到水位时,会触发重调度。

重调度:当节点资源不足时,将节点上的 Pod 调度到其他节点上。

Koord-Manager

Koord-Manager 以 Deployment 的形式部署,通常由两个实例组成,一个 leader 实例和一个 backup 实例。Koordinator Manager 由几个控制器和 webhooks 组成,用于协调混部场景下的工作负载,资源超卖(resource overcommitment)和 SLO 管理。

目前,提供了三个组件:

  • Colocation Profile,用于支持混部而不需要修改工作负载。用户只需要在集群中做少量的配置,原来的工作负载就可以在混部模式下运行。
  • SLO 控制器,用于资源超卖(resource overcommitment)管理,根据节点混部时的运行状态,动态调整集群的超发(overcommit)配置比例。该控制器的核心职责是管理混部时的 SLO,如智能识别出集群中的异常节点并降低其权重,动态调整混部时的水位和压力策略,从而保证集群中 pod 的稳定性和吞吐量。
  • Recommender(即将推出),它使用 histograms 来统计和预测工作负载的资源使用细节,用来预估工作负载的峰值资源需求,从而支持更好地分散热点,提高混部的效率。此外,资源 profiling 还将用于简化用户资源规范化配置的复杂性,如支持 VPA。

Koordlet

Koordlet 以 DaemonSet 的形式部署在 Kubernetes 集群中,用于支持混部场景下的资源超卖(resource overcommitment)、干扰检测、QoS 保证等。

在 Koordlet 内部,它主要包括以下模块:

  • 资源 Profiling,估算 Pod 资源的实际使用情况,回收已分配但未使用的资源,用于低优先级 Pod 的 overcommit。
  • 资源隔离,为不同类型的 Pod 设置资源隔离参数,避免低优先级的 Pod 影响高优先级 Pod 的稳定性和性能。
  • 干扰检测,对于运行中的 Pod,动态检测资源争夺,包括 CPU 调度、内存分配延迟、网络、磁盘 IO 延迟等。
  • QoS 管理器,根据资源剖析、干扰检测结果和 SLO 配置,动态调整混部节点的水位,抑制影响服务质量的 Pod。
  • 资源调优,针对混部场景进行容器资源调优,优化容器的 CPU Throttle、OOM 等,提高服务运行质量。

Koord-RuntimeProxy

Koord-RuntimeProxy 以 systemd service 的形式部署在 Kubernetes 集群的节点上,用于代理 Kubelet 与 containerd/docker 之间的 CRI 请求。这一个代理被设计来支持精细化的资源管理策略,比如为不同 QoS Pod 设置不同的 cgroup 参数,包括内核 cfs quota,resctl 等等技术特性,以改进 Pod 的运行时质量。

资源模型

混部是一套资源调度解决方案,用于对延迟敏感的工作负载与大数据计算工作负载进行精细化编排。它需要解决两个主要问题:

  1. 如何为延迟敏感的工作负载调度资源,以满足性能和长尾延迟的要求。这里涉及到的关键点是资源调度策略和 QoS 感知策略。
  2. 如何调度和编排大数据计算工作负载,以较低的成本满足任务对计算资源的需求。这里涉及到的关键是如何在极端异常情况下实现合理的资源超额配置和 QoS 保障。

定义

Resource Model

上图是 Koordinator 的混部资源模型,其基本思想是利用那些已分配但未使用的资源来运行低优先级的 pod。如图所示,有四条线:

  • limit:灰色,高优先级 Pod 所请求的资源量,对应于 Kubernetes 的 Pod 请求。
  • usage:红色,Pod 实际使用的资源量,横轴为时间线,红线为 Pod 负载随时间变化的波动曲线。
  • short-term reservation:深蓝色,这是基于过去(较短)时期内的资源使用量,对未来一段时间内其资源使用量的估计。预留和限制的区别在于,分配的未使用(未来不会使用的资源)可以用来运行短期执行的批处理 Pod。
  • long-term reservation:浅蓝色,与 short-term reservation 类似,但估计的历史使用期更长。从保留到限制的资源可以用于生命周期较长的 Pod,与短期的预测值相比,可用的资源较少,但更稳定。

整个混部资源调度是基于上图所示的资源模型构建的,不仅可以满足各种工作负载的资源需求,还可以充分利用集群的闲置资源。

SLO 描述

在集群中运行的 Pod 资源 SLO 由两个概念组成,即优先级和 QoS。

  • 优先级,即资源的优先级,代表了请求资源被调度的优先级。通常情况下,优先级会影响 Pod 在调度器待定队列中的相对位置。
  • QoS,代表 Pod 运行时的服务质量。如 cgroups cpu share、cfs 配额、LLC、内存、OOM 优先级等等。

需要注意的是,Priority 和 QoS 是两个维度的概念,但在实际业务场景中,两者之间会有一些约束(不是所有的组合都是合法的)。

优先级

Koordinator 在 Kubernetes 优先级类型的基础上定义了一套规范,并扩展了优先级的一个维度以对混部场景的细粒度支持。

优先级用数字表示,目前定义了四个类:

PriorityClass 目前留有一些暂未使用的区间,以支持未来可能的扩展。

Koordinator 将不同类型的工作负载匹配到不同的优先级:

  • koord-prod,运行典型的延迟敏感型服务,一般是指需要 "实时 "响应的服务类型,比如通过点击移动 APP 中的按钮调用的典型服务。
  • koord-mid,对应于长周期的可用资源,一般用于运行一些实时计算、人工智能训练任务/作业,如 tensorflow/pytorch 等。
  • koord-batch,对应于的短周期可用资源,运行典型的离线批处理作业,一般指离线分析类作业,如日级大数据报告、非交互式 SQL 查询。
  • koord-free,运行低优先级的离线批处理作业,一般指不做资源预算,利用闲置资源尽量完成,如开发人员为测试目提交的作业。

Koordinator 在 Kubernetes 集群中部署时会初始化这四个 PriorityClass:

apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
  name: koord-prod
value: 9000
description: "This priority class should be used for prod service pods only."
---
apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
  name: koord-mid
value: 7000
description: "This priority class should be used for mid service pods only."
---
apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
  name: koord-batch
value: 5000
description: "This priority class should be used for batch service pods only."
---
apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
  name: koord-free
value: 3000
description: "This priority class should be used for free service pods only."

在每个 PriorityClass 内,Koordinator 允许用户为精细化资源调度设置混部 Pod 的优先级。

比如下面的 YAML 是一个 Pod 配置的例子,它使用了前面例子中创建的 PriorityClass 和优先级。

apiVersion: v1
kind: Pod
metadata:
  name: nginx
  labels:
    env: test
    koordinator.sh/priority: "5300"
spec:
  containers:
    - name: nginx
      image: nginx
      imagePullPolicy: IfNotPresent
  priorityClassName: koord-batch

Qos

QoS 用于表达节点上 Pod 的运行质量,如获取资源的方式、获取资源的比例、QoS 保障策略等。

Koordinator 调度系统支持的 QoS 有五种类型:

QoS CPU 编排隔离与共享

Koordinator QoS 与 Kubernetes QoS 的对比

从上面可以看出,Koordinator 的 QoS 比 Kubernetes 的 QoS 更复杂,因为在混部场景下,我们需要对延迟敏感的工作负载的 QoS 进行微调,以满足混部时性能的需求。Koordinator 和 Kubernetes QoS 之间是有对应关系的:

Koordlet 根据 Pod 的优先级和 QoS 定义,触发相应的资源隔离和 QoS 保障。

安装

Koordinator 依赖 1.18 及以上版本的 Kubernetes。另外 Koordinator 可能会从 kubelet 只读端口收集指标(默认设置为禁用,即采用了安全端口)。

为了最好的体验,koordinator 推荐 linux kernel 4.19 或者更高版本。

这里我们推荐使用 Helm 安装:

helm repo add koordinator-sh https://koordinator-sh.github.io/charts/

helm repo update

helm install koordinator koordinator-sh/koordinator --version 1.5.0 --set imageRepositoryHost=registry.cn-beijing.aliyuncs.com --set manager.hostNetwork=true

上面的命令默认会安装在 koordinator-system namespace 下,如果需要安装在其他 namespace 下,可以使用 installation.namespace 参数,我们可以使用 kubectl get pods -n koordinator-system 命令查看安装的组件。

$ kubectl get pods -n koordinator-system
NAME                                READY   STATUS    RESTARTS   AGE
koord-descheduler-7569579bc-knr85   1/1     Running   0          7m8s
koord-manager-8897597b6-79wck       1/1     Running   0          7m8s
koord-scheduler-78bccfc65c-wt2n6    1/1     Running   0          7m8s
koordlet-972kj                      1/1     Running   0          7m8s
koordlet-kmtqh                      1/1     Running   0          7m8s
koordlet-s9c4s                      1/1     Running   0          7m8s

使用

现在我们就可以使用 Koordinator 来调度我们的工作负载了。

负载感知调度

负载感知调度(Load Aware Scheduling)是 koord-scheduler 提供的一种调度能力,调度 Pod 时根据节点的负载情况选择合适的节点,均衡节点间的负载情况。

负载均衡是资源调度中的常见问题。资源未充分利用的节点会带来很大的资源浪费,而过度使用的节点可能会导致性能下降。这些问题都不能高效的管理和使用资源。原生 Kubernetes Scheduler 根据 Requests 和节点可分配总量来调度 Pod,既不考虑实时负载,也不估计使用量。当我们期望使用原生调度器均匀的打散 Pod 并保持节点间的负载均衡,我们需要为应用程序设置精确的资源规格。此外,当 Koordinator 通过超卖机制提升资源使用效率时,我们需要一种机制尽量避免性能回退,并避免负载过高的问题。

Koordinator 调度插件过滤异常节点并根据资源使用情况对其进行评分。这个调度插件扩展了 Kubernetes 调度框架中定义的 Filter/Score/Reserve/Unreserve 扩展点。

过滤不健康的节点

默认过滤异常节点,但是用户可以根据需要通过配置来决定是否开启。

  • 过滤 Koordlet 无法更新 NodeMetric 的节点。如果配置启用,插件将排除 nodeMetrics.status.updateTime >= LoadAwareSchedulingArgs.nodeMetricExpirationSeconds 的节点。
  • 按利用率阈值过滤节点。如果配置启用,插件将排除 latestUsageUtilization >= 利用率阈值的节点。在过滤阶段,仅从最新的 NodeMetric 中获取资源利用率,已分配但尚未统计的 Pod 的资源利用率不参与计算,以便为新创建的 Pod 分配资源,避免因估算不合理而导致调度失败。

评分算法

评分算法的核心逻辑是选择资源使用量最小的节点。但是考虑到资源使用上报的延迟和 Pod 启动时间的延迟,时间窗口内已经调度的 Pod 和当前正在调度的 Pod 的资源请求也会被估算出来,并且估算值将参与计算。

全局配置

对于需要深入定制的用户,可以通过修改 Helm Chart 中的 ConfigMap koord-scheduler-config 规则来配置负载感知调度。

apiVersion: v1
kind: ConfigMap
metadata:
  name: koord-scheduler-config
  ...
data:
  koord-scheduler-config: |
    apiVersion: kubescheduler.config.k8s.io/v1beta2
    kind: KubeSchedulerConfiguration
    profiles:
      - schedulerName: koord-scheduler
        plugins:
          filter:  # 过滤
            enabled:
              - name: LoadAwareScheduling  # 启用负载感知调度插件
              ...
          score: # 打分
            enabled:
              - name: LoadAwareScheduling
                weight: 1 # 打分权重
              ...
          reserve:  # 预留资源
            enabled:
              - name: LoadAwareScheduling
          ...
        pluginConfig: # 配置阈值和权重
        - name: LoadAwareScheduling
          args:
            apiVersion: kubescheduler.config.k8s.io/v1beta2
            kind: LoadAwareSchedulingArgs
            filterExpiredNodeMetrics: true  # 是否过滤掉无法更新 NodeMetric 的节点
            nodeMetricExpirationSeconds: 300  # 使用 NodeMetric 的过期时间
            # 资源权重
            resourceWeights:
              cpu: 1
              memory: 1
            usageThresholds:  # 资源利用率阈值
              cpu: 75
              memory: 85
            prodUsageThresholds:  # prod资源利用率阈值
              cpu: 55
              memory: 65
            scoreAccordingProdUsage: true  # 是否根据prod资源利用率阈值进行打分
            estimatedScalingFactors:  # 资源利用率估算因子
              cpu: 80
              memory: 70
            aggregated:  # 资源利用率百分比统计
              usageThresholds:
                cpu: 65
                memory: 75
              usageAggregationType: "p99"
              scoreAggregationType: "p99"

可配置的参数如下所示:

字段说明版本
filterExpiredNodeMetricsfilterExpiredNodeMetrics 表示是否过滤 koordlet 更新 NodeMetric 失败的节点。默认情况下启用,但在 Helm chart 中,它被禁用。>= v0.4.0
nodeMetricExpirationSecondsnodeMetricExpirationSeconds 指示 NodeMetric 过期时间(以秒为单位)。当 NodeMetrics 过期时,节点被认为是异常的。默认为 180 秒。>= v0.4.0
resourceWeightsresourceWeights 表示资源的权重。CPU 和 Memory 的权重默认都是 1。>= v0.4.0
usageThresholdsusageThresholds 表示整机的资源利用率阈值。CPU 的默认值为 65%,内存的默认值为 95%。>= v0.4.0
estimatedScalingFactorsestimatedScalingFactors 表示估计资源使用时的因子。CPU 默认值为 85%,Memory 默认值为 70%。>= v0.4.0
prodUsageThresholdsprodUsageThresholds 表示 Prod Pod 相对于整机的资源利用率阈值。默认情况下不启用。>= v1.1.0
scoreAccordingProdUsagescoreAccordingProdUsage 控制是否根据 Prod Pod 的利用率进行评分。>= v1.1.0
aggregatedaggregated 支持基于百分位数统计的资源利用率过滤和评分。>= v1.1.0

Aggregated 支持的字段:

字段说明版本
usageThresholdsusageThresholds 表示机器基于百分位统计的资源利用率阈值。>= v1.1.0
usageAggregationTypeusageAggregationType 表示过滤时机器利用率的百分位类型。目前支持 avgp50p90p95p99>= v1.1.0
usageAggregatedDurationusageAggregatedDuration 表示过滤时机器利用率百分位数的统计周期。不设置该字段时,调度器默认使用 NodeMetrics 中最大周期的数据。>= v1.1.0
scoreAggregationTypescoreAggregationType 表示评分时机器利用率的百分位类型。目前支持 avgp50p90p95p99>= v1.1.0
scoreAggregatedDurationscoreAggregatedDuration 表示打分时 Prod Pod 利用率百分位的统计周期。不设置该字段时,调度器默认使用 NodeMetrics 中最大周期的数据。>= v1.1.0

通过插件的配置可以作为集群默认的全局配置,当然用户也可以通过在节点上附加 annotation 来设置节点维度的负载阈值。当节点上存在 annotation 时,会根据注解指定的参数进行过滤。

测试

我们这里的集群一共三个节点:

  • master:2 核 4G
  • node1:4 核 8G
  • node2:4 核 8G

下面我们创建一个如下所示的 Deployment 来测试负载感知调度:

# pod-stress.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: stress-demo
  namespace: default
  labels:
    app: stress-demo
spec:
  selector:
    matchLabels:
      app: stress-demo
  template:
    metadata:
      name: stress-demo
      labels:
        app: stress-demo
    spec:
      containers:
        - args:
            - "--vm"
            - "2"
            - "--vm-bytes"
            - "1600M"
            - "-c"
            - "3"
            - "--vm-hang"
            - "3"
          command:
            - stress
          image: jockerhub.com/polinux/stress
          imagePullPolicy: Always
          name: stress
      schedulerName: koord-scheduler # use the koord-scheduler

直接创建上面的 Deployment 即可:

$ kubectl apply -f pod-stress.yaml
deployment.apps/stress-demo created

创建完成后观察 Pod 的状态:

$ kubectl get pods -owide
NAME                                      READY   STATUS    RESTARTS      AGE   IP           NODE    NOMINATED NODE   READINESS GATES
stress-demo-64968d6446-8wdtr              1/1     Running   0             89s   10.0.1.5     node2   <none>           <none>

可以看到 Pod 已经调度到了 node2 节点上。

现在我们可以检查下每个节点的负载情况:

$ kubectl top pods stress-demo-64968d6446-8wdtr
NAME                           CPU(cores)   MEMORY(bytes)
stress-demo-64968d6446-8wdtr   2979m        3206Mi
$ kubectl top nodes
NAME     CPU(cores)   CPU%   MEMORY(bytes)   MEMORY%
master   92m          4%     2682Mi          71%
node1    54m          1%     1761Mi          22%
node2    1711m        42%    4381Mi          56%

从上面输出结果显示,节点 node1 的负载最低(master 不考虑),node2 的负载最高。

接下来我们再部署几个 Pod 来测试下效果:

# nginx-with-loadaware.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-with-loadaware
  labels:
    app: nginx
spec:
  replicas: 6
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      name: nginx
      labels:
        app: nginx
    spec:
      schedulerName: koord-scheduler # use the koord-scheduler
      containers:
        - name: nginx
          image: jockerhub.com/library/nginx
          resources:
            limits:
              cpu: 100m
            requests:
              cpu: 100m

同样直接创建上面的 Deployment:

$ kubectl apply -f nginx-with-loadaware.yaml
deployment.apps/nginx-with-loadaware created

创建完成后检查 nginx 这些 Pods 的调度结果:

$ kubectl get pods -owide
NAME                                      READY   STATUS    RESTARTS      AGE     IP           NODE    NOMINATED NODE   READINESS GATES
nginx-with-loadaware-57db6b7758-6gbfz     1/1     Running   0             78s     10.0.2.187   node1   <none>           <none>
nginx-with-loadaware-57db6b7758-gvxdk     1/1     Running   0             78s     10.0.2.243   node1   <none>           <none>
nginx-with-loadaware-57db6b7758-hzdqf     1/1     Running   0             77s     10.0.2.200   node1   <none>           <none>
nginx-with-loadaware-57db6b7758-n779g     1/1     Running   0             77s     10.0.2.69    node1   <none>           <none>
nginx-with-loadaware-57db6b7758-trt64     1/1     Running   0             78s     10.0.1.156   node1   <none>           <none>
nginx-with-loadaware-57db6b7758-xtvsh     1/1     Running   0             77s     10.0.1.75    node2   <none>           <none>

我们可以看到 nginx pods 绝大部分都被调度在 node2 (负载最高的节点) 以外的节点上。

感知 Prod Pods 的负载进行调度

如果一个 Node 中调度了很多 BestEffort Pod,可能会因为节点的负载已达到使用限制而导致延迟敏感的 Pod 无法调度。在 Koordinator v1.1.0 中,负载感知调度针对这种场景进行了优化。对于延迟敏感(LSE/LSR/LS)的 Pod,优先调度到 Prod Pod 总利用率较低的节点,而 BestEffort(BE) Pod 根据整机利用率水平进行调度。

通过设置以下参数启用相关优化:

字段说明版本
prodUsageThresholdsprodUsageThresholds 表示 Prod Pod 相对于整机的资源利用率阈值。默认情况下不启用。>= v1.1.0
scoreAccordingProdUsagescoreAccordingProdUsage 控制是否根据 Prod Pod 的利用率进行评分。>= v1.1.0

感知基于百分位数统计的利用率进行调度

Koordinator v1.0 及以前的版本都是按照 koordlet 上报的平均利用率数据进行过滤和打分。但平均值隐藏了比较多的信息,因此在 Koordinator v1.1 中 koordlet 新增了根据百分位数统计的利用率聚合数据。调度器侧也跟着做了相应的适配。

通过设置以下参数启用相关优化:

字段说明版本
aggregatedaggregated 支持基于百分位数统计的资源利用率过滤和评分。>= v1.1.0

Aggregated 支持的字段:

字段说明版本
usageThresholdsusageThresholds 表示机器基于百分位统计的资源利用率阈值。>= v1.1.0
usageAggregationTypeusageAggregationType 表示过滤时机器利用率的百分位类型。目前支持 avgp50p90p95p99>= v1.1.0
usageAggregatedDurationusageAggregatedDuration 表示过滤时机器利用率百分位数的统计周期。不设置该字段时,调度器默认使用 NodeMetrics 中最大周期的数据。>= v1.1.0
scoreAggregationTypescoreAggregationType 表示评分时机器利用率的百分位类型。目前支持 avgp50p90p95p99>= v1.1.0
scoreAggregatedDurationscoreAggregatedDuration 表示打分时 Prod Pod 利用率百分位的统计周期。不设置该字段时,调度器默认使用 NodeMetrics 中最大周期的数据。>= v1.1.0

aggregatedusageThresholds 参数是互斥的。当两者都配置时,将使用 aggregated

负载感知重调度

调度器中支持的负载感知调度能够在调度时选择负载较低的节点运行新的 Pod,但随着时间、集群环境变化以及工作负载面对的流量/请求的变化时,节点的利用率会动态的发生变化,集群内节点间原本负载均衡的情况被打破,甚至有可能出现极端负载不均衡的情况,影响到工作负载运行时质量。

koord-descheduler 感知集群内节点负载的变化,自动的优化超过负载水位安全阈值的节点,防止出现极端负载不均衡的情况。

koord-descheduler 组件中 LowNodeLoad 插件负责感知负载水位完成热点打散重调度工作。LowNodeLoad 插件 与 Kubernetes 原生的 descheduler 的插件 LowNodeUtilization 不同的是,LowNodeLoad 是根据节点真实利用率的情况决策重调度,而 LowNodeUtilization 是根据资源分配率决策重调度。

LowNodeLoad 插件有两个最重要的参数:

  • highThresholds 表示负载水位的目标安全阈值,超过该阈值的节点上的 Pod 将参与重调度;
  • lowThresholds 表示负载水位的空闲安全水位。低于该阈值的节点上的 Pod 不会被重调度。

以下图为例,lowThresholds 为 45%,highThresholds 为 70%,我们可以把节点归为三类:

  1. 空闲节点(Idle Node)。资源利用率低于 45% 的节点;
  2. 正常节点(Normal Node)。资源利用率高于 45% 但低于 70% 的节点,这个负载水位区间是我们期望的合理的区间范围
  3. 热点节点(Hotspot Node)。如果节点资源利用率高于 70%,这个节点就会被判定为不安全了,属于热点节点,应该驱逐一部分 Pod,降低负载水位,使其不超过 70%。

在识别出哪些节点是热点后,koord-descheduler 将会执行迁移驱逐操作,驱逐热点节点中的部分 Pod 到空闲节点上。如果 Idle Node 数量是 0 或者 Hotspot Node 数量是 0,则 descheduler 不会执行任何操作。

如果一个集群中空闲节点的总数并不是很多时会终止重调度。这在大型集群中可能会有所帮助,在大型集群中,一些节点可能会经常或短时间使用不足。默认情况下,numberOfNodes 设置为零。可以通过设置参数 numberOfNodes 来开启该能力。

在迁移前,koord-descheduler 会计算出实际空闲容量,确保要迁移的 Pod 的实际利用率之和不超过集群内空闲总量。这些实际空闲容量来自于空闲节点,一个空闲节点实际空闲容量 = (highThresholds - 节点当前负载) _ 节点总容量。假设节点 A 的负载水位是 20%,highThresholdss 是 70%,节点 A 的 CPU 总量为 96C,那么 (70%-20%) _ 96 = 48C,这 48C 就是可以承载的空闲容量了。

另外,在迁移热点节点时,会过滤筛选节点上的 Pod,目前 koord-descheduler 支持多种筛选参数,可以避免迁移驱逐非常重要的 Pod:

  • 按 namespace 过滤。可以配置成只筛选某些 namespace 或者过滤掉某些 namespace
  • 按 pod selector 过滤。可以通过 label selector 筛选出 Pod,或者排除掉具备某些 Label 的 Pod
  • 配置 nodeFit 检查调度规则是否有备选节点。当开启后,koord-descheduler 根据备选 Pod 对应的 Node Affinity/Node Selector/Toleration ,检查集群内是否有与之匹配的 Node,如果没有的话,该 Pod 将不会去驱逐迁移。如果设置 nodeFit 为 false,此时完全由 koord-descheduler 底层的迁移控制器完成容量预留,确保有资源后开始迁移。

当筛选出 Pod 后,从 QoSClass、Priority、实际用量和创建时间等多个维度对这些 Pod 排序。

筛选 Pod 并完成排序后,开始执行迁移操作。迁移前会检查剩余空闲容量是否满足和当前节点的负载水位是否高于目标安全阈值,如果这两个条件中的一个不能满足,将停止重调度。每迁移一个 Pod 时,会预扣剩余空闲容量,同时也会调整当前节点的负载水位,直到剩余容量不足或者水位达到安全阈值。

需要注意 ⚠️ 的是负载感知重调度默认是禁用的,可以通过修改配置 ConfigMap koord-descheduler-config 启用该能力。对于需要深入定制的用户,可以按照需要更改 Helm Chart 中的 ConfigMap koord-descheduler-config 设置参数。

apiVersion: v1
kind: ConfigMap
metadata:
  name: koord-descheduler-config
  ...
data:
  koord-descheduler-config: |
    apiVersion: descheduler/v1alpha2
    kind: DeschedulerConfiguration
    ...
    # 执行 LowNodeLoad 插件的间隔时间
    deschedulingInterval: 60s
    profiles:
      - name: koord-descheduler
        plugins:
          deschedule:
            disabled:
              - name: "*"
          balance:
            enabled:
              - name: LowNodeLoad  # Configure to enable the LowNodeLoad plugin
          ....
        pluginConfig:
        - name: LowNodeLoad
          args:
            apiVersion: descheduler/v1alpha2
            kind: LowNodeLoadArgs
            evictableNamespaces:
              # include 和 exclude 是互斥的,只能配置一个
              # include 表示只处理下面配置的 namespace
              # include:
              #   - test-namespace
              # exclude 表示只处理除下面配置的 namespace 以外的 namespace
              exclude:
                - "kube-system"
                - "koordinator-system"
            # lowThresholds 定义资源利用率的低阈值
            lowThresholds:
              cpu: 20
              memory: 30
            # highThresholds 定义资源利用率的高阈值
            highThresholds:
              cpu: 50
              memory: 60
        ....

除了上面这些配置之外还有其他一些配置,可以参看下面的表格:

字段说明
pausedPaused 控制 LowNodeLoad 插件是否工作.
dryRunDryRun 表示只执行重调度逻辑,但不重复啊迁移/驱逐 Pod
numberOfNodesNumberOfNodes 可以配置为仅当未充分利用的节点数高于配置值时才激活该策略。这在大型集群中可能会有所帮助,在大型集群中,一些节点可能会经常或短时间使用不足。默认情况下,NumberOfNodes 设置为零。
evictableNamespaces可以参与重调度的 Namespace。可以配置 include 和 exclude 两种,但两种策略只能二选一。include 表示只处理指定的 namespace;exclude 表示只处理指定之外的 namespace。
nodeSelector通过 label selector 机制选择目标节点。
podSelectors通过 label selector 选择要处理的 Pod。
nodeFit表示是否按照备选要迁移的 Pod 中指定的 Node Affinity/Node Selector/Resource Requests/TaintToleration 判断是否有空闲节点。没有则不参与调度。默认开启。可以设置为 false 禁用该能力。
useDeviationThresholds如果 useDeviationThresholds 设置为 true,则阈值被视为与平均资源使用率的百分比偏差。lowThresholds 将从所有节点的平均值中减去,highThresholds 将添加到平均值中。高于此窗口的资源消耗被视为过度利用的,即热点节点。
highThresholds表示负载水位的目标安全阈值,超过该阈值的节点上的 Pod 将参与重调度。
lowThresholds表示负载水位的空闲安全水位。低于该阈值的节点上的 Pod 不会被重调度。

同样接下来我们重新创建 stress Pods 来测试下效果:

# pod-stress.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: stress-demo
  namespace: default
  labels:
    app: stress-demo
spec:
  selector:
    matchLabels:
      app: stress-demo
  template:
    metadata:
      name: stress-demo
      labels:
        app: stress-demo
    spec:
      containers:
        - args:
            - "--vm"
            - "2"
            - "--vm-bytes"
            - "2000M"
            - "-c"
            - "4"
            - "--vm-hang"
            - "200"
          command:
            - stress
          image: jockerhub.com/polinux/stress
          imagePullPolicy: Always
          name: stress
      schedulerName: koord-scheduler # use the koord-scheduler

直接创建上面的 Deployment:

$ kubectl apply -f pod-stress.yaml
deployment.apps/stress-demo created

创建完成后检查 Pod 的调度结果:

$ kubectl get pods -owide -l app=stress-demo
NAME                           READY   STATUS    RESTARTS   AGE   IP          NODE    NOMINATED NODE   READINESS GATES
stress-demo-7cc84459c9-4b5wc   1/1     Running   0          12s   10.0.1.46   node2   <none>           <none>

可以看到这个 Pod 分别调度到了 node2 节点上。

现在我们检查下节点的负载情况:

$ kubectl top nodes
NAME     CPU(cores)   CPU%   MEMORY(bytes)   MEMORY%
master   78m          3%     2381Mi          63%
node1    64m          1%     1527Mi          19%
node2    1844m        46%    5266Mi          67%

可以看到 node2 的负载都挺高的,接下来我们更新下 koord-descheduler-config 配置,启用负载感知重调度的插件 LowNodeLoad。然后观察前面我们创建的 nginx Pod 的调度情况:

$ kubectl get pods -w
NAME                                      READY   STATUS    RESTARTS      AGE
stress-demo-7cc84459c9-qk85s              1/1     Running   0             4s
nginx-with-loadaware-57c7bbbbc-btz7g      1/1     Running   0             6m13s
nginx-with-loadaware-57c7bbbbc-btz7g      1/1     Terminating   0             6m13s
nginx-with-loadaware-57c7bbbbc-btz7g      1/1     Terminating   0             6m13s
nginx-with-loadaware-57c7bbbbc-8dz96      0/1     Pending       0             0s
nginx-with-loadaware-57c7bbbbc-8dz96      0/1     Pending       0             0s
nginx-with-loadaware-57c7bbbbc-8dz96      0/1     Pending       0             0s
nginx-with-loadaware-57c7bbbbc-8dz96      0/1     ContainerCreating   0             0s
nginx-with-loadaware-57c7bbbbc-2hc8r      1/1     Running             0             6m13s
nginx-with-loadaware-57c7bbbbc-2hc8r      1/1     Terminating         0             6m13s
nginx-with-loadaware-57c7bbbbc-2hc8r      1/1     Terminating         0             6m13s
nginx-with-loadaware-57c7bbbbc-97shv      0/1     Pending             0             0s
nginx-with-loadaware-57c7bbbbc-97shv      0/1     Pending             0             0s
nginx-with-loadaware-57c7bbbbc-97shv      0/1     Pending             0             0s
nginx-with-loadaware-57c7bbbbc-97shv      0/1     ContainerCreating   0             0s
nginx-with-loadaware-57c7bbbbc-btz7g      0/1     Terminating         0             6m14s
nginx-with-loadaware-57c7bbbbc-2hc8r      0/1     Terminating         0             6m14s
nginx-with-loadaware-57c7bbbbc-2hc8r      0/1     Terminating         0             6m14s
nginx-with-loadaware-57c7bbbbc-2hc8r      0/1     Terminating         0             6m14s
nginx-with-loadaware-57c7bbbbc-btz7g      0/1     Terminating         0             6m14s
nginx-with-loadaware-57c7bbbbc-btz7g      0/1     Terminating         0             6m14s
nginx-with-loadaware-57c7bbbbc-8dz96      1/1     Running             0             3s
nginx-with-loadaware-57c7bbbbc-97shv      1/1     Running             0             3s

观察 Events 信息,可以看到如下所示的迁移记录:

$ kubectl get event |grep Descheduled
3m37s       Normal    Descheduled               pod/nginx-with-loadaware-57c7bbbbc-2hc8r                Pod evicted from node "master" by the reason "node is overutilized, memory usage(57.92%)>threshold(50.00%)"
57s         Normal    Descheduled               pod/nginx-with-loadaware-57c7bbbbc-8dz96                Pod evicted from node "node2" by the reason "node is overutilized, cpu usage(75.22%)>threshold(45.00%), memory usage(65.70%)>threshold(50.00%)"
3m37s       Normal    Descheduled               pod/nginx-with-loadaware-57c7bbbbc-btz7g                Pod evicted from node "master" by the reason "node is overutilized, memory usage(57.98%)>threshold(50.00%)"
57s         Normal    Descheduled               pod/nginx-with-loadaware-57c7bbbbc-jhdmd                Pod evicted from node "node2" by the reason "node is overutilized, cpu usage(75.22%)>threshold(45.00%), memory usage(65.65%)>threshold(50.00%)"

现在 Pod 就从高负载的 node2 节点上迁移到了其他节点上去。

总结

上面介绍的这些功能只是 Koordinator 提供的负载感知功能,还有更多强大的功能等待大家去探索。

  • 参考文档:https://koordinator.sh/zh-Hans/docs

运维开发故事
由一群志同道合的小伙伴共同维护,有运维也有开发,内容不限于Linux运维,devops工具链,k8s容器化技术,监控,日志收集,网络安全,Python或GO开发,团队成员有乔克、wanger、冬哥、素心、华仔、郑哥、夏老师
 最新文章