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

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

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

本文链接:https://wujiuye.com/article/085456ac59254f3592e81f6e3acc8c9f

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

之二:基于request、均值和标准差的K8s负载感知调度器

本篇介绍的内容是scheduler-plugins框架的LoadVariationRiskBalancing插件,这是一个k8s调度框架的评分插件。 我们通过实验去理解和验证该插件实现的负载感知均衡调度算法。 官方文档:https://github.com/kubernetes-sigs/scheduler-plugins/tree/master/kep/61-Trimaran-real-load-aware-scheduling。

LoadVariationRiskBalancing插件是scheduler-plugins提供的负载感知调度的三大插件之一,也是基于requests的负载感知调度器。其目的是实现将负载平均到每个Node,目前支持CPU+Memory,会同时考虑CPU和Memory。

基于requests负载感知调度意味着,LoadVariationRiskBalancing插件不会考虑实际负载,因此依赖request值非常接近真实值才准确,并不是真的动态平衡。

LoadVariationRiskBalancing插件的算法是利用节点负载在某段时间内(滑动窗口)的平均值(M)和标准差(V)这两个指标,假设集群所有节点的CPU利用率的M+V是0.3(30%),那么每个节点的cpu利用率的M+V越接近0.3,得分应该越小。

标准差越接近平均值意味着节点的利用率分布越集中。关于标准差可以参考这篇文章(非常简单易懂):https://zhuanlan.zhihu.com/p/172506729

平均值和标准差都是计算的资源利用率,如CPU利用率的平均值和标准差、内存利用率的平均值和标准差,利用率不会超过100%,取值[0~1],因此平均值和标准差的取值也是[0~1]。

LoadVariationRiskBalancing是分别计算每种资源的得分,再取得分的最小值,例:假设CPU得分0,内存得分10,则节点的最终得分是0。

怎么理解官方文档的这张图: Trimaran-real-load-aware-scheduling

当每个节点的利用率的平均值和标准差符合关系:V + M = c 时(c为整个集群的利用率的平均值加上标准差),说明集群中每个节点的负载接近集群负载。

mu-sigma图的mu指的是均值(μ),sigma指标准差(σ)。

对角线公式:V + M = c ===> sigma + mu <= 1 (100%)

个人对这张图的整体理解:斜线为集群的负载,点为节点的负载。而插件的目标是,节点的负载(M+V)应该无限接近于集群的负载(c)。因集群的整体负载c是固定的(也是集群的M+V),每个节点的M和V是不同的,但一定是M+V=C,才符合期望的目标-负载均衡。也可以反过来理解,斜线c = M+V,根据集群中所有节点的(M,V)画到坐标轴上,这些点应该分布在虚线c附近,即任何一个点(M+V)都无限接近于c。

如何理解官方文档的举例?

插件的算法实现: 1. 获取当前要调度的Pod所请求的资源,记为r。 2. 获取当前节点各类资源(CPU、Memory)当前滑动窗口(比如一分钟、或者一个小时)的平均值和标准差。 3. 计算当前节点对各类资源的得分:Si = M + r + V 4. 获取每种类型资源的分数并将其绑定到 [0,1],意思就是最小值为0,最大值为1,小于最小值取最小值,大于最大值取最大值:Si = min(Si,1.0)。 5. 计算当前节点每种资源的优先级得分:Ui = (1-Si) x MaxPriority。 6. 当前节点最终的得分为:U = min(Ui),意思是cpu、内存的分数,哪个低取哪个。

现在来看例子: 假设我们有三个节点N1、N2、N3,要调度的Pod的CPU和内存请求分别为500m和1GB,所有节点容量均为4核、8GB。

第一步:获取Pod请求的资源:

* r(cpu) = 500/4*1000 = 0.125 
* r(memory) = 1024/8*1024 = 0.125

第二步:每个节点的当前M和V为:

* N1:
    * M(cpu) = 0.8核/4 = 0.2     M(memory) = 1.6G/8 = 0.2
    * V(cpu) = 3.2核/4 = 0.8     V(memory) = 4.8G/8 = 0.6
* N2:
    * M(cpu) = 2核/4 = 0.5         M(memory) = 6.4G/8 = 0.8
    * V(cpu) = 0.04核/4 = 0.01     V(memory) = 0.004G/8 = 0.0005
* N3:
    * M(cpu) = 1.2核/4 = 0.3     M(memory) = 3.2G/8 = 0.4
    * V(cpu) = 0.4核/4 = 0.1     V(memory) = 0.4G/8 = 0.05

