3、存储基础:PV与PVC概念、StorageClass动态供给、访问模式

聊到Kubernetes的持久化存储,我脑子里第一个蹦出来的词就是「解耦」。说白了,就是让Pod不用关心底层存储到底长什么样。你想想看,一个应用开发者,他只需要知道「我要存数据」,至于数据是存在NFS上、Ceph里、还是云厂商的EBS上,他根本不想管。Kubernetes的PV和PVC就是干这个事的。

3.1 PV与PVC:存储的供需关系

PV(PersistentVolume)是集群管理员准备的存储资源。它就像仓库里的硬盘,已经接好线、格式化好了,等着被使用。PVC(PersistentVolumeClaim)是用户的存储请求。用户说「我要5GB的快速存储」,PVC就是这个需求单。

我刚开始接触K8s存储时,总觉得PV和PVC的关系有点绕。后来想通了——这不就是租房吗?PV是房东手里的房源,PVC是租客的找房需求。房东把房子挂出来(PV创建),租客提交需求(PVC创建),系统自动匹配,匹配上了就绑定(Bound)。

核心要点:PV和PVC是一对一的绑定关系。一旦绑定,PVC就独占这个PV,直到被删除。

来看一个简单的PV定义:

apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv-example
spec:
  capacity:
    storage: 10Gi
  accessModes:
    - ReadWriteOnce
  persistentVolumeReclaimPolicy: Retain
  storageClassName: manual
  hostPath:
    path: /data/pv-example

对应的PVC:

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: pvc-example
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 5Gi
  storageClassName: manual

这里有个细节——storageClassName必须匹配。如果PV的storageClassNamemanual,PVC也得写manual,否则绑定不上。我曾经踩过这个坑,排查了半天才发现是class名字写错了。

3.2 StorageClass:动态供给的魔法

手动创建PV的方式,在开发环境还行。到了生产环境,几十上百个应用,每个都要手动配PV?那运维得累死。StorageClass就是来解决这个问题的。

StorageClass相当于一个「存储模板」。它告诉K8s:当用户创建PVC时,自动调用存储后端(比如云厂商的API)去创建对应的存储卷。这就是动态供给。

我习惯把StorageClass比作「自动售货机」。用户投币(提交PVC),机器自动吐出饮料(创建PV)。整个过程不需要管理员手动干预。

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: fast-ssd
provisioner: kubernetes.io/aws-ebs
parameters:
  type: gp3
  fsType: ext4
  encrypted: "true"
reclaimPolicy: Delete
allowVolumeExpansion: true

这个例子用的是AWS EBS的provisioner。你换成GCP就用pd.csi.storage.gke.io,换成Azure就用kubernetes.io/azure-disk。每个云厂商都有自己的CSI驱动。

我的建议:生产环境一定要用动态供给。手动管理PV不仅效率低,还容易出错。我见过一个团队因为PV回收策略没配好,导致数据泄露的事故。

3.3 访问模式:ReadWriteOnce vs ReadWriteMany

访问模式决定了存储卷能被多少个Pod以什么方式挂载。这是存储选型时最容易忽略的点,也是坑最多的地方。

访问模式 缩写 含义 典型场景
ReadWriteOnce RWO 单节点读写 数据库、有状态应用
ReadOnlyMany ROX 多节点只读 配置文件、静态资源
ReadWriteMany RWX 多节点读写 共享文件系统、日志聚合

ReadWriteOnce(RWO)是最常用的模式。一个PV只能被一个节点上的Pod挂载读写。MySQL、PostgreSQL这类数据库都用它。为什么?因为数据库不允许两个实例同时写同一份数据,会出大问题。

ReadWriteMany(RWX)就灵活多了。多个Pod可以同时读写同一个PV。但要注意——不是所有存储后端都支持RWX。NFS支持,CephFS支持,但大多数云厂商的块存储(如AWS EBS、GCP Persistent Disk)不支持。

避坑指南:我曾经在生产环境用AWS EBS配了RWX,结果Pod一直Pending。查了半天才发现EBS根本不支持RWX。后来换成EFS才解决。所以选存储后端前,一定要先确认它支持哪些访问模式。

还有一个容易混淆的点——访问模式是PV级别的属性,不是PVC级别的。PVC只能声明它需要什么模式,最终能不能满足,取决于PV支持什么。

3.4 知识体系总览

下面这张图把本章的核心逻辑串起来了。你可以看到从PVC发起请求,到StorageClass动态创建PV,再到Pod挂载使用的完整流程。

K8s持久化存储核心流程 用户创建PVC K8s调度器:查找匹配的PV 检查 storageClassName、容量、访问模式 静态供给 管理员预先创建PV 动态供给 StorageClass自动创建PV PV与PVC绑定(Bound) Pod通过PVC挂载使用 用户/控制面 调度逻辑 供给方式

从图中可以看到,整个流程其实就三步:用户提需求(PVC)→ 系统找资源(PV或自动创建)→ 绑定使用。嗯,就是这么简单。但简单背后藏着不少细节,比如回收策略、扩容支持、CSI驱动版本兼容性等等。

个人经验:在vLLM项目中,我通常用ReadWriteOnce配合本地SSD做模型缓存。推理任务对IO延迟敏感,RWO模式能保证单节点独占磁盘带宽。如果多个推理副本需要共享同一个模型文件,那就得上RWX了,这时候NFS或JuiceFS是不错的选择。

最后说一句——存储选型没有银弹。RWO简单可靠,但限制了扩展性。RWX灵活,但性能和一致性要打折扣。你想想看,你的vLLM推理服务到底需要什么?是低延迟,还是多副本共享?想清楚了,再选。


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