基于CPU和RabbitMQ进行自动伸缩

2022-03-27 10:31:19 浏览数 (1)

最终用户客座文章作者:Ratnadeep Debnath,Zapier 网站可靠性工程师

Zapier[1]RabbitMQ[2]是 Zap 处理的核心。在 Zap 中,每一步我们都会将消息队列发送到 RabbitMQ。这些消息被运行在 Kubernetes 上的后端工作器(worker)使用。为了跟上 Zapier 中不断变化的任务负载,我们需要用消息积压(backlog)来扩展工作器。

很长一段时间以来,我们使用 Kubernetes 原生 Horizontal Pod Autoscaling(HPA)来实现基于 CPU 的自动伸缩。一般来说,更多的任务会导致更多的处理,产生更多的 CPU 使用量,最终触发我们的工作器的自动伸缩。它似乎工作得很好,除了某些边缘情况。

我们在 Python 中做了大量的阻塞 I/O[3](我们在用 Python 编写的 worker 中不使用基于事件的循环)。这意味着我们可以有一群工作器闲置在阻塞 I/O 时使用低 CPU 配置文件,而队列不断增长无限,因为低 CPU 使用率会阻止自动缩放的启动。如果工作器在等待 I/O 时处于空闲状态,那么我们可能会有越来越多的消息积压,而基于 CPU 的自动标度器可能会错过这些消息。这种情况会导致通信阻塞,并在处理 Zap 任务时引入延迟。

理想情况下,我们希望在 CPU 和 RabbitMQ 中扩展我们的 worker。不幸的是,Kubernetes 的原生 HPA 不支持基于 RabbitMQ 队列长度的即时扩展。有一个潜在的解决方案,在 Prometheus 中收集 RabbitMQ 指标,创建一个自定义指标服务器,并配置 HPA 来使用这些指标。然而,这是一项大量的工作,当有KEDA[4]的时候,为什么要另起炉灶呢?

KEDA 是什么?

KEDA 是一个基于 Kubernetes 的事件驱动自动伸缩器,旨在使自动伸缩变得非常简单。使用 KEDA,你可以通过使用 40 多个可用的 scaler 来驱动 Kubernetes 中任何容器的扩展,包括基于 RabbitMQ 中的未决消息。

KEDA 是一个单一用途的轻量级组件,可以添加到任何 Kubernetes 集群中。KEDA 通过扩展Horizontal Pod Autoscaler[5],并根据使用的 scaler 提供外部指标,在 Kubernetes 中完成了所有的自动伸缩重载。有了 KEDA,你可以很容易地定义你想要扩展的应用程序,而不改变应用程序本身,其他应用程序继续运行。这使得 KEDA 成为一个灵活和安全的选择,可以与任意数量的其他 Kubernetes 应用程序或框架一起运行。

我们如何使用 KEDA?

我们已经在 Kubernetes 集群中安装了 KEDA,并开始选择使用 KEDA 进行自动伸缩。我们的目标是,不仅要根据 CPU 使用率,还要根据 RabbitMQ 队列中 ready 消息的数量来自动伸缩 worker。

代码语言:javascript复制
priorityClassName: system-cluster-critical
prometheus:
 enabled: true
 metricServer:
   enabled: true
   podMonitor:
     enabled: true
     namespace: keda
 operator:
   enabled: true
   podMonitor:
     enabled: true
     namespace: keda
   prometheusRules:
     enabled: true
     alerts:
       - alert: KedaMetricsServerDown
         expr: absent(up{job="keda-operator-metrics-apiserver"})
         for: 1m
         labels:
           severity: critical
         annotations:
           summary: "Keda metrics server is down"
       - alert: KedaScalerErrorsHigh
         expr: rate(keda_metrics_adapter_scaler_errors{}[5m]) > 0
         for: 1m
         labels:
           severity: critical
         annotations:
           summary: "Keda scaler errors is high for {{ $labels.namespace }}/{{ $labels.scaledObject }} {{ $labels.scaler }}"
       - alert: KedaScaledObjectErrorsHigh
         expr: rate(keda_metrics_adapter_scaled_object_errors{}[5m]) > 0
         for: 1m
         labels:
           severity: critical
         annotations:
           summary: "Keda scaledObject errors is high for {{ $labels.namespace }}/{{ $labels.scaledObject }}"

