极致成本优化背景下,如何通过优化k8s调度器实现计算资源的按需付费(一)

原创 吴就业 277 0 2024-02-04

本文为博主原创文章,未经博主允许不得转载。

本文链接:https://wujiuye.com/article/3e359f0644834201b8fbd2488e625f80

作者:吴就业
链接:https://wujiuye.com/article/3e359f0644834201b8fbd2488e625f80
来源:吴就业的网络日记
本文为博主原创文章,未经博主允许不得转载。

在降低增笑的大背景下,如何在保证稳定性的前提下,做到极致压缩k8s资源的使用,实现基础设施真正的按需付费,是我们云原生项目的目标之一。

要实现如Railway这种产品的基础设施按实际使用付费,不能简单的使用云Serverless产品,因为这些产品都有最低限额的要求,例如阿里云最低限制要求Pod至少0.25cpu+0.5g内存,但其实大多数应用这个配额都用不到,大量的时间cpu负载几乎为0,内存消耗可能也就几十M(Java应用除外),大量的低使用率的Pod会造成资源的浪费。

即便对钱不感兴趣,浪费资源也不好。

基础设施按需付费,包括了计算和存储资源,以及网络资源,此次话题重点聚焦在计算资源。

计算资源成本最终体现在购买的云虚拟机上,要实现按实际使用付费,需要从两个不同维度的资源调度着手,一个是节点维度,一个是Pod维度。

  1. 节点维度的难点在于,按需购买和终止节点,达到动态平衡,且需要在快速响应pod调度和预留资源成本之间寻找平衡点。
  2. Pod维度难点在于,不能直接使用用户申请的request去调度,需要在按需和稳定之间寻找动态平衡点。

节点调度的研究方向

目前公有云大多都提供节点动态扩缩容的能力,能够通过利用Spot节点(抢占式虚拟节点),降低集群节点的使用成本。或直接集成在云产品中可直接启用,或通过安装插件的方式启用。

  1. Google的gke自带Autopilot,只需要开启节点自动扩缩容即可,基于官方开源项目autoscaler实现。相关文档:https://cloud.google.com/kubernetes-engine/docs/how-to/cluster-autoscaler?hl=zh-cn
  2. Karpenter是aws开源的节点自动扩缩器,只支持aws自家产品,提供helm安装。文档:https://karpenter.sh/docs/
  3. Aliyun基于k8s官方autoscaler二次开发,提供集群节点自动扩缩插件(Cluster Autoscaler)。github:https://github.com/AliyunContainerService/autoscaler

这些需要克金去做实验验证。目前我们针对GKE做了实验,数据后续一篇独立的文章介绍。 实验的结论是,节点扩容时间1分钟左右,缩容的话时间是可以调的,默认是差不多空闲11分钟才完成回收。节点扩容的耗时主要是节点的启动和初始化,这部分自己实现调度器也是优化不了的。因此节点扩缩容这块,直接使用云的功能就可以。

只是,全部使用Spot节点是否是最优解?

基本上公有云上按需的费用总体会比按年合约的费用高。如果想做到极低的成本,完全使用弹性节点是做不到的。但在稳定性前提下,由于团队缺少经验,云产品可谓是短期内可以直接用的最优解。云产品的产品只会考虑Spot池子怎么更好的卖出去,想获得极致的成本优势,长期可能需要考虑调优出一个组合策略。

例如,后期的成本优化策略可以考虑如,统计一整年最低峰期所需要的节点数,第二年按年购买最低所需的节点,统计上月最低所需节点数,按月增量节点(上月最低节点数-上年最低节点数),最后再是弹性节点。实现按年合约节点+按月合约节点+按需节点的节点调度策略。

还有非常难的点,需要考虑节点规格,不能单一的全部一个规格:

Pod调度的研究方向

Kubernetes的调度器会根据Pod的资源requests和资源limits来进行调度决策。它会选择一个满足Pod的资源request的节点运行Pod,并确保运行时Pod的资源消耗不会超出limits。

如果想要达到资源利用率最大化的目标,使用k8s的默认调度器,只能将requests设置很低,或者不设置,这样可以在每个节点上容纳更多 Pod ,但这样会存在以下问题:

  1. CPU:在业务比较繁忙的时候,节点的CPU全负荷运行,业务延迟会增加,甚至机器会进入CPU软死锁等“假死”状态。
  2. Memory:可能会出现有些Pod不断地失败重启,可预测的是Java应用。

想要实现按需,就是要实现资源超卖,而不是Pod想要多少就给它多少,实际没有使用的就浪费掉了。默认的调度器不支持超卖,除非request非常接近真实值。

那么,是否有开源的解决方案?

阅读了一些网文,发现国内很多互联网企业都在研究在线离线混部这个方向,基于QoS的混部方案。

