K8S 之 持久卷 PV 和 PVC

摘要

PV 和 PVC 介绍

  • PV(PersistentVolume) 是 Kubernetes 中的一种存储资源,用于将底层的物理存储(如 NFS、iSCSI、Ceph、云存储等)抽象成 Kubernetes 资源,供 Pod 使用。它是对存储的一种“声明式”管理,类似于 Pod 声明计算资源。PV 是集群级别的存储资源,不支持 Namespace。

  • PVC(PersistentVolumeClaim),持久卷声明, 是 Kubernetes 中用于申请存储资源的对象。PVC 是 Namespace 级别的资源。

  • 简而言之:

    • PV 是集群中的一块存储,由管理员提前配置或动态创建。
    • PVC(PersistentVolumeClaim) 是用户对存储的申请。
    • Pod 通过 PVC 绑定到 PV,使用持久化存储。
    • 开发者用 PVC 来申请存储空间,不关心存储的具体实现方式。
    • PVC 通过 Kubernetes 自动匹配一个合适的 PersistentVolume(PV)进行绑定。
  • PV 与 PVC 的基本流程

    • 管理员创建 PV(或者集群通过 StorageClass 自动创建)。
    • 用户提交 PVC,声明自己需要多少容量、什么访问模式。
    • Kubernetes 查找可用的 PV,条件符合(容量、访问模式、StorageClass)就自动绑定。
    • Pod 挂载 PVC,实现持久化存储。

PV 示例

  • 一个使用 NFS 存储卷的 PV 示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# pv-nfs.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv-nfs-1g
spec:
volumeMode: Filesystem # 存储卷模式,默认为 Filesystem
capacity: # 存储能力
storage: 1Gi # 容量大小,Gi 或 Mi
accessModes: # 访问模式
- ReadWriteOnce # 访问模式
persistentVolumeReclaimPolicy: Retain # 回收策略
nfs: # 持久卷类型(如 hostPath、nfs、ceph 等)
path: /nfs-server/data/pv-nfs-1g # 存储路径要确保已经存在
server: 10.211.55.88
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv-nfs-2g
spec:
capacity: # 存储能力
storage: 2Gi # 容量大小,Gi 或 Mi
accessModes: # 访问模式
- ReadWriteMany # 访问模式
persistentVolumeReclaimPolicy: Retain # 回收策略
nfs: # 存储类型(如 hostPath、nfs、ceph 等)
path: /nfs-server/data/pv-nfs-2g # 存储路径要确保已经存在
server: 10.211.55.88

存储卷模式

  • 官网文档:存储卷模式

  • Kubernetes 支持两种卷模式(volumeModes):Filesystem(文件系统) 和 Block(块)。

  • 如果该参数被省略,默认的卷模式是 Filesystem。

  • volumeMode 属性设置为 Filesystem 的卷会被 Pod 挂载(Mount) 到某个目录。

持久卷类型

插件类型 描述
csi 容器存储接口(CSI),推荐的现代存储接口
fc Fibre Channel 存储
hostPath HostPath 卷(仅供单节点测试使用;多节点不推荐,推荐使用 local 卷替代)
iscsi iSCSI(基于 IP 的 SCSI 存储)
local 节点本地存储设备
nfs 网络文件系统(NFS)存储
  • ⚠️ 已弃用但仍可用(需 CSI 迁移)

插件类型 描述 CSI 迁移状态
awsElasticBlockStore AWS EBS 块存储 从 v1.23 开始默认迁移
azureDisk Azure 磁盘存储 从 v1.23 开始默认迁移
azureFile Azure 文件存储 从 v1.24 开始默认迁移
cinder OpenStack 块存储 从 v1.21 开始默认迁移
flexVolume FlexVolume(无迁移计划,但未计划移除) 从 v1.23 开始弃用
gcePersistentDisk GCP 持久磁盘 从 v1.23 开始默认迁移
portworxVolume Portworx 存储卷 从 v1.31 开始默认迁移
vsphereVolume vSphere VMDK 卷 从 v1.25 开始默认迁移
  • ❌ 已废弃/即将移除的 In-Tree 插件

插件类型 描述 弃用/移除版本
cephfs Ceph 文件系统卷 v1.31 之后不可用
flocker Flocker 存储 v1.25 之后不可用
glusterfs GlusterFS 存储 v1.26 之后不可用
photonPersistentDisk Photon 持久磁盘 v1.15 之后不可用
quobyte Quobyte 卷 v1.25 之后不可用
rbd Rados 块设备(Ceph RBD) v1.31 之后不可用
scaleIO ScaleIO 卷 v1.21 之后不可用
storageos StorageOS 卷 v1.25 之后不可用

