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 挂载的完整链路:

Kubernetes 存储核心链路 StorageClass 动态供给模板 PV 持久化存储资源 PVC 存储资源请求 PV 与 PVC 绑定(一对一匹配) 根据容量、AccessModes、StorageClass 匹配 Pod 通过 PVC 挂载存储卷 本地卷(Local) 高性能,节点绑定 网络卷(NFS/Ceph) 共享存储,节点无关 动态供给 静态资源

从这张图可以看得很清楚:StorageClass 负责动态创建 PV,PV 和 PVC 通过匹配条件绑定,最终 Pod 通过 PVC 挂载存储。底层存储要么选本地卷追求性能,要么选网络卷追求共享。

最后说一句:Kubeflow 里最常用的存储模式是 RWX 网络卷 + 动态供给。你配置好 StorageClass,用户提交 Pipeline 时直接声明 PVC,系统自动创建存储。省心又高效。


专注资料整理