kuernetes – 自定义指标HPA,实现PHP-FPM弹性伸缩

HPA通过监控POD的CPU和MEM使用率,实现了默认的弹性伸缩功能。

然而,在实际环境中,伸缩的判断依据通常不单单是CPU/MEM,还可能是:

  • POD的线程使用率
  • POD的QPS
  • POD的带宽

无法一一列举。

HPA支持我们扩展自定义指标,只要我们让prometheus采集到POD的这些指标,那么HPA就可以通过一定的机制查询prometheus来得到这些指标值,进而做出伸缩依据。

HPA基础指标架构

HPA版本要求

为了最终使用自定义指标,需要使用如下版本的HPA:

HPA工作原理

HPA实现在kube-controller-manager中,是一个控制器。

该控制器周期性扫描所有配置的HPA对象,每个HPA对象关联到1个Deployment,进而关联到N个POD。

控制器将获取这些POD的CPU和MEM资源使用量,求这些POD的平均值,然后做出是否伸缩的决策。

问题来了,HPA是从哪里取到POD的CPU和MEM使用率的呢?

我们也知道,通过下面的命令同样可以查看POD的资源使用量:

其实top命令和HPA都采用了同一种方法来获取CPU/MEM指标,下面是调用流程:

metrics-server是一个开源项目,它采集集群所有kubelet中的容器CPU/MEM指标数据,保存到内存里。

当HPA调用apiserver的特定URL时,apiserver转发请求给metrics-server得到应答,也就是我们上面kubectl top看到的数据了。

我们完全可以直接请求apiserver的特定URL,方法如下:

这个命令调用apiserver,获取abtest11这个namespace下面所有pod的cpu/mem指标,将得到metrics-server的JSON应答。

这个转发关系是通过配置apiservice资源类型实现的:

也就是说,apiserver收到/apis/metrics.k8s.io/v1beta1/前缀的调用,就会转发流量给metrics-server这个service。

当然,metrics-server是需要我们自己部署配置的,具体方法大家去github主页参考即可。

HPA自定义指标架构

其实呢,HPA Controller逻辑上已经预埋了自定义指标的扩展能力,自定义指标的获取与上述基础指标的获取,原理上是一模一样的。

HPA控制器会通过调用apiserver的另外一个URL前缀,经过代理后调用到k8s-prometheus-adaptor服务,该服务会从prometheus采集到自定义指标,并转换成JSON格式作为返回。

可见,

  • 基础指标:metrics-server采集kubelet
  • 自定义指标:k8e-prometheus-adaptor采集prometheus

因为我们有大量指标存储在prometheus中,因此有了k8s-prometheus-adaptor的加持,我们就可以把他们利用到HPA中。

k8s-prometheus-adaptor架构

在了解这个开源项目的过程中,我发现最难理解的还是它的工作原理,所以我还是阅读了一下它的代码。

该程序由”定时拉取”和”实时查询”两部分组成,既然有一个实时查询prometheus指标的功能,为什么还要定时拉取?拉取什么?这是我在接触该项目过程中最大的困惑,直接影响到我们对其规则配置的理解。

要解释这个问题,很难离开实际的配置例子,所以我们直接进入本篇博客的主题,根据PHP-FPM进程使用率的HPA。(PHP是依靠启动多个PHP-FPM进程来实现并发的,如果POD里面的FPM进程使用率太高的话,我们认为应该扩容)

明确指标计算

给HPA自定义指标叫做fpm_active_rate,代表1个POD的FPM进程使用率。

在prometheus中并没有fpm_active_rate这个东西,只有2类原始数据:

pm_status是prometheus采集的原始序列数据。

标签state分为”active processes”和”total processes”,分别代表活跃进程数和总进程数。

标签namespace和pod_name则标识了所归属的POD。

因此,每个POD的fpm_active_rate的计算思路就是用active除以total即可。

配置文件

现在,我们必须看一下配置文件了:

adapter支持配置多条规则,为了搞定fpm_active_rate指标,我只需要一个rule。

定时拉取

adapter会定时执行seriesQuery,这是一个promql。

它过滤出最新采集到的所有pm_status,并且还需要满足state是active processes的,这是干啥呢?

假设prom中只有这两条记录:

那么,上述语句将得到结果:

然后adapter根据resources.overwrites配置,从这条记录中进行提取+映射,得到如下信息:

  • namespace标签的abtest11就是POD所属的namespace
  • pod_name标签的abtest-bgm-smzdm-com-b56679fdf-2fhlv就是POD的name。

