通过HPA/VPA,聊聊K8S监控那些事儿

3,228 阅读10分钟

小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。

本文已参与「掘力星计划」,赢取创作大礼包,挑战创作激励金。

前言

对于 Kubernetes 集群来说,弹性伸缩总体上应该包括以下几种:

  • Cluster-Autoscale(CA)
  • Vertical Pod Autoscaler(VPA)
  • Horizontal-Pod-Autoscaler(HPA)

弹性伸缩依赖集群监控数据,如CPU、内存等,这篇文章会介绍其数据链路和实现原理,同时阐述 k8s 中的监控体系,最后回答常见的一些问题:

  1. 什么是VPA?什么是HPA?
  2. metrics-server,cadvisor,kube-state-metrics 有什么区别?
  3. kubectl top 为什么会报错?
  4. kubectl top pod 和 exec 进入 pod 后看到的 top 不一样?

什么是VPA?HPA?

为了解决业务服务负载时刻存在的巨大波动和资源实际使用与预估之间差距,就有了针对业务本身的“扩缩容”解决方案: Horizontal Pod Autoscaler(HPA)和 Vertical Pod Autoscaler(VPA)。

为了充分利用集群现有资源优化成本,当一个资源占用已经很大的业务需要扩容时,其实可以先尝试优化业务负载自身的资源需求配置(request与实际的差距),只有当集群的资源池确实已经无法满足负载的实际的资源需求时,再调整资源池的总量保证资源的可用性,这样可以将资源用到极致。

所以总的来说弹性伸缩应该包括:

  1. Cluster-Autoscale: 集群容量(node数量)自动伸缩,跟自动化部署相关的,依赖iaas的弹性伸缩,主要用于虚拟机容器集群
  2. Vertical Pod Autoscaler: 工作负载Pod垂直(资源配置)自动伸缩,如自动计算或调整deployment的Pod模板limit/request,依赖业务历史负载指标
  3. Horizontal-Pod-Autoscaler: 工作负载Pod水平自动伸缩,如自动scale deployment的replicas,依赖业务实时负载指标

其中VPA和HPA都是从业务负载角度从发的优化

VPA是解决资源配额(Pod的CPU、内存的limit/request)评估不准的问题。

HPA则要解决的是业务负载压力波动很大,需要人工根据监控报警来不断调整副本数的问题。

有了HPA后,被关联上HPA的deployment,后续副本数修改就不用人工管理,HPA controller将会根据业务忙闲情况自动帮你动态调整。当然还有一种固定策略的特殊HPA: cronHPA,也就是直接按照cron的格式设定扩容和缩容时间及对应副本数,可以简单理解为定时伸缩,这种不需要动态识别业务繁忙度,属于静态HPA,适用于业务流量变化有固定时间周期规律的情况。

HPA是怎么实现的?

既然是自动根据业务忙闲来调整业务工作负载的副本数,其实HPA的实现思路很容易想到:通过监控业务繁忙情况,在业务忙时,就要对workload扩容副本数;等到业务闲下来时,自然又要把副本数再缩下去。所以实现水平扩缩容的关键就在于:

  • 如何识别业务的忙闲程度
  • 使用什么样的副本调整策略

kubernetes 提供了一种标准 metrics 接口,HPA controller 通过这个统一 metrics 接口可以查询到任意一个HPA对象关联的 deployment 业务的繁忙指标 metrics 数据,不同的业务的繁忙指标均可以自定义,只需要在对应的HPA里定义关联 deployment 对应的 metrics 即可。

标准的 metrics 查询接口有了,还需要实现 metrics API 的服务端,并提供各种 metrics 数据,我们知道k8s的所有核心组件之间都是通过 apiserver 进行通信,所以作为 k8s API 的扩展,metrics APIserver 自然选择了基于API Aggregation 聚合层,这样 HPA controller 的 metrics 查询请求就自动通过 apiserver 的聚合层转发到后端真实的metrics API的服务端,对应下图的 promesheus adapter和 metrics server。

可以简单看作如下的数据链路:

用户可通过配置APIService资源对象以使用API聚合机制,如下是metrics API的配置文件:

[root@centos ~]$ kubectl get APIservice v1beta1.metrics.k8s.io
NAME                     SERVICE                       AVAILABLE   AGE
v1beta1.metrics.k8s.io   kube-system/metrics-service   True        32d

[root@centos ~]$ kubectl get APIservice v1beta1.metrics.k8s.io -o yaml
apiVersion: apiregistration.k8s.io/v1
kind: APIService
metadata:
  name: v1beta1.metrics.k8s.io
