本节导读:从开发机的
vllm serve到支撑核心业务的生产服务,中间隔着K8s编排、弹性伸缩、可观测性与高可用四道关。本节给出一套可直接落地的企业级部署参考架构。
| 维度 | 开发环境 | 生产环境 |
|---|---|---|
| 进程管理 | 手动启动 | 容器编排、自动重启 |
| 扩缩容 | 无 | 按负载自动伸缩 |
| 可观测 | 看日志 | 指标+告警+链路追踪 |
| 发布 | 停机换模型 | 滚动升级不中断 |
| 容量 | 单实例 | 多副本+过载保护 |
客户端 → 网关(限流/鉴权/会话亲和) → Ingress → Service → vLLM Pod×N (K8s Deployment) → Prometheus ← /metrics采集 → Grafana/告警
网关层承担限流与熔断——推理服务过载时返回429排队信息,远好于让请求在服务内部雪崩。
apiVersion: apps/v1 kind: Deployment metadata: name: vllm-qwen spec: replicas: 2 strategy: rollingUpdate: {maxSurge: 1, maxUnavailable: 0} selector: matchLabels: {app: vllm-qwen} template: metadata: labels: {app: vllm-qwen} annotations: prometheus.io/scrape: "true" prometheus.io/port: "8000" spec: containers: - name: vllm image: vllm/vllm-openai:v0.9.1 args: - --model=Qwen/Qwen2.5-7B-Instruct-AWQ - --quantization=awq - --max-model-len=8192 - --gpu-memory-utilization=0.9 resources: requests: {nvidia.com/gpu: 1, memory: 32Gi, cpu: 8} limits: {nvidia.com/gpu: 1, memory: 48Gi} readinessProbe: httpGet: {path: /health, port: 8000} initialDelaySeconds: 120 # 模型加载慢,给足启动时间 periodSeconds: 10 volumeMounts: - {name: shm, mountPath: /dev/shm} - {name: model-cache, mountPath: /root/.cache/huggingface} volumes: - name: shm emptyDir: {medium: Memory, sizeLimit: 16Gi} - name: model-cache persistentVolumeClaim: {claimName: hf-cache}
三个关键点:maxUnavailable: 0 保证滚动升级不断流;共享内存用Memory型emptyDir(NCCL/PyTorch依赖大shm);模型缓存挂PVC避免每个副本重复下载。
vLLM的负载特征是"GPU已满载但请求还在堆积",必须用应用指标而非CPU/GPU利用率驱动伸缩:
apiVersion: autoscaling/v2 kind: HorizontalPodAutoscaler metadata: name: vllm-qwen-hpa spec: scaleTargetRef: {apiVersion: apps/v1, kind: Deployment, name: vllm-qwen} minReplicas: 2 maxReplicas: 8 metrics: - type: Pods pods: metric: {name: vllm:num_requests_waiting} target: {type: AverageValue, averageValue: "5"} behavior: scaleUp: stabilizationWindowSeconds: 60 # 推理Pod启动含模型加载,伸缩要保守
配套Prometheus Adapter把 /metrics 的 num_requests_waiting 暴露为自定义指标。缩容稳定窗口建议不小于300秒,防止抖动。
# PrometheusRule核心告警项 - alert: VLLMHighP99TTFT expr: histogram_quantile(0.99, rate(vllm:time_to_first_token_seconds_bucket[5m])) > 3 for: 5m - alert: VLLMPrefixCacheHitRateLow expr: rate(vllm:gpu_prefix_cache_hits_total[10m]) / rate(vllm:gpu_prefix_cache_queries_total[10m]) < 0.2 for: 15m - alert: VLLMPreemptionStorm expr: rate(vllm:preemptions_total_total[5m]) > 1 for: 5m
大盘四象限:延迟(TTFT/TPOT的P50/P99)、吞吐(token/s、RPS)、队列(running/waiting/preemption)、资源(GPU利用率、显存、KV Cache使用率)。缓存命中率异常下跌往往意味着上游提示词结构被改坏,是极其灵敏的业务回归探测器。
模型即制品:CI构建镜像时把模型权重打进镜像(或固定PVC版本),kubectl set image 触发滚动升级。maxUnavailable: 0 + readinessProbe保证新副本就绪前旧副本持续服务。升级后用5%流量的小流量验证(网关按权重分流)再全量。
# 网关层限流+熔断 limit_req_zone $binary_remote_addr zone=llm:10m rate=5r/s; location /v1/ { limit_req zone=llm burst=20 nodelay; proxy_read_timeout 300s; # 长生成需要放宽 proxy_next_upstream error timeout; # 5xx自动换副本 }
容量公式:峰值副本数 = 峰值RPS × 单请求平均耗时 / 单副本并发能力 × 安全系数(1.5)。宁可让网关拒绝流量并提示排队,也不要让全部请求在引擎内部超时——前者是可控降级,后者是不可控雪崩。
业务:智能客服,峰值200 RPS,平均输入1.5K/输出300 token。
Q1:Pod启动健康检查失败被反复重启?
模型加载动辄数分钟,readinessProbe的 initialDelaySeconds 与失败阈值要覆盖最慢加载路径。用 /health(进程存活)做readiness已足够;不要用Liveness探测业务接口,模型加载中会被误杀。
Q2:HPA为什么不用GPU利用率做指标?
推理服务GPU利用率长期高位(这是好事),无法区分"忙而不堵"与"排队严重"。waiting队列深度直接反映用户体感,才是正确的伸缩信号。
Q3:多副本如何共享模型缓存?
PVC以ReadOnlyMany挂载给全部副本,首次下载由独立Job完成。跨节点场景用EFS/NFS类存储时注意随机读性能,必要时权衡"每节点本地缓存"。
Q4:滚动升级时前缀缓存全失效,怎么缓解?
新副本冷启动必然缓存为空。做法:①升级窗口选低峰;②新副本就绪后由预热Job灌入标准模板(见3.3节);③网关对新副本渐进放量而非瞬时全量。
Q5:GPU节点池混部不同型号卡,怎么处理?
同一Deployment内型号不一致会导致性能与显存特征不同。按GPU型号拆分两组Deployment(节点选择器+污点隔离),网关按容量权重分流,监控按组观察。
企业级部署的核心是把vLLM纳入标准云原生体系:K8s管生命周期、HPA按队列深度伸缩、Prometheus四象限监控、网关做限流与灰度。其中最容易被忽视的两点是"伸缩要看排队而非GPU"与"过载保护要在网关层做"。下一节深入引擎内部,做最后的性能深度调优。