参考:
https://www.kubernetes.org.cn/pvpvcstorageclass
https://kubernetes.io/docs/concepts/storage/persistent-volumes/
概述
PersistentVolume(PV)和 PersistentVolumeClaim(PVC)提供了方便的持久化卷:PV 提供网络存储资源,而 PVC 请求存储资源。这样,设置持久化的工作流包括配置底层文件系统或者云数据卷、创建持久性数据卷、最后创建 PVC 来将 Pod 跟数据卷关联起来。PV 和 PVC 可以将 pod 和数据卷解耦,pod 不需要知道确切的文件系统或者支持它的持久化引擎。
生命周期
PV 是集群中的资源。PVC 是对这些资源的请求,也是对资源的索赔检查。PV 和 PVC 之间的相互作用遵循以下生命周期:
Provisioning ---> Binding ---> Using ---> Releasing ---> Recycling
-
Provisioning:创建 PV。
- Static:集群管理员创建多个 PV。它们携带可供集群用户使用的真实存储的详细信息。它们存在于 Kubernetes API 中,可用于消费。
- Dynamic:当管理员创建的静态 PV 都不匹配用户的 PersistentVolumeClaim 时,集群可能会尝试为 PVC 动态配置卷。此配置基于 StorageClasses:PVC 必须请求一个类,并且管理员必须已创建并配置该类才能进行动态配置。要求该类的声明有效地为自己禁用动态配置。
-
Binding:将 PV 分配给 PVC。如果匹配的卷不存在,PVC 将保持无限期。 随着匹配卷变得可用,PVC 将被绑定。 例如,提供许多 50Gi PV 的集群将不匹配要求 100Gi的PVC。 当集群中添加 100Gi PV 时,可以绑定 PVC。
值得注意的是,PV 与 PVC 是一对一绑定的。
-
Using:Pod 使用 PVC 作为卷。 集群检查声明以找到绑定的卷并挂载该卷的卷。 对于支持多种访问模式的卷,用户在将其声明用作 Pod 中的卷时指定所需的模式。
-
Releasing:Pod 释放 Volume 并删除 PVC。当用户完成卷时,他们可以从允许资源回收的 API 中删除 PVC 对象。 当 PVC 被删除时,PV 被认为是“释放的”,但是它不能被其它 PVC 使用。 Pod 数据仍然保留在 PV 中。
-
Reclaiming:回收 PV,可以保留 PV 以便下次使用,也可以直接从云存储中删除。删除 PV 将从集群中删除PV 对象,以及删除外部基础架构(如 AWS EBS,GCE PD,Azure Disk 或 Cinder 卷)中关联的存储资产。 动态配置的卷始终被删除。
PV 类型
PersistentVolume 类型作为插件实现。Kubernetes 当前支持以下插件:
-
GCEPersistentDisk
、AWSElasticBlockStore
、AzureFile
、AzureDisk
、CSI
、FC (Fibre Channel)
-
FlexVolume
、Flocker
、NFS
、iSCSI
、RBD (Ceph Block Device)
、CephFS
、Cinder (OpenStack block storage)
-
Glusterfs
、VsphereVolume
、Quobyte Volumes
、Portworx Volumes
、ScaleIO Volumes
、StorageOS
-
HostPath (Single node testing only – local storage is not supported in any way and WILL NOT WORK in a multi-node cluster)
PV 示例
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv0003
spec:
capacity:
storage: 5Gi
volumeMode: Filesystem
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Recycle
storageClassName: slow
mountOptions:
- hard
- nfsvers=4.1
nfs:
path: /tmp
server: 172.17.0.2
capacity
storage
指定了 PV 的容量大小。
当前,存储大小是可以设置或请求的唯一资源。 将来的属性可能包括IOPS,吞吐量等。
volumeMode
文件系统类型,有两种类型:Filesystem 和 Block。这是一个可选的参数,默认为 Filesystem。
如果使用的是 Filesystem,那么在创建 Pod 时会将挂载的卷嵌入到 Pod 的文件目录中。
如果使用的是 Block,此模式对于为 Pod 提供最快的访问卷的方式很有用,而 Pod 和卷之间没有任何文件系统层。
accessModes
主机挂载 PV 的关系,这是一个集合属性,即可以在一个 PV 里面声明多个。PVC 的 accessModes 必须和 PV 的 accessModes 一样或者是 PV 的子集才能够绑定到。
- ReadWriteOnce – 只能在一个节点中挂载,能够读写。
- ReadOnlyMany – 能够在多个节点挂载,只能读。
- ReadWriteMany – 能够在多个节点挂载,能够读写。
storageClassName
为 PV 声明一个类名。PVC 的 storageClassName 必须和这个一致。但是 PV 和 PVC 都可以不设置这个值而使用默认的类名。
persistentVolumeReclaimPolicy
回收策略。Retain、Delete 和 Recycle。默认为 Delete。
-
Retain:保留。删除 PVC 后,PV 仍然存在,并且该 PV 被视为”已释放“。这时 PV 不能再被其它对象绑定,并且数据以然存在该 PV 中。
通常删除一个 PV 的步骤如下:
- 删除 PV,但是与之关联的外部基础架构(如 AWS EBS,GCE PD,Azure Disk 或 Cinder 卷)数据仍然存在。
- 手动清理外部基础架构数据。
- 手动删除外部基础架构。如果要重复使用这个 PV,请重新创建一个。
-
Delete:删除(
rm -rf /thevolume/*
)。从集群中删除 PV 对象以及外部基础架构中的关联存储资产,例如 AWS EBS,GCE PD,Azure Disk 或Cinder 卷。动态预配置的卷将继承其 StorageClass 的回收策略。管理员应根据用户的期望配置 StorageClass。否则,PV 必须在创建后进行编辑或打补丁。
-
Recycle:回收。不推荐使用此策略。相反,推荐的方法是使用动态配置。
mountOptions
安装选项。安装选项是一个字符串,在将卷安装到磁盘时将被累积地连接和使用。当 PV 被创建在某一节点时,管理可以指定额外的安装选项。
值得注意的是并非所有的 PV 类型都支持这个。
如果该选项没有经过验证,那么挂载将会失败。
PVC 示例
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: myclaim
spec:
accessModes:
- ReadWriteOnce
volumeMode: Filesystem
resources:
requests:
storage: 8Gi
storageClassName: slow
selector:
matchLabels:
release: "stable"
matchExpressions:
- {key: environment, operator: In, values: [dev]}
accessModes
必须和 PV 的 accessModes 一样或者是 PV 的子集。
volumeMode
必须和 PV 的 volumeMode 一样。
resources
声明(如pod)可以请求的资源大小。 PVC 会自动去寻找合适大小的 PV 进行匹配。
selector
可以指定标签选择器以进一步过滤 PV。 只有标签与选择器匹配的 PV 才能绑定到 PVC。 选择器可以由两个字段组成:
- matchLabels:PV 必须带有这个标签和值。
- matchExpressions:通过指定关键字和值的关键字,值列表和运算符所做的要求列表。 有效运算符包括 In,NotIn,Exists 和 DoesNotExist。
storageClassName
必须和 PV 的 storageClassName 一样。PV 与 PVC 都没有这个属性。
示例 - NFS
安装 NFS
我们在 harbor 私有仓库这个机器上安装 nfs 服务,IP 地址为:192.168.19.170:
# 安装 nfs 服务及相关工具
[root@hub ~]# yum install -y nfs-common nfs-utils rpcbind
已加载插件:fastestmirror
Determining fastest mirrors
* base: mirrors.nju.edu.cn
* extras: mirrors.njupt.edu.cn
* updates: mirrors.njupt.edu.cn
......
完毕!
# 创建4个共享文件夹
[root@hub ~]# mkdir /nfs1 /nfs2 /nfs3 /nfs4
[root@hub ~]# chmod 777 /nfs1 /nfs2 /nfs3 /nfs4
[root@hub ~]# chown nfsnobody /nfs1 /nfs2 /nfs3 /nfs4
[root@hub ~]# vim /etc/exports
/nfs1 *(rw,no_root_squash,no_all_squash,sync)
/nfs2 *(rw,no_root_squash,no_all_squash,sync)
/nfs3 *(rw,no_root_squash,no_all_squash,sync)
/nfs4 *(rw,no_root_squash,no_all_squash,sync)
[root@hub ~]# systemctl start rpcbind
[root@hub ~]# systemctl start nfs
在集群中所有节点安装 nfs 客户端:
yum install -y nfs-utils rpcbind
安装完成后在集群任一节点测试挂载 nfs 文件系统:
# 测试连接 nfs,结果显示了 nfs 服务器的可挂载目录
[root@k8s-master01 testnfs]# showmount -e 192.168.19.170
Export list for 192.168.19.170:
/nfs4 *
/nfs3 *
/nfs2 *
/nfs1 *
[root@k8s-master01 ~]# pwd
/root
[root@k8s-master01 ~]# mkdir nfs2
# 将本地 /root/nfs2/ 目录挂载到 nfs2 服务器目录
[root@k8s-master01 testnfs]# mount -t nfs 192.168.19.170:/nfs2 /root/nfs2/
[root@k8s-master01 ~]# cd nfs2/
[root@k8s-master01 testnfs]# touch test.txt
[root@k8s-master01 testnfs]# ls
test.txt
# 此时 192.168.19.170 机器上的 /nfs2 目录可以看到 test.txt 这个文件
# 退出连接
[root@k8s-master01 testnfs]# umount -l /root/nfs2/
错误排查:
如果执行命令 showmount -e 192.168.19.170
提示: clnt_create: RPC: Port mapper failure - Unable to receive: errno 113 (No route to host),可以关掉 nfs 服务器的防火墙(systemctl status firewalld),或者开启某些端口(自行网上搜索下)。
创建 PV
编写资源清单 nfs1-pv.yaml:
apiVersion: v1
kind: PersistentVolume
metadata:
name: nfs1-pv
spec:
capacity:
storage: 1Gi
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Recycle
storageClassName: "nfs"
nfs:
path: /nfs1
server: 192.168.19.170
然后再依此编写 nfs2-pv.yaml、nfs3-pv.yaml、nfs4-pv.yaml,基本信息一致,除了以下几点:
- nfs2-pv.yaml
- metadata.name: nfs2-pv
- spec.capacity: 4Gi
- spec.nfs.path: /nfs2
- nfs3-pv.yaml
- metadata.name: nfs3-pv
- spec.capacity: 7Gi
- spec.nfs.path: /nfs3
- nfs4-pv.yaml
- metadata.name: nfs4-pv
- spec.capacity: 10Gi
- spec.nfs.path: /nfs4
这里创建多个 PV 的目的是为了验证 PVC 可以根据容量大小自动选择合适的 PV。
创建 PV:
[root@k8s-master01 pv]# kubectl apply -f nfs1-pv.yaml
persistentvolume/nfs1-pv created
[root@k8s-master01 pv]# kubectl apply -f nfs2-pv.yaml
persistentvolume/nfs2-pv created
[root@k8s-master01 pv]# kubectl apply -f nfs3-pv.yaml
persistentvolume/nfs3-pv created
[root@k8s-master01 pv]# kubectl apply -f nfs4-pv.yaml
persistentvolume/nfs4-pv created
[root@k8s-master01 pv]# kubectl get pv -o wide
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE VOLUMEMODE
nfs1-pv 1Gi RWO Recycle Available nfs 51s Filesystem
nfs2-pv 4Gi RWO Recycle Available nfs 47s Filesystem
nfs3-pv 7Gi RWO Recycle Available nfs 43s Filesystem
nfs4-pv 10Gi RWO Recycle Available nfs 40s Filesystem
加上 -o wide
参数,会多显示 VOLUMEMODE 字段,你会发现,默认的 VOLUMEMODE 是 Filesystem。
上面显示所有 PV 都是 Available 的,即未绑定状态。
创建 Pod 和 PVC
编写资源清单 nfs-pvc-pod.yaml:
apiVersion: v1
kind: Service
metadata:
name: nginx-svc
labels:
app: nginx-app
spec:
ports:
- port: 80
name: web
clusterIP: "None"
selector:
app: nginx-app
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: web
spec:
replicas: 1
serviceName: "nginx-app"
selector:
matchLabels:
app: nginx-app
template:
metadata:
labels:
app: nginx-app
spec:
containers:
- name: nginx-container
image: hub.xixihaha.com/library/mynginx:v1
ports:
- containerPort: 80
name: web
volumeMounts:
- name: pvc-volume
mountPath: /usr/share/nginx/html
volumeClaimTemplates:
- metadata:
name: pvc-volume
spec:
accessModes: ["ReadWriteOnce"]
storageClassName: "nfs"
resources:
requests:
storage: 6Gi
上面是使用 volumeClaimTemplates
创建的 PVC,这种方式会每次都创建一个新的 pvc,即有几个 Pod 就会有几个 pvc。
因此个人建议,将 PVC 单独拿出来创建。这样的话,创建的这个 pvc 能够同时被多个 Pod 绑定:
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: pvc-volume
spec:
accessModes: ["ReadWriteOnce"]
storageClassName: "nfs"
resources:
requests:
storage: 6Gi
如果是单独创建的 PVC,那么我们可以在 Pod 里这样配置它:
# 部分配置
metadata:
labels:
app: nginx-app
spec:
containers:
- name: nginx-container
image: hub.xixihaha.com/library/mynginx:v1
ports:
- containerPort: 80
name: web
volumeMounts:
- name: pvc-volume
mountPath: /usr/share/nginx/html
volumes:
- name: pvc-volume
persistentVolumeClaim:
claimName: pvc-volume
创建 Pod 及 PVC(pvc 使用 volumeClaimTemplates
一并创建):
[root@k8s-master01 pv]# kubectl apply -f nfs-pvc-pod.yaml
service/nginx-svc created
statefulset.apps/web created
查看此时的 pvc、pv 以及 pod:
[root@k8s-master01 pv]# kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
pvc-volume-web-0 Bound nfs3-pv 7Gi RWO nfs 8s
[root@k8s-master01 pv]# kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
nfs1-pv 1Gi RWO Recycle Available nfs 88s
nfs2-pv 4Gi RWO Recycle Available nfs 84s
nfs3-pv 7Gi RWO Recycle Bound default/pvc-volume-web-0 nfs 80s
nfs4-pv 10Gi RWO Recycle Available nfs 77s
[root@k8s-master01 pv]# kubectl get pod
NAME READY STATUS RESTARTS AGE
web-0 1/1 Running 0 3m13s
从结果看到,pvc 绑定到了 nfs3-pv 上面。因为它的大小 7Gi 更适合 pvc 的 6Gi。
因为指定了 pvc 的 accessModes 为 ReadWriteOnce,即一个 pvc 只能被一个 pod 绑定,所以现在我们验证一下。
我们将 statefulSet 扩容到 2 个 Pod,然后查看 pvc 的绑定情况:
[root@k8s-master01 pv]# kubectl scale statefulSet web --replicas 2
statefulset.apps/web scaled
[root@k8s-master01 pv]# kubectl get statefulSet
NAME READY AGE
web 2/2 10m
[root@k8s-master01 pv]# kubectl get pod
NAME READY STATUS RESTARTS AGE
web-0 1/1 Running 0 10m
web-1 1/1 Running 0 12s
[root@k8s-master01 pv]# kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
pvc-volume-web-0 Bound nfs3-pv 7Gi RWO nfs 10m
pvc-volume-web-1 Bound nfs4-pv 10Gi RWO nfs 18s
[root@k8s-master01 pv]# kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
nfs1-pv 1Gi RWO Recycle Available nfs 12m
nfs2-pv 4Gi RWO Recycle Available nfs 12m
nfs3-pv 7Gi RWO Recycle Bound default/pvc-volume-web-0 nfs 11m
nfs4-pv 10Gi RWO Recycle Bound default/pvc-volume-web-1 nfs 11m
因为 nfs3-pv 已经被绑定了,所以扩容的 Pod 只能绑定在除了 nfs3-pv 之外的合适的 nfs4-pv 上面。
-- end --