4. 事件源基础:Scaler 接口规范、事件源注册机制、自定义 Scaler 开发概述
KEDA 的核心价值在于其丰富的事件源生态。本章将深入剖析 KEDA 事件源的底层架构,包括 Scaler 接口规范、事件源注册机制以及自定义 Scaler 开发的完整流程。理解这些基础是后续配置流水线触发器的前提。
4.1 Scaler 接口规范:事件源的契约
在 KEDA 中,每一个事件源(如 Kafka、Prometheus、AWS SQS)都被抽象为一个 Scaler。所有 Scaler 都必须实现 KEDA 定义的 Scaler 接口,这是 KEDA 与外部系统交互的契约。
Scaler 接口定义在 KEDA 的 pkg/scalers 包中,核心方法如下:
| 方法签名 | 功能描述 | 返回值说明 |
|---|---|---|
GetMetricSpecForScaling(context.Context) []MetricSpec |
返回 Scaler 暴露的指标名称和类型。KEDA 根据此信息向 HPA 注册指标。 | 返回 MetricSpec 切片,每个元素包含指标名(如 kafka-lag)和指标类型(如 ExternalMetric)。 |
GetMetrics(context.Context, MetricIdentifier, *ScalerConfig) ([]ExternalMetricValue, error) |
核心方法。KEDA 定期调用此方法获取当前指标值(如 Kafka 消费者滞后数)。 | 返回 ExternalMetricValue 切片,包含指标名称、时间戳和当前值。 |
Run(ctx context.Context, activeThreshold int) (bool, error) |
判断事件源是否处于“活跃”状态。用于决定是否将 Deployment 从 0 扩展到 1。 | 返回 bool:true 表示有事件需要处理,应保持至少一个 Pod 运行。 |
Close(context.Context) error |
清理资源。当 Scaler 被销毁时调用,用于关闭连接、释放句柄。 | 返回 error,表示清理是否成功。 |
关键理解:
GetMetrics 返回的指标值直接驱动 HPA 的扩缩容决策;而 Run 方法则决定了 KEDA 的“从零到一”缩放能力。两者缺一不可。
4.2 事件源注册机制:Scaler 的发现与加载
KEDA 采用 插件式架构 来管理 Scaler。所有内置 Scaler 在 KEDA Operator 启动时自动注册。注册机制的核心是 Scaler 工厂模式。
下图展示了 Scaler 的注册与调用流程:
注册流程的关键步骤:
- ScaledObject 解析:KEDA 控制器监听到
ScaledObject资源变更,解析其triggers字段。 - 工厂匹配:根据
triggers[].type(如kafka、prometheus)查找已注册的 Scaler 工厂。 - 实例化:工厂解析
triggers[].metadata和authenticationRef,创建具体的 Scaler 实例。 - 生命周期管理:KEDA 持有 Scaler 实例,定期调用
GetMetrics和Run,并在 ScaledObject 删除时调用Close。
4.3 自定义 Scaler 开发概述
当内置 Scaler 无法满足需求时(如对接自研消息队列、内部监控系统),可以开发自定义 Scaler。KEDA 提供了两种方式:外部 Scaler (External Scaler) 和 内置 Scaler (Built-in Scaler)。
4.3.1 两种开发方式对比
| 特性 | 外部 Scaler (External Scaler) | 内置 Scaler (Built-in Scaler) |
|---|---|---|
| 部署方式 | 独立 gRPC 服务,作为 Sidecar 或独立 Deployment 运行 | 编译进 KEDA Operator 二进制文件 |
| 开发语言 | 任意语言(通过 gRPC 协议通信) | Go 语言(必须遵循 KEDA 代码库规范) |
| 维护成本 | 低,独立迭代,不影响 KEDA 核心 | 高,需要与 KEDA 版本同步,提交 PR 合并 |
| 性能 | 中等,有 gRPC 网络开销 | 高,进程内调用,无网络延迟 |
| 适用场景 | 快速原型、私有协议、非 Go 技术栈 | 通用性强、性能敏感、社区贡献 |
4.3.2 外部 Scaler 开发核心步骤
外部 Scaler 通过 gRPC 暴露 ExternalScaler 服务,KEDA 作为客户端调用。核心接口定义在 keda.external.scaler.externalscaler.proto 中:
// 核心 gRPC 服务定义
service ExternalScaler {
// 返回 Scaler 的指标规范
rpc GetMetricSpec(ScaledObjectRef) returns (GetMetricSpecResponse) {}
// 获取当前指标值
rpc GetMetrics(GetMetricsRequest) returns (GetMetricsResponse) {}
// 判断是否活跃
rpc IsActive(ScaledObjectRef) returns (IsActiveResponse) {}
// 关闭 Scaler,清理资源
rpc Close(ScaledObjectRef) returns (CloseResponse) {}
}
开发一个外部 Scaler 的典型流程:
- 定义 proto 文件:基于 KEDA 的
externalscaler.proto生成服务端代码。 - 实现服务接口:实现
GetMetricSpec、GetMetrics、IsActive、Close四个方法。 - 部署 gRPC 服务:将服务部署为 Kubernetes Deployment,并暴露 Service。
- 配置 ScaledObject:在
triggers中使用type: external,并指定metadata.scalerAddress指向 gRPC 服务。
最佳实践: 对于流水线构建触发器场景,推荐优先评估外部 Scaler 方案。它允许团队使用熟悉的语言(如 Python、Java)快速对接 Jenkins、GitLab CI 等系统的 Webhook 或 API,且不影响 KEDA 核心的稳定性。
4.3.3 内置 Scaler 开发核心步骤
内置 Scaler 需要直接修改 KEDA 源码。核心步骤包括:
- 创建 Scaler 文件:在
pkg/scalers/目录下创建my_scaler.go,实现Scaler接口。 - 实现工厂函数:编写
NewMyScaler(config *ScalerConfig) (Scaler, error)工厂函数。 - 注册 Scaler:在
pkg/scaling/scale_handler.go的buildScaler方法中添加case "my-scaler":分支。 - 编译测试:重新编译 KEDA Operator 镜像,部署到集群验证。
内置 Scaler 的代码骨架示例:
package scalers
import (
"context"
"fmt"
v2 "k8s.io/api/autoscaling/v2"
"k8s.io/metrics/pkg/apis/external_metrics/v1beta1"
)
type MyScaler struct {
// 自定义字段,如客户端连接、配置参数
client *MyCustomClient
threshold int
}
func NewMyScaler(config *ScalerConfig) (Scaler, error) {
// 1. 解析 metadata
threshold, err := config.GetInt("threshold")
if err != nil {
return nil, fmt.Errorf("missing threshold: %w", err)
}
// 2. 初始化客户端
client, err := NewMyCustomClient(config)
if err != nil {
return nil, err
}
return &MyScaler{client: client, threshold: threshold}, nil
}
func (s *MyScaler) GetMetricSpecForScaling(ctx context.Context) []v2.MetricSpec {
metricName := "my-custom-metric"
metricSpec := v2.MetricSpec{
External: &v2.ExternalMetricSource{
Metric: v2.MetricIdentifier{
Name: metricName,
},
Target: createMetricTarget(s.threshold),
},
Type: v2.ExternalMetricSourceType,
}
return []v2.MetricSpec{metricSpec}
}
func (s *MyScaler) GetMetrics(ctx context.Context, metricIdentifier v2.MetricIdentifier, config *ScalerConfig) ([]v1beta1.ExternalMetricValue, error) {
// 调用自定义客户端获取指标值
value, err := s.client.GetCurrentValue()
if err != nil {
return nil, err
}
return []v1beta1.ExternalMetricValue{
{
MetricName: metricIdentifier.Name,
Value: resource.NewQuantity(int64(value), resource.DecimalSI),
Timestamp: metav1.Now(),
},
}, nil
}
func (s *MyScaler) Run(ctx context.Context, activeThreshold int) (bool, error) {
// 判断是否有事件待处理
return s.client.HasPendingEvents()
}
func (s *MyScaler) Close(ctx context.Context) error {
return s.client.Close()
}
注意事项: 内置 Scaler 开发需要深入理解 KEDA 的代码结构和测试框架。每次 KEDA 版本升级,内置 Scaler 可能需要同步适配。对于非核心场景,外部 Scaler 是更灵活的选择。