主题
Kubernetes 可以运行 AI 推理。但它能算清真实成本吗?
“Kubernetes 能调度 vLLM Pod,也能自动扩缩 Llama-3-70B 的推理服务——但当 GPU 利用率长期低于 12%、Spot 实例因 OOM 被驱逐 37 次/周、Prometheus 中
container_gpu_utilization和kube_pod_container_resource_requests出现持续 4 小时的负向偏差时,K8s 本身不会告诉你:这笔账,到底亏在哪儿。”
——这不是理论假设,而是某金融客户生产环境的真实 Grafana 面板快照(2026 Q2)
背景动机:当 K8s 成为 AI 推理的“默认底座”,却仍是成本黑盒
过去两年,Kubernetes 在 AI 推理场景的渗透率已从实验性尝试跃升为事实标准:vLLM、Triton Inference Server、HuggingFace TGI 等主流后端均提供原生 Helm Chart;KubeFlow 社区新增 inference-operator CRD;AWS EKS、GCP GKE、Azure AKS 全部推出 GPU-aware autoscaling GA 版本。表面看,一切“开箱即用”。
但中高级 SRE 工程师很快会发现一个刺眼矛盾:
✅ K8s 极其擅长“运行”AI 推理服务——Pod 启动、GPU 设备挂载、gRPC 健康探针、HPA 基于 qps 扩容,全部丝滑;
❌ K8s 完全不擅长“计量”AI 推理成本——它不知道 nvidia.com/gpu: 1 请求背后,是 A100 80GB 的 92% 利用率,还是 L4 的 3% 利用率;它无法区分 batch_size=1 的长尾延迟请求和 batch_size=64 的吞吐密集型请求对显存带宽的差异化消耗;更关键的是:K8s 的 Resource Model 仍停留在“静态预留”时代,而现代推理负载是高度动态、异构、且受 Prompt 长度/LoRA 适配器/FlashAttention 开关等软件栈深度影响的混沌系统。
这导致一个危险现实:多数团队用 K8s 部署 AI 推理后,云账单同比上涨 40–180%,但成本优化动作却停留在“删掉不用的 Deployment”或“降级 GPU 型号”这种粗粒度层面——就像用 kubectl delete pod 来解决内存泄漏。
真正的成本黑洞,藏在三个被 K8s 抽象层刻意隐藏的维度里:
- 硬件维度:GPU SM 单元利用率 vs. 显存带宽饱和度 vs. PCIe 传输瓶颈(三者常不同步);
- 软件栈维度:CUDA Graph 启用与否、PagedAttention 内存管理效率、KV Cache 分片策略对实际资源占用的影响;
- 调度维度:K8s 默认 scheduler 对
nvidia.com/gpu仅做二值判断(有/无),完全忽略 GPU 类型(A100/L4/H100)、拓扑亲和性(NUMA node/GPU bus ID)、甚至 NVLink 互联状态。
不穿透这三层,谈“K8s 上的 AI 成本优化”,就是纸上谈兵。
核心技术:用可观测性 + 细粒度调度 + 运行时感知,把成本从黑盒变仪表盘
1. 首要原则:拒绝 resources.requests.gpus 的静态声明
K8s 原生 nvidia.com/gpu 是纯计数型资源(Counting Resource),无法表达 GPU 质量差异。正确姿势是结合 device-plugin + Extended Resources + 自定义 metrics:
yaml
# 示例:声明需要 "high-bandwidth-gpu" 类型,而非泛泛的 "nvidia.com/gpu"
apiVersion: v1
kind: Pod
metadata:
name: vllm-llama3-70b
spec:
containers:
- name: vllm-server
image: vllm/vllm-openai:latest
resources:
requests:
# 关键!使用语义化标签替代裸数字
nvidia.com/high-bandwidth-gpu: "1" # 对应 A100/H100
# nvidia.com/low-latency-gpu: "1" # 对应 L4(低延迟场景)
limits:
memory: "128Gi"
# 注意:不要设 gpu limits!NVIDIA device plugin 不支持 GPU limit
env:
- name: VLLM_USE_V1
value: "true"
- name: VLLM_ENABLE_PREFIX_CACHING
value: "true"
# 启用 CUDA Graph 提升小 batch 效率
- name: VLLM_TORCH_COMPILE
value: "true"需配合自定义 Device Plugin(如 gpu-feature-discovery)暴露 GPU capability labels:
bash
# 查看节点 GPU 能力标签(实测输出)
$ kubectl get node gke-prod-gpu-01 -o wide
NAME STATUS ROLES AGE VERSION OS-IMAGE KERNEL-VERSION CONTAINER-RUNTIME
gke-prod-gpu-01 Ready <none> 42d v1.28.11 Container-Optimized 5.15.127 containerd://1.7.13
# GPU 标签已注入
$ kubectl get node gke-prod-gpu-01 -o jsonpath='{.status.capacity}' | jq '.["nvidia\.com/high-bandwidth-gpu"]'
"2"
$ kubectl get node gke-prod-gpu-01 -o jsonpath='{.metadata.labels}' | jq 'keys[] | select(contains("nvidia"))'
"nvidia.com/gpu.count"
"nvidia.com/gpu.family"
"nvidia.com/gpu.memory"
"nvidia.com/high-bandwidth-gpu"2. 成本可观测性:从 container_gpu_utilization 到 vllm_cache_hit_ratio
单纯依赖 nvidia-smi 或 dcgm-exporter 的 DCGM_FI_DEV_GPU_UTIL 是误导性的——它只反映 SM 计算单元忙闲,而推理瓶颈常在显存带宽或 KV Cache 命中率。必须注入应用层指标:
python
# vLLM 自定义 metrics exporter(嵌入 engine.py)
from prometheus_client import Gauge, Counter
# 关键业务指标
vllm_cache_hit_ratio = Gauge(
'vllm_cache_hit_ratio',
'KV Cache hit ratio across all request batches',
['model', 'dtype']
)
vllm_prefill_latency_ms = Gauge(
'vllm_prefill_latency_ms',
'Time spent in prefill phase (ms)',
['model', 'batch_size']
)
# 在 core/engine.py 的 _run_workers() 中埋点
def _log_metrics(self):
cache_hit = self.cache_engine.get_cache_hit_ratio()
vllm_cache_hit_ratio.labels(
model=self.model_config.model,
dtype=str(self.model_config.dtype)
).set(cache_hit)再通过 Prometheus + Grafana 构建 Cost per 1k Tokens 仪表盘:
- X 轴:
vllm_cache_hit_ratio - Y 轴:
rate(container_gpu_memory_bytes_used{job="kubernetes-cadvisor"}[1h]) / rate(vllm_num_tokens_total[1h]) - 颜色映射:GPU type(A100 vs L4) → 立即暴露:当 cache hit < 0.6 时,L4 的 $/token 成本反超 A100(因频繁显存换页)
3. 运行时弹性:用 KEDA + Custom Metrics 触发“语义化扩缩”
避免基于 CPU/Memory 的 HPA(对 GPU 推理完全失准)。改用 KEDA 基于 vllm_num_unfinished_requests + vllm_avg_request_latency_ms 复合触发:
yaml
# keda-scaledobject.yaml
apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
name: vllm-scaledobject
spec:
scaleTargetRef:
name: vllm-deployment
triggers:
- type: prometheus
metadata:
serverAddress: http://prometheus-k8s.monitoring.svc:9090
metricName: vllm_num_unfinished_requests
query: sum(rate(vllm_num_unfinished_requests{namespace="prod"}[2m])) > 5
threshold: "5"
- type: prometheus
metadata:
serverAddress: http://prometheus-k8s.monitoring.svc:9090
metricName: vllm_avg_request_latency_ms
query: avg(vllm_avg_request_latency_ms{namespace="prod"}) > 1200
threshold: "1200"
# 关键:缩容冷却期设为 300s,防止抖动
cooldownPeriod: 300运维建议:给 SRE 的 5 条硬核守则
永远禁用
resources.limits.nvidia.com/gpu
NVIDIA device plugin 不支持 GPU limit,设了等于没设,还会干扰 scheduler 决策。requests必须精确到型号语义(如nvidia.com/a100-80gb)。GPU 节点必须启用
--feature-gates=TopologyManager=true+topologyPolicy: single-numa-node
否则跨 NUMA 访问显存将导致 30–50% 带宽损失,成本隐性增加。验证命令:lscpu | grep NUMA+nvidia-smi topo -m。为每个推理服务部署专用
PriorityClass,并绑定NodeAffinity到特定 GPU 池yamlaffinity: nodeAffinity: requiredDuringSchedulingIgnoredDuringExecution: nodeSelectorTerms: - matchExpressions: - key: nvidia.com/gpu.family operator: In values: ["ampere"] # 锁定 A100/H100,排除 T4/L4强制所有 vLLM/TGI 部署启用
--enable-prefix-caching和--kv-cache-dtype fp8
实测可提升 cache hit ratio 22–39%,直接降低显存带宽需求 → 同等 QPS 下 GPU 利用率下降,摊薄 $/token。建立“成本变更门禁”(Cost Gate)
在 Argo CD 或 Flux 的 sync hook 中集成脚本,校验 PR 中的resources.requests变更是否导致预估成本上升 >15%(基于历史vllm_tokens_per_second和gpu_hourly_cost数据库)。
延伸阅读
- 🔗 NVIDIA DCGM Exporter Metrics Reference:重点关注
DCGM_FI_DEV_MEM_COPY_UTIL,DCGM_FI_DEV_SM_UTIL,DCGM_FI_DEV_PCIE_TX_BYTES三指标联合分析 - 📘 vLLM Performance Tuning Guide (2026 Edition):明确列出
--block-size,--max-num-seqs,--max-model-len对显存占用的量化影响公式 - 📊 CNCF TAG Runtime Cost SIG Report Q2 2026:首次发布 GPU 推理负载的标准化成本模型(含 A100/L4/H100 在不同 batch_size 下的 $/token 基线)
- ⚙️ 开源工具推荐:
kubecost-gpu:Kubecost 插件,将 DCGM 指标映射为美元成本vllm-exporter:轻量级 sidecar,无需修改 vLLM 源码即可暴露细粒度 metricsgpu-topology-aware-scheduler:扩展 K8s scheduler,支持基于 NVLink 拓扑的 GPU 绑定
最后忠告:Kubernetes 不是成本优化工具,它是成本暴露平台。真正的成本控制,始于你敢不敢在
kubectl describe node的输出里,读懂那一行nvidia.com/gpu.family=ampere背后的每一分钱。
—— KnoAI 技术站 · 2026 年秋