基于K8s的云原生AI基础设施:架构、部署与实践【012】-AI算力的弹性供给

在 Kubernetes 中,自动扩缩容通常从 HPA 开始。对于普通 Web 服务来说,CPU 或内存利用率往往已经能够较好地反映业务压力:请求变多,CPU 升高;请求变少,CPU 回落。基于这种关系,Metrics Server 提供的 CPU/内存指标通常就能支撑一套基础的自动伸缩能力。

但在 AI 类型的服务中,情况并没有这么简单。大模型推理服务的核心瓶颈通常不在 CPU。一个 vLLM 或 SGLang 实例可能 CPU 并不高,但 GPU 已经接近满负载;也可能显存长期处于高位,但系统并没有真实请求压力;还可能在并发升高时,实例内部已经开始排队,用户侧的 TTFT 和 TPOT 明显变差,但传统的基于Metrics Server 的HPA并不能及时感知。

因此,对于AI 推理服务的自动扩缩容,不能停留在CPU和内存层面,而需要引入更贴近推理过程本身的指标,例如 GPU 利用率、请求队列、KV Cache 使用率、running requests、waiting requests,以及用户体验侧的 TTFT / TPOT。

本节内容的重点是梳理 AI 推理服务在 Kubernetes 中应该如何选择扩缩容指标,以及哪些指标适合做触发条件,哪些指标更适合做告警和容量观测。


1. 从 Metrics Server 开始:K8s原生资源指标管道

为了文章的完整性,笔者依然选择从 CPU / Memory 资源指标开始来展开HPA,而这部分能力依赖 Metrics Server 提供的资源指标管道。Metrics Server 会从各节点上的 Kubelet 采集Pod 和 Node的CPU、Memory使用情况,并通过 Kubernetes API Server 暴露为 metrics.k8s.io API。这样,HPA 就可以读取这些资源指标,并根据 CPU 或内存利用率自动调整工作负载副本数量。

 # 获取安装所需yaml文件
 wget https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml
 
 # 直接应用yaml来部署
 kubectl apply -f components.yaml 
 
 # 检查
 # Metrics API 同时也被kubectl top使用,用于查看节点和Pod的基础资源使用情况。
 kubectl top nodes
 
 kubectl top pods

 

在普通应用中,这一步通常就足够支撑基础 HPA。例如,可以创建一个基于 CPU 利用率的 HPA,让某个工作负载在CPU平均利用率超过 60% 时扩容:

apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: vllm-hpa
  namespace: default
spec:
  scaleTargetRef:
    apiVersion: leaderworkerset.x-k8s.io/v1
    kind: LeaderWorkerSet
    name: vllm
  minReplicas: 1
  maxReplicas: 3
  metrics:
  - type: Resource
    resource:
      name: cpu
      target:
        type: Utilization
        averageUtilization: 60

 

在测试中,当整个 group 的 CPU 平均利用率超过阈值后,HPA 可以将副本数扩展到 3 组:

NAME       REFERENCE              TARGETS      MINPODS   MAXPODS   REPLICAS
vllm-hpa   LeaderWorkerSet/vllm   cpu:72%/60%     1         3         3

 

但这只是起点。对于大模型推理服务来说,CPU 显然不是其关键制约因素。真正影响吞吐、延迟和稳定性的,更多来自 GPU、显存、KV Cache 和请求队列。


2. 为什么 AI 推理服务不能只看 CPU/内存

在推理场景中,一个请求的成本并不固定。短 prompt 和长 prompt 的资源消耗不同;生成 100 个 token 和生成 2048 个 token 的耗时不同;上下文长度、并发数、KV Cache 命中情况,也都会影响模型实例的实际压力。因此,CPU 利用率低,并不代表模型服务空闲;显存占用高(通常是预分配),也不一定代表业务繁忙。对 LLM 推理服务来说,比较有价值的指标大致可以分成三类。

第一类:容量压力指标(Capacity Metrics)

例如 GPU 利用率、KV Cache 使用率、running requests。这类指标描述的是模型实例当前正在承载多少计算和缓存压力。

第二类:排队结果指标(Queue Metrics)

例如 vllm:num_requests_waitingsglang:num_queue_reqsinference_pool_average_queue_sizeinference_pool_per_pod_queue_size。这类指标说明模型实例或推理池已经开始出现等待队列。