我们的研究方向:

  1. 阿里开源的Koordinator,是否能满足按需调度的需求,或者是否可以通过二次开发达到目的。
  2. 是否还有其他开源解决方案?
  3. 最坏的情况下需要自研。

Koordinator的可行性

Koordinator是一款定位为基于QoS的混部调度器,实现离线资源和在线资源的穿插填充使得资源利用率最大化。但我们云原生系统的定位,面向的场景并不存在离线资源,所有资源都为在线资源,解决的是微服务这种场景,那么使用Koordinator是否能够带来收益?

Koordinator定义的五种QoS

Koordinator定义的五种QoS,由于微服务场景,只会存在LS(Latency Sensitive)这种QoS。基于模版部署的中间件也会是这种QoS。因此,Koordinator提供的能力,我们将只会使用到“负载感知调度”、“负载感知重调度”。

Koordinator节点的资源利用率阈值默认是根据经验配置的,cpu阈值是65%,memory阈值是95%。这个阈值可配置。在实验阶段我们先使用默认的阈值。

实验:

使用go编写用于验证的go-web-demo,占用非常小的cpu和内存资源(实现代码略)。 调request和limit的值看现象。(观察负载感知调度)

部署的yaml模版:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: go-web-demo
  namespace: default
spec:
  replicas: 1
  selector:
    matchLabels:
      app: go-web-demo
  template:
    metadata:
      name: go-web-demo
      namespace: default
      labels:
        app: go-web-demo 
        koordinator.sh/enable-colocation: "true"
    spec:
      containers:
        - name: go-web-demo
          image: >-
            us-central1-docker.pkg.dev/xxx/yyy/web-demo:v1.0.0
          resources:
            limits:
              cpu: 2000m
              memory: 4048Mi
            requests:
              cpu: 500m
              memory: 1024Mi
      restartPolicy: Always
      terminationGracePeriodSeconds: 30

指定Pod使用Koordinator的调度器,把优先级调到最高、设置QoS为LS。

apiVersion: config.koordinator.sh/v1alpha1
kind: ClusterColocationProfile
metadata:
  name: colocation-profile-go
spec:
  selector:
    matchLabels:
      koordinator.sh/enable-colocation: "true"
  qosClass: LS
  priorityClassName: koord-prod
  koordinatorPriority: 9999
  schedulerName: koord-scheduler

节点数据:

% kubectl top node                 
NAME                                                 CPU(cores)   CPU%   MEMORY(bytes)   MEMORY%   
gke-nebula-test-cluster-default-pool-2072be92-16rv   252m         26%    1190Mi          42%       
gke-nebula-test-cluster-default-pool-2072be92-9wwg   332m         35%    1382Mi          49%       
gke-nebula-test-cluster-default-pool-2072be92-vscw   308m         32%    1337Mi          47%       
gke-nebula-test-cluster-pool-1-b15967b4-kgpn         236m         25%    1207Mi          43%       
gke-nebula-test-cluster-pool-1-b15967b4-x8ql         215m         22%    1189Mi          42%

实验数据:

实验项目 期望 结论 备注
requests > 节点的cap 能够超卖部署 node可用资源必须>=requests
requests < 节点的cap & 副本数增加到5 所有pod都能成功 2副本成功,3副本失败。3副本失败原因:没有节点满足requests,即便已经运行pod实际只占用非常小的资源。
requests cpu=100m,memory=100m。代码中进程启动内存就超过request > 512MB。副本数 = n (100 * n < 所有节点满足n个pod的request < 512*n ) Pod能正常启动 有些pod 因request通过,但运行的时候由于没有足够内存,会出现重启。
requests cpu=200m,memory=500m。然后调pod的模拟内存增长的接口,一直涨到node最大内存,但 < limit pod调度到可用内存更多的节点 pod原地重启。

结论: Koordinator不支持基于requests的超卖,使用Koordinator必须设置一个requests值。虽然可以根据经验值自动加上一个非常小的值,然后limit给很大,但内存和cpu使用涨上去后并没有重新调度,如果容器一启动就占用内存超过request,容器就不停的重启。所以依然非常依赖request的设置。

另一个开源项目scheduler-plugins

