Skip to content

Kubernetes KEDA 事件驱动自动扩缩容

KEDA(Kubernetes Event-driven Autoscaling)是 Kubernetes 生态中专门为事件驱动型工作负载设计的自动扩缩容解决方案。与传统的 HPA 不同,KEDA 以事件源为核心,支持更加丰富的触发器类型,能够根据外部消息队列、HTTP 流量、Prometheus 指标等多种信号进行扩缩容决策。在 GPU 推理服务场景中,KEDA 尤其适合处理突发流量和消息队列堆积的场景,其内置的 scale-to-zero 能力可以显著降低成本。

KEDA 核心概念

什么是 KEDA

KEDA 是一个云原生的事件驱动自动扩缩容工具,它的核心设计理念是将外部事件源与 Kubernetes 的 Pod 扩缩容相结合。KEDA 通过监视外部事件源(如消息队列、数据库记录、HTTP 请求等),根据事件积压情况自动调整工作负载的副本数。与 HPA 不同的是,KEDA 不仅仅依赖于 Metrics API,还可以直接与各种外部系统集成,这使得它更加适合处理微服务架构中的各种流量模式。

KEDA 的工作原理可以概括为以下几个步骤:首先,KEDA 作为 Operator 运行在 Kubernetes 集群中,监视用户定义的 ScaledObject 资源;然后,KEDA 与外部事件源建立连接,持续监听事件积压情况;接着,根据配置的扩缩容策略,KEDA 计算目标副本数并更新 ScaledObject 的副本数;最后,Kubernetes Deployment 或其他控制器根据更新的副本数创建或删除 Pod。这种设计使得 KEDA 能够对外部事件做出快速响应,同时也支持更加复杂的扩缩容策略。

KEDA 与 HPA 的对比

虽然 KEDA 和 HPA 都是用于自动扩缩容的工具,但它们在设计理念和适用场景上有显著区别。HPA 是 Kubernetes 原生的扩缩容组件,通过 Metrics Server 获取资源指标,适合稳定的、可预测的工作负载。KEDA 则是专门为事件驱动工作负载设计的,支持更多种类的触发器和更灵活的扩缩容策略。

从功能特性来看,HPA 支持 CPU、内存和自定义 Metrics API 指标,而 KEDA 支持 20+ 种触发器,包括 Prometheus、Kafka、RabbitMQ、Redis、SQS 等。从扩缩行为来看,HPA 不支持 scale-to-zero,而 KEDA 原生支持零副本缩放。从响应特性来看,HPA 更适合持续稳定的工作负载,KEDA 更适合突发性、脉冲型工作负载。从复杂度来看,HPA 配置相对简单,KEDA 支持更复杂的策略但配置也更复杂。

在 GPU 推理服务场景中,选择 KEDA 还是 HPA 取决于具体需求。如果工作负载具有明显的波峰波谷特征,消息队列中有大量积压需要处理,或者需要支持 scale-to-zero,KEDA 是更好的选择。如果工作负载相对稳定,只需要基于 CPU 或内存进行扩缩容,HPA 足够了。很多生产环境会同时使用两者,HPA 用于基础资源的扩缩容,KEDA 用于处理突发流量。

KEDA 支持的触发器类型

KEDA 支持丰富的触发器类型,几乎覆盖了所有常见的外部系统和协议。

Prometheus 触发器

Prometheus 触发器是 GPU 推理服务最常用的类型,它可以根据应用的业务指标进行扩缩容。在 GPU 推理场景中,最常用的指标包括等待队列长度、并发请求数、KV Cache 使用率、首 Token 延迟等。这些指标比单纯的 GPU 利用率更能反映服务的真实负载情况。

Prometheus 触发器配置示例:

yaml
apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
  name: llm-inference-scaledobject
  namespace: inference