第三类:用户体验指标(SLA Metrics)

例如 TTFT 和 TPOT。TTFT 反映从请求进入系统到首个 token 返回之间的延迟,通常会受到排队、调度以及 prefill 阶段影响;TPOT 反映生成阶段每输出一个 token 的平均耗时,更能体现 decode 阶段的持续输出能力。对于用户来说,TTFT 变高意味着“模型迟迟不开始回答”,TPOT 变高则意味着“模型开始回答后,输出速度变慢”。

需要注意的是,推理压力的变化并不一定严格遵循固定顺序。短请求高并发、长上下文请求、不同 batch 策略、KV Cache 配置以及推理引擎调度策略,都会影响各类指标的变化节奏。但在实际观测中,一种常见的压力传导路径可以理解为:

running requests 持续升高
          
KV Cache usage 上升,或模型实例接近并发处理上限
          
waiting queue / InferencePool queue 持续出现
          
TTFT 明显变高
          
如果 decode 阶段也出现资源竞争,TPOT 变高或波动

 

也就是说,扩缩容最好不要只看最终的用户体验指标。TTFT 和 TPOT 已经明显变差时,用户通常已经感知到了问题。更理想的策略,是组合观察推理服务的容量压力指标和排队指标,例如 running requests、KV Cache usage、GPU 利用率、vLLM waiting queue 以及 InferencePool queue,在排队开始持续出现、KV Cache 接近压力区间、GPU 利用率持续升高时提前触发扩容。

其中,running requests、KV Cache usage、GPU 利用率更偏向容量压力信号;waiting queue 和 InferencePool queue 更直接反映请求是否已经开始积压;TTFT 和 TPOT 则更适合作为 SLA 观测和告警指标。这样做的目的,是尽量在用户体验恶化之前,通过 Prometheus 和 Prometheus Adapter 将推理侧指标接入 Kubernetes HPA,让扩缩容策略基于真实推理压力,而不是简单依赖 CPU / Memory 这类传统资源指标。


3. 通过 Prometheus Adapter 引入自定义指标

Kubernetes HPA 并不直接读取 Prometheus。要让 HPA 使用 Prometheus 中的指标,需要在中间增加 Prometheus Adapter。Prometheus Adapter 的作用,是把 Prometheus 中的时间序列转换成 Kubernetes 可以识别的 custom.metrics.k8s.ioexternal.metrics.k8s.io API。这样 HPA 就可以像使用 CPU 指标一样,使用 GPU 利用率、vLLM 队列、InferencePool queue 等自定义指标。Prometheus Adapter部署非常简单,可通过helm部署。

# 通过helm添加prometheus-adapter仓库
helm repo add prometheus-community https://prometheus-community.github.io/helm-charts

helm repo update
# 搜索prometheus-adapter 的chart版本
helm search repo prometheus-community/prometheus-adapter -l

# 此处选择了将Helm chart拉取至本地部署
helm pull prometheus-community/prometheus-adapter --version 5.2.0
tar -xvf prometheus-adapter-5.2.0.tgz

# 安装prometheus-adapter
helm install prome-adapter ./ -n monitoring

# 检查资源情况

<span style="color: rgba(68, 131, 97, 1);">kubectl -n monitoring get pods | grep -i adapter
</span>prome-adapter-prometheus-adapter-54dbb794f-zth7j   1/1     Running            0                9h

<span style="color: rgba(68, 131, 97, 1);">kubectl -n monitoring get svc  | grep -i adapter
</span>prome-adapter-prometheus-adapter      ClusterIP   10.109.188.22    <none>        443/TCP        9h

<span style="color: rgba(68, 131, 97, 1);">kubectl -n monitoring get deploy
</span>NAME                               READY   UP-TO-DATE   AVAILABLE   AGE
prome-adapter-prometheus-adapter   1/1     1            1           9h

 

部署完成后,需要确认 Custom Metrics API 是否注册成功:

kubectl get apiservices | egrep 'custom.metrics|external.metrics|metrics.k8s.io'

 

如果可以看到类似下面的结果:

v1beta1.custom.metrics.k8s.io   monitoring/prome-adapter-prometheus-adapter   True

 

说明 Prometheus Adapter 已经成功注册到 Kubernetes 聚合 API 中。