第三步:计算当前节点对各类资源的得分:

* N1:
    * Si(cpu) = M(cpu) + r(cpu) + v(cpu) = 0.2 + 0.125 + 0.8 = 1.125 ===> 保留两位小数 = 1.2
    * Si(memory) = M(memory) + r(memory) + v(memory) = 0.2 + 0.125 + 0.6 = 0.925 ===> 保留两位小数 = 0.93
* N2:
    * Si(cpu) = M(cpu) + r(cpu) + v(cpu) = 0.5 + 0.125 + 0.01 = 0.635 ===> 保留两位小数 = 0.64
    * Si(memory) = M(memory) + r(memory) + v(memory) = 0.8 + 0.125 + 0.0005 = 0.9255 ===> 保留两位小数 = 0.93
* N3:
    * Si(cpu) = M(cpu) + r(cpu) + v(cpu) = 0.3 + 0.125 + 0.1 = 0.525 ===> 保留两位小数 = 0.53
    * Si(memory) = M(memory) + r(memory) + v(memory) = 0.4 + 0.125 + 0.05 = 0.575 ===> 保留两位小数 = 0.58

第四步:取值[0,1]:

* N1:
    * Si(cpu) = 1
    * Si(memory) = 0.93
* N2:
    * Si(cpu) = 0.64
    * Si(memory) = 0.93
* N3:
    * Si(cpu) = 0.53
    * Si(memory) =  0.58

第五步:计算得分(案例中没看懂MaxPriority等于多少,假如为100,那么Ui = (1-Si) * 100,计算结果跟官方案例给的值有点出入,可能是保留两位小数的时机不一样):

* N1:
    * Ui(cpu) = (1 - 1) * 100 = 0
    * Ui(memory) = (1 - 0.93) * 100 = 7
* N2:
    * Ui(cpu) = (1 - 0.64) * 100 = 36
    * Ui(memory) = (1 - 0.93) * 100 = 7
* N3:
    * Ui(cpu) = (1 - 0.53) * 100 = 47
    * Ui(memory) = (1 - 0.58) * 100 = 42

第六步:节点最终的得分:

* N1:
    * U = 0
* N2:
    * U = 7
* N3:
    * U = 42

根据得分,Pod将被调度到N3节点。

实验数据

代码的修改

通过阅读文档和源码,官方打包的helm不包含TargetLoadPacking、LoadVariationRiskBalancing、LowRiskOverCommitment这三个负载感知调度插件,备注是因为这几个插件需要依赖指标服务。

