Kubernetes HPA之metrics-server

712 阅读3分钟

一、前言

metrics-server、custom-metrics-server以aggregator方式集成到apiserver中,集成方式见下图: image.png 图1 出自:blog.jetstack.io/blog/resour…

二、metrics-server

1、metrics-server获取指标的方式

image.png 图2 Resource Metrics Pipeline

图中从右到左的架构组件包括以下内容:

  • cAdvisor: 用于收集、聚合和公开 Kubelet 中包含的容器指标的守护程序。
  • kubelet: 用于管理容器资源的节点代理。 可以使用 /metrics/resource 和 /stats kubelet API 端点访问资源指标。
  • Summary API: kubelet 提供的 API,用于发现和检索可通过 /stats 端点获得的每个节点的汇总统计信息。
  • metrics-server: 集群插件组件,用于收集和聚合从每个 kubelet 中提取的资源指标。 API 服务器提供 Metrics API 以供 HPA、VPA 和 kubectl top 命令使用。Metrics Server 是 Metrics API 的参考实现。
  • Metrics API: Kubernetes API 支持访问用于工作负载自动缩放的 CPU 和内存。 要在你的集群中进行这项工作,你需要一个提供 Metrics API 的 API 扩展服务器。

说明:  cAdvisor 支持从 cgroups 读取指标,它适用于 Linux 上的典型容器运行时。 如果你使用基于其他资源隔离机制的容器运行时,例如虚拟化,那么该容器运行时必须支持 CRI 容器指标 以便 kubelet 可以使用指标。

2、可扩展性限制

我们希望从集群中运行的每个 pod 和节点收集最多 10 个指标。从 Kubernetes 1.6 开始,我们支持 5000 个节点的集群,每个节点 30 个 pod。假设我们想要以 1 分钟的粒度收集指标,这意味着:

10 x 5000 x 30 / 60 = 25000 metrics per second by average

Kubernetes apiserver 将所有 Kubernetes 资源保存在其键值存储etcd中。它无法处理这样的负载。另一方面,指标往往会经常变化,是暂时的,如果它们丢失了,我们可以在下一次内务处理操作中收集它们。 然后我们将它们存储在内存中。 这意味着我们不能重用主要的apiserver,而是引入一个新的 - metrics server。

3、Metrics API

特性状态:  Kubernetes 1.8 [beta]

metrics-server 实现了 Metrics API。此 API 允许你访问集群中节点和 Pod 的 CPU 和内存使用情况。 它的主要作用是将资源使用指标提供给 K8s 自动缩放器组件。

下面是一个 minikube 节点的 Metrics API 请求示例,通过 jq 管道处理以便于阅读:

kubectl get --raw "/apis/metrics.k8s.io/v1beta1/nodes/minikube" | jq '.'

这是使用 curl 来执行的相同 API 调用:

curl http://localhost:8080/apis/metrics.k8s.io/v1beta1/nodes/minikube

响应示例:

{
  "kind": "NodeMetrics",
  "apiVersion": "metrics.k8s.io/v1beta1",
  "metadata": {
    "name": "minikube",
    "selfLink": "/apis/metrics.k8s.io/v1beta1/nodes/minikube",
    "creationTimestamp": "2022-01-27T18:48:43Z"
  },
  "timestamp": "2022-01-27T18:48:33Z",
  "window": "30s",
  "usage": {
    "cpu": "487558164n",
    "memory": "732212Ki"
  }
}

下面是一个 kube-system 命名空间中的 kube-scheduler-minikube Pod 的 Metrics API 请求示例, 通过 jq 管道处理以便于阅读:

kubectl get --raw "/apis/metrics.k8s.io/v1beta1/namespaces/kube-system/pods/kube-scheduler-minikube" | jq '.'

这是使用 curl 来完成的相同 API 调用:

curl http://localhost:8080/apis/metrics.k8s.io/v1beta1/namespaces/kube-system/pods/kube-scheduler-minikube

响应示例:

{
  "kind": "PodMetrics",
  "apiVersion": "metrics.k8s.io/v1beta1",
  "metadata": {
    "name": "kube-scheduler-minikube",
    "namespace": "kube-system",
    "selfLink": "/apis/metrics.k8s.io/v1beta1/namespaces/kube-system/pods/kube-scheduler-minikube",
    "creationTimestamp": "2022-01-27T19:25:00Z"
  },
  "timestamp": "2022-01-27T19:24:31Z",
  "window": "30s",
  "containers": [
    {
      "name": "kube-scheduler",
      "usage": {
        "cpu": "9559630n",
        "memory": "22244Ki"
      }
    }
  ]
}

4、节点指标获取

kubectl get --raw "/api/v1/nodes/minikube/proxy/metrics/resource

说明: 从metrics-server0.6.x 开始,metricsserver查询metrics/resource kubelet 端点, 不查询 /stats/summary

5、metrics-server从节点获取指标最长周期

GetMetrics-->collectNode-->Scrape-->NewScraper-->Config-->runCommand-->options.ServerConfig()-->o.KubeletClient.KubeletRequestTimeout

获取指标周期:maxDelayMs(4s) + KubeletRequestTimeout(10s) = 14s

...
delayMs := delayPerSourceMs * len(nodes)
if delayMs > maxDelayMs {
   delayMs = maxDelayMs
}
...

for _, node := range nodes {
   go func(node *corev1.Node) {
      // Prevents network congestion.
      sleepDuration := time.Duration(rand.Intn(delayMs)) * time.Millisecond
      time.Sleep(sleepDuration)
      // make the timeout a bit shorter to account for staggering, so we still preserve
      // the overall timeout
      ctx, cancelTimeout := context.WithTimeout(baseCtx, c.scrapeTimeout)
      defer cancelTimeout()
      klog.V(2).InfoS("Scraping node", "node", klog.KObj(node))
      m, err := c.collectNode(ctx, node)
      if err != nil {
         if errors.Is(err, context.DeadlineExceeded) {
            klog.ErrorS(err, "Failed to scrape node, timeout to access kubelet", "node", klog.KObj(node), "timeout", c.scrapeTimeout)
         } else {
            klog.ErrorS(err, "Failed to scrape node", "node", klog.KObj(node))
         }
      }
      responseChannel <- m
   }(node)

   ...
   func NewKubeletClientOptions() *KubeletClientOptions {
	o := &KubeletClientOptions{
		KubeletPort:                  10250,
		KubeletPreferredAddressTypes: make([]string, len(utils.DefaultAddressTypePriority)),
		KubeletRequestTimeout:        10 * time.Second,
	}

	for i, addrType := range utils.DefaultAddressTypePriority {
		o.KubeletPreferredAddressTypes[i] = string(addrType)
	}

	return o
}
   ...

文章引用: kubernetes官方文档kubernetes.io/zh-cn/docs/…