spec:
  scaleTargetRef:
    name: llm-inference
  minReplicaCount: 1
  maxReplicaCount: 30
  pollingInterval: 15
  cooldownPeriod: 300
  advanced:
    horizontalPodAutoscalerConfig:
      behavior:
        scaleUp:
          stabilizationWindowSeconds: 30
        scaleDown:
          stabilizationWindowSeconds: 600
  triggers:
    - type: prometheus
      metadata:
        serverAddress: http://prometheus-operated.monitoring.svc:9090
        metricName: inference_requests_waiting_total
        query: |
          sum(inference_requests_waiting{namespace="inference",app="llm-inference"})
        threshold: "8"

这个配置表示当等待队列长度总和超过 8 时开始扩容,最小保留 1 个副本,最大 30 个副本。pollingInterval 表示每 15 秒检查一次指标,cooldownPeriod 表示扩容后需要等待 300 秒才能开始缩容,防止抖动。

Kafka 触发器

Kafka 触发器适用于消费消息队列的工作负载。当消费者处理速度跟不上消息产生速度时,KEDA 可以根据消费 lag(积压)进行自动扩容。在 AI 服务场景中,如果推理请求通过 Kafka 队列传递,这个触发器非常适用。

Kafka 触发器配置示例:

yaml
triggers:
  - type: kafka
    metadata:
      bootstrapServers: kafka:9092
      consumerGroup: llm-inference-group
      topic: inference-requests
      lagThreshold: "100"

Redis 触发器

Redis 触发器可以根据 Redis 中的队列长度进行扩缩容,适用于分布式任务队列场景。

HTTP 触发器

HTTP 触发器可以根据 HTTP 请求数量或响应时间进行扩缩容,适用于基于 HTTP 的推理服务。

KEDA 在 GPU 推理中的最佳实践

基于队列长度的扩缩容

在 GPU 推理服务中,使用队列长度作为扩缩容指标是最推荐的做法。相比于 GPU 利用率,队列长度更能反映用户的实际等待时间。当队列积压时,说明当前的处理能力不足,需要扩容;当队列为空时,说明处理能力过剩,可以缩容。

配置示例:

yaml
apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
  name: llm-inference
  namespace: inference
spec:
  scaleTargetRef:
    name: llm-inference
  minReplicaCount: 2
  maxReplicaCount: 30
  pollingInterval: 15
  cooldownPeriod: 300
  triggers:
    - type: prometheus
      metadata:
        serverAddress: http://prometheus:9090
        metricName: queue_length
        query: sum(inference_queue_length{pod=~"llm-inference-.*"})
        threshold: "4"
        # 使用更保守的策略
    - type: prometheus
      metadata:
        serverAddress: http://prometheus:9090
        metricName: p99_latency
        query: histogram_quantile(0.99, rate(inference_latency_bucket{pod=~"llm-inference-.*"}[5m]))
        threshold: "2000"

这里配置了两个触发器:队列长度和 P99 延迟。KEDA 会取两个触发器中需要副本数较大的那个,确保既不会因为短暂抖动频繁扩缩容,又能够及时响应真实的负载变化。

Scale-to-Zero 的使用

KEDA 支持将副本数缩容到 0,这对于非工作时间或低峰期的 GPU 推理服务非常有用。但启用 scale-to-zero 需要注意以下几点:

首先,业务必须允许冷启动。如果服务对首包延迟有严格要求,不建议启用 scale-to-zero。其次,需要确保网关层有超时和重试机制。当服务从 0 扩容到有实例时,首批请求可能会有较长延迟,需要上层有相应保护。最后,模型和镜像需要有预加载机制,否则冷启动时间会很长。

启用 scale-to-zero 的配置:

yaml
spec:
  minReplicaCount: 0  # 允许缩容到 0
  # 需要配合健康检查和预热

多指标联合扩缩容

在实际生产环境中,单一指标往往不够可靠。建议使用多指标联合判断:

yaml
triggers:
  # 主指标:队列长度
  - type: prometheus
    metadata:
      metricName: queue_length
      query: sum(inference_queue{namespace="inference"})
      threshold: "10"
  # 护栏指标 1:P99 延迟
  - type: prometheus
    metadata:
      metricName: p99_latency
      query: histogram_quantile(0.99, inference_latency_bucket)
      threshold: "3000"
  # 护栏指标 2:KV Cache 使用率
  - type: prometheus
    metadata:
      metricName: kv_cache_usage
      query: avg(inference_kv_cache_ratio{namespace="inference"})
      threshold: "0.85"