LoadVariationRiskBalancing and TargetLoadPacking are not enabled by default. as they need extra RBAC privileges on metrics.k8s.io. (来自代码中的注释:https://github.com/kubernetes-sigs/scheduler-plugins/blob/master/manifests/install/charts/as-a-second-scheduler/values.yaml)

因此需要自己修改代码打包。参考官方提供的as-a-second-scheduler这个Chart去改。scheduler-plugins包含Scheduler和Controller,基于负载调度的几个插件并不依赖Controller,因此Controller不需要部署,相关的yaml不需要写。

为访问metrics-api的相关资源授予权限(rbac)。

metrics-api-rbac.yaml:
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: sched-plugins-metrics-api-reader-role-binding
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: sched-plugins-metrics-api-reader-role
subjects:
  - kind: ServiceAccount
    name: {{ .Values.scheduler.name }}
    namespace: {{ .Release.Namespace }}
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: sched-plugins-metrics-api-reader-role
rules:
  - apiGroups:
      - "metrics.k8s.io"
    resources:
      - PodMetrics
      - NodeMetrics
    verbs:
      - get
      - list
      - watch

插件启用LoadVariationRiskBalancing,禁用默认的Score插件。

plugins:
  enabled: ["LoadVariationRiskBalancing"]
  disabled: ["NodeResourcesLeastAllocated","NodeResourcesBalancedAllocation"] # 禁用默认的

插件的配置,其中safeVarianceMargin配置项对应算法中的ita值。 这个是对使用量不超过节点容量的置信度系数,涉及到高斯分布,不展开这个话题,这里配置为1即可。

ita这里假设实际使用量遵循高斯分布并遵循68-96-99.5 规则,对使用量不超过节点容量的置信度进行建模。所以当ita得到不同的值时,我们得到不同的不超过容量的置信度。 * ita = 1,我们有 16% 的风险是实际使用量超过节点容量。 * ita = 2,我们有 2.5% 的机会实际使用量超过节点容量。 * ita = 3,我们有 0.15% 的机会实际使用量超过节点容量。默认情况下,我们选择它ita = 1是因为我们希望提高整体利用率。ita可以通过SafeVarianceMargin插件进行配置。

pluginConfig:
  - name: LoadVariationRiskBalancingArgs
    args:
      safeVarianceMargin: 1
      metricProvider:
        type: KubernetesMetricsServer

启用的插件给一个非常大的权重值,避免存在其它不知道的Score插件影响实验数据。(由于个人没有权限查看集群中启用了哪些Score插件。)

{{- if .Values.plugins.enabled }}
apiVersion: v1
kind: ConfigMap
metadata:
  name: scheduler-config
  namespace: {{ .Release.Namespace }}
data:
  scheduler-config.yaml: |
    apiVersion: kubescheduler.config.k8s.io/v1
    kind: KubeSchedulerConfiguration
    leaderElection:
      leaderElect: {{ .Values.scheduler.leaderElect }}
    profiles:
    # Compose all plugins in one profile
    - schedulerName: {{ .Values.scheduler.name }}
      plugins:
        multiPoint:
          enabled:
          {{- range $.Values.plugins.enabled }}
          - name: {{ title . }}
            weight: 10000
          {{- end }}
          disabled:
          {{- range $.Values.plugins.disabled }}
          - name: {{ title . }}
          {{- end }}
      {{- if $.Values.pluginConfig }}
      pluginConfig: {{ toYaml $.Values.pluginConfig | nindent 6 }}
      {{- end }}
{{- end }}

接着就是通过helm chart将调度器部署到k8s集群。

单个Pod验证算法

我们需要查看调度器获取输出的一些日记,因为指标是实时变化的,通过日记获取输出插件运行算法逻辑当时的节点的cpu和内存的使用率和标准差, 这样才有数据验证Pod的调度结果是否符合算法预期。

用于实验的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 
    spec:
      containers:
        - name: go-web-demo
          image: us-central1-docker.pkg.dev/infrastructure-410808/nebula/web-demo:v1.0.2
          imagePullPolicy: Always
          resources:
            limits:
              cpu: 2000m
              memory: 4048Mi
            requests:
              cpu: 100m
              memory: 100Mi
      restartPolicy: Always
      terminationGracePeriodSeconds: 30
      tolerations:
        - key: "sandbox.gke.io/runtime"
          operator: "Equal"
          value: "gvisor"
          effect: "NoSchedule"
      schedulerName: scheduler-plugins-scheduler

apply后,得到的调度器日记输出如下。

I0123 01:52:35.418722       1 eventhandlers.go:126] "Add event for unscheduled pod" pod="default/go-web-demo-56b6c86796-27p5h"
I0123 01:52:35.419284       1 scheduling_queue.go:537] "Pod moved to an internal scheduling queue" pod="default/go-web-demo-56b6c86796-27p5h" event="PodAdd" queue="Active"
I0123 01:52:35.419507       1 schedule_one.go:80] "About to try and schedule pod" pod="default/go-web-demo-56b6c86796-27p5h"
I0123 01:52:35.419568       1 schedule_one.go:93] "Attempting to schedule pod" pod="default/go-web-demo-56b6c86796-27p5h"

I0123 01:52:35.421880       1 loadvariationriskbalancing.go:82] "Calculating score" pod="default/go-web-demo-56b6c86796-27p5h" nodeName="gke-nebula-test-cluster-default-pool-521b9803-ncck"
I0123 01:52:35.422109       1 resourcestats.go:72] "Resource usage statistics for node" node="gke-nebula-test-cluster-default-pool-521b9803-ncck" resource="cpu" capacity=940 required=100 usedAvg=21.15 usedStdev=0
I0123 01:52:35.422142       1 analysis.go:58] "Evaluating risk factor" mu=0.12888297872340426 sigma=0 margin=1 sensitivity=1 risk=0.06444148936170213
I0123 01:52:35.422163       1 loadvariationriskbalancing.go:103] "Calculating CPUScore" pod="default/go-web-demo-56b6c86796-27p5h" nodeName="gke-nebula-test-cluster-default-pool-521b9803-ncck" cpuScore=93.55585106382979
I0123 01:52:35.422184       1 resourcestats.go:72] "Resource usage statistics for node" node="gke-nebula-test-cluster-default-pool-521b9803-ncck" resource="memory" capacity=2805.98828125 required=100 usedAvg=779.5381542436246 usedStdev=0
I0123 01:52:35.422237       1 analysis.go:58] "Evaluating risk factor" mu=0.3134504018141557 sigma=0 margin=1 sensitivity=1 risk=0.15672520090707784
I0123 01:52:35.422277       1 loadvariationriskbalancing.go:110] "Calculating MemoryScore" pod="default/go-web-demo-56b6c86796-27p5h" nodeName="gke-nebula-test-cluster-default-pool-521b9803-ncck" memoryScore=84.32747990929221

