在 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 这类传统资源指标。
