
AI全栈知识14GPU资源弹缩实战 - 从HPA到Cluster Autoscaler写在前面GPU贵。一张T4按量付费大概10-30元/小时不同云厂商价格不同A100更是几十上百。如果你的AI推理服务24小时跑着但实际只有白天8小时有流量那16个小时的钱就白花了。怎么办让GPU资源跟着流量走忙的时候自动加机器闲的时候自动减机器。这就是弹缩。但GPU弹缩不是简单把CPU弹缩的配置复制过来就行。GPU节点启动慢、成本高、缩容有风险需要特殊处理。这篇帮你搞定GPU场景下的完整弹缩方案。为什么GPU资源必须做弹缩算笔账场景一个AI推理服务用一张T4 GPU 不做弹缩24小时运行 10元/小时 × 24小时 × 30天 7200元/月 做弹缩只在有流量时运行白天10小时 10元/小时 × 10小时 × 30天 3000元/月 节省4200元/月58%如果用竞价实例打3-4折 弹缩成本还能再降竞价实例3折 弹缩10小时 3元/小时 × 10小时 × 30天 900元/月 相比全天按量付费节省87%不做弹缩 烧钱。GPU越贵弹缩价值越大。K8s弹缩体系两层架构在K8s中弹缩分两层第一层HPAPod水平自动伸缩作用自动调整Pod副本数。流量大了加Pod流量小了减Pod。流量增加 → HPA检测到指标超阈值 → 自动增加Pod数量 流量减少 → HPA检测到指标低于阈值 → 自动减少Pod数量生活类比超市收银台。顾客多了多开几个窗口顾客少了关掉几个窗口。第二层Cluster Autoscaler节点自动伸缩作用自动调整节点数量。Pod创建出来但没地方跑Pending就自动加节点。节点空了就自动删。HPA想加Pod → 但集群没有空余GPU节点 → Pod处于Pending状态 Cluster Autoscaler检测到Pending Pod → 向云厂商申请新GPU节点 新节点Ready → Pod调度上去 → 服务恢复生活类比停车场。车位满了就扩建新的停车层。空了就关掉省电费。两层协同工作用户请求增加 ↓ HPA推理队列变长了需要从2个Pod扩到4个 ↓ 调度器目前只有1张GPU卡只能跑2个Pod剩下2个Pod Pending ↓ Cluster Autoscaler检测到Pending申请新的GPU节点 ↓ 云厂商分配一台T4实例加入集群 ↓ 调度器把Pending的Pod调度到新节点 ↓ 服务恢复正常 --- 用户请求减少 ↓ HPA队列空了从4个Pod缩到2个 ↓ 新节点上的Pod被删掉了节点空闲 ↓ Cluster Autoscaler检测到节点空闲超过10分钟删除节点 ↓ 省钱了HPA基础怎么配置Pod自动伸缩最简单的HPA基于CPUapiVersion:autoscaling/v2kind:HorizontalPodAutoscalermetadata:name:my-app-hpaspec:scaleTargetRef:apiVersion:apps/v1kind:Deploymentname:my-appminReplicas:1maxReplicas:10metrics:-type:Resourceresource:name:cputarget:type:UtilizationaverageUtilization:70# CPU使用率超过70%就扩意思是最少1个Pod最多10个当所有Pod的平均CPU使用率超过70%就加Pod低于70%就减Pod这对GPU推理服务够用吗不够。因为问题原因GPU推理服务的CPU使用率通常很低瓶颈在GPU不在CPUCPU没超70%但用户已经在排队了GPU满了请求堆积需要看的是GPU相关指标CPU利用率反映不了真实负载GPU推理服务应该用什么指标做HPA可选指标对比指标来源优点缺点GPU利用率DCGM Exporter直观波动大不够灵敏GPU显存使用率DCGM Exporter好理解模型加载后就是固定值没意义等待中的请求数vLLM暴露的指标最能反映真实排队情况需要推理框架支持每秒处理请求数(QPS)自定义反映吞吐量不同请求长度不同QPS不准请求延迟P99自定义反映用户体验滞后指标等慢了才扩来不及最佳实践用推理队列长度vLLM暴露了一个关键指标vllm:num_requests_waiting # 正在等待处理的请求数逻辑队列里有人在等 当前Pod处理不过来 该加Pod了。这是领先指标leading indicator能提前感知压力比延迟已经变高了的滞后指标好。实战配置GPU推理服务的HPA前置条件要用自定义指标做HPA需要安装1. Prometheus采集指标 2. Prometheus Adapter把Prometheus指标转成K8s metrics API 3. 推理服务暴露指标vLLM自带/metrics端口架构vLLM Pod --暴露指标-- Prometheus --采集-- Prometheus Adapter --转换-- K8s Metrics API --供给-- HPA第一步确认vLLM暴露了指标vLLM启动后默认在端口暴露Prometheus格式的指标# 验证指标是否存在curlhttp://vllm-service:8000/metrics|grepnum_requests_waiting输出类似vllm:num_requests_waiting 3第二步配置Prometheus采集# prometheus-scrape-config-job_name:vllmmetrics_path:/metricsstatic_configs:-targets:[vllm-service:8000]第三步配置Prometheus Adapter# prometheus-adapter-config.yamlapiVersion:v1kind:ConfigMapmetadata:name:prometheus-adapter-configdata:config.yaml:|rules: - seriesQuery: vllm:num_requests_waiting resources: overrides: namespace: {resource: namespace} pod: {resource: pod} name: matches: ^(.*)$ as: vllm_requests_waiting metricsQuery: sum(vllm:num_requests_waiting{.LabelMatchers}) by (.GroupBy)第四步配置HPAapiVersion:autoscaling/v2kind:HorizontalPodAutoscalermetadata:name:vllm-hpaspec:scaleTargetRef:apiVersion:apps/v1kind:Deploymentname:vllm-inferenceminReplicas:1maxReplicas:5metrics:-type:Podspods:metric:name:vllm_requests_waitingtarget:type:AverageValueaverageValue:5# 每个Pod平均等待超过5个请求就扩behavior:scaleUp:stabilizationWindowSeconds:30# 扩容等30秒确认避免抖动policies:-type:Podsvalue:2# 每次最多加2个PodperiodSeconds:60scaleDown:stabilizationWindowSeconds:300# 缩容等5分钟确认避免缩了又要扩policies:-type:Podsvalue:1# 每次最多减1个PodperiodSeconds:120关键配置解释配置值为什么这样设minReplicas1至少保持一个Pod避免冷启动maxReplicas5控制最大成本averageValue5每个Pod平均等5个请求就扩容scaleUp stabilization30sGPU Pod启动慢别频繁抖动scaleDown stabilization300s缩容要谨慎等5分钟确认真不需要了scaleDown value1每次只缩1个避免一下子缩太多Cluster Autoscaler自动加减GPU节点为什么需要节点级别弹缩HPA只能加Pod。但如果集群里没有空闲GPU节点了Pod就会一直Pending情况 当前集群1台T4节点已经跑了1个vLLM Pod占满GPU HPA要加Pod → 新Pod需要GPU → 没有空余GPU → Pending 如果没有Cluster Autoscaler 新Pod一直Pending → 用户一直等 → 直到运维手动加节点 有Cluster Autoscaler 检测到Pending Pod → 自动申请新T4节点 → Pod调度上去 → 自动恢复配置示例AWS EKS# cluster-autoscaler配置节点组apiVersion:eksctl.io/v1alpha5kind:ClusterConfigmetadata:name:ai-clusterregion:us-east-1managedNodeGroups:# 固定的CPU节点组不弹缩-name:cpu-nodesinstanceType:t3.mediumdesiredCapacity:2minSize:2maxSize:2# 可弹缩的GPU节点组-name:gpu-nodesinstanceType:g4dn.xlarge# T4 GPUdesiredCapacity:1minSize:0# 可以缩到0完全没流量时不花钱maxSize:4# 最多4台labels:gpu:truetaints:-key:nvidia.com/gpuvalue:trueeffect:NoSchedule# 只有需要GPU的Pod才调度到这里关键点minSize: 0? 完全没流量时GPU节点缩到0台一分钱不花maxSize: 4? 设上限防止失控taint ? 防止普通Pod跑到GPU节点上占资源阿里云ACK配置# 阿里云节点池配置节点池名称gpu-autoscale-pool 实例类型ecs.gn6i-c4g1.xlargeT4 付费模式按量付费或竞价实例 最小节点数0 最大节点数4 扩容策略当有Pod因GPU不足Pending时自动扩容 缩容策略节点空闲10分钟后自动缩容GPU弹缩的特殊问题问题一GPU节点启动慢阶段CPU节点GPU节点云厂商分配实例30-60秒30-60秒节点加入集群30秒30秒拉取镜像10秒镜像小2-5分钟GPU镜像大几个GB模型加载不需要1-3分钟把模型加载到GPU显存总计1-2分钟4-10分钟从扩容触发到Pod真正能服务可能要5-10分钟解决方案方案做法适合保留最小副本minReplicas1不缩到0有基础流量的服务预热节点保留一台空闲GPU节点常驻对延迟敏感的服务提前扩容用更敏感的指标queue2就扩高峰可预测的场景定时扩容CronHPA每天早9点提前扩流量有规律的服务CronHPA示例定时扩容apiVersion:autoscaling.alibabacloud.com/v1beta1kind:CronHorizontalPodAutoscalermetadata:name:vllm-cron-hpaspec:scaleTargetRef:apiVersion:apps/v1kind:Deploymentname:vllm-inferencejobs:-name:morning-scale-upschedule:0 8 * * 1-5# 工作日早8点targetSize:3# 提前扩到3个Pod-name:evening-scale-downschedule:0 20 * * 1-5# 工作日晚8点targetSize:1# 缩回1个Pod-name:weekend-minimumschedule:0 0 * * 0,6# 周末targetSize:1# 保持最小问题二缩容时正在推理怎么办缩容时如果Pod正在处理请求直接杀掉会导致请求失败。解决方案优雅终止Graceful Shutdown# Deployment配置spec:template:spec:terminationGracePeriodSeconds:60# 给60秒处理完在手的请求containers:-name:vllmlifecycle:preStop:exec:command:-/bin/sh--c-sleep 10 kill -SIGTERM 1# 先等10秒让新请求去别的Pod同时配合PDBPod Disruption BudgetapiVersion:policy/v1kind:PodDisruptionBudgetmetadata:name:vllm-pdbspec:minAvailable:1# 缩容时至少保留1个Pod在运行selector:matchLabels:app:vllm-inference问题三扩容抖动流量刚超阈值就扩刚降下来就缩反复折腾。解决方案设置稳定窗口behavior:scaleUp:stabilizationWindowSeconds:60# 超阈值持续60秒才扩scaleDown:stabilizationWindowSeconds:300# 低于阈值持续5分钟才缩经验值扩容窗口短一些30-60秒快速响应缩容窗口长一些5-10分钟避免刚缩了又要扩竞价实例GPU省钱利器什么是竞价实例云厂商把空闲的GPU实例打折卖通常3-4折但随时可能被回收提前2分钟通知。对比按量付费竞价实例价格原价3-4折稳定性不会被回收可能被回收2分钟通知适合基础负载不能断弹性负载断了有兜底混合策略基础负载1台按量付费的GPU节点稳定不会被回收 弹性负载0-3台竞价实例的GPU节点便宜被回收了也没关系为什么竞价实例被回收也没关系因为有多个Pod Cluster Autoscaler兜底竞价实例被回收 → 上面的Pod被驱逐Pod变成Pending → Cluster Autoscaler申请新的竞价实例新实例到了 → Pod重新调度上去期间其他Pod继续服务有按量付费的兜底AWS配置示例# 混合节点组managedNodeGroups:# 基础节点按量稳定-name:gpu-ondemandinstanceType:g4dn.xlargedesiredCapacity:1minSize:1maxSize:1capacityType:ON_DEMAND# 弹性节点竞价便宜-name:gpu-spotinstanceTypes:-g4dn.xlarge-g4dn.2xlarge# 多选几种提高竞价成功率desiredCapacity:0minSize:0maxSize:3capacityType:SPOTlabels:capacity-type:spot阿里云竞价实例配置节点池gpu-spot-pool 实例类型ecs.gn6i-c4g1.xlargeT4 付费模式抢占式实例竞价 保护期1小时创建后1小时内不会被回收 最大价格按量付费的40% 最小节点数0 最大节点数3成本对比方案A全按量付费2台GPU24小时 10元 × 2台 × 24h × 30天 14400元/月 方案B1台按量 弹缩竞价白天10小时平均多1台 按量10元 × 1台 × 24h × 30天 7200元 竞价3元 × 1台 × 10h × 30天 900元 总计8100元/月 节省6300元/月44%完整架构图用户请求 ↓ Ingress / Load Balancer ↓ vLLM Pod × NN由HPA控制 ↓ ↑ GPU Node Pool Cluster Autoscaler 按量1台 竞价0-3台 检测Pending Pod自动加节点 ↓ Prometheus采集vLLM指标queue_length、latency等 ↓ HPA基于queue_length调整Pod数量监控和告警弹缩做好了还要能看到运行状态监控指标说明告警条件Pod副本数当前有几个Pod在跑达到maxReplicas扛不住了Pending Pod数有几个Pod在等GPU节点持续Pending超过5分钟节点数量变化今天扩了几次缩了几次频繁扩缩抖动竞价实例回收事件有没有被云厂商回收短时间多次回收每日GPU成本今天花了多少钱超预算80%推理队列长度当前排队的请求数持续10扩容可能有问题Grafana面板建议第一行Pod数量趋势 节点数量趋势看弹缩是否正常 第二行推理队列长度 请求延迟P99看用户体验 第三行GPU利用率 显存使用率看资源效率 第四行每小时成本 竞价实例回收次数看钱面试怎么说如果被问GPU资源怎么做弹缩GPU弹缩分两层Pod级别用HPA节点级别用Cluster Autoscaler。HPA指标不能用CPU利用率GPU推理服务CPU利用率很低要用推理队列长度。vLLM暴露了num_requests_waiting指标队列超过阈值就扩Pod。Cluster Autoscaler负责在Pod因为没有GPU节点而Pending时自动向云厂商申请新节点。我会设minSize0让完全无流量时GPU节点缩到零。GPU弹缩有几个特殊处理一是启动慢5-10分钟所以用CronHPA提前扩容或保留预热节点。二是缩容要优雅terminationGracePeriodPDB避免杀掉正在推理的Pod。三是用竞价实例省钱基础负载按量付费弹性部分用竞价3-4折被回收了Cluster Autoscaler会自动补充。整套方案在我的集群上大约节省了40-50%的GPU成本。延伸思考问题答案没有K8s能做GPU弹缩吗可以用云厂商的弹性伸缩组Auto Scaling Group但没K8s灵活竞价实例真的会被回收吗高峰期会。T4相对稳定A100竞价更容易被回收缩到0台后第一个请求怎么办会等5-10分钟。如果不能接受minSize设为1保留一台多个模型共享GPU怎么弹缩用NVIDIA MIG或时间片共享HPA按每个模型独立配置弹缩和成本哪个优先先保证用户体验不排队再优化成本。宁可多花点钱也别让用户等太久小结本篇核心收获GPU必须弹缩不做弹缩 烧钱。弹缩竞价可省40-80%成本两层弹缩HPA管Pod数量Cluster Autoscaler管节点数量GPU的HPA指标不用CPU利用率用推理队列长度vllm:num_requests_waitingGPU启动慢5-10分钟用CronHPA提前扩或保留预热节点应对缩容要优雅terminationGracePeriod PDB避免杀掉正在推理的请求竞价实例省钱基础按量弹性竞价的混合策略被回收有兜底下一篇预告AI全栈知识15AI应用的可观测性 - Token监控与成本控制下一篇进入可观测性领域Token使用量统计和分析AI调用链追踪成本归因哪个团队/功能花了多少Token异常检测突然Token暴涨怎么发现参考链接K8s HPA官方文档Cluster AutoscalerPrometheus AdapterAWS EKS Spot实例最佳实践vLLM Metrics文档