使用 KEDA 为推理服务配置自动扩缩容

简介

在生产环境中部署机器学习模型会带来一些独特的挑战,其中最关键的一点是确保你的推理服务能够高效且可靠地应对不同水平的流量。AI 工作负载的不可预测性——例如流量可能突然激增,并且资源需求会根据输入序列长度、token 生成长度或并发请求数量等因素而变化——往往意味着传统的自动扩缩容方法难以满足需求。

仅依赖 CPU 或内存指标,可能会导致资源过度预留和浪费,或者资源不足并影响用户体验。同样,GPU 利用率既可能表示资源使用高效,也可能表示系统已接近饱和。因此,LLM 自动扩缩容的行业最佳实践已经逐步转向更具工作负载特征的指标。

本指南将通过利用 KEDA(Kubernetes Event-driven Autoscaling)以及由 vLLM 导出的自定义应用级指标,介绍如何配置 KServe 自动扩缩容。通过这种组合,推理服务可以根据真实的工作负载信号进行扩缩容,而不是依赖通用的基础设施指标。

INFO

KEDA 扩展了标准的 Kubernetes Horizontal Pod Autoscaler (HPA),使应用能够基于多种事件源——包括 Prometheus 指标——从零扩展到 N 个实例,再缩回去。它引入了一个开放且可扩展的框架,使 KServe 能够基于与你的 AI 模型性能相关的几乎任何信号进行扩缩容。

前提条件

  • 已安装 Alauda AI Operator plugin。
  • 已安装 KEDA Operator plugin。
  • 一个使用 Standard 模式且运行时为 vLLM 的 InferenceService
  • 集群中已安装并可访问 Prometheus

授予 KServe 访问 KEDA 资源的权限

在继续之前,请应用以下 RBAC 资源,以允许 kserve-controller-manager 管理 KEDA 对象(ScaledObjectTriggerAuthentication 等):

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: kserve-keda-manager-role
rules:
- apiGroups:
  - keda.sh
  resources:
  - "*"
  verbs:
  - "*"
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: kserve-keda-manager-binding
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: kserve-keda-manager-role
subjects:
- kind: ServiceAccount
  name: kserve-controller-manager
  namespace: kserve
[安装顺序]

如果 KEDA 是在 Alauda AI 之后安装的,请在应用上述 RBAC 之后,重启 kserve-controller-manager pod(位于 kserve namespace 中),以便它能够发现 KEDA CRD:

kubectl rollout restart deployment kserve-controller-manager -n kserve

步骤

停止正在运行的 InferenceService

在进行更改之前,请先停止正在运行的 InferenceService,以避免现有 HPA 与新的由 KEDA 管理的 scaler 之间发生冲突。添加以下 annotation 以停止它:

kubectl annotate inferenceservice <your-isvc-name> -n <your-namespace> \
  serving.kserve.io/stop='true'
WARNING

如果正在运行的 InferenceService 已经拥有 HPA resource,那么在未先停止它的情况下切换到 KEDA 将会导致 resource 冲突。

创建 Prometheus TriggerAuthentication

KEDA 需要在与你的 InferenceService 相同的 namespace 中创建一个 TriggerAuthentication resource,以便通过 Prometheus 进行身份验证。

Prometheus 凭据存储在 cpaas-system namespace 中的平台 secret kube-prometheus-alertmanager-basic-auth 里。运行以下命令将其复制到你的 namespace 中:

kubectl create secret generic prom-basic-auth-secret \
  --namespace=<your-namespace> \
  --from-literal=username=$(kubectl get secret kube-prometheus-alertmanager-basic-auth \
    -n cpaas-system -o jsonpath='{.data.username}' | base64 -d) \
  --from-literal=password=$(kubectl get secret kube-prometheus-alertmanager-basic-auth \
    -n cpaas-system -o jsonpath='{.data.password}' | base64 -d)

然后创建引用该 secret 的 TriggerAuthentication