service:
 annotations:
   prometheus.io/path: /metrics
   prometheus.io/port: "9022"
   prometheus.io/scrape: "true"

resources:
 requests:
   cpu: "100m"
   memory: "100Mi"
 limits:
   cpu: "1"
   memory: "1000Mi"

部署

我们使用Helm[6]在 Kubernetes 集群中部署了 KEDA。

为 KEDA 贡献特性

因为我们的 worker 从多个 RabbitMQ 主机读取队列的消息,所以我们需要根据多个 RabbitMQ 主机上队列的就绪消息进行扩展。但是 KEDA 2.3.0 版本的 KEDA RabbitMQ scaler 不支持为多个 RabbitMQ 主机定义同一个队列的触发器,因为 KEDA RabbitMQ scaler 会使用队列名自动生成指标名称。

KEDA 是一个开源项目,所以我们可以自己添加功能来支持我们的设置。使用与其他伸缩器相同的模式(如 Kafka),我们修改了指标名称的生成方式,并将更改提交给 KEDA 维护者[7]。他们能够审查、合并这些变更,并将其作为2.4.0 版本[8]的一部分发布。感谢 KEDA 团队的可爱和乐于助人。

使用 KEDA 配置自动伸缩

在我们用于在 Kubernetes 上启动服务的私有 Helm chart 中,我们增加了对基于 KEDA 的自动缩放的支持。我们现在需要做的就是在服务的 Helm 值中配置自动伸缩部分。

代码语言:javascript复制
autoscaling:
    minReplicas: 3
    maxReplicas: 10
    keda:
      enabled: true
      rabbitmq:
        auth:
          vaultSecret: zapier/data/keda/rabbitmq
        servers:
          - "rabbitmq-1"
          - "rabbitmq-2"
      metrics:
        - type: cpu
          metadata:
            type: Utilization
            value: "82"
        - type: rabbitmq
          metadata:
            protocol: amqp
            queueName: celery
            mode: QueueLength
            value: "180"