接下来可以进一步探测 Custom Metrics API:

kubectl get --raw /apis/custom.metrics.k8s.io/v1beta1 | head

 

这一步很关键。只有 Custom Metrics API 可用,HPA 才能引用 Prometheus 中经过映射后的指标。


4. 先确认指标标签:能否映射到 Kubernetes 资源

在配置 Prometheus Adapter 之前,必须先确认 Prometheus 中的原始指标是否带有 Kubernetes 资源标签。例如,要把 ht_gpu_usage 映射成 Pod 级自定义指标,就必须确认该指标中带有 namespacepod 标签。否则 Adapter 无法知道某条 Prometheus 时间序列对应 Kubernetes 中哪个 Pod。可以通过 Prometheus API 查看指标 series:

curl -sS -G "http://10.96.49.126:80/api/v1/series" 
  --data-urlencode 'match[]=ht_gpu_usage' | head -c 2000; echo

 

返回结果中可以看到类似标签:

namespace=""
pod="deepseek-r1-bf16-0"
container="vllm-leader"
deviceId="2"
uuid="GPU-..."

 

这一步验证的结论是:ht_gpu_usage 已经具备 namespacepod 标签,因此可以映射为 Kubernetes Pod 级自定义指标。


5. 映射 GPU 指标:从 Prometheus 指标到 HPA 指标

确认原始指标标签具备映射条件后,就可以在 Prometheus Adapter 中添加规则。例如,可以将 Prometheus 中的ht_gpu_usage映射成 Kubernetes Custom Metrics API 中的:pods/gpu_usage 同时将:ht_memory_usage{type=”vram”}映射成:pods/vram_usage核心配置如下:

rules:
  default: false
  custom:
    - seriesQuery: 'ht_gpu_usage{namespace!="",pod!=""}'
      resources:
        overrides:
          namespace: { resource: "namespace" }
          pod:       { resource: "pod" }
      name:
        matches: "^ht_gpu_usage$"
        as: "gpu_usage"
      metricsQuery: 'avg(avg_over_time(<<.Series>>{<<.LabelMatchers>>}[1m])) by (<<.GroupBy>>)'

 

这里有几个关键点。

seriesQuery 决定 Adapter 去 Prometheus 中发现哪些时间序列。加上 namespace!=""pod!="",是为了确保这些指标能够映射到 Kubernetes Pod 资源。

resources.overrides 明确告诉 Adapter:Prometheus 里的 namespace 标签对应 Kubernetes 的 namespace,pod 标签对应 Kubernetes 的 pod。这个显式声明很重要,因为有些 exporter 同时会暴露 namespace/podexported_namespace/exported_pod,如果不明确指定,容易造成资源映射混乱。

name.as 则决定 Custom Metrics API 中暴露出来的指标名。最终 HPA 引用的是 gpu_usage,而不是原始 Prometheus 指标名。

metricsQuery决定 Adapter 最终返回给 Custom Metrics API 的值。对于按设备上报的 GPU 和显存指标,如果一个 Pod 中包含多张 GPU,就需要先把多条设备级时间序列聚合成一个 Pod 级指标。

在本文实践中,GPU 利用率使用 avg by (namespace,pod) 聚合,表示同一个 Pod 内多张 GPU 的平均利用率,用于描述该推理副本整体的 GPU 使用水平;这种处理方式的语义是GPU 使用率更偏运行负载观察,使用平均值可以反映整体计算压力;如果希望扩缩容策略更保守,也可以将 GPU 利用率改成 max by (namespace,pod),但本文配置中采用的是 gpu_usage 用 avg。

接下来验证指标是否已经进入 Custom Metrics API:

kubectl get --raw "/apis/custom.metrics.k8s.io/v1beta1/namespaces/demo/pods/*/gpu_usage"

 

如果能够返回每个 Pod 对应的指标值,说明GPU 指标已经可以被 HPA 使用。需要注意的是,Custom Metrics API 返回的 value 是 Kubernetes Quantity 类型。Prometheus 中的浮点值在经过 Adapter 转换后,可能会以 m 的形式展示。例如 Prometheus 中的 88.849,在 Custom Metrics API 中可能显示为 88849m,二者语义等价,都是表示约 88.849。


