一篇学会用 KEDA 根据工作负载进行快速扩容

2023年 11月 30日 41.5k 0

历史问题

众所周知,Kubernetes 有个亲生的 HPA 组件,在云原生早期,这个名义上的自动扩缩容的能力给 Kubernetes 赢得了不少掌声。当然现在回头看看,仅仅根据 CPU 和内存这样“贫瘠”的指标,不论是用于判断负载水平,还是用于计算扩容目标,都不是很够用的。这个阶段里,HPA 的扩缩容效率也是广受诟病的一个问题,在一个多级微服务调用的业务场景里,压力是逐级传递的,下图展示了一个常见情况:

图片图片

如上图,用户流量进入集群之后:

  • 首先在 Deploy A 造成负载,指标变化迫使 Deploy A 扩容
  • A 扩容之后,吞吐量变大,B 受到压力,再次采集到指标变化,扩容 Deploy B
  • B 吞吐变大,C ..
  • 这个逐级传递的过程不仅缓慢,而且可以说是步步惊心——每一级的扩容都是直接被 CPU 或内存的飙高触发的,被“冲垮”的可能性是普遍存在的。这种被动、滞后的方式,很明显是有问题的。

    推陈出新

    造成 HPA 窘境的原因之一,就是“自扫门前雪”,每个 Pod 都只能根据自身负载情况来进行扩缩容决策。如果能够直接根据业务流量的变化进行决策,并且将流量流经的所有微服务进行扩缩容,看起来情况就会好很多了。HPA 的自定义指标支持,给这个问题了一个可行的方案。该能力让 HPA 可以用其它的指标来作为扩缩容的触发器,例如我们可以用 Promethues 采集消息中间件的深度或者负载均衡器的队列长度,作为一个更能如实反映业务流量的指标,直接用来触发相关的多个微服务的扩缩容,如下图所示:

    图片图片

    在上图中:

  • Prometheus 采集消息队列和负载均衡等更能反映业务流量的指标
  • 使用 Prometheus Adapter 将 Promethues Metrics 转换为 Kubernetes 的 Aggregated API
  • HPA 使用自定义指标,同时对多个应用进行扩缩容。
  • 这中间涉及到的 Prometheus Adapter,通过配置文件完成步骤 2 的转换:

    - seriesQuery: '{__name__=~"^container_.*_total",container!="POD",namespace!="",pod!=""}'
      resources:
        overrides:
          namespace: {resource: "namespace"}
          pod: {resource: "pod"}
      seriesFilters:
      # since this is a superset of the query above, we introduce an additional filter here
      - isNot: "^container_.*_seconds_total$"
      name: {matches: "^container_(.*)_total$"}
      metricsQuery: "sum(rate({,container!="POD"}[2m])) by ()"

    当然,完全可以自行实现 Aggregated API 来支持这种指标的采集和呈现工作。Prometheus 所提供的大量 Exporter 是吸引我们写这种古怪语法的最大动力。

    那么如果是 KEDA 的话,这个问题又如何呢?KEDA 提供了几十个被称为 Scaler 的东西,其中除了 Promethues 之外,还包括 Kafka、Redis、PostgreSQL 等多种选择。所以在很多场景中,无需 Promethues,也能使用 Scaler 完成对输入指标的读取和判断。下面用 KEDA 为例,看看这种伸缩方法的具体实现。

    KEDA

    假设一个容器化应用由多个工作负载组成:

    • Ingress:负责接收业务流量
    • Backend 1、Backend 2:负责处理 Ingress 发来的任务
    • Database:数据库

    我们希望达成的效果是 —— Ingress、Backend 1、Backend 2、Database,实例数量保持在 1:2:1.5:2 的关系,Keda 的大致流程如下图所示:

    图片图片

    首先使用 Helm 安装 KEDA:

    $ helm repo add kedacore https://kedacore.github.io/charts
    $ helm install keda kedacore/keda --namespace default
    NAME: keda
    LAST DEPLOYED: Wed Nov 29 18:56:36 2023
    NAMESPACE: default
    STATUS: deployed
    REVISION: 1
    ...

    随便创建几个工作负载,冒充微服务:

    $ kubectl create deploy ingress --image=nginx
    deployment.apps/ingress created
    $ kubectl create deploy backend1 --image=nginx
    deployment.apps/backend1 created
    $ kubectl create deploy backend2 --image=nginx
    deployment.apps/backend2 created
    $ kubectl create deploy database --image=nginx
    deployment.apps/database created
    $ kubectl get pods | cut -d - -f 1 | grep -v keda | sort
    ...
    backend1
    backend2
    database
    ingress

    运行成功后,我们可以看到,四个微服务,每个微服务都有一个实例。

    按照刚才瞎掰的比例,编写一个 ScaleObject:

    apiVersion: keda.sh/v1alpha1
    kind: ScaledObject
    metadata:
      name: bk1
    spec:
      scaleTargetRef:
        name: backend1
      triggers:
      - type: kubernetes-workload
        metadata: 
          podSelector: 'app=ingress'
          value: '0.5'
    ---
    apiVersion: keda.sh/v1alpha1
    kind: ScaledObject
    metadata:
      name: bk2
    spec:
      scaleTargetRef:
        name: backend2
      triggers:
      - type: kubernetes-workload
        metadata: 
          podSelector: 'app=ingress'
          value: '0.67'      
    ---
    apiVersion: keda.sh/v1alpha1
    kind: ScaledObject
    metadata:
      name: db
    spec:
      scaleTargetRef:
        name: database
      triggers:
      - type: kubernetes-workload
        metadata: 
          podSelector: 'app=ingress'
          value: '0.5'

    上述代码引入了 kubernetes-workload 类型的触发器,他会监控 app=ingress 的容器,并对 scaleTargetRef 中提到的工作负载数量比例进行扩缩容。

    提交到集群之后,会看到实例数量数量发生了变化:

    $ kubectl get pods | cut -d - -f 1 | sort | uniq --count
    ...
       2 backend1
       2 backend2
       2 database
       1 ingress
       3 keda

    我们把 Ingress 扩容到 2 实例,再次统计:

    $ kubectl scale deployment ingress --replicas=2
    deployment.apps/ingress scaled
    $ kubectl get pods | cut -d - -f 1 | sort | uniq --count
    ...
       4 backend1
       3 backend2
       4 database
       2 ingress
       3 keda

    可以看到,的确是按照我们设定的比例,同步产生了缩放。如果缩减 Ingress 服务实例数,几分钟之后,其它工作负载也会随之缩容。

    $ kubectl scale deployment ingress --replicas=1
    deployment.apps/ingress scaled
    $ kubectl get pods | cut -d - -f 1 | sort | uniq --count                                                         
    ...
       2 backend1
       2 backend2
       2 database
       1 ingress

    结论

    虽说云原生架构的复杂性问题越来越被强调,但是这一生态的宗旨应该还是没有变化——用简单的透明的手段解决复杂问题。

    相关文章

    JavaScript2024新功能:Object.groupBy、正则表达式v标志
    PHP trim 函数对多字节字符的使用和限制
    新函数 json_validate() 、randomizer 类扩展…20 个PHP 8.3 新特性全面解析
    使用HTMX为WordPress增效:如何在不使用复杂框架的情况下增强平台功能
    为React 19做准备:WordPress 6.6用户指南
    如何删除WordPress中的所有评论

    发布评论