apiVersion: keda.sh/v1alpha1
kind: TriggerAuthentication
metadata:
  name: prom-basic-auth
  namespace: <your-namespace>
spec:
  secretTargetRef:
    - parameter: username
      name: prom-basic-auth-secret
      key: username
    - parameter: password
      name: prom-basic-auth-secret
      key: password
TIP

以下名称必须在所有资源之间保持一致:

  • prom-basic-auth-secretSecret 名称,必须与 TriggerAuthentication 中的 secretTargetRef.name 匹配。
  • prom-basic-authTriggerAuthentication 名称,必须与 InferenceService spec 中的 authenticationRef.authenticationRef.name 匹配。

为 KEDA 配置 InferenceService

服务停止后,使用 KEDA 自动扩缩容配置更新 InferenceService manifest:

apiVersion: serving.kserve.io/v1beta1
kind: InferenceService
metadata:
  name: <your-isvc-name>
  namespace: <your-namespace>
  annotations:
    serving.kserve.io/deploymentMode: Standard
    serving.kserve.io/autoscalerClass: keda
spec:
  predictor:
    maxReplicas: 2
    autoScaling:
      metrics:
        - type: External
          external:
            authenticationRef:
              authModes: basic
              authenticationRef:
                name: prom-basic-auth
            metric:
              backend: prometheus
              query: >
                sum(vllm:num_requests_running{isvc_name="<your-isvc-name>",namespace="<your-namespace>"})
              serverAddress: http://prometheus-operated.cpaas-system.svc.cluster.local:9090
            target:
              type: Value
              value: '1'
    # ... rest of your predictor configuration
  1. 禁用内置的 KServe HPA,并将扩缩容委托给 KEDA。
  2. 设置最大副本数。要使服务能够根据流量增加自动扩容,请确保该值大于 1(例如,2)。
  3. 引用保存用于与 Prometheus 进行身份验证凭据的 TriggerAuthentication resource。请将 prom-basic-auth 替换为你实际的 TriggerAuthentication 名称。
  4. 返回当前负载单个数值的 PromQL 查询。请将 <your-model-name><your-namespace> 替换为你的实际值。
  5. Prometheus 实例的内部地址,例如 http://prometheus-operated.cpaas-system.svc.cluster.local:9090
  6. 每个副本的目标值。KEDA 会根据 ceil(metricValue / value) 计算所需的副本数。

用于自动扩缩容的 vLLM 指标

平台会自动收集 vLLM 指标。选择合适的指标可以说是整个配置中最关键的部分——Prometheus 查询必须返回一个单一数值,并且能够准确反映模型当前的负载。

以下 vLLM 指标通常用于自动扩缩容:

指标描述
vllm:num_requests_running当前由模型正在处理的请求数量
vllm:num_requests_waiting已排队并等待处理的请求数量
vllm:gpu_cache_usage_perc当前正在使用的 GPU KV cache 百分比
vllm:e2e_request_latency_seconds_bucket端到端请求延迟 histogram
vllm:time_per_output_token_seconds_buckettoken 间延迟(Time Per Output Token,TPOT)

使用 sum() 聚合函数,确保查询在你部署的所有 pod 上返回单个值。例如,要根据等待中的请求数进行扩缩容:

sum(vllm:num_requests_waiting{isvc_name="<your-isvc-name>", namespace="<your-namespace>"})

这样会汇总所有 predictor pod 中的待处理请求,为 KEDA 提供一个单一的聚合信号来进行处理。

验证配置

应用更新后的 InferenceService 之后,KServe 会自动为你创建一个 KEDA ScaledObject。请验证一切是否正常工作:

# Check the ScaledObject created by KServe
kubectl get scaledobject -n <your-namespace>

# Check the KEDA-managed HPA
kubectl get hpa -n <your-namespace>

# Watch replica counts in real time
kubectl get hpa -n <your-namespace> -w

HPA 输出将显示当前指标值、扩缩容阈值以及当前/期望的副本数。随着推理流量增加,TARGETS 值会升高,副本数也会自动扩容。