6. 显存指标为什么不适合作为 HPA 主指标

通常显存利用率并不适合作为 HPA 的主触发指标。笔者从推理服务实践的一些历史数据来看,VRAM 利用率长期处在 80% 到 90% 的高位,但 GPU 利用率在过去 6 小时内峰值可能只有 10% 左右。这说明显存占用主要来自模型权重常驻、KV Cache 预分配以及推理引擎的显存管理策略,不能实时清晰的展现请求压力。

对于 vLLM 这类推理服务来说,模型一旦加载,显存就会长期保持高位;即使没有请求,模型权重和预留缓存也不会释放。因此,“显存高”并不等于“业务忙”。如果把 VRAM 作为 HPA 主指标,很容易出现一个错误的正反馈:模型加载后显存长期高位—HPA 判断持续超过阈值—不断触发扩容—新副本加载模型后同样占用高显存—继续维持高显存状态。这种扩容并不能降低单个 Pod 的显存占用,因为每个新副本都必须重新加载模型。最终结果可能是整体显存消耗更大,但业务吞吐和延迟并没有成比例改善。

因此,显存更适合作为容量保护和告警指标,而不是扩容的主指标。它可以用于判断是否接近 OOM、是否存在 KV Cache 压力、是否需要调整 gpu-memory-utilizationmax-model-lenmax-num-seqs 等参数,但不应该简单作为“超过 90% 就扩容”的唯一依据。更合理的 HPA 主指标,应该优先选择能够反映实时负载的指标,例如:

GPU 利用率
vLLM waiting requests
InferencePool queue size
running requests
TTFT / TPOT 的趋势

 

其中,queue 类指标尤其重要。因为一旦出现持续排队,就说明当前副本已经无法及时消化进入的请求。


7. 从 GPU 指标扩展到推理队列指标

在 AI 推理服务中,仅有 GPU 利用率还不够。GPU 利用率会受到 batch、prefill、decode、调度策略、长短请求混合等因素影响,有时并不能稳定反映用户侧压力。因此,还需要进一步引入推理队列指标。在当前实践中,比较关键的指标包括:

inference_pool_average_queue_size
inference_pool_per_pod_queue_size
vllm:num_requests_waiting
vllm:num_requests_running
vllm:kv_cache_usage_perc

 

其中,inference_pool_average_queue_size 用于观察整个 InferencePool 的平均排队情况,适合作为 Service 层面的 Object 指标。

inference_pool_per_pod_queue_size 用于观察每个模型后端 Pod 的排队情况,适合做问题定位和负载倾斜分析。

vllm:num_requests_waiting 表示 vLLM 内部等待队列中的请求数。如果这个值持续大于 0,说明请求已经开始排队。

vllm:num_requests_running 表示当前正在处理的请求数量。running 持续很高但 waiting 仍为 0,说明实例繁忙但还没有明显积压;running 高且 waiting 大于 0,说明实例已经压不住了。

vllm:kv_cache_usage_perc 用于观察 KV Cache 使用率。如果 KV Cache 长期接近高位,同时 waiting queue 开始上升,就说明实例已经接近容量压力区间。

在 Prometheus Adapter 中,可以将这些指标继续映射为 Custom Metrics:

- seriesQuery: 'inference_pool_average_queue_size{namespace!="",service!=""}'
  resources:
    overrides:
      namespace:
        resource: namespace
      service:
        resource: service
  name:
    matches: "^inference_pool_average_queue_size$"
    as: "inference_pool_average_queue_size_1m"
  metricsQuery: |
    avg by (<<.GroupBy>>) (
      avg_over_time(inference_pool_average_queue_size{<<.LabelMatchers>>}[1m])
    )

- seriesQuery: 'vllm:kv_cache_usage_perc{namespace!="",pod!=""}'
  resources:
    overrides:
      namespace:
        resource: namespace
      pod:
        resource: pod
  name:
    matches: "^vllm:kv_cache_usage_perc$"
    as: "vllm_kv_cache_usage_perc_1m"
  metricsQuery: |
    max by (<<.GroupBy>>) (
      max_over_time(vllm:kv_cache_usage_perc{<<.LabelMatchers>>}[1m])
    )

 

这里使用 1 分钟窗口,是为了避免瞬时波动直接触发扩缩容。对于 queue 类指标,短时间 spike 并不一定代表需要扩容;只有持续排队,才说明当前容量不足。