spec:
  service:
    name: metrics-server
    namespace: kube-system
  group: metrics.k8s.io
  version: v1beta1
  insecureSkipTLSVerify: true
  groupPriorityMinimum: 100
  versionPriority: 100

如上,APIService提供了一个名为v1beta1.metrics.k8s.io的 API,并绑定至一个名为metrics-server的Service资源对象。

因此,访问Metrics-Server的方式如下:

            /apis/metrics.k8s.io/v1beta1  --->   metrics-server.kube-system.svc  --->   x.x.x.x
​
+---------+       +-----------+                   +------------------------+        +-----------------------------+
| 发起请求 +----->+ API Server +----------------->+ Service:metrics-server +-------->+ Pod:metrics-server-xxx-xxx |
+---------+       +-----------+                   +------------------------+        +-----------------------------+

有了访问Metrics-Server的方式,HPA,kubectl top等对象就可以正常工作了。

讲清楚了metrics,也就解决了识别业务的忙闲程度的问题,那么HPA Controller是怎么利用metrics数据进行扩缩容控制的呢,也就是使用什么样的副本调整机制呢?

如上图右边所示,用户需要在HPA里设置的metrics类型和期望的目标metrics数值,HPA Controller会定期(horizontal-pod-autoscaler-sync-period配置,默认15s)reconcile 每个HPA对像,reconcile里面又通过metrics的API获取该HPA的metrics实时最新数值(在当前副本数服务情况下),并将它与目标期望值比较。

首先根据比较的大小结果确定是要扩缩容方向:扩容、缩容还是不变,若不需要要进行扩缩容调整就直接返回当前副本数,否则才使用HPA metrics 目标类型对应的算法来计算出deployment的目标副本数,最后调用deployment的scale接口调整当前副本数,最终实现尽可能将deployment下的每个pod的最终metrics指标(平均值)基本维持到用户期望的水平。

注意HPA的目标metrics是一个确定值,而不是一个范围。

监控体系

metrics-server

在提出 metric api 的概念时,官方页提出了新的监控体系,监控资源被分为了2种:

  • Core metrics(核心指标):从 Kubelet、cAdvisor 等获取度量数据,再由metrics-server提供给 Dashboard、HPA 控制器等使用。
  • Custom Metrics(自定义指标):由Prometheus Adapter提供API custom.metrics.k8s.io,由此可支持任意Prometheus采集到的指标。

核心指标只包含node和pod的cpu、内存等,一般来说,核心指标作HPA已经足够,但如果想根据自定义指标:如请求qps/5xx错误数来实现HPA,就需要使用自定义指标了。

目前Kubernetes中自定义指标一般由Prometheus来提供,再利用k8s-prometheus-adpater聚合到apiserver,实现和核心指标(metric-server)同样的效果。

kubelet

前面提到,无论是 heapster还是 metric-server,都只是数据的中转和聚合,两者都是调用的 kubelet 的 api 接口获取的数据,而 kubelet 代码中实际采集指标的是 cadvisor 模块,你可以在 node 节点访问 10255 端口 (read-only-port)获取监控数据:

  • Kubelet Summary metrics: 127.0.0.1:10255/metrics,暴露 node、pod 汇总数据
  • Cadvisor metrics: 127.0.0.1:10255/metrics/cadvisor,暴露 container 维度数据

cadvisor

cadvisor 不仅可以搜集一台机器上所有运行的容器信息,包括CPU使用情况、内存使用情况、网络吞吐量及文件系统使用情况,还提供基础查询界面和HTTP接口,方便其他组件进行数据抓取。在K8S中集成在 Kubelet 里作为默认启动项。

cadvisor 获取指标时,实际上也只是个转发者,它的数据来自于 cgroup 文件。

cgroup

cgroup文件中的值是监控数据的最终来源,如

  • mem usage的值,来自于

    /sys/fs/cgroup/memory/docker/[containerId]/memory.usage_in_bytes

  • 如果没限制内存,Limit = machine_mem,否则来自于 /sys/fs/cgroup/memory/docker/[id]/memory.limit_in_bytes

  • 内存使用率 = memory.usage_in_bytes/memory.limit_in_bytes

回答问题

1 什么是VPA?HPA?

见文章开头部分