accessModes 访问模式

访问模式 说明 是否跨节点挂载 是否支持多 Pod 挂载 是否支持读写
ReadWriteOnce (RWO) 卷可以被一个节点以读写方式挂载,同一节点内多个 Pod 可共享使用。 ❌ 否 ✅ 是(同一节点) ✅ 是
ReadOnlyMany (ROX) 卷可以被多个节点以只读方式挂载。 ✅ 是 ✅ 是 ❌ 否(只读)
ReadWriteMany (RWX) 卷可以被多个节点以读写方式挂载。 ✅ 是 ✅ 是 ✅ 是
ReadWriteOncePod (RWOP) 卷只能被单个 Pod 以读写方式挂载,确保集群中只有一个 Pod 使用该卷(v1.29+稳定)。 ❌ 否 ❌ 否(只能一个 Pod) ✅ 是
  • NFS 支持前三种访问模式,hostPath 只支持 ReadWriteOnce。

persistentVolumeReclaimPolicy 回收策略

回收策略 含义 回收行为 典型使用场景
Retain 保留:删除 PVC 后,PV 和后端存储仍然保留 手动回收,PVC 删除后 PV 状态为 Released,需要手动清理或重新绑定 重要数据,避免误删;如数据库数据盘
Delete 删除:删除 PVC 后,PV 和后端存储都会被删除 自动回收,PVC 删除时自动删除 PV 和后端存储资源(如云盘) 临时数据、不重要的存储
Recycle 回收:简单清空数据 自动执行 rm -rf /thevolume/*,然后 PV 变回 Available 状态 旧版本集群的小文件临时存储
  • 注意: 创建 Recycle 的 PV 时,会提示如下内容

1
Warning: spec.persistentVolumeReclaimPolicy: The Recycle reclaim policy is deprecated. Instead, the recommended approach is to use dynamic provisioning.
  • 意思就是 Recycle 策略已被弃用,建议使用动态供应模式。但官方文档中却提示 对于 Kubernetes 1.33 来说,只有 nfs 和 hostPath 卷类型支持回收(Recycle)。

  • 本人在 Kubernetes 1.33.2 中测试,NFS 支持 Recycle,删除 PVC 后 PV 状态会变为 Available

管理 PV

1
2
3
4
5
6
7
8
9
10
11
12
# 创建
kubectl create -f pv-nfs.yaml

# 查看
$ k get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS VOLUMEATTRIBUTESCLASS REASON AGE
pv-nfs-1g 1Gi RWO Retain Available <unset> 80s
pv-nfs-2g 2Gi RWX Retain Available <unset> 80s

# 删除
kubectl delete pv pv-nfs-1g
k delete -f pv-nfs.yaml

PVC 示例

  • Pod 通过 PVC 向 PV 申请存储空间,如果 PVC 一直无法匹配到 PV,则 PVC 处于 Pending 状态。

  • Pod 必须与 PVC 处于同一命名空间。

  • PVC 与 PV 是 1:1 的关系。一旦 PV 绑定到 PVC,则 PVC 状态变为 Bound。该 PV 将不再被其他 PVC 绑定。

  • PVC 与 PV 匹配的条件

匹配条件 说明
容量 PVC 请求的容量 ≤ PV 提供的容量。PV 必须至少满足 PVC 的容量请求。
访问模式 PV 支持 PVC 请求的访问模式。PVC 要求的所有访问模式,PV 都必须具备。例如:PVC 要求 ReadWriteOnce,PV 至少要支持 ReadWriteOnce
StorageClass PVC 和 PV 的 storageClassName 必须一致。如果 PVC 指定了 storageClassName,只能绑定同名的 PV。
Selector(可选) 如果 PVC 有设置 selector(基于标签),PV 的标签也必须匹配。

小贴士

  • PV 中声明的 accessModes 仅作为与 PVC 的访问模式进行匹配,实际挂载到 Pod 后,不会限制 Pod 的读写访问
  • 一个简单的 yaml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: mypvc
namespace: default
spec:
accessModes:
- ReadWriteMany # 申请匹配的访问模式
resources:
requests:
storage: 500Mi # 申请的容量,实际容量以匹配的 PV 为准
# selector: # 匹配的 PV 标签
# matchLabels:
# pv: nfs-pv1 # pv 的标签,创建 pv 时要指定

管理 PVC

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 创建 PVC
kubectl apply -f pvc.yaml

# 查看PVC,此时看到 pv-nfs-1g 绑定了 PVC
$ k get pv,pvc
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS VOLUMEATTRIBUTESCLASS REASON AGE
persistentvolume/pv-nfs-1g 1Gi RWO Retain Available <unset> 16s
persistentvolume/pv-nfs-2g 2Gi RWX Retain Bound default/mypvc <unset> 16s

NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS VOLUMEATTRIBUTESCLASS AGE
persistentvolumeclaim/mypvc Bound pv-nfs-2g 2Gi RWX <unset> 9s

# 删除 PVC
kubectl delete pvc mypvc

# 此时再次查看 PV,pv-nfs-1g 状态变更为 Released,表示已经释放,但是不能再被其它 PVC 绑定了,只能删除重建了
$ k get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS VOLUMEATTRIBUTESCLASS REASON AGE
pv-nfs-1g 1Gi RWO Retain Available <unset> 18m
pv-nfs-2g 2Gi RWX Retain Released default/mypvc <unset> 18m

# 删除 PV
k delete pv pv-nfs-1g

Pod 绑定 PVC

  • 这里以 nginx deployment 为例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# nginx-pvc-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-pvc-deployment
namespace: default
spec:
replicas: 3 # 3个 pod会共享一个 PVC
selector:
matchLabels:
app: nginx-pvc
template:
metadata:
labels:
app: nginx-pvc
spec:
containers:
- name: nginx
image: nginx
ports:
- containerPort: 80
volumeMounts:
- name: nginx-storage
mountPath: /usr/share/nginx/html # 挂载到 nginx 的默认网页目录
volumes:
- name: nginx-storage
persistentVolumeClaim: # 指定存储卷类型是 PVC
claimName: mypvc # 对应你创建的 PVC 名称
  • 一个 PVC 对应一个 PV,但是一个 PVC 可以对应多个 Deployment 等控制器。下面我们就再创建一个 Deployment,将 相同的 PVC 挂载到容器中,并且每隔5秒修改一次 PVC 挂载的网页,并通过 nginx 容器查看结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# busybox-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: busybox-deployment
spec:
replicas: 1
selector:
matchLabels:
app: busybox
template:
metadata:
labels:
app: busybox
spec:
containers:
- name: busybox
image: busybox
command: ["sh", "-c", "while true; do echo $(date) > /data/index.html; sleep 5; done"]
volumeMounts:
- name: shared-volume
mountPath: /data
volumes:
- name: shared-volume
persistentVolumeClaim:
claimName: mypvc
  • 创建后查看 pod ip

1
2
3
4
5
6
$ k get pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
busybox-deployment-d7589665d-dx2rs 1/1 Running 0 2m59s 10.244.126.6 k8s-worker2 <none> <none>
nginx-pvc-deployment-775b8c4f8b-89cw7 1/1 Running 0 44s 10.244.126.8 k8s-worker2 <none> <none>
nginx-pvc-deployment-775b8c4f8b-fszlf 1/1 Running 0 44s 10.244.194.123 k8s-worker1 <none> <none>
nginx-pvc-deployment-775b8c4f8b-w9qkt 1/1 Running 0 44s 10.244.126.7 k8s-worker2 <none> <none>
  • 访问任意一个 nginx pod

1
2
# 此时会看到页面内容在变化
watch -n 5 curl 10.244.194.123

后记

  • 为 pod 绑定 pvc时,每次都要先创建 pv 和 pvc,非常麻烦。有什么好的解决方案吗?

    使用 StorageClass 实现自动创建 PV,我们下文将介绍如何实现。

  • 如果 PVC 被 Pod 使用,则此时可以删除 PVC 吗?

    不可以,PVC 被 Pod 使用,此时只能等待 Pod 删除后才能删除 PVC。
    若此时执行了删除 PVC 命令,终端会一直等待,ctrl + c 退出后再次查看 PVC 状态,会看到 PVC 状态为 Terminating。但此时不会影响存Pod对储卷的使用。
    此时一旦Pod 删除,PVC 就会被删除。

  • 如果PV 被 PVC 使用,则此时可以删除 PV 吗?

    不可以,PV 被 PVC 使用,此时只能等待 PVC 删除后才能删除 PV。
    若此时执行了删除 PV 命令,终端会一直等待,ctrl + c 退出后再次查看 PV 状态,会看到 PV 状态为 Terminating。但此时不会影响Pod对存储卷的使用。
    此时一旦 PVC 删除,PV 就会被删除。