8. 为 Deployment 配置基于队列和 KV Cache 的 HPA

当自定义指标准备好之后,就可以为常见的单机多卡的推理方式 Deployment 类型的资源配置HPA。在示例中,HPA 同时使用两个指标:

第一个是 Service 层面的 inference_pool_average_queue_size_1m。当推理池平均队列超过阈值时,说明请求已经开始积压,需要扩容。

第二个是 Pod 层面的 vllm_kv_cache_usage_perc_1m。当 KV Cache 使用率超过阈值时,说明模型实例接近缓存容量压力,也可以作为扩容参考。示例配置如下:

# 注:样例中的阈值只为测试验证,生产环境请根据实际情况来配置
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: qwen3-6-35b-a3b-w8a8-api-hpa
  namespace: demo
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: qwen3-6-35b-a3b-w8a8-api
  minReplicas: 1
  maxReplicas: 3
  behavior:
    scaleUp:
      stabilizationWindowSeconds: 30
      policies:
      - type: Pods
        value: 1
        periodSeconds: 60
      selectPolicy: Max
    scaleDown:
      stabilizationWindowSeconds: 3600
      policies:
      - type: Pods
        value: 1
        periodSeconds: 300
      selectPolicy: Min
  metrics:
  - type: Object
    object:
      describedObject:
        apiVersion: v1
        kind: Service
        name: qwen3-6-35b-a3b-w8a8-api-epp
      metric:
        name: inference_pool_average_queue_size_1m
      target:
        type: Value
        value: "2"
  - type: Pods
    pods:
      metric:
        name: vllm_kv_cache_usage_perc_1m
      target:
        type: AverageValue
        averageValue: "700m"

 

这里想说明的关键不是YAML 本身,而是指标组合方式。

inference_pool_average_queue_size_1m > 2 表示推理池已经出现持续排队,这是比较直接的扩容信号。

vllm_kv_cache_usage_perc_1m > 70% 表示 KV Cache 已经进入较高使用区间,可以作为容量压力信号。

behavior 中的扩缩容策略用于防止抖动:扩容时每 60 秒最多增加 1 个副本,缩容时设置更长稳定窗口,避免长请求尚未完成时过早缩容。对于大模型推理服务来说,缩容通常应该比扩容更谨慎。因为一个推理请求可能持续几十秒甚至数分钟,尤其在长上下文和大输出场景下,如果缩容过于激进,容易影响正在处理的请求。


9. 为 LWS 配置自动扩缩容

对于多机分布式推理,Deployment 不一定是最合适的抽象。很多大模型需要以 leader + worker 的方式组成一个推理单元,例如一个模型副本跨多个 Pod、多个节点、多个 GPU 运行。此时,LeaderWorkerSet 更适合描述这种“以组为单位”的推理工作负载。

LWS 的关键价值在于:它把一组 leader / worker Pod 作为一个复制单元。扩容时不是单独增加一个普通 Pod,而是增加一组完整的推理 group。因此,为LWS配置HPA时,scaleTargetRef 需要指向 LeaderWorkerSet:

# 注:样例中的阈值只为测试验证,生产环境请根据实际情况来配置
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: deepseek-v3-1-w8a8-api-hpa
  namespace: mosuanguichao
spec:
  scaleTargetRef:
    apiVersion: leaderworkerset.x-k8s.io/v1
    kind: LeaderWorkerSet
    name: deepseek-v3-1-w8a8-api
  minReplicas: 1
  maxReplicas: 3
  behavior:
    scaleUp:
      stabilizationWindowSeconds: 30
      policies:
      - type: Pods
        value: 1
        periodSeconds: 60
      selectPolicy: Max
    scaleDown:
      stabilizationWindowSeconds: 900
      policies:
      - type: Pods
        value: 1
        periodSeconds: 300
      selectPolicy: Min
  metrics:
  - type: Object
    object:
      describedObject:
        apiVersion: v1
        kind: Service
        name: deepseek-v3-1-w8a8-api-epp
      metric:
        name: inference_pool_average_queue_size_1m
      target:
        type: Value
        value: "2"
  - type: Pods
    pods:
      metric:
        name: vllm_kv_cache_usage_perc_1m
      target:
        type: AverageValue
        averageValue: "700m"

 