扩缩容行为配置

KEDA 允许精细控制扩缩容行为,主要通过 advanced 字段配置:

yaml
spec:
  advanced:
    horizontalPodAutoscalerConfig:
      behavior:
        scaleUp:
          stabilizationWindowSeconds: 30  # 扩容稳定窗口
          policies:
            - type: Percent
              value: 100  # 每次最多扩容 100%
              periodSeconds: 60
        scaleDown:
          stabilizationWindowSeconds: 600  # 缩容稳定窗口更长
          policies:
            - type: Percent
              value: 20  # 每次最多缩容 20%
              periodSeconds: 60

在 GPU 推理场景中,扩容应该相对激进(快速响应),缩容应该保守(防止抖动)。因为 GPU 服务冷启动很慢,如果缩容太快又扩容,会导致系统不稳定。

与 Karpenter 结合

KEDA 负责 Pod 级别的扩缩容,但 Pod 扩出来之后,如果节点上没有可用的 GPU,Pod 会一直处于 Pending 状态。这时候需要 Karpenter 或 Cluster Autoscaler 来补充 GPU 节点。

典型的组合方案:

yaml
# Karpenter NodePool 配置
apiVersion: karpenter.sh/v1
kind: NodePool
metadata:
  name: gpu-inference
spec:
  template:
    spec:
      requirements:
        - key: kubernetes.io/arch
          operator: In
          values: [amd64]
        - key: karpenter.k8s.aws/instance-category
          operator: In
          values: [g]
        - key: karpenter.k8s.aws/instance-gpu-count
          operator: In
          values: ["1", "4", "8"]
      taints:
        - key: nvidia.com/gpu
          effect: NoSchedule
  disruption:
    consolidationPolicy: WhenEmptyOrUnderutilized
    consolidateAfter: 300s

当 KEDA 扩容 Pod 导致 Pending 时,Karpenter 会自动补充 GPU 节点。这种组合方案能够很好地解决 GPU 推理服务的弹性需求。

监控和调试

查看 KEDA 状态

bash
# 查看 ScaledObject 状态
kubectl get scaledobject -n inference

# 查看 ScaledObject 详情
kubectl describe scaledobject llm-inference -n inference

# 查看 KEDA operator 日志
kubectl logs -n keda -l app=keda-operator

常见问题排查

问题:Pod 扩不出来

可能原因:节点不足或 Karpenter/Cluster Autoscaler 未正常工作。需要检查 Pending Pod 和节点状态。

问题:扩缩容响应太慢

可能原因:pollingInterval 太长或 cooldownPeriod 太长。可以调整这些参数。

问题:频繁扩缩容

可能原因:阈值设置太低或 stabilizationWindowSeconds 太短。需要增加稳定窗口时间。

问题:scale-to-zero 后无法启动

可能原因:健康检查失败或预热逻辑问题。需要检查 readlinessProbe 和 startupProbe。

KEDA 安装

bash
# 使用 Helm 安装
 Helm repo add kedacore https://kedacore.github.io/charts
 helm install keda kedacore/keda --namespace keda --create-namespace

# 或者使用 YAML 直接安装
 kubectl apply -f https://github.com/kedacore/keda/releases/download/v2.14.0/keda-2.14.0.yaml

总结

KEDA 是 GPU 推理服务弹性扩缩容的重要工具,特别适合以下场景:消息队列驱动的工作负载、需要 scale-to-zero 的非核心服务、突发流量明显的应用、多指标联合扩缩容需求。在使用 KEDA 时,需要注意冷启动延迟、扩缩容稳定性、与节点供给器的配合。同时,建议结合业务指标(队列长度、延迟)而非单纯的资源指标(GPU 利用率)进行扩缩容决策,这样才能真正反映用户体验。