2 metrics-server,kube-state-metrics 有什么区别?

  • metric-server 是从 api-server 中获取CPU、内存使用率这种监控指标,并把他们发送给存储后端,当前的核心作用是:为HPA等组件提供决策指标支持。
  • kube-state-metrics 关注于获取k8s各种资源的最新状态,如 deployment 或者 daemonset,之所以没有把kube-state-metrics 纳入到 metric-server 的能力中,是因为他们的关注点本质上是不一样的。metric-server 仅仅是获取、格式化现有数据,写入特定的存储,实质上是一个监控系统。而 kube-state-metrics 是将k8s的运行状况在内存中做了个快照,并且获取新的指标,但他没有能力导出这些指标
  • 换个角度讲,kube-state-metrics 本身是 metric-server的一种数据来源,虽然现在没有这么做。
  • 另外,像 Prometheus 这种监控系统,并不会去用 metric-server 中的数据,都是自己做指标收集、集成的(Prometheus包含了metric-server的能力),但 Prometheus 可以监控 metric-server 本身组件的监控状态并适时报警,这里的监控就可以通过 kube-state-metrics 来实现,如 metric-server Pod 的运行状态。

3 kubectl top 为什么会报错?

一般情况下 top 报错有以下几种,可以 kubectl top pod -v=10看到具体的调用日志:

  1. 没有部署metric-server,或者pod运行异常,可以排查对应 Pod日志
  2. 要看的 Pod 刚刚建出来,还没来得及采集指标,报 not found 错误,默认1 分钟
  3. 以上两种都不是,可以检查下kubelet 的 10255 端口是否开放,默认情况下会使用这个只读端口获取指标,也可以在 metric-server 的配置中增加证书,换成 10250 认证端口
[root@centos ~]$ kubectl top node -v=10
I0925 10:28:36.123146    6251 loader.go:375] Config loaded from file:  /data/home/mervinwang/.kube/config
I0925 10:28:36.124198    6251 round_trippers.go:423] curl -k -v -XGET  -H "Accept: application/json, */*" -H "User-Agent: kubectl-1.16/v1.16.3 (linux/amd64) kubernetes/9e16566" -H "Authorization: Bearer" 'https://x.x.x.x:19170/api?timeout=32s'
I0925 10:28:36.154052    6251 round_trippers.go:443] GET https://x.x.x.x:19170/api?timeout=32s 200 OK in 29 milliseconds
I0925 10:28:36.154104    6251 round_trippers.go:449] Response Headers:
I0925 10:28:36.154119    6251 round_trippers.go:452]     Content-Length: 158
I0925 10:28:36.154181    6251 round_trippers.go:452]     Date: Sat, 25 Sep 2021 02:28:36 GMT
I0925 10:28:36.154194    6251 round_trippers.go:452]     Audit-Id: 75ac3ab4-f3f8-4b8f-9abc-701256a805d3
I0925 10:28:36.154252    6251 round_trippers.go:452]     Content-Type: application/json
I0925 10:28:36.154321    6251 request.go:968] Response Body: {"kind":"APIVersions","versions":["v1"],"serverAddressByClientCIDRs":[{"clientCIDR":"0.0.0.0/0","serverAddress":"cls-o5eb179u.ccs.tencent-cloud.com:60002"}]}
I0925 10:28:36.154683    6251 round_trippers.go:423] curl -k -v -XGET  -H "User-Agent: kubectl-1.16/v1.16.3 (linux/amd64) kubernetes/9e16566" -H "Authorization: Bearer" -H "Accept: application/json, */*" 'https://x.x.x.x:19170/apis?timeout=32s'
I0925 10:28:36.233840    6251 round_trippers.go:443] GET https://x.x.x.x:19170/apis?timeout=32s 200 OK in 79 milliseconds
I0925 10:28:36.233883    6251 round_trippers.go:449] Response Headers:
I0925 10:28:36.233895    6251 round_trippers.go:452]     Audit-Id: d7b4c46c-3dff-42a8-a64c-8b2423372710
I0925 10:28:36.233912    6251 round_trippers.go:452]     Content-Type: application/json
I0925 10:28:36.233927    6251 round_trippers.go:452]     Date: Sat, 25 Sep 2021 02:28:36 GMT
......

4 kubectl top pod 和 exec 进入 Pod 后看到的 top 不一样?

top 命令的差异和上边一致,无法直接对比,同时,就算你对 pod 做了limit 限制,Pod 内的 top 看到的内存和 CPU总量仍然是机器总量,并不是 Pod 可分配量

  • 进程的RSS为进程使用的所有物理内存(file_rss+anon_rss),即Anonymous pages+Mapped apges(包含共享内存)
  • cgroup RSS为(anonymous and swap cache memory),不包含共享内存。两者都不包含file cache。