基于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_waiting、sglang:num_queue_reqs、inference_pool_average_queue_size、inference_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.io 或 external.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 级自定义指标,就必须确认该指标中带有 namespace 和 pod 标签。否则 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 已经具备 namespace 和 pod 标签,因此可以映射为 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/pod 和 exported_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-utilization、max-model-len、max-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