I0123 01:52:35.421883       1 loadvariationriskbalancing.go:82] "Calculating score" pod="default/go-web-demo-56b6c86796-27p5h" nodeName="gke-nebula-test-cluster-default-pool-521b9803-71zk"
I0123 01:52:35.422483       1 loadvariationriskbalancing.go:119] "Calculating totalScore" pod="default/go-web-demo-56b6c86796-27p5h" nodeName="gke-nebula-test-cluster-default-pool-521b9803-ncck" totalScore=84
I0123 01:52:35.422603       1 resourcestats.go:72] "Resource usage statistics for node" node="gke-nebula-test-cluster-default-pool-521b9803-71zk" resource="cpu" capacity=940 required=100 usedAvg=21.15 usedStdev=0
I0123 01:52:35.422702       1 analysis.go:58] "Evaluating risk factor" mu=0.12888297872340426 sigma=0 margin=1 sensitivity=1 risk=0.06444148936170213
I0123 01:52:35.422806       1 loadvariationriskbalancing.go:103] "Calculating CPUScore" pod="default/go-web-demo-56b6c86796-27p5h" nodeName="gke-nebula-test-cluster-default-pool-521b9803-71zk" cpuScore=93.55585106382979
I0123 01:52:35.422891       1 resourcestats.go:72] "Resource usage statistics for node" node="gke-nebula-test-cluster-default-pool-521b9803-71zk" resource="memory" capacity=2805.98046875 required=100 usedAvg=805.8856221138391 usedStdev=0
I0123 01:52:35.422964       1 analysis.go:58] "Evaluating risk factor" mu=0.32284102908149975 sigma=0 margin=1 sensitivity=1 risk=0.16142051454074988
I0123 01:52:35.423029       1 loadvariationriskbalancing.go:110] "Calculating MemoryScore" pod="default/go-web-demo-56b6c86796-27p5h" nodeName="gke-nebula-test-cluster-default-pool-521b9803-71zk" memoryScore=83.85794854592501
I0123 01:52:35.423101       1 loadvariationriskbalancing.go:119] "Calculating totalScore" pod="default/go-web-demo-56b6c86796-27p5h" nodeName="gke-nebula-test-cluster-default-pool-521b9803-71zk" totalScore=84

I0123 01:52:35.421915       1 loadvariationriskbalancing.go:82] "Calculating score" pod="default/go-web-demo-56b6c86796-27p5h" nodeName="gke-nebula-test-cluster-default-pool-521b9803-r408"
I0123 01:52:35.423269       1 resourcestats.go:72] "Resource usage statistics for node" node="gke-nebula-test-cluster-default-pool-521b9803-r408" resource="cpu" capacity=940 required=100 usedAvg=39.48 usedStdev=0
I0123 01:52:35.423318       1 analysis.go:58] "Evaluating risk factor" mu=0.14838297872340425 sigma=0 margin=1 sensitivity=1 risk=0.07419148936170213
I0123 01:52:35.423364       1 loadvariationriskbalancing.go:103] "Calculating CPUScore" pod="default/go-web-demo-56b6c86796-27p5h" nodeName="gke-nebula-test-cluster-default-pool-521b9803-r408" cpuScore=92.58085106382978
I0123 01:52:35.423453       1 resourcestats.go:72] "Resource usage statistics for node" node="gke-nebula-test-cluster-default-pool-521b9803-r408" resource="memory" capacity=2805.98828125 required=100 usedAvg=1086.6674462019755 usedStdev=0
I0123 01:52:35.423517       1 analysis.go:58] "Evaluating risk factor" mu=0.4229053464447627 sigma=0 margin=1 sensitivity=1 risk=0.21145267322238134
I0123 01:52:35.423543       1 loadvariationriskbalancing.go:110] "Calculating MemoryScore" pod="default/go-web-demo-56b6c86796-27p5h" nodeName="gke-nebula-test-cluster-default-pool-521b9803-r408" memoryScore=78.85473267776186
I0123 01:52:35.423562       1 loadvariationriskbalancing.go:119] "Calculating totalScore" pod="default/go-web-demo-56b6c86796-27p5h" nodeName="gke-nebula-test-cluster-default-pool-521b9803-r408" totalScore=79

