vpa recommender源码分析

418 阅读5分钟

1. 介绍

Recommender 是 VPA 的主要组成部分。它负责计算推荐资源。在启动时,Recommender 从 History Storage 中获取所有 Pod 的历史资源利用率(无论它们是否使用 VPA)以及 Pod OOM 事件的历史记录。它聚合这些数据并将其保存在内存中。

2. Recommender 设计理念

VPA 控制容器的 Resource Requests 值(内存和 CPU)。 Request 值的计算是基于对同一组控制器下的所有 pod 中具有相同名称的容器的当前和先前运行的分析来计算的。

推荐模型 (MVP) 假设内存和 CPU 利用率是独立的随机变量,其分布等于过去 N 天观察到的变量(推荐值为 N=8 以捕获每周峰值)。未来更高级的模型可能会尝试检测趋势、周期性和其他与时间相关的模式。

  • 对于 CPU,目标是将容器使用率超过请求的高百分比(例如 95%)时的时间部分保持在某个阈值(例如 1% 的时间)以下。在此模型中,“CPU 使用率”被定义为在短时间间隔内测量的平均使用率。测量间隔越短,针对尖峰、延迟敏感的工作负载的建议质量就越高。最小合理分辨率为 1/min,推荐为 1/sec。
  • 对于内存,目标是将特定时间窗口内容器使用率超过请求的概率保持在某个阈值以下(例如,24 小时内低于 1%)。窗口必须很长(≥ 24 小时)以确保由 OOM 引起的驱逐不会明显影响 (a) 服务应用程序的可用性 (b) 批处理计算的进度(更高级的模型可以允许用户指定 SLO 来控制它)。

vpa Recommender只管推荐。VPA Recommender 监视所有 Pod,不断为它们计算新的推荐资源,并将推荐值存储在 VPA 对象中。

vpa Recommender的推荐算法深受Google Borg Autopilot的moving window推荐器的启发,moving window推荐器的原理可以看下Autopilot论文。Vertical Pod Autoscaler的推荐器vpa-recommend为每个vpa对象的每个container创建存储cpu和memory使用值的decay histogram对象,定期从prometheus中拉取所有pod的资源使用情况,将container的usage写入histogram中。decay histogram的桶的大小是按照指数增长的,cpu第一个桶的大小(firstBucketSize)是0.01,memory是1e7,指数值ratio是1.05,第一个桶存储[0..firstBucketSize)[0..firstBucketSize)的使用值的权重,则第n个桶的起始值是

value(n)=firstBucketSize∗(1+ratio+ratio2+...+ratio(n−1))=firstBucketSize∗(ration−1)(ratio−1)

每个使用值权重写入到histogram中的桶的位置是

index=floor(logratio(value∗(ratio−1)firstBucketSize+1))

value代表当前的usage值,写入cpu histogram权重值是

weight=Max(cpuRequestCores,minSampleWeight)∗2(time−begin)/CPUHistogramDecayHalfLife

其中minSampleWeight=0.1,begin是记录的第一个使用值的时间,time是当前usage的时间,默认CPUHistogramDecayHalfLife=24h。

每次推荐的预测值即为0.9*totalWeight对应的桶的初始值,计算过程:从weight不为0的最小的桶开始将weight值相加,当结果大于等于0.9倍的totalWeight后,取这个桶的初始值作为推荐值。对于memory资源,vpa-recommend还会watch集群的oom事件,对于发生oom的pod,会自动增加预测值。

Google Borg Autopilot的moving window 论文下载地址:dl.acm.org/doi/pdf/10.…

2.1 源码分析

image-20220708174627387.png