这里的逻辑与 Deployment 类似,但扩容对象不同。对于 Deployment,扩容增加的是单个 Pod 副本;对于 LWS,扩容增加的是一组完整的 leader-worker 推理单元。

需要特别注意的是,LWS 扩容通常比普通 Deployment 更重。因为每扩一组,都可能意味着加载完整模型、申请多张 GPU、初始化分布式通信、建立 RDMA/NCCL 链路。因此,LWS 的扩容阈值和速率限制应该更保守,避免因为短暂流量波动造成大规模资源震荡。


10. 用压测验证指标和扩缩容策略

自动扩缩容配置完成后,必须通过压测验证指标是否符合预期。在本次实践中,使用 evalscope 对模型接口进行压测,逐步提升并发:

evalscope perf 
  --url "https://llm.xxx.cn/xxx/qwen3-6-35b-a3b-w8a8-api/v1/completions" 
  --api-key "sk-xxx" 
  --model "Qwen3.6-35B-A3B-W8A8" 
  --dataset speed_benchmark 
  --min-tokens 2048 
  --max-tokens 2048 
  --api openai 
  --parallel 8 16 32 64 
  --number 80 160 320 640 
  --extra-args '{"use_cache": false, "ignore_eos": true}' 
  --stream

 

压测最终提供的结果非常有指导意义。比如笔者针对某模型在并发 8 和 16 测试时,请求成功率为 100%,但随着并发升高,TTFT 和 TPOT 开始明显上升。在并发 32 时,成功率下降到 96.9%;并发 64 时,成功率只有 55%,平均延迟接近 500 秒,TTFT P95 超过 60 秒,TPOT 也显著恶化。这说明系统并不是线性扩展的。并发继续升高后,模型实例已经无法及时消化请求,请求开始积压,用户体验迅速下降。因此通过压测我们也可以更加直观的找到扩容阈值。比如,当并发 32 接近系统较优边界,而并发 64 明显崩溃时,就说明 HPA 应该尽量在进入并发 64 这种状态之前,通过队列或 KV Cache 指标提前扩容。


11. KEDA ,另一种事件驱动扩缩容方式

前面介绍的是通过 Prometheus Adapter 将 Prometheus 中的指标暴露为 custom.metrics.k8s.io,再由 HPA 直接消费这些自定义指标完成扩缩容。这是一种比较标准的 Kubernetes 自定义指标接入方式。除此之外,KEDA 也提供了另一种思路:通过 ScaledObject 声明扩缩容目标和触发器,由 KEDA 对接外部事件源或指标源,并在背后与 Kubernetes HPA 协同完成扩缩容。

KEDA 的定位是 Kubernetes Event-driven Autoscaler。它并不是要替代 HPA,而是通过丰富的 scaler 把消息队列、数据库、Prometheus、云监控等外部事件源接入 Kubernetes 扩缩容体系。KEDA 官方文档也明确说明,它可以与标准 Kubernetes 组件如 HPA 协同工作,并在不覆盖原生能力的前提下扩展扩缩容能力。

在 AI 推理服务场景中,如果已经通过 Prometheus 采集了 GPU 利用率、推理队列、KV Cache 或请求指标,就可以使用 KEDA 的 Prometheus scaler 直接查询 Prometheus,并基于查询结果驱动扩缩容。这样做的好处是配置相对集中:扩缩容目标、Prometheus 地址、PromQL 查询和阈值都可以写在一个 ScaledObject 中。

例如,在一个基于 LWS 运行的 vLLM 服务中,可以创建如下 ScaledObject

apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
  name: vllm-gpu-scaler
  namespace: tenant-a
spec:
  scaleTargetRef:
    apiVersion: leaderworkerset.x-k8s.io/v1
    kind: LeaderWorkerSet
    name: vllm

  minReplicaCount: 1
  maxReplicaCount: 3

  advanced:
    horizontalPodAutoscalerConfig:
      behavior:
        scaleUp:
          stabilizationWindowSeconds: 15
          policies:
          - type: Pods
            value: 1
            periodSeconds: 120
        scaleDown:
          stabilizationWindowSeconds: 600
          policies:
          - type: Pods
            value: 1
            periodSeconds: 120

  triggers:
  - type: prometheus
    metadata:
      serverAddress: http://prometheus-server.prometheus-system.svc.cluster.local:80
      metricName: ht_gpu_usage_avg_vllm
      threshold: "20"
      query: |
        avg(ht_gpu_usage{pod=~"vllm-.*", namespace="tenant-a"})

 