I0123 01:52:35.424599       1 default_binder.go:53] "Attempting to bind pod to node" pod="default/go-web-demo-56b6c86796-27p5h" node="gke-nebula-test-cluster-default-pool-521b9803-71zk"
I0123 01:52:35.434890       1 round_trippers.go:553] POST https://10.116.0.1:443/api/v1/namespaces/default/pods/go-web-demo-56b6c86796-27p5h/binding 201 Created in 9 milliseconds
I0123 01:52:35.435213       1 handler.go:133] "updateCache" pod="default/go-web-demo-56b6c86796-27p5h"
I0123 01:52:35.435348       1 eventhandlers.go:197] "Add event for scheduled pod" pod="default/go-web-demo-56b6c86796-27p5h"
I0123 01:52:35.435161       1 eventhandlers.go:171] "Delete event for unscheduled pod" pod="default/go-web-demo-56b6c86796-27p5h"
I0123 01:52:35.435657       1 scheduling_queue.go:1120] "Event received while pods are in flight" event="AssignedPodAdd" numPods=1
I0123 01:52:35.435748       1 cache.go:404] "Finished binding for pod, can be expired" podKey="e4a23d77-72a4-44f8-9876-d5a61b7e398e" pod="default/go-web-demo-56b6c86796-27p5h"
I0123 01:52:35.436133       1 schedule_one.go:286] "Successfully bound pod to node" pod="default/go-web-demo-56b6c86796-27p5h" node="gke-nebula-test-cluster-default-pool-521b9803-71zk" evaluatedNodes=3 feasibleNodes=3

从日记中拿到的数据如下: Cap表示节点总的可用的资源上限,UsedAvg表示滑动窗口内节点资源的平均使用量,UsedStdev表示滑动窗口内节点资源使用的标准差。

* ncck节点:
    * Cap(cpu): 940m        Cap(memory): 2805.98M
    * UsedAvg(cpu): 21.15m  UsedAvg(memory): 779.53M
    * UsedStdev(cpu): 0     UsedStdev(memory): 0
* 71zk节点:
    * Cap(cpu): 940m        Cap(memory): 2805.98M
    * UsedAvg(cpu): 21.15m  UsedAvg(memory): 805.88M
    * UsedStdev(cpu): 0     UsedStdev(memory): 0
* r408节点:
    * Cap(cpu): 940m        Cap(memory): 2805.98M
    * UsedAvg(cpu): 39.48m  UsedAvg(memory): 1086.66M
    * UsedStdev(cpu): 0     UsedStdev(memory): 0

由当前调度的Pod请求的资源cpu是100m,内存是100M,简化公式,将第二步、第三步、第四步合并一起计算各资源得分Si:

* ncck节点:
    * Si(cpu): M(cpu) + r(cpu) + V(cpu) = (21.15 + 100 + 0) / 940m = 0.1288 (与日记输出的mu符合)      
    * Si(memory): M(memory) + r(memory) + V(memory) = (779.53 + 100 + 0) / 2805.98 = 0.3134 (与日记输出的mu符合)
* 71zk节点:
    * Si(cpu): (21.15 + 100 + 0) / 940 = 0.1288 (与日记输出的mu符合)      
    * Si(memory): (805.88 + 100 + 0) / 2805.98 = 0.3228 (与日记输出的mu符合)
* r408节点:
    * Si(cpu): (39.48 + 100 + 0) / 940 = 0.1483 (与日记输出的mu符合)      
    * Si(memory): (1086.66 + 100 + 0) / 2805.98 = 0.4229 (与日记输出的mu符合)

根据公式第五步、第六步计算节点得分:

* U(ncck节点): min(1 - Si(cpu) * 100 = 87.12, 1 - Si(memory) * 100 = 68.66) = 87 (结果舍弃小数部分,四舍五入)
* U(71zk节点): min(1 - Si(cpu) * 100 = 87.12, 1 - Si(memory) * 100 = 67.72) = 87 (结果舍弃小数部分,四舍五入)
* U(r408节点): min(1 - Si(cpu) * 100 = 85.17, 1 - Si(memory) * 100 = 57.71) = 85 (结果舍弃小数部分,四舍五入)

计算结果与日记输出的得分有些出入:U(ncck节点) = 84 、 U(71zk节点) = 84、U(r408节点) = 79。 从代码找到,计算结果*了一个风险系数。

risk := (mu + sigma) / 2 