Recommender启动时,首先会加载历史数据到内存中去。历史数据来源包括两部分(二选一):

  • 一是 VPA checkpoint,借助 ClusterStateFeeder 接口的 InitFromCheckpoints 方法将历史的 VPA checkpoint 加载至内存。
  • 二是 prometheus,需要配置 prometheus 采集 k8s metrics-server 上报的 cpu、memory 资源使用情况,再通过 prometheus client 借助 ClusterStateFeeder 接口的 InitFromHistoryProvider 方法加载至内存。除此之外,历史 Pod 的标签也需要从 prometheus 采集。

加载历史数据后,Recommender 由一个定时器启动(默认周期为 1min),分别执行 RunOnce 与健康检查两个步骤。 其中我们主要关注 RunOnce 方法,在一个运行周期内,Recommender 分别执行了如下 6 个步骤:

  • LoadVPAs:将 VPA 对象加载至 ClusterState (Recommender的一个核心结构体)
  • LoadPods:LoadPods 将 Pods 对象加载至 ClusterState
  • LoadRealTimeMetrics:将 metrics server 聚合后的容器资源使用情况加载至 ClusterState
  • UpdateVPAs:以半衰指数直方图为输入,计算 cpu/memory 资源推荐值,并更新到 k8s 集群中 VPA 的 status 字段
  • MaintainCheckpoints:将推荐模型使用的半衰指数直方图备份至 VPA checkpoint
  • GarbageCollect:回收内存中无用数据,保证 Recommender 不占用过多的内存和 VPA 推荐值的新鲜度

Recommender中有一个核心结构图ClusterState。通过 LoadVPAs 函数获取了所有vpa信息;通过LoadPods获取的所有pod和container信息;通过 LoadRealTimeMetrics 获取所有容器的当前cpu,mem使用量。

然后再通过UpdateVPAs算出 容器的推荐cpu, mem值。

2.2 LoadRealTimeMetrics

LoadRealTimeMetrics就是往 metric-server发送请求,然后获取 容器的cpu,mem使用值。

具体的过程就是: LoadRealTimeMetrics -> apiserver -> metric-server

这里就两个关键的问题:

(1)LoadRealTimeMetrics的请求的什么

(2)LoadRealTimeMetrics收到的是什么数据

LoadRealTimeMetrics发送的的就是 /apis/metrics.k8s.io/v1beta1/pods

这个请求最终会被metrics-server处理。

v1beta1.metrics.k8s.io                 kube-system/metrics-server   True        12d

这里只有 /pods 的原因是在于, metrics-server提供了这样的借口,返回所有pod(all-namespaces)的监控数据。

具体格式如下:

# kubectl get --raw /apis/metrics.k8s.io/v1beta1/pods | jq    //这个是请求
{
  "kind": "PodMetricsList",
  "apiVersion": "metrics.k8s.io/v1beta1",
  "metadata": {},
  "items": [
    {
      "metadata": {                            //pod 信息
        "name": "hamster-6cd8cc9554-8mj6g",
        "namespace": "default",
        "creationTimestamp": "2021-08-10T09:57:45Z",
        "labels": {
          "app": "hamster",
          "pod-template-hash": "6cd8cc9554"
        }
      },
      "timestamp": "2021-08-10T09:57:29Z",  //当前访问时间
      "window": "16s",             // 间隔时间
      "containers": [
        {
          "name": "hamster",       //容器名
          "usage": {
            "cpu": "366978272n",   //cpu使用量
            "memory": "856Ki"      // 内存使用量
          }
        }
      ]
    },
    {
      "metadata": {
        "name": "web-0",
        "namespace": "default",
        "creationTimestamp": "2021-08-10T09:57:45Z",
        "labels": {
          "app": "nginx",
          "controller-revision-hash": "web-6bc849cb6b",
          "statefulset.kubernetes.io/pod-name": "web-0"
        }
      },
      "timestamp": "2021-08-10T09:57:10Z",
      "window": "12s",
      "containers": [
        {
          "name": "nginx",
          "usage": {
            "cpu": "0",
            "memory": "2096Ki"
          }
        }
      ]
    }
  ]
}