另一个开源项目是scheduler-plugins(https://github.com/kubernetes-sigs/scheduler-plugins),该项目核心能力也是通过实现负载感知调度实现资源最大利用率。不同于Koordinator,scheduler-plugins的侧重点不在在线/离线的混部。相同的是,scheduler-plugins也基于requests调度,非常依赖request的设置。

scheduler-plugins完全基于原生的扩展点编写插件,不需要对k8s集群本身作任何的修改定制。关于k8s的调度框架及框架定义的扩展点可用看官方的介绍:https://kubernetes.io/zh-cn/docs/concepts/scheduling-eviction/scheduling-framework/

我们可能比较关心的是其中的几个插件,这几个插件都是在Scoring阶段负责给节点打分的插件。

scheduler-plugins还提供其它一些项目后期很大可能需要用到的调度器插件,例如:NodeResourcesMostAllocated。

我们重点理解LoadVariationRiskBalancing的算法实现,以及对LoadVariationRiskBalancing和TargetLoadPacking这两个插件做了实验验证,后续两篇文章会分别介绍。

除了实验过程中遇到的一些问题,例如目标负载感知调度插件是使用limits而不是requests、以及缓存数据过期问题。还有一个坑,就是这三个负载感知调度插件,如果指标提供者类型使用KubernetesMetricsServer,那这三个插件不能同时启用,否则会报错(panic: http: multiple registrations for /watcher)。另外,不管指标提供者类型选择什么,每个插件都会单独缓存一份数据,如果需要优化,需要改掉这部分代码。

结论

如果通过控制requests的值来实现超卖,从官方的介绍数据来看,使用Koordinator和scheduler-plugins差别都不大,都需要解决如何收集历史指标数据,基于历史指标计算requests经验值。假设requests经验值非常可靠,那么我们应该选择哪个开源框架。

scheduler-plugins没有太多的概念,基于k8s的调度框架开发,代码很轻量,插件可插拔,可扩展性强。基于我们云原生平台的定位:解决微服务场景下的应用部署,实现按需付费。所以建议是使用scheduler-plugins。

requests值的问题如何解决:

可能的一个方向是,基于Google的论文:Autopilot: workload autoscaling at Google,实现requests的计算。

假如,requests非常的接近真实值,那是否不需要scheduler-plugins了? 还是需要的,即便requests非常的接近真实值:

  1. 默认调度器不支持负载上限调度,也就是TargetLoadPacking插件的功能。
  2. 默认调度器不考虑limits均衡调度,也就是LowRiskOverCommitment插件的功能。
  3. 默认调度器可能达不到LoadVariationRiskBalancing实现的负载感知均衡调度的效果,可能存在负载感知倾斜严重。
  4. 另话的课题:我们可能还需要自研插件,尽可能的将相同镜像的Pod分散到不同节点。
#云原生

声明:公众号、CSDN、掘金的曾用名:“Java艺术”,因此您可能看到一些早期的文章的图片有“Java艺术”的水印。

文章推荐

极致成本优化背景下,如何通过优化k8s调度器实现计算资源的按需付费(四)

验证gce的自动缩容时机以及扩容需要的时长:扩容一个节点需要等待多长时间,一个节点在没有Pod的情况下多久后会回收。结合scheduler-plugins框架验证。由于scheduler-plugins只是在Score阶段对节点打分,并未在其它阶段阻止Pod调度到分数为0的Node上,例如基于目标负载感知调度,当所有Node的负载都达到目标负载后,即便节点的requests满足Pod所需,是否能走扩容节点,而不是硬塞到现有节点上。

极致成本优化背景下,如何通过优化k8s调度器实现计算资源的按需付费(三)

本篇介绍的内容是scheduler-plugins框架的TargetLoadPacking插件,这是一个k8s调度框架的评分插件。TargetLoadPacking即目标负载调度器,用于控制节点的CPU利用率不超过目标值x%(例如65%),通过打分让所有cpu利用率超过x%的都不被选中。目标负载调度器只支持CPU。

极致成本优化背景下,如何通过优化k8s调度器实现计算资源的按需付费(二)

本篇介绍的内容是scheduler-plugins框架的LoadVariationRiskBalancing插件,这是一个k8s调度框架的评分插件,基于request、均值和标准差的K8s负载感知调度器。 我们通过实验去理解和验证该插件实现的负载感知均衡调度算法。

云原生项目用于验证负载感知调度的go-web-demo

我们在做云原生调度技术调研的时候,为了做实验获取一些数据,需要编写一个demo,支持动态模拟cup使用率和内存使用,所以用go开发了这么一个web程序。

KubeVela完结篇:我为Terraform Controller贡献了3个PR

KubeVela于2020年年底开源,距离现在还未满三年时间,是一个非常年轻的产物。KubeVela是非常创新的产物,如OAM模型的抽象设计。所以也并未成熟,除了官方文档,找不到更多资料,在使用过程中,我们也遇到各种大大小小的问题。

KubeVela篇14:如何实现存量业务的基础设施导入Kubevela+Terraform

由于我们的使用场景是将基础设施资源定义成KubeVela的组件,一个terraform “module”对应的就是一个kubevela的组件,对应terraform-controller的一个Configuration资源。因此导入的最小粒度是组件,即一个terraform “module”。