这行代码实际是 Si / 2,因为这里的mu是 UsedAvg + podReq计算出来的。计算各节点资源的risk为:

* U(ncck节点):
    * risk(cpu): 0.1288 / 2 = 0.064
    * risk(memory): 0.3134 / 2 = 0.1567
* U(71zk节点):
    * risk(cpu): 0.1288 / 2 = 0.064
    * risk(memory): 0.3228 / 2 = 0.1614
* U(r408节点):
    * risk(cpu): 0.1483 / 2 = 0.0741
    * risk(memory): 0.4229 / 2 = 0.2114

计算结果与日记输出的risk值相符。

把risk值代入公式重新计算节点得分:

* U(ncck节点): min(1 - risk(cpu) * 100 = 87.12, 1 - risk(memory) * 100 = 68.66) = 84 (结果舍弃小数部分,四舍五入)
* U(71zk节点): min(1 - risk(cpu) * 100 = 87.12, 1 - risk(memory) * 100 = 67.72) = 84 (结果舍弃小数部分,四舍五入)
* U(r408节点): min(1 - risk(cpu) * 100 = 85.17, 1 - risk(memory) * 100 = 57.71) = 79 (结果舍弃小数部分,四舍五入)

根据最终得分,应选择ncck节点或71zk节点,实际选择71zk节点,符合预期。

多个Pod验证Pod的分布

把副本数增加到10,观察各节点的Pod分布。

各节点负载情况:

wujiuye@wujiuyedeMacBook-Pro cloud_native % kubectl top node
NAME                                                 CPU(cores)   CPU%   MEMORY(bytes)   MEMORY%   
gke-nebula-test-cluster-default-pool-521b9803-71zk   47m          5%     1178Mi          41%       
gke-nebula-test-cluster-default-pool-521b9803-ncck   54m          5%     1127Mi          40%       
gke-nebula-test-cluster-default-pool-521b9803-r408   88m          9%     1520Mi          54% 

符合预期。

把71zk节点上的某个pod的cpu调到5,使得总体的节点cpu在10%以内,等待一个滑动窗口时间(不具体是多少的情况下,等个几分钟)。

各节点负载情况:

wujiuye@wujiuyedeMacBook-Pro cloud_native % kubectl top node
NAME                                                 CPU(cores)   CPU%   MEMORY(bytes)   MEMORY%   
gke-nebula-test-cluster-default-pool-521b9803-71zk   94m          10%    1176Mi          41%       
gke-nebula-test-cluster-default-pool-521b9803-ncck   53m          5%     1114Mi          39%       
gke-nebula-test-cluster-default-pool-521b9803-r408   84m          8%     1518Mi          54% 

随后把副本数增加到15个,观察Pod分布情况。

各节点负载情况:

wujiuye@wujiuyedeMacBook-Pro cloud_native % kubectl top node                 
NAME                                                 CPU(cores)   CPU%   MEMORY(bytes)   MEMORY%   
gke-nebula-test-cluster-default-pool-521b9803-71zk   101m         10%    1183Mi          42%       
gke-nebula-test-cluster-default-pool-521b9803-ncck   56m          5%     1146Mi          40%       
gke-nebula-test-cluster-default-pool-521b9803-r408   85m          9%     1521Mi          54%  

算法同时考虑cpu和内存,由于ncck节点的内存消耗接近71zk节点的内存消耗,所以节点得分是差别不大的,结果符合预期。

总结

通过简单的实验验证了算法的准确性,但由于实验数据还是太过简单,实际效果还需真实场景验证。

#云原生

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

文章推荐

为什么java不适合云原生

云原生的优势在于利用Serverless技术优化基础设施成本,要求应用启动速度快且内存占用低。然而,Java应用在自动弹性扩缩容和内存消耗方面存在问题。文章以部署个人项目的视角,通过比较小明使用Go语言和小聪使用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调度器实现计算资源的按需付费(一)

在降低增笑的大背景下,如何在保证稳定性的前提下,做到极致压缩k8s资源的使用,实现基础设施真正的按需付费,是我们云原生项目的目标之一。要实现如Railway这种产品的基础设施按实际使用付费,不能简单的使用云Serverless产品,因为这些产品都有最低限额的要求,例如阿里云最低限制要求Pod至少0.25cpu+0.5g内存,但其实大多数应用这个配额都用不到,大量的时间cpu负载几乎为0,内存消耗可能也就几十M(Java应用除外),大量的低使用率的Pod会造成资源的浪费。

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

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

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

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