2、Kubernetes 存储基础回顾:PV 与 PVC 概念、StorageClass 动态供给、AccessModes 详解、本地卷与网络卷的选择
说实话,很多做 Kubeflow 的朋友,最后翻车都翻在存储上。模型训练到一半,Pod 重启了,数据丢了——这种事故我见过不止一次。所以咱们在深入 Kubeflow 之前,先把 Kubernetes 存储这块地基打牢。
我个人习惯把 K8s 存储理解成「租房」的逻辑。你想想看,Pod 是租客,它需要一个地方放东西。但 Pod 本身是飘忽不定的,今天在这台机器上,明天可能就跑到另一台去了。所以 K8s 搞了一套抽象层,把存储和 Pod 的生命周期解耦。
PV 与 PVC:存储界的「房源」和「租房合同」
PV(PersistentVolume)就是房源。管理员提前准备好的一块存储,可能是 NFS 服务器上的一个目录,也可能是云硬盘。PV 是集群级别的资源,不归某个 Namespace 管。
PVC(PersistentVolumeClaim)就是租房合同。用户声明「我需要 10Gi 的存储,读写方式要支持多个 Pod 同时用」。K8s 看到这个声明,就会去匹配一个合适的 PV 绑定上。
核心要点:Pod 不直接挂载 PV,而是挂载 PVC。PVC 和 PV 绑定后,Pod 才能用。这层抽象让应用开发者不用关心底层存储是什么。
我在项目中遇到过一种情况:开发同学直接写死了 PV 的名字,结果换环境就报错。正确的做法是只声明 PVC,让 K8s 自动匹配。说白了,你租房时只需要告诉中介你的需求,不用管房东具体是谁。
# PV 示例
apiVersion: v1
kind: PersistentVolume
metadata:
name: my-pv
spec:
capacity:
storage: 10Gi
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Retain
nfs:
path: /data/nfs
server: 192.168.1.100
# PVC 示例
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: my-pvc
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 10Gi
StorageClass 动态供给:告别手动创建 PV
手动创建 PV 太累了。你想想看,每个用户申请存储,你都得去配一个 NFS 目录或者云硬盘,然后写 PV 的 YAML。这哪是 DevOps,这是体力活。
StorageClass 就是来解决这个问题的。它定义了一个「存储模板」,告诉 K8s 用什么驱动、什么参数来动态创建 PV。用户提交 PVC 时,如果指定了 StorageClass,K8s 会自动调用对应的 Provisioner 去创建底层存储,然后生成 PV 并绑定。
我的建议:生产环境一定要用动态供给。我曾经在一个项目里看到 50 多个手动创建的 PV,维护起来简直噩梦。换成 StorageClass 后,PVC 提交后几秒钟就能用上存储。
# StorageClass 示例
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: fast-ssd
provisioner: kubernetes.io/aws-ebs
parameters:
type: gp3
fsType: ext4
reclaimPolicy: Delete
allowVolumeExpansion: true
这里有个细节要注意:reclaimPolicy 决定了 PVC 删除后 PV 怎么处理。Delete 模式会直接删掉底层存储,Retain 模式会保留数据。嗯,测试环境用 Delete 省心,生产环境我建议用 Retain,防止误删。
AccessModes 详解:单读单写还是群读群写?
AccessModes 定义了存储的访问模式。说白了就是:这个存储能同时被几个 Pod 用?怎么用?
| 模式 | 含义 | 典型场景 |
|---|---|---|
| ReadWriteOnce (RWO) | 单节点读写 | 大多数数据库、单副本应用 |
| ReadOnlyMany (ROX) | 多节点只读 | 配置文件分发、静态数据共享 |
| ReadWriteMany (RWX) | 多节点读写 | 共享文件系统、分布式训练数据 |
为什么会有这些限制?因为底层存储的能力不同。比如云硬盘(EBS、云盘)只能挂载到一台机器上,所以只支持 RWO。而 NFS、GlusterFS 这类网络文件系统,天然支持多节点同时读写。
避坑指南:我曾经在项目里用 AWS EBS 做共享存储,结果多个 Pod 同时写数据时直接报错。后来才发现 EBS 只支持 RWO。记住:不是所有存储都支持 RWX,选型前一定要看文档。
对于 Kubeflow 来说,RWX 模式特别重要。因为分布式训练时,多个 Worker 需要同时读写同一份数据集。这时候你就得选 NFS、CephFS 或者 JuiceFS 这类支持 RWX 的存储。
本地卷与网络卷:选哪个?
这是个经典问题。本地卷(Local Volume)就是直接用宿主机上的磁盘,网络卷(如 NFS、Ceph)则是通过网络访问远程存储。
本地卷的优势:性能好,延迟低,没有网络开销。适合数据库、缓存这类对 IO 敏感的应用。
本地卷的劣势:Pod 被调度到其他节点后,数据就丢了。因为数据在原来的机器上,新机器访问不到。
网络卷的优势:数据独立于节点,Pod 跑到哪都能访问。适合共享数据、分布式训练。
网络卷的劣势:有网络延迟,性能不如本地盘。如果网络不稳定,还会影响应用。
我的选择原则:训练数据用网络卷(方便共享),模型 checkpoint 用本地卷(追求写入速度)。但要注意,本地卷需要配合节点亲和性使用,确保 Pod 始终调度到有数据的节点上。
嗯,这里再补充一点。Kubernetes 1.14 之后引入了 local 类型的 PV,可以直接声明本地磁盘。但你需要手动创建 PV,或者用 local-path-provisioner 这类工具实现动态供给。
# 本地卷 PV 示例
apiVersion: v1
kind: PersistentVolume
metadata:
name: local-pv
spec:
capacity:
storage: 100Gi
volumeMode: Filesystem
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Delete
storageClassName: local-storage
local:
path: /mnt/disks/ssd1
nodeAffinity:
required:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/hostname
operator: In
values:
- node-1
看到 nodeAffinity 了吗?这就是关键。本地卷必须绑定到特定节点,否则 Pod 调度到其他机器上就找不到数据了。
知识体系总览
下面这张图总结了本章的核心逻辑,从存储供给到 Pod 挂载的完整链路:
从这张图可以看得很清楚:StorageClass 负责动态创建 PV,PV 和 PVC 通过匹配条件绑定,最终 Pod 通过 PVC 挂载存储。底层存储要么选本地卷追求性能,要么选网络卷追求共享。
最后说一句:Kubeflow 里最常用的存储模式是 RWX 网络卷 + 动态供给。你配置好 StorageClass,用户提交 Pipeline 时直接声明 PVC,系统自动创建存储。省心又高效。