一、项目背景:为什么需要K8s持久化存储vLLM?

说实话,我第一次在Kubernetes上跑vLLM推理服务时,踩了不少坑。那时候模型文件直接放在Pod里,每次重启都要重新下载,一个70B的模型动辄上百GB,下载时间够我喝三杯咖啡了。

你想想看,大语言模型推理和传统应用最大的区别在哪?模型文件太大了。我见过不少团队把模型塞进容器镜像里,结果镜像体积直奔200GB,拉取一次镜像能把集群带宽打满。这显然不是长久之计。

核心痛点:模型文件的管理困境

我个人习惯把问题拆成三个维度来看:

  • 存储维度:模型文件动辄几十GB到几百GB,Pod重启就得重新加载,效率极低
  • 共享维度:多个推理副本需要读取同一份模型,总不能每个副本都存一份吧?
  • 版本维度:模型迭代频繁,如何优雅地切换版本而不中断服务?

我在项目中遇到过最典型的情况:团队用StatefulSet部署vLLM,每个Pod挂载自己的EmptyDir。结果模型更新时,需要逐个Pod重建,整个过程耗时半小时以上。说白了,这就是没有持久化存储的锅。

核心结论:K8s持久化存储是vLLM推理服务的基础设施,不是可选项,而是必选项。

为什么是vLLM?

vLLM这个框架,我用了快两年了。它最大的特点是PagedAttention机制,能把显存利用率提升到90%以上。但再好的推理引擎,也得有稳定的模型加载方式。

vLLM启动时需要加载完整的模型权重,这个过程对I/O要求很高。如果存储性能跟不上,模型加载时间会直接拖慢Pod的就绪时间。嗯,这里要注意:K8s的健康检查如果配置不当,Pod可能因为加载超时被反复重启。

二、整体架构设计:组件与数据流

下面这张图是我在实际项目中沉淀下来的架构。它解决了一个核心问题:模型文件如何高效、可靠地在K8s集群中流转

K8s持久化存储vLLM架构图 模型仓库层 HuggingFace Hub MinIO / S3 对象存储 NFS / 分布式文件系统 K8s持久化存储层 PV / PVC 声明式管理 StorageClass 动态供给 ReadOnlyMany 共享模式 vLLM推理服务层 Deployment / StatefulSet Pod挂载PVC读取模型 HPA自动扩缩容 流量接入层 Service / Ingress 暴露API 负载均衡 + 灰度发布

组件说明:每一层都在做什么

这张图分四层,我按数据流向从上往下讲:

1. 模型仓库层

模型文件的源头。可以是HuggingFace Hub、自建的MinIO对象存储,或者NFS服务器。我个人推荐用MinIO,因为它兼容S3协议,而且部署在K8s内部,延迟更低。

我的经验:模型文件建议先下载到本地,再上传到MinIO。直接让Pod从HuggingFace拉取,网络波动会让你怀疑人生。

2. 持久化存储层

这是K8s的核心能力。通过PV和PVC,我们把模型文件挂载到Pod里。关键参数是accessModes: ReadOnlyMany,这样多个推理副本可以共享同一份模型数据。

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: vllm-model-pvc
spec:
  accessModes:
    - ReadOnlyMany
  resources:
    requests:
      storage: 200Gi
  storageClassName: nfs-client

StorageClass这里我建议用NFS或者CephFS。为什么?因为它们支持ReadOnlyMany模式。你想想看,如果每个Pod都复制一份模型,200GB的模型复制10份就是2TB,太浪费了。

3. vLLM推理服务层

Pod启动时,通过volume挂载PVC,模型文件就直接出现在容器里了。vLLM启动命令也很简单:

vllm serve /mnt/models/llama-70b \
  --tensor-parallel-size 4 \
  --gpu-memory-utilization 0.9 \
  --max-model-len 4096

这里有个坑:--tensor-parallel-size要跟GPU数量匹配。我曾经遇到过有人设成8,但节点只有4张卡,结果OOM了。

4. 流量接入层

Service把vLLM的API暴露出去,Ingress做域名和TLS终结。我习惯用Nginx Ingress Controller,配合nginx.ingress.kubernetes.io/proxy-body-size: "0"注解,避免请求体被截断。

三、避坑指南:我踩过的那些坑

讲几个真实案例,希望能帮你少走弯路:

坑一:PVC权限问题
我曾经把PVC配成ReadWriteOnce,结果第二个Pod启动时一直Pending。排查了半天才发现是accessModes的问题。记住:多副本共享模型,必须用ReadOnlyMany。
坑二:模型加载超时
70B模型加载需要5-8分钟,但K8s默认的liveness probe是3秒间隔、10次失败就重启。结果Pod一直在重启循环。解决方案:把initialDelaySeconds设成600秒。
坑三:存储性能瓶颈
用NFS挂载时,如果网络带宽不够,模型加载会非常慢。我后来改用Local PV + 节点亲和性,把模型固定在特定节点上,加载时间从15分钟降到了2分钟。

四、总结:架构设计的核心原则

说了这么多,其实就三个原则:

  • 存储与计算分离:模型文件独立于Pod生命周期,Pod挂了模型还在
  • 共享而非复制:一份模型数据,多个推理副本共享,节省存储空间
  • 声明式管理:用PVC和StorageClass描述存储需求,K8s自动搞定底层细节

这套架构我在生产环境跑了半年多,支撑了日均百万级的推理请求。说白了,持久化存储不是锦上添花,而是让vLLM推理服务真正可运维的基础。

一句话总结:没有持久化存储的vLLM,就像没有地基的房子——看着能用,但随时可能塌。

公众号:蓝海资料掘金营,微信deep3321