同时,adapter根据name.as配置,将pm_status名字改为fpm_active_rate,也就是HPA自定义指标的名字。

此后,adapter在内存中记录一个这样的映射关系:

fpm_active_rate, abtest11, abtest-bgm-smzdm-com-b56679fdf-2fhlv  —->  rule

也就是这个POD到这条配置规则的关系。

可见,定时任务的目的就是『发现』: 发现所有的POD,记录它们与所属rule之间的关系。

实时查询

之所以要定时构建上述索引关系,其目的是为了满足实时查询。

 

实际上,HPA调用k8s-prometheus-adapter的时候,它会这样请求:

显然,URL中体现了3个要素:

  • namespace是abtest11
  • pod是所有的
  • 请求的是fpm_active_rate自定义指标

根据这3个信息,就可以根据上述索引得到所关联的rule。

因为rule.resources.overwrite和rule.name.as记录了转换关系,所以adapter此时会进行一轮转换:

  • 对应到prom中,指标名是pm_status
  • 对应到prom中,pod的过滤标签是pod_name,也就是*
  • 对应到prom中,namespace的过滤标签还是namespace,也就是abtest11

此时,adapter就会根据这些筛选条件,去prometheus获取一次实时数据,得到实时的FPM使用率了。

所以,此时执行的Promql使用的是metricsQuery:

sum(max_over_time(<<.Series>>{<<.LabelMatchers>>,state=”active processes”}[2m])) by (<<.GroupBy>>) / sum(max_over_time(<<.Series>>{<<.LabelMatchers>>,state=”total processes”}[2m])) by (<<.GroupBy>>)

其中,<<.Series>>就是pm_status,<<.LabelMatchers>>就是namespace=”abtest11″,pod_name=”*”,而<<.GroupBy>>总是Pod。

实际会像这样:

sum(max_over_time(pm_status{pod_name=”abtest-bgm-smzdm-com-b56679fdf-2fhlv”, namespace=”abtest11″, state=”active processes”}[2m])) by (pod_name)/ sum(max_over_time(pm_status{pod_name=”abtest-bgm-smzdm-com-b56679fdf-2fhlv”, namespace=”abtest11″, state=”total processes”}[2m])) by (pod_name)

也许你对Promql不太熟悉,我简单拆解一下这个查询。

  • max_over_time(pm_status{pod_name=”abtest-bgm-smzdm-com-b56679fdf-2fhlv”, namespace=”abtest11″, state=”active processes”}[2m]),求每个POD在过去2分钟内最大活跃进程数。
  • sum(max_over_time(pm_status{pod_name=”abtest-bgm-smzdm-com-b56679fdf-2fhlv”, namespace=”abtest11″, state=”active processes”}[2m])) by (pod_name),利用sum by (pod_name)把其他标签删除,只保留pod_name标签。
  • 分母类似。
  • 最终,分子/分母,按pod_name做JOIN,得到每个POD的使用率指标。

上述Promql会输出每个POD的FPM使用率(每个POD一行):

{pod_name=”abtest-bgm-smzdm-com-b56679fdf-2fhlv”} 0.3333333333333333

搭建一下吧

搭建过程我就不废话了,直接把过程贴给大家。

首先,大家下载官方代码:https://github.com/DirectXMan12/k8s-prometheus-adapter

创建命名空间,adaptor会部署到里面:

创建账号:

由于官方的rolebinding有bug,我们修改custom-metrics-resource-reader-cluster-role.yaml,额外授权一下configmap的权限:

创建角色并授予给账号:

执行脚本,生成adaptor的服务端证书(自签CA,因为apiserver反向代理adaptor的时候配置了不校验CA),cfssl与cfssljson从官网下载二进制(https://github.com/cloudflare/cfssl/releases):

修改adaptor配置文件,仅保留自己需要的metrics指标(custom-metrics-config-map.yaml),其他的不要了,否则性能太费了。

提交一波:

修改adaptor的启动参数(custom-metrics-apiserver-deployment.yaml):

提交deployment,服务启动:

创建service:

注册聚合API,反向代理到service,不校验adaptor的证书有效性(默认就这样):

现在,我们可以请求API查看数据了:

现在创建HPA:

大功告成。

如果文章帮助您解决了工作难题,您可以帮我点击屏幕上的任意广告,或者赞助少量费用来支持我的持续创作,谢谢~