这将生成必要的工件:ExternalSecret(external-secrets 控制器,https://github.com/external-secrets/kubernetes-external-secrets 通过从Vault[9]中获取秘密,从一个 ExternalSecret 对象中生成 Kubernetes Secret)、TriggerAuthentications、ScaledObject,用于为服务配置 KEDA 的自动伸缩。在这个例子中,ScaledObject zapier-worker-celery 告诉 KEDA 在以下触发器上扩展 zapier-worker-celery 的部署:

  1. 当 pod 的 CPU 利用率为 82%
  2. 当 rabbitmq-1 主机中 celery 队列的 Ready 消息数为 180 条时
  3. 当 rabbitmq-2 主机中 celery 队列的 Ready 消息数为 180 条时

上述 ScaledObject 中的 rabbitmq 触发器使用触发器的 authenticationRef 中提到的 TriggerAuthentication 来认证 rabbitmq 主机,为 scaler 收集 rabbitmq 的指标。

代码语言:javascript复制
# Secret containing RabbitMQ host connection strings for use in KEDA TriggerAuthentication. This is autogenerated by ExternalSecrets for us
---
apiVersion: v1
kind: Secret
metadata:
  name: zapier-worker-celery
data:
  rabbitmq-1: <AMQP URI connection string> # base64 encoded value of format amqp://guest:password@localhost:5672/vhost
  rabbitmq-2: <AMQP URI connection string> # base64 encoded value of format amqp://guest:password@localhost:5672/vhost
---
# KEDA TriggerAuthentications for use in KEDA’s RabbitMQ triggers in ScaledObject
apiVersion: keda.sh/v1alpha1
kind: TriggerAuthentication
metadata:
 name: worker-celery-rabbitmq-1
 namespace: zapier
spec:
 secretTargetRef:
 - key: rabbitmq-1
   name: zapier-worker-celery
   parameter: host
---
apiVersion: keda.sh/v1alpha1
kind: TriggerAuthentication
metadata:
 name: worker-celery-rabbitmq-2
 namespace: zapier
spec:
 secretTargetRef:
 - key: rabbitmq-2
   name: zapier-worker-celery
   parameter: host
---
# KEDA ScaledObject to define how to scale a deployment
apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
 finalizers:
 - finalizer.keda.sh
 name: zapier-worker-celery
 namespace: zapier
spec:
 cooldownPeriod: 300
 maxReplicaCount: 10
 minReplicaCount: 3
 pollingInterval: 15
 scaleTargetRef:
   name: zapier-worker-celery # Name of target deployment to scale
 triggers:
 # CPU trigger
 - metadata:
     type: Utilization
     value: "82"
   type: cpu
 # RabbitMQ trigger for “celery” queue length on rabbitmq-1 host
 - authenticationRef:
     name: worker-celery-rabbitmq-1
   metadata:
     metricName: celery-rabbitmq-1-worker-celery
     mode: QueueLength
     protocol: amqp
     queueName: celery
     value: "180"
   type: rabbitmq
 # RabbitMQ trigger for “celery” queue length on rabbitmq-2 host
 - authenticationRef:
     name: worker-celery-rabbitmq-2
   metadata:
     metricName: celery-rabbitmq-2-worker-celery
     mode: QueueLength
     protocol: amqp
     queueName: celery
     value: "180"
   type: rabbitmq

监控和警报

在 Zapier,我们使用Grafana[10]来可视化Thanos[11](具有长期存储能力的高可用 Prometheus 设置)的指标。为了确保系统按预期运行,我们使用自定义的 Prometheus 规则在出现问题时发出警报。

为此,我们依赖 KEDA 提供的内置指标,允许我们定义以下规则(见上文 helm values 文件):

  • KedaMetricsServerDown:当 keda-operator-metrics-server 宕机 1m 时被引发
  • KedaScalerErrorsHigh:当 keda_metrics_adapter_scaler_errors_total > 0 为 1m 时被触发
  • KedaScaledObjectErrorsHigh:当 keda_metrics_adapter_scaled_object_errors > 0 为 1m 时被触发

我们还通过跟踪以下指标来使用它们进行监控:

  • up{job=”keda-operator-metrics-apiserver”}:这表示 KEDA 服务是否已启动并正在运行
  • keda_metrics_adapter_scaled_object_errors:ScaledObject 错误,例如,scaler 错误,TriggerAuthentication 缺少秘密,需要扩展到最大副本等等。
  • keda_metrics_adapter_scaler_errors_total:集群中所有伸缩器的错误总数
  • keda_metrics_adapter_scaler_errors:ScaledObject 中按每个触发器分组的伸缩器错误
  • keda_metrics_adapter_scaler_metrics_value:由 ScaledObject 中的每个触发器分组的 KEDA 伸缩器的指标值

这种监控和警报设置帮助我们掌握来自 KEDA 控制器和伸缩器的任何错误。

结果

KEDA 在基于 CPU 的扩展和基于 RabbitMQ ready 消息的无缝自动扩展方面的性能与 Kubernetes 原生 HPA 相当。

使用 KEDA 来自动伸缩我们的工作器,可以显著避免由于阻塞的 I/O 调用而导致的 Zap 处理延迟。我们正在慢慢更新 Zapier 的应用程序以使用 KEDA。

总结

我们很高兴 KEDA 能够帮助扩展我们的工作负载。它使用简单,有良好的文档记录,并且有大量现成的伸缩器。

除此之外,KEDA 还有一个非常活跃和乐于助人的贡献者社区。我们刚刚开始使用 KEDA,但我们已经看到了将 KEDA 推向极限的新用例。我们期待与 KEDA 社区在未来的合作。

关于作者

Ratnadeep Debnath 是 Zapier 的一名网站可靠性工程师。他是整个组织中开源技术的热情倡导者。他花了大量时间研究 Kubernetes 和其他 CNCF 项目,并积极参与其中的一些项目。他喜欢在闲暇时间与朋友和家人在一起,做冥想、阅读和种植食物。

参考资料

[1]Zapier: https://zapier.com/

[2]RabbitMQ: https://www.rabbitmq.com/

[3]阻塞 I/O: https://medium.com/coderscorner/tale-of-client-server-and-socket-a6ef54a74763

[4]KEDA: https://keda.sh/

[5]Horizontal Pod Autoscaler: https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale/

[6]Helm: https://keda.sh/docs/2.5/deploy/#helm

[7]将更改提交给 KEDA 维护者: https://github.com/kedacore/keda/pull/1976

[8]2.4.0 版本: https://github.com/kedacore/keda/releases/tag/v2.4.0

[9]Vault: https://www.vaultproject.io/

[10]Grafana: https://grafana.com/

[11]Thanos: https://thanos.io/

0 人点赞