上一篇文章 中我们探讨了如何使用 Prometheus Adapter 与 HPA 结合来解决生产者 - 消费者模型中由于消费者处理能力不足导致生产者积压的问题。在这个过程中我们了解了 Metrics API 的概念以及如何在 HPA 中使用自定义指标和外部指标来实现更灵活更贴合业务的自动扩容策略。

虽然这个方案适合大多数场景但实施起来并不那么容易,主要原因是依赖 Prometheus,这意味着在集群中需要部署 Prometheus,业务也需要进行指标的埋点,这对于一些小型项目来说可能会增加不必要的复杂度,更不用说那些年代久远的项目了。今天我们来介绍一种更加轻量级的自动扩容方案:KEAD (Kubernetes Event-driven Autoscaling)

KEDA 提供了一种基于事件驱动的自动扩容方案,KEDA 支持丰富的 事件来源,例如:ActiveMQ 队列长度、Redis List 长度、MySQL 查询结果等。KEDA 会直接访问这些事件源无需额外的指标采集工作,这对于一些没有指标采集需求的项目来说是非常友好的。接下来我们还是基于上篇文章的生产者 - 消费者场景,来看看如何使用 KEDA 来实现自动扩容。

安装 KEDA

KEDA 提供了 Helm Chart,通过简单的命令即可安装 KEDA 到集群中:

1
2
3
$ helm repo add kedacore https://kedacore.github.io/charts
$ helm repo update
$ helm install keda kedacore/keda -n keda --create-namespace

安装完成后可以通过以下命令查看 KEDA 的状态:

1
2
3
4
5
$ kubectl get po -n keda
NAME READY STATUS RESTARTS AGE
keda-admission-webhooks-5856bc7f9c-f76vn 1/1 Running 0 4h10m
keda-operator-9c5c94bfd-nzxhb 1/1 Running 0 4h10m
keda-operator-metrics-apiserver-5c5f5ff697-k6vfv 1/1 Running 0 4h4m

keda-operator 是核心组件,它负责对用户提交的 CRD 进行处理;keda-operator-metrics-apiserver 是一个 Metrics API 适配器,作用和 Prometheus Adapter 相似,负责将事件源的数据转换为 Metrics API 指标。下面我们来看看如何使用 KEDA。

ScaledObject

ScaledObject 是 KEDA 的核心资源,用于定义扩容目标以及事件源和触发规则:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
name: hpa-app
namespace: default
spec:
scaleTargetRef: # 扩容目标对象
name: hpa-app-consumer
kind: Deployment
apiVersion: apps/v1
minReplicaCount: 2 # 最小副本数
maxReplicaCount: 6 # 最大副本数
triggers: # 触发规则
- metadata:
address: hpa-app-redis.default:6379 # Redis 地址
listName: hpa-app # Redis Key 名称
listLength: "100" # 触发队列长度
type: redis # 事件源类型

我们需要扩容的目标对象是 Deployment/hpa-app-consumer,业务使用的是 Redis 列表作为消息队列所以我们选择 Redis Lists 来作为触发事件源。规则中指定了 Redis 连接地址以及列表 key 名称,当列表长度 > 100 时触发,详细的的配置可以参考文档。使用 kubectl 提交 ScaledObject 后运行 hpa-example 中的测试用例向生产者发送请求:

hpa-example 部署方式参考 k8s: 基于消息队列长度自动扩容业务 Pods (HPA)

1
2
3
4
5
6
7
8
9
10
$ helm test hpa-app --timeout=30m
NAME: hpa-app
LAST DEPLOYED: Tue Sep 24 11:06:37 2024
NAMESPACE: default
STATUS: deployed
REVISION: 11
TEST SUITE: hpa-app-k6
Last Started: Tue Sep 24 15:44:09 2024
Last Completed: Tue Sep 24 15:50:17 2024
Phase: Succeeded

运行完毕后查看监控数据:

20240924155250
20240924155250

可以看到当 队列长度 超过 100 后 消费者数量 开始增加从而避免了消息积压。

工作原理

当我们创建ScaledObject 后 KEDA 会同步创建一个 HPA 对象:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: keda-hpa-hpa-app
namespace: default
spec:
maxReplicas: 6
metrics:
- external:
metric:
name: s0-redis-hpa-app
selector:
matchLabels:
scaledobject.keda.sh/name: hpa-app
target:
averageValue: "100"
type: AverageValue
type: External
minReplicas: 2
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: hpa-app-consumer

接下来的流程我们就很熟悉了,HPA 使用外部指标 s0-redis-hpa-app 驱动,而这个指标是 operator-metrics-apiserver 根据事件源生成的,相比起上篇文章中介绍的 Prometheus Adapter 方案,KEDA 简化了指标采集和外部指标的配置。

ScaledJob

ScaledJob 是 KEDA 的另一个核心资源,无论是上面介绍的 ScaledObject 亦或者是 Prometheus Adapter 方案,最终还是依靠 HPA 来完成扩容,而 HPA 针对的是 Deployment、StatefulSet 这类长期运行的工作负载,但有些场景下我们希望仅在需要的时候才创建对应的 Kubernetes 资源,这时候就可以使用 ScaledJob,和 ScaledObject 的区别在于当需要扩容时 ScaleJob 会创建适合短期运行的 Job 资源,运行结束后会自动删除。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
kind: ScaledJob
apiVersion: keda.sh/v1alpha1
metadata:
name: hpa-app
namespace: default
spec:
jobTargetRef: # Job 模板
backoffLimit: 0
ttlSecondsAfterFinished: 300
activeDeadlineSeconds: 600
template:
spec:
restartPolicy: Never
containers:
- name: consumer
args:
- consumer
- '--redis.host=hpa-app-redis.default:6379'
- '--redis.queue-name=hpa-app'
image: registry.cn-hangzhou.aliyuncs.com/lin2ur/hpa-app:1724143858
imagePullPolicy: IfNotPresent
maxReplicaCount: 6
triggers:
- metadata:
address: hpa-app-redis.default:6379
listName: hpa-app
listLength: "100"
type: redis

ScaledObject 类似 ScaledJob 也需要指定触发规则,但无需指定扩容目标而是通过 jobTargetRef 指定 Job 模板。需要注意的是,KEDA 不会对 Job 进行「缩容」操作,需要在 Job 模板中设置 activeDeadlineSeconds 来控制 Job 的生命周期,以上面的配置为例,当 Job 运行时间超过 600 秒后会被 Kubernetes 终止并在 300 秒后删除,此时如果队列里还有消息 KEDA 会再次创建 Job 直到触发规则不满足。