2.1 持久存储卷(PersistentVolume)
管理存储和管理计算实例是完全不同的两回事,kubernetes 系统为集群管理者和用户提供了一个 API 来详细定义存储的使用方式。这对应着两个新的资源对象: PersistentVolume 和 PersistentVolumeClaim。
PV 的使用方式有两种,分别为静态存储和动态存储:
- 静态存储:由集群管理员创建大量的 PV,在 PV 中定义实际存储的详细信息,然后提供给集群用户使用。
- 动态存储:当管理员创建的静态 PV 中没有一个与用户需要的 PVC 匹配时,集群会尝试动态地为 PVC 提供一个卷。动态存储需要使用到 StorageClass,管理员需要先创建并配置 StorageClass,然后 PVC 设置使用的 StorageClass 类型,系统会自动创建符合要求的 PV 与 PVC 绑定。
PV 生命周期的 4 个阶段
- Available: 可用状态,表示还没有与 PVC 绑定。
- Bound: 已经 PVC 绑定。
- Released: 绑定的 PVC 已经删除,资源已经释放,但没有被集群回收。
- Failed: 自动资源回收失败。
它们之间的状态转换关系如下图所示:

1:这里表示的是 Available -> Bound,PV 的初始状态是 Available,当与某个 PVC 绑定后,状态变为 Bound。
2:这里表示的是 Bound -> Released,当 PV 删除绑定的 PVC 后,PV 状态变为 Released。
3:这里表示的是 Released -> Available,当设置 PV 的 Reclaim Policy 为 Released 后,集群会尝试自动回收 PV(执行 rm -rf /thevolume/*),如果成功回收则 PV 状态变为 Available。还有一种方式是用户手动修改 PV,解除 PV 对 PVC 的引用。
4:这里表示的是 Released -> Failed,当 3 中回收不成功时(比如存储突然不可用),PV 的状态会变为 Failed。
5:这里表示的是 Failed -> Available,出现 4 的情况时,用户手动解除 PV 对 PVC 的引用。
PV 详解
示例
下面是一个标准的 PV 定义:
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv0001
spec:
capacity: # 存储能力,设置为 5GiB 的存储空间
storage: 5Gi
volumeMode: Filesystem # 存储卷模式,设置为文件系统
accessModes: # 访问模式,设置为读写权限,并只能被单个 Node 挂载
- ReadWriteOnce
persistentVolumeReclaimPolicy: Recycle # 回收策略,设置为回收空间
storageClassName: slow # 存储类型,设置为 slow(系统中必须已经存在名为 slow 的 StorageClass)
mountOptions: # 挂载参数,设置了 hard 和 nfsvers=4.1
- hard
- nfsvers=4.1
nfs: # 后端存储类型为 nfs,路径为 /tmp,地址为 172.17.0.2
path: /tmp
server: 172.17.0.2
通过上面的例子可以看出,在定义 PV 时有很多的字段选项都需要设置。
capacity(存储能力)
主要设置的是存储空间的大小,以后可能还支持 IOPS、吞吐率等设置。
volumeMode(存储卷模式)
存储卷的模式有两种:Filesystem(文件系统) 和 Block(块设备)。如果省略这个设置的话,默认为 Filesystem。
支持块类型的存储卷有:AWSElasticBlockStore、AzureDisk、FC、GCEPersistentDisk、iSCSI、Local volume、RBD(Ceph Block Device)、VsphereVolume。
accessModes(访问模式)
设置 PV 的访问模式,描述应用对存储资源的访问权限,现在的访问模式有如下 3 类:
- ReadWriteOnce(RWO):读写权限,只能被单个 Node 挂载。
- ReadOnlyMany(ROX):只读权限,可以被多个 Node 挂载。
- ReadWriteMany(RWX):读写权限,可以被多个 Node 挂载。
某些 PV 可以支持多种访问模式(比如:NFS 可以支持多个应用的读写操作),但是在挂载的时候只能支持一种访问模式,多种访问模式并不能同时生效。
下面的表格展示了不同卷类型所支持的访问模式:
| Volume Plugin | ReadWriteOnce | ReadOnlyMany | ReadWriteMany |
|---|---|---|---|
| AWSElasticBlockStore | ✔️ | - | - |
| AzureFile | ✔️ | ✔️ | ✔️ |
| AzureDisk | ✔️ | - | - |
| CephFS | ✔️ | ✔️ | ✔️ |
| Cinder | ✔️ | - | - |
| CSI | 视驱动而定 | 视驱动而定 | 视驱动而定 |
| FC | ✔️ | ✔️ | - |
| FlexVolume | ✔️ | ✔️ | 视驱动而定 |
| Flocker | ✔️ | - | - |
| GCEPersistentDisk | ✔️ | ✔️ | - |
| Glusterfs | ✔️ | ✔️ | ✔️ |
| HostPath | ✔️ | - | - |
| iSCSI | ✔️ | ✔️ | - |
| Quobyte | ✔️ | ✔️ | ✔️ |
| NFS | ✔️ | ✔️ | ✔️ |
| RBD | ✔️ | ✔️ | - |
| VsphereVolume | ✔️ | - | - |
| PortworxVolume | ✔️ | - | ✔️ |
| ScaleIO | ✔️ | ✔️ | - |
| StorageOS | ✔️ | - | - |
persistentVolumeReclaimPolicy(PV 回收策略)
当前的回收策略有 3 种:
- Retain(保留):后续需要手工处理。
- Recycle(回收空间):简单清除文件的操作(执行 rm -rf /thevolume/* 命令)。
- Delete(删除):与 PV 相连的后端存储完成 Volume 的删除操作(比如:AWS EBS、GCE PD、Azure Disk 和 OpenStack Cinder 等设备内部的 Volume 清理)。
支持对应策略的卷分别有:
- Recycle 策略:NFS、HostPath。
- Delete 策略:AWS EBS、GCE PD、Azure Disk 和 OpenStack Cinder。
storageClassName(StorageClass 的名称)
如果需要支持动态存储的话,需要配置这个字段选项。这里填写的是 StorageClass 的名称,并且需要事先创建好对应名称的 StorageClass 才能够在这里使用。
mountOptions(挂载参数)
当 PV 挂载到 Node 上时,根据不同卷的特点可以设置额外的挂载参数。
现在支持挂载参数的卷有:AWSElasticBlockStore、AzureDisk、AzureFile、CephFS、Cinder、GCEPersistentDisk、Glusterfs、NFS、Quobyte Volumes、RBD(Ceph Block Device)、StorageOS、VsphereVolume、iSCSI。
由于挂载参数没有验证,所以如果其中的一个参数是无效的,那么挂载就会失败。
nodeAffinity(节点亲和性)
使用这个字段可以限制只有符合条件的 Node 才能访问存储卷,而使用这些存储卷的 Pod 也只会被调度到满足条件的 Node 上。
这个参数只能用于 Local 存储卷。对于公有云提供的存储卷(比如:AWS EBS、GCE PD、Azure Disk 等)是公有云自动完成节点亲和性设置,不需要手工设置。
2.2 持久存储卷声明(PersistentVolumeClaim)
PVC 是对 PV 的声明使用。
PVC 生命周期的 3 个阶段
- Pending: 等待状态,还没有与 PV 绑定。
- Bound: 绑定状态,已经与 PV 绑定。
- Lost: 丢失状态,与 PV 失去了绑定关系。
它们之间的状态转换关系如下图所示:

1:这里表示的是 Pending -> Bound,当集群找到符合条件的 PV 后,将 PV 与 PVC 绑定。
2:这里表示的是 Bound -> Lost,当 PV 被删除后,PVC 的状态变为 Lost。
3:这里表示的是 Lost -> Bound,被删除的 PV 又被添加回来,与 PVC 重新绑定。
PVC 详解
示例
PVC 是用户对存储资源的需求申请,下面是一个配置示例:
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: myclaim
spec:
accessModes: # 访问模式,设置为 ReadWriteOnce
- ReadWriteOnce
volumeMode: Filesystem # 存储卷模式,设置为 Filesystem
resources:
requests: # 资源请求,设置 storage 为 8GiB
storage: 8Gi
storageClassName: slow # 绑定的 StorageClass 的名称
selector: # 选择条件
matchLabels: # 符合标签
release: "stable"
matchExpressions: # 符合标签表达式
- { key: environment, operator: In, values: [dev] }
这其中涉及到了比较多的字段选项,下面来一一详解。
accessModes(访问模式)
这个字段,PVC 与 PV 相同,也可以设置访问模式,作用也是描述应用对存储资源的访问权限。
volumeMode(存储卷模式)
这个字段,PVC 与 PV 也是相同的,描述的是希望使用的 PV 存储卷模式,可以配置的模式为文件系统和块设备。
resources(资源请求)
这个字段用于描述对存储资源的请求,目前仅支持设置 requests.storage,也就是对存储空间的设置。
storageClassName(绑定的 StorageClass 的名称,即:存储类别)
这个字段用于设置后端存储类别(即:绑定 StorageClass),可以减少对后端存储特性详细信息的依赖。分为两种情况:
- 如果设置这个字段为某个 StorageClass,那么只有设置了相同 Class 的 PV 才会被选中,然后与 PVC 绑定。
- 如果设置这个字段为空 storageClassName="",即不设置 Class。这时取决于系统是否启用名为 DefaultStorageClass 的 admission controller:
- 没有启动 DefaultStorageClass:将选择没有设定 Class 的 PV 进行匹配和绑定。
- 启用 DefaultStorageClass:管理员定义默认的 StorageClass,系统将使用默认 StorageClass 的后端存储创建一个 PV 并自动与 PVC 绑定。设置的具体方法为:在 StorageClass 的定义中添加一个 annotation "storageclass.kubernetes.io/is-default-class=true"。需要注意的是:只能定义一个默认的 StorageClass。
selector(选择条件)
这个字段可以对系统中已经存在的各种 PV 进行筛选,根据筛选条件选出最符合条件的 PV 进行绑定。这下面包含两个字段:
- matchLabels:PV 必须有这个标签值。
- matchExpressions:通过键值对和操作符指定标签选择器列表,操作符包括:In、Notln、Exists 和 DoesNotExist。
如果两个字段都设置了,就必须满足所有的条件才能完成匹配。
最后需要注意的是:Pod、PV 和 PVC 都必须在同一命名空间下才起作用。
另外,如果使用动态存储管理,即不预先定义 PV,只通过 StorageClass 交给系统自动完成 PV 的动态创建,那么 PVC 设置的 selector 就是无效的。如果用户删除了 PVC,与它绑定的 PV(默认回收策略为 Delete)也会被删除,可以在绑定成功后手动将 Delete 策略修改为 Retain。
2.3 静态存储实例
在这里使用一个简单的例子演示 PV、PVC 和 Pod 的搭配使用方式,我们创建 hostPath 类型的 PersistentVolume(也就是使用 Node 文件系统中的文件)。
在本地创建要映射进容器的文件:
$ mkdir tmp
$ echo "Hello Kubernetes" | tee -a /root/learning/tmp/index.html
Hello Kubernetes
$ scp tmp/index.html root@kubesphere02:/root/learning/tmp
$ scp tmp/index.html root@kubesphere03:/root/learning/tmp
定义PV,新建 pv-volume.yaml 文件,并向其中写入如下内容:
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv-volume
labels:
type: local
spec:
storageClassName: manual # 这里需要定义 storageClassName
capacity:
storage: 10Gi # 设置存储空间大小为 10GiB
accessModes:
- ReadWriteOnce
hostPath: # 为 hostPath 类型
path: "/root/learning/tmp" # 路径为本地的 /root/learning/tmp
执行创建:
$ kubectl create -f pv-volume.yaml
persistentvolume/pv-volume created
# 现在 pv-volume 已经创建好了,状态为 Available
$ kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
pv-volume 10Gi RWO Retain Available manual 31s
接下来创建 PVC,新建 pv-claim.yaml 文件,并向其中写入如下内容:
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: pv-claim
spec:
storageClassName: manual # 这里一定要设置 storageClassName 的名称与 PV 的定义中相同,否则会使用默认的 StorageClass 进行动态创建
accessModes:
- ReadWriteOnce
resources: # 这里申请使用 3GiB 的存储空间
requests:
storage: 3Gi
执行创建:
$ kubectl create -f pv-claim.yaml
persistentvolumeclaim/pv-claim created
再一次查看 PV 的状态:
$ kubectl get pv pv-volume
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
pv-volume 10Gi RWO Retain Bound user-test/pv-claim manual 3m51s
可以发现 pv-volume 的状态已经是 Bound。
查看 PVC 的状态:
# PVC 的状态也变为了 Bound
$ kubectl get pvc -n user-test
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
pv-claim Bound pv-volume 10Gi RWO manual 2m24s
创建一个 Pod 使用 pv-claim PersistentVolumeClaim 作为卷,新建 pv-pod.yaml 文件并向其中写入如下内容:
apiVersion: v1
kind: Pod
metadata:
name: pv-pod
spec:
volumes: # 为 Pod 设置卷
- name: pv-storage # 卷名
persistentVolumeClaim: # 引用 PVC 的名称
claimName: pv-claim
containers:
- name: pv-container
image: nginx:1.19
ports:
- containerPort: 80
name: "http-server"
volumeMounts: # 将卷挂载到容器中的 /usr/share/nginx/html 路径下
- mountPath: "/usr/share/nginx/html"
name: pv-storage
执行创建:
$ kubectl create -f pv-pod.yaml
pod/pv-pod created
$ kubectl get pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
pv-pod 1/1 Running 0 28s 10.20.138.50 kubesphere02 <none> <none>
测试,查看 Pod 对应目录下是否存在文件:
$ kubectl exec -it pv-pod bash
root@pv-pod:/# cd /usr/share/nginx/html/
root@pv-pod:/usr/share/nginx/html# ls
index.html
root@pv-pod:/usr/share/nginx/html# cat index.html
Hello Kubernetes
root@pv-pod:/usr/share/nginx/html# echo 'Hello World' >> index.html
[root@kubesphere02 ~]# cat /root/learning/tmp/index.html
Hello Kubernetes
Hello World
评论区