天天看点

如何使用 KEDA 自动缩放 Grafana Loki Queries

作者:新钛云服

介绍

Grafana Loki (https://grafana.com/oss/loki/?pg=blog&plcmt=body-txt) 是 Grafana Labs 的开源日志聚合系统,灵感来自 Prometheus(https://prometheus.io/) 。Loki 具有水平可扩展性、高可用性和多租户特性。

Grafana Cloud 大规模运营 Grafana Cloud Logs,集群分布在不同的区域和云平台,如 AWS、Microsoft Azure 和 Google Cloud。Grafana Cloud 每天还摄取数百 TB 的数据,并在数千个租户中查询数 PB 的数据。最重要的是,每天数据查询与处理过程中,资源消耗都会有很大的波动,这使得以对用户响应且对 Grafana Cloud 来说具有成本效益的方式手动扩展集群变得非常困难。

在这篇博客中,我们将描述 Grafana Labs 的工程师如何使用基于 Kubernetes 的事件驱动自动缩放器 ( KEDA(https://keda.sh/) ) 来更好地处理后端的 Loki 查询。

为什么我们需要 autoscaling

负责处理 Grafana Cloud Logs 查询的 Loki 读取路径组件之一是 querier,它是将 LogQL(https://grafana.com/docs/loki/latest/logql/?pg=blog&plcmt=body-txt) 查询与日志匹配的组件。可以想象,我们需要很多 querier 来实现如此高的吞吐量。但是,由于我们的集群全天工作负载发生重大变化,这种需求会发生波动。直到最近,我们还是根据工作负载手动扩展 querier,但这种方法存在三个主要问题。

1、它会适当地扩展 querier 以响应工作量的增加。

2、我们可能会过度配置集群并使 querier 闲置一段时间。

3、这会导致操作繁琐 (https://sre.google/sre-book/eliminating-toil/) ,因为我们必须手动上下扩展 querier。

为了克服这些问题,我们决定在 Kubernetes 中的 querier 部署中添加自动缩放功能。

为什么选择 KEDA

Kubernetes 附带了一个用于水平自动缩放 Pod 的内置解决方案:HorizontalPodAutoscaler ( HPA)。您可以使用 HPA 根据来自 Kubernetes metrics-server(https://kubernetes.io/docs/tasks/debug-application-cluster/resource-metrics-pipeline/#metrics-server) 的指标为 StatefulSets 和 Deployments 等组件配置自动缩放。metrics-server 公开 pod 的 CPU 和内存使用情况,但如果需要,您可以为其提供更多指标。

CPU 和内存使用指标通常足以决定何时扩大或缩小规模,但可能还有其他指标或事件需要考虑。在我们的案例中,我们对基于一些 Prometheus 指标的扩展感兴趣。从 Kubernetes 1.23 开始,HPA 已经支持外部指标(https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale/#scaling-on-custom-metrics) ,所以我们可以选择一个 Prometheus adapter 来根据 Prometheus 指标进行扩展。然而,KEDA 使这变得更加容易。

KEDA 是最初由 Microsoft 和 Red Hat 开发的开源项目,我们已经在内部将它用于与Grafana Mimir(https://grafana.com/oss/mimir/?pg=blog&plcmt=body-txt) 类似的用例。除了熟悉之外,我们之所以选择它,是因为它更成熟,而且它允许根据来自不同来源(例如 Prometheus)的事件和指标扩展任何 Pod。KEDA 构建在 HPA 之上,并公开了一个新的 Kubernetes 指标服务器,该服务器为 KEDA 创建的 HPA 提供新指标。

我们如何在 queriers 上使用 KEDA

Queriers 从查询调度程序队列中提取查询并在所有 querier 上处理它们。因此,根据以下条件进行扩展是有意义的:

· 调度程序队列大小

· 在 querier 中运行的查询

最重要的是,我们希望避免因短期工作负载高峰而扩大规模。在这些情况下,查询可能会比扩展所需的时间更快地处理工作负载。

考虑到这一点,我们根据排队查询的数量加上正在运行的查询进行扩展。我们称这些为 inflight requests。查询调度程序组件将公开一个新指标 ,cortex_query_scheduler_inflight_requests 指标结果使用百分比(https://grafana.com/blog/2022/03/01/how-summary-metrics-work-in-prometheus/?pg=blog&plcmt=body-txt) 跟踪正在进行的请求。通过使用百分比,我们可以避免在指标出现短期峰值时进行扩容。

使用结果度量,我们可以在查询中使用分位数 (Q) 和范围 (R) 参数来微调我们的缩放。Q 越高,指标对短期尖峰越敏感。随着 R 的增加,我们会随着时间的推移减少度量变化。较高的 R 值有助于防止自动缩放器过于频繁地修改副本数量。

sum(
max_over_time(
  cortex_query_scheduler_inflight_requests{namespace="%s", quantile="<Q>"}[<R>]
)
)

           

然后我们需要设置一个阈值,以便我们可以根据度量值计算所需的副本数。Querier 程序处理来自队列的查询,每个 querier 配置为运行六个 worker。我们希望为高峰留出一些富余处理空间,因此我们的目标是使用这些 worker 中的 75%。因此,我们的门槛将是每个 querier 6 个 worker 的 75%,即 4 个 worker。

这个等式定义了当前副本中所需的副本数、度量值以及我们配置的阈值:

desiredReplicas = ceil[currentReplicas * ( currentMetricValue / threshold )]

例如,如果我们有一个副本和 20 个正在进行的请求,并且我们的目标是使用每个 worker 可用的六个 worker 中的 75%(四个),那么新的所需副本数量将是五个。

desiredReplicas = ceil[1 * (20 / 4)] = 5

考虑到这一点,我们现在可以创建 KEDA ScaledObject 来控制查询器的自动缩放。以下资源定义将 KEDA 配置为从 http://prometheus.default:9090/prometheus 提取指标。它还可以扩展到最大 50 个 querier,缩小到最小 10 个 querier,将 75% 用于 inflight requests 指标,并在两分钟内聚合其最大值。扩展阈值仍然是每个 querier 四个 worker。

apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
name: querier
namespace: <REDACTED INTERNAL DEV ENV>
spec:
maxReplicaCount: 50
minReplicaCount: 10
pollingInterval: 30
scaleTargetRef:
  kind: Deployment
  name: querier
triggers:
- metadata:
    metricName: querier_autoscaling_metric
    query: sum(max_over_time(cortex_query_scheduler_inflight_requests{namespace=~"REDACTED", quantile="0.75"}[2m]))
    serverAddress: http://prometheus.default:9090/prometheus
    threshold: "4"
  type: prometheus

           

使用 Grafana k6 Cloud 进行测试

在部署到我们的内部和生产环境之前,我们进行了多次实验和基准测试来验证。

Grafana k6 Cloud 是 Grafana k6 的完全托管版本,它是一个免费、开源、以开发人员为中心且可扩展的负载测试工具,可让工程团队轻松进行性能测试。

使用 k6 的 Grafana Loki 扩展(https://grafana.com/blog/2022/06/08/a-quick-guide-to-load-testing-grafana-loki-with-grafana-k6/?pg=blog&plcmt=body-txt) ,我们创建了一个 k6 测试 (https://gist.github.com/salvacorts/7f6fe8e53dcbdfc38606f3892918cfcc) ,该测试迭代地从多个虚拟用户(VU;有效的运行线程)向 Loki 开发集群发送不同类型的查询。我们尝试使用以下序列模拟真实的可变流量:

1、在两分钟内,将 VU 从 5 个增加到 50 个。

2、用 50 个 VU 保持一分钟。

3、在 30 秒内从 50 个 VU 增加到 100 个 VU,并在另外 30 秒内增加到 50 个,以创建工作负载峰值。

4、重复上一个尖峰。

5、最后,在两分钟内从 50 个 VU 变为零。

在下图中,我们可以看到测试在 k6 Cloud 中的表现,以及在测试期间进行中的请求和查询器副本的数量如何变化。首先,querier 会随着工作负载的增加而扩大,最后,querier 会在测试完成几分钟后回退。

如何使用 KEDA 自动缩放 Grafana Loki Queries

一个 Grafana k6 Cloud 测试,它迭代地将不同类型的查询发送到 Grafana Loki 开发集群。

如何使用 KEDA 自动缩放 Grafana Loki Queries

随着 inflight request 数量的增加(顶部),querier 的数量增加(底部)。在工作负载减少后的某个时间,querier 的数量也会减少。

如何使用 KEDA 自动缩放 Grafana Loki Queries

一旦我们确认我们的方法按预期工作,下一步就是在一些实际工作负载上进行尝试。

部署和微调

为了验证我们的实现是否满足我们在真实场景中的需求,我们在内部环境中部署了自动缩放器。到目前为止,我们已经在 k6 创建所有工作负载的隔离环境中进行了实验。接下来,我们必须估计最大和最小副本数的适当值。

对于最小副本数,我们在前 7 天 75% 的时间内检查了平均 inflight request,目标是 querier 的利用率为 75%。

clamp_min(ceil(
  avg(
      avg_over_time(cortex_query_scheduler_inflight_requests{namespace=~"REDACTED", quantile="0.75"}[7d])
  ) / scalar(floor(vector(6 * 0.75)))
), 2)

           

对于最大副本数,我们将当前副本数与在前 7 天内处理 50% 的 inflight request 所需的查询器数相结合。由于每个 querier 运行六个 worker,我们将 inflight 中的请求除以六。

clamp_min(ceil(
  (
      max(
          max_over_time(cortex_query_scheduler_inflight_requests{namespace=~"REDACTED", quantile="0.5"}[7d])
      ) / 6
      >
      max (kube_deployment_spec_replicas{namespace=~"REDACTED", deployment="querier"})
  )
  or
  max(
      kube_deployment_spec_replicas{namespace=~"REDACTED", deployment="querier"}
  )
), 20)

           

在估计了最小和最大副本的一些数字后,我们在内部环境中部署了自动缩放器。如下图所示,我们实现了预期的结果:querier 随着工作负载的增加而扩大,在工作负载减少后缩小。

如何使用 KEDA 自动缩放 Grafana Loki Queries

随着我们部署中 inflight request 数量的增加(顶部),querier 的数量增加(底部)。当 worker 减少时,querier 的数量也会减少。

您可能已经注意到由于缩放指标值的不断变化,副本数量的频繁波动(也称为抖动)(https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale/#flapping) 。如果您在扩展大量 pod 后过早缩减它们,最终会消耗 Kubernetes 中的大量资源来调度 pod。此外,它可能会影响查询延迟,因为我们需要经常启动新的 querier 来处理这些查询。幸运的是,HPA 提供了一种机制来配置稳定窗口(https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale/#stabilization-window) 以减轻这些频繁的波动,正如您可能已经猜到的那样,KEDA 也是如此。spec.advanced.horizontalPodAutoscalerConfig.behavior.scaleDown.stabilizationWindowSeconds参数允许您在 0 秒(立即缩放)和 3,600 秒(一小时)之间配置冷却时间,默认值为 300 秒(五分钟)。该算法很简单:我们使用一个跨越配置时间的滑动窗口,并将副本数设置为该时间范围内报告的最高数量。在我们的例子中,我们配置了一个 30 分钟的稳定窗口:

spec:
advanced:
  horizontalPodAutoscalerConfig:
    behavior:
      scaleDown:
        stabilizationWindowSeconds: 1800

           

下图显示了图表的形状现在在副本数量方面如何更加统一。在某些情况下,querier 在缩小后不久就会扩大规模,但总会有边缘情况发生这种情况。我们仍然需要为这个参数找到一个适合我们工作负载形状的好值,但总的来说,我们可以看到峰值更少。

如何使用 KEDA 自动缩放 Grafana Loki Queries

配置稳定窗口后,与上图相似的工作负载相比,副本数量波动较小。

即使我们配置的最大副本数比手动扩展 Loki 时要高得多,但添加自动扩展程序后的平均副本数会更低。较低的平均副本数转化为较低的查询器部署的拥有成本。

如何使用 KEDA 自动缩放 Grafana Loki Queries

启用查询器自动缩放器后,集群中运行的平均副本数减少了。

此外,我们可以看到自动缩放器没有出现查询延迟下降的问题。下图显示了 7 月 21 日 12:00 UTC 在启用自动缩放之前和之后的查询和范围查询的 p90 延迟(以秒为单位)。

如何使用 KEDA 自动缩放 Grafana Loki Queries

启用查询器自动缩放器后,查询延迟并没有变差。

最后,我们需要一种方法来知道何时增加我们的最大副本数。为此,我们创建了一个警报,当自动缩放器长时间以配置的最大副本数运行时触发。以下代码片段包含此指标,如果它在至少三个小时内输出为 true,则会触发。

name: LokiAutoscalerMaxedOut
expr: kube_horizontalpodautoscaler_status_current_replicas{namespace=~"REDACTED"} == kube_horizontalpodautoscaler_spec_max_replicas{namespace=~"REDACTED"}
for: 3h
labels:
severity: warning
annotations:
description: HPA {{ $labels.namespace }}/{{ $labels.horizontalpodautoscaler }} has been running at max replicas for longer than 3h; this can indicate underprovisioning.
summary: HPA has been running at max replicas for an extended time

           

*原文:https://grafana.com/blog/2022/10/20/how-to-autoscale-grafana-loki-queries-using-keda/

继续阅读