这个配置表达的含义是:KEDA 定期查询 Prometheus 中的 ht_gpu_usage 指标,计算 tenant-a 命名空间下 vllm-* 相关 Pod 的平均 GPU 利用率;当查询结果超过阈值时,由 KEDA 生成并驱动对应的 HPA,对 LeaderWorkerSet/vllm 进行扩容。配置生效后,可以通过下面命令确认 KEDA 是否已经创建 HPA,通常可以看到类似下面的对象:

kubectl get hpa -n tenant-a
NAME                         REFERENCE              TARGETS      MINPODS   MAXPODS   REPLICAS
keda-hpa-vllm-gpu-scaler     LeaderWorkerSet/vllm   0/20 (avg)   1         3         1

 

也可以查看 ScaledObject 状态:

kubectl get scaledobject vllm-gpu-scaler -n tenant-a

 

如果 READY=True,说明 KEDA 已经能够识别扩缩容目标和触发器;如果 ACTIVE=True,说明当前触发器指标已经达到激活条件。在压测过程中,当 Prometheus 查询到的 GPU 利用率持续超过阈值时,KEDA 会通过 HPA 推动此样例中 LWS 扩容。

如果平台已经大量使用 Prometheus,并且希望用 ScaledObject 这种更声明式的方式集中管理扩缩容规则,KEDA 是一个很好的选择。它的优势在于 scaler 生态丰富、接入外部事件源方便、天然支持 scale-to-zero,并且可以与 HPA 保持协同。但如果只是希望把 Prometheus 指标以 Kubernetes Custom Metrics API 的形式提供给多个 HPA 复用,Prometheus Adapter 的方式会更直接。

12. 自动扩缩容指标的选择建议

通过这次实践,可以得出一个比较清晰的结论:AI 推理服务的自动扩缩容指标,不能简单照搬传统 Web 服务的 CPU/内存模型。更合理的指标分工如下:

CPU / Memory:
用于基础资源观测和普通服务 HPA,不适合作为 LLM 推理主指标。

GPU Utilization:
可以作为实时计算压力参考,但可能受 batch、prefill、decode 阶段影响。

VRAM / KV Cache:
适合作为容量压力与风险告警,不建议单独作为 HPA 主触发指标。

vLLM waiting requests / SGLang queue requests:
非常适合作为扩容信号,因为它直接反映请求已经开始排队。

InferencePool average queue:
适合作为网关或推理池层面的扩容信号,可以反映整体模型服务池压力。

TTFT / TPOT:
适合做 SLA 和用户体验观测,也可以用于告警,但作为 HPA 指标需要谨慎,避免响应过慢。

 

如果要给一个生产级建议,可以采用“主指标 + 辅助指标 + 告警指标”的方式:

主扩容指标:
InferencePool queue / vLLM waiting requests

辅助扩容指标:
GPU utilization / KV Cache usage

告警指标:
TTFT p95 / TPOT p95 / VRAM 高水位 / 请求失败率

 

这样做的好处是,HPA 不会因为显存常驻高位而误扩容,也不会等到用户体验已经明显下降后才扩容,而是尽量在排队刚出现时提前响应。因此,在本文的实践体系中,可以把两者理解为两条不同路径:

Prometheus Adapter 路径:
Prometheus
  → Prometheus Adapter
  → custom.metrics.k8s.io
  → HPA
  → Deployment / LWS

KEDA 路径:
Prometheus / 外部事件源
  → KEDA Scaler
  → KEDA Metrics Adapter
  → HPA
  → Deployment / LWS

 

两条路径并不是谁替代谁,而是适合不同的使用习惯和平台治理方式。Prometheus Adapter 更贴近 Kubernetes 自定义指标管道;KEDA 更偏事件驱动和声明式触发器管理。

对于 AI 推理平台来说,真正重要的是:无论选择哪条路径,都要让扩缩容基于真实推理压力,而不是只依赖 CPU / Memory 这类传统资源指标。

Leave a Reply