PersistentVolume 详解:Kubernetes 的持久化存储抽象

This article is extracted from the chat log with AI. Please identify it with caution.

说明:本文由 Codex 根据作者提供的主题、素材与公开资料辅助生成,属于 AI 生成/整理内容,非作者原创。请读者自行甄别并交叉验证。

为什么需要 PV 和 PVC#

Pod 是短生命周期资源。

Pod 被删除、重建、迁移到其他节点时,容器里的临时文件系统通常也会跟着消失。

如果应用需要保存状态,例如:

  • 数据库数据。
  • 用户上传文件。
  • 消息队列数据目录。
  • 构建缓存。
  • 需要跨 Pod 重启保留的业务状态。

就需要把存储从 Pod 生命周期里抽象出来。

Kubernetes 用三类对象完成这个抽象:

对象角色
PersistentVolume集群里的真实存储资源
PersistentVolumeClaim用户对存储资源的申请
StorageClass动态创建存储资源的模板和策略

简单理解:

PV 是存储资源;
PVC 是申请单;
StorageClass 是供应规格。

PV 是什么#

PersistentVolume 简称 PV。

它是集群级别资源,不属于某个 namespace。

PV 描述的是一块已经准备好,或者可以被 Kubernetes 管理的存储资源。

这块资源可能来自:

  • 云厂商块存储。
  • NFS。
  • iSCSI。
  • Ceph。
  • CSI 驱动提供的存储。
  • 本地磁盘。

一个静态 PV 示例:

apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv-nfs-demo
spec:
  capacity:
    storage: 20Gi
  volumeMode: Filesystem
  accessModes:
    - ReadWriteMany
  persistentVolumeReclaimPolicy: Retain
  storageClassName: nfs-retain
  nfs:
    server: 192.0.2.10
    path: /exports/app-data

PV 里通常会描述:

  • 容量。
  • 访问模式。
  • 回收策略。
  • 存储类别。
  • 真实后端存储信息。
  • 节点亲和性。
  • 卷模式:文件系统或块设备。

PVC 是什么#

PersistentVolumeClaim 简称 PVC。

它是 namespace 级别资源。

应用通常不会直接声明自己要使用哪个 PV,而是创建 PVC 来表达需求:

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: app-data
  namespace: app
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 10Gi
  storageClassName: standard

这表示:

我需要一块 10Gi 的存储;
访问模式是 ReadWriteOnce;
希望使用 standard 这个 StorageClass。

控制面会寻找匹配的 PV,或者通过 StorageClass 动态创建 PV,然后把 PVC 和 PV 绑定。

绑定关系是一对一的:

一个 PVC 绑定一个 PV;
一个 PV 同一时间只绑定一个 PVC。

StorageClass 是什么#

StorageClass 用来描述一类存储。

例如:

  • 普通磁盘。
  • 高性能 SSD。
  • 多副本高可用存储。
  • 支持快照和扩容的存储。
  • 特定区域或拓扑的存储。

示例:

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: fast-ssd
provisioner: csi.example.com
parameters:
  type: ssd
reclaimPolicy: Delete
allowVolumeExpansion: true
volumeBindingMode: WaitForFirstConsumer

几个关键字段:

字段含义
provisioner谁来创建真实存储
parameters传给 provisioner 的参数
reclaimPolicyPVC 删除后如何处理 PV 和后端存储
allowVolumeExpansion是否允许扩容 PVC
volumeBindingMode什么时候绑定和创建卷

如果 PVC 没有指定 storageClassName,并且集群有默认 StorageClass,Kubernetes 会使用默认 StorageClass。

如果想明确禁用动态供应,可以写:

storageClassName: ""

这和不写 storageClassName 不是一回事。

生命周期#

PV 和 PVC 的生命周期可以概括为:

Provisioning -> Binding -> Using -> Reclaiming

Provisioning#

供应方式有两种。

静态供应:

管理员提前创建 PV;
用户创建 PVC;
控制面把 PVC 绑定到匹配 PV。

动态供应:

用户创建 PVC;
PVC 指定 StorageClass;
CSI 或其他 provisioner 创建真实存储和 PV;
控制面绑定 PVC 和 PV。

现代集群更常见的是动态供应。

Binding#

控制面会根据 PVC 的需求匹配 PV:

  • storageClassName
  • 请求容量。
  • accessModes
  • volumeMode
  • selector。

如果没有匹配的 PV,PVC 会保持 Pending

查看:

kubectl get pvc -n app
kubectl describe pvc -n app app-data
kubectl get pv

Using#

Pod 通过 PVC 使用存储:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: app
  namespace: app
spec:
  replicas: 1
  selector:
    matchLabels:
      app: app
  template:
    metadata:
      labels:
        app: app
    spec:
      containers:
        - name: app
          image: nginx
          volumeMounts:
            - name: data
              mountPath: /data
      volumes:
        - name: data
          persistentVolumeClaim:
            claimName: app-data

Pod 不需要知道底层是云盘、NFS 还是别的存储。它只关心 PVC。

Reclaiming#

PVC 删除后,PV 如何处理取决于回收策略。

策略行为
Retain保留 PV 和真实数据,需要人工处理
Delete删除 PV,并尝试删除后端存储
Recycle已废弃,不建议使用

动态供应的 PV 通常继承 StorageClass 的 reclaimPolicy,默认常见是 Delete

生产环境里,如果数据非常重要,应认真确认回收策略,避免误删 PVC 后连底层数据一起删除。

Access Modes#

常见访问模式:

模式含义
ReadWriteOnce可被一个节点以读写方式挂载
ReadOnlyMany可被多个节点以只读方式挂载
ReadWriteMany可被多个节点以读写方式挂载
ReadWriteOncePod只允许一个 Pod 以读写方式使用

访问模式表达的是存储卷挂载能力,不等于应用层并发写一定安全。

例如某些文件系统支持 ReadWriteMany,但应用是否能多副本同时写,还要看应用自己的数据一致性设计。

volumeBindingMode#

StorageClass 里的 volumeBindingMode 很关键。

常见两个值:

Immediate
WaitForFirstConsumer

Immediate 表示 PVC 创建后就立即创建或绑定 PV。

WaitForFirstConsumer 表示等到 Pod 被调度时再创建或绑定 PV。

对于有拓扑限制的存储,比如只能挂载到某个可用区的云盘,WaitForFirstConsumer 更安全。它可以让调度器先结合 Pod 调度位置,再决定卷应该在哪个区域创建。

扩容#

如果 StorageClass 设置了:

allowVolumeExpansion: true

就可以通过修改 PVC 请求容量来扩容:

kubectl patch pvc app-data -n app \
  -p '{"spec":{"resources":{"requests":{"storage":"20Gi"}}}}'

注意:

  • 不是所有存储驱动都支持扩容。
  • 文件系统扩容可能需要 Pod 重新挂载或由 kubelet 在线完成,取决于驱动和文件系统。
  • 不要直接改 PV 的容量来假装扩容,这可能让控制面误判状态。

常见问题#

PVC 一直 Pending#

常见原因:

  • 没有默认 StorageClass。
  • PVC 指定的 storageClassName 不存在。
  • 静态 PV 不满足容量或访问模式。
  • CSI provisioner 没有正常运行。
  • 拓扑限制导致无法创建卷。
  • WaitForFirstConsumer 下还没有 Pod 使用这个 PVC。

排查:

kubectl describe pvc -n app app-data
kubectl get storageclass
kubectl get pv
kubectl get events -n app --sort-by=.metadata.creationTimestamp
kubectl get pod -n kube-system

Pod 挂载失败#

常见原因:

  • PV/PVC 未绑定。
  • 节点无法访问后端存储。
  • CSI node plugin 异常。
  • 权限、文件系统、挂载参数错误。
  • 多节点挂载和访问模式冲突。

排查:

kubectl describe pod -n app <pod-name>
kubectl describe pvc -n app app-data
kubectl describe pv <pv-name>
kubectl get csinode
kubectl get pods -A | grep -i csi

删除 PVC 后数据没了#

先看回收策略:

kubectl get pv
kubectl describe pv <pv-name>

如果策略是 Delete,删除 PVC 可能触发底层存储删除。

如果策略是 Retain,PV 和底层数据会保留,但需要管理员手动清理或重新绑定。

一个实践建议#

生产环境里,建议把存储策略显式化:

重要数据:使用 Retain 或明确的备份策略;
临时缓存:可以使用 Delete;
有区域限制的块存储:优先考虑 WaitForFirstConsumer;
需要扩容:确认 CSI 和 StorageClass 支持 allowVolumeExpansion;
多副本同时读写:确认底层存储和应用都支持。

不要只看 PVC 申请成功,还要看数据生命周期:

谁创建?
谁挂载?
谁扩容?
谁备份?
谁删除?
删除后数据是否保留?

小结#

PV/PVC 的本质是把“应用怎么消费存储”和“集群怎么提供存储”解耦。

管理员或 StorageClass 提供 PV;
应用通过 PVC 申请存储;
Pod 通过 PVC 挂载使用;
回收策略决定 PVC 删除后的数据命运。

理解这条链路后,排查存储问题时就能把问题定位到 PVC 申请、PV 绑定、CSI 创建、节点挂载、权限和数据回收中的某一段。

参考资料#

本文共 2580 字,创建于 Jun 26, 2026

相关标签: Kubernetes, DevOps, ByAI