一、在k8s中为什么要做持久化存储?

在k8s中部署的应用都是以pod容器的形式运行的,假如我们部署MySQL、Redis等数据库,需要对这些数据库产生的数据做备份。因为Pod是有生命周期的,如果pod不挂载数据卷,那pod被删除或重启后这些数据会随之消失,如果想要长久的保留这些数据就要用到pod数据持久化存储。


二、k8s支持哪些存储?

2.1、k8s支持哪些存储?

通过帮助命令查看k8s支持哪些存储:

帮助命令:kubectl explain pods.spec.volumes

root@k8s-master:~# kubectl explain pod.spec.volumes |grep '<*>'
FIELD: volumes <[]Volume>
  awsElasticBlockStore  <AWSElasticBlockStoreVolumeSource>
  azureDisk     <AzureDiskVolumeSource>
  azureFile     <AzureFileVolumeSource>
  cephfs        <CephFSVolumeSource>
  cinder        <CinderVolumeSource>
  configMap     <ConfigMapVolumeSource>
  csi   <CSIVolumeSource>
  downwardAPI   <DownwardAPIVolumeSource>
  emptyDir      <EmptyDirVolumeSource>
  ephemeral     <EphemeralVolumeSource>
  fc    <FCVolumeSource>
  flexVolume    <FlexVolumeSource>
  flocker       <FlockerVolumeSource>
  gcePersistentDisk     <GCEPersistentDiskVolumeSource>
  gitRepo       <GitRepoVolumeSource>
  glusterfs     <GlusterfsVolumeSource>
  hostPath      <HostPathVolumeSource>
  iscsi <ISCSIVolumeSource>
  name  <string> -required-
  nfs   <NFSVolumeSource>
  persistentVolumeClaim <PersistentVolumeClaimVolumeSource>
  photonPersistentDisk  <PhotonPersistentDiskVolumeSource>
  portworxVolume        <PortworxVolumeSource>
  projected     <ProjectedVolumeSource>
  quobyte       <QuobyteVolumeSource>
  rbd   <RBDVolumeSource>
  scaleIO       <ScaleIOVolumeSource>
  secret        <SecretVolumeSource>
  storageos     <StorageOSVolumeSource>
  vsphereVolume <VsphereVirtualDiskVolumeSource>

从上述帮助命令的结果看,k8s支持多种存储方式。

2.2、常用的存储方式

常用的存储方式:

  • emptyDir
  • hostPath
  • nfs
  • persistentVolumeClaim
  • glusterfs
  • cephfs
  • configMap
  • secret

如何使用存储卷?

需要经历如下步骤:

1、定义pod的volume,这个volume指明它要关联到哪个存储上的

2、在容器中要使用volumemounts挂载对应的存储

经过以上两步才能正确的使用存储卷。


三、常用存储方式介绍及举例

3.1、k8s持久化存储:emptyDir

图片中有些地方字母c不知为何打不出来,用空格代替的,需注意。

emptyDir类型的Volume是在Pod分配到Node上时被创建,Kubernetes会在Node上自动分配一个目录,因此无需指定宿主机Node上对应的目录文件。 这个目录的初始内容为空,当Pod从Node上移除时,emptyDir中的数据会被永久删除。emptyDir Volume主要用于某些应用程序无需永久保存的临时目录,多个容器的共享目录等。

创建一个pod,挂载临时目录emptyDir:

这里用这个挂载临时目录emptyDir的pod举例,介绍一下我学到的书写yaml文件的技巧,不需要死记硬背。以创建Pod为例,其他资源技巧一样。

首先这里我们明确是要创建一个pod资源,然后需要挂载一个临时目录。

第一步:

通过帮助命令查看pod资源下有哪些字段:kubectl explain pod

​K8s持久化存储,pv?pvc?_持久化存储

上面的帮助命令显示的是全部的帮助文档,主要是为了获得KIND、VERSION、GROUP等这几个值,一般在帮助命令的前几行,主要是为了确定字段apiVersion和kind这两个字段的值,这里我们可以确定apiVersion的值为v1;kind的值为Pod,必须是和帮助命令中的值是一样的有大小写的区分。

熟练之后我们不需要显示所有的帮助命令信息,只需要我们需要的信息就可以了。我们可以用以下命令简化帮助命令:

kubectl explain pod |grep '<*>', 过滤帮助命令输出的信息中包含<*>的行,因为这样的行基本都是资源的字段

​K8s持久化存储,pv?pvc?_hostPath_02

通过上述帮助命令,我们可以开始写Pod的yaml文件,创建一个Eg-Pod.yaml

其中apiVersion的值为v1,kind的值为Pod。status的值不可修改,目前还用不到这个字段;另外只剩下两个字段spec和metadata。

​K8s持久化存储,pv?pvc?_pvc_03

第二步:

通过帮助命令查看Pod资源下metadata字段下有什么字段:

​K8s持久化存储,pv?pvc?_hostPath_04

根据帮助命令,我们可以看到metadata字段下有多个子字段,不知道注意到没有?每个字段后面有个<>,这个我理解为字段的数据类型。比如:

name  <string>     # 我理解为字符串类型

finalizers  <[]string>    # 我理解为字符串列表类型

labels   <map[string]string>   # 我理解为键值对类型

generation   <integer>    # 我理解为整数类型

其他带[]的数据类型,我都理解为一种列表类型

不同的数据类型,在书写yaml的时候都有其书写技巧,不同的资源类型,对应的字段根据数据类型书写方式是一样的,一种弄懂了,其他的也是一样的。

字符串类型:字段名称: 字段值     # 字段名称直接用": "+字段的值

举例如下:

name: pod-emptydir          # name字段是字符串类型,用冒号空格+字段值

字符串列表类型:字段名称:换行用"- "连接子字段,只有第一个子字段需要用"- "连接,多个子字段则与第一个子字段左对齐即可

举例如下:

ports:									# ports字段是字符串列表类型
- name: http						# 第一个字段name用"- "和父字段ports连接
  containerPort: 80			# 第二个字段和第一个子字段name左对齐

整数类型:字段名称: 整数值       # 字段名称直接用": "+字段的整数值

举例如下:

replicas: 3				# replicas字段为整数类型,用冒号空格+字段整数值

键值对类型:字段名称:换行用缩进2个字符连接键值对,多个键值对与第一个键值对对齐

举例如下:

metadata:
  labels:								# labels字段是键值对类型
    app: myapp					# 第一个键值对和父字段缩进2个字符连接
    name: nginx					# 多个键值对和第一个键值对对齐

这里我们继续写yaml文件,继续写metadata字段的子字段,这里选取name和namespace,以及labels字段。

​K8s持久化存储,pv?pvc?_nfs_05

第三步:

通过帮助命令查看Pod资源下spec字段下有什么字段:

root@k8s-master:~/K8sStudy/Chapter2-10# kubectl explain pod.spec |grep '<*>'
FIELD: spec <PodSpec>
  activeDeadlineSeconds <integer>
  affinity      <Affinity>
  automountServiceAccountToken  <boolean>
  containers    <[]Container> -required-
  dnsConfig     <PodDNSConfig>
  dnsPolicy     <string>
  enableServiceLinks    <boolean>
  ephemeralContainers   <[]EphemeralContainer>
  hostAliases   <[]HostAlias>
  hostIPC       <boolean>
  hostNetwork   <boolean>
  hostPID       <boolean>
  hostUsers     <boolean>
  hostname      <string>
  imagePullSecrets      <[]LocalObjectReference>
  initContainers        <[]Container>
  nodeName      <string>
  nodeSelector  <map[string]string>
  os    <PodOS>
  overhead      <map[string]Quantity>
  preemptionPolicy      <string>
  priority      <integer>
  priorityClassName     <string>
  readinessGates        <[]PodReadinessGate>
  resourceClaims        <[]PodResourceClaim>
  restartPolicy <string>
  runtimeClassName      <string>
  schedulerName <string>
  schedulingGates       <[]PodSchedulingGate>
  securityContext       <PodSecurityContext>
  serviceAccount        <string>
  serviceAccountName    <string>
  setHostnameAsFQDN     <boolean>
  shareProcessNamespace <boolean>
  subdomain     <string>
    "<hostname>.<subdomain>.<pod namespace>.svc.<cluster domain>". If not
  terminationGracePeriodSeconds <integer>
  tolerations   <[]Toleration>
  topologySpreadConstraints     <[]TopologySpreadConstraint>
  volumes       <[]Volume>

通过帮助命令,我们可以看到spec字段下有很多个子字段,也是有字段值和数据类型,但是唯一值得注意的是containers    <[]Container> -required-字段,这个字段后面有个required,表示这个字段是必须要写的,同时这个字段的数据类型中有个[],表示这个字段的数据类型是列表。这里我选去了spec的两个字段:containers    <[]Container> -required-和volumes       <[]Volume>,两个字段都是列表类型。

​K8s持久化存储,pv?pvc?_hostPath_06

继续写yaml文件,spec字段下选取了两个字段containers和volumes。

​K8s持久化存储,pv?pvc?_hostPath_07

继续查看containers字段下还有哪些子字段?

​K8s持久化存储,pv?pvc?_pvc_08

这里选取了4个子字段:

image:    字符串类型,配置镜像名称,直接冒号空格+镜像名称

imagePullPolicy:    字符串类型,镜像拉取策略,直接冒号空格+拉取策略。IfNotPresent,Nerver,Always等可选其一。

name:      不许字段必须写,字符串类型,直接冒号空格+名称

volumeMounts:   列表类型,子字段用"- "连接

继续查看volumeMounts字段下还有哪些字段?

​K8s持久化存储,pv?pvc?_hostPath_09

继续写yaml文件,containers字段下选取了4个字段,image、imagePullPolicy、name、volumeMounts;其中volumeMounts字段下还有两个字段mountPath和name。

​K8s持久化存储,pv?pvc?_nfs_10

继续查看spec下volumes字段下还有哪些字段?

​K8s持久化存储,pv?pvc?_pvc_11

可以看到spec.volumes字段下有很多子字段,这里选取两个字段name和emptyDir。

name字段是指存储卷的名称,这个名称和spec.containers.volumeMounts.name的名称需要一致。

继续写yaml文件,spec.volumes字段下选取两个字段name和emptyDir。

​K8s持久化存储,pv?pvc?_pv_12

至此,一个完整的Pod的资源清单文件就写完了。

查看一下完整的Pod 的yaml文件。

root@k8s-master:~/K8sStudy/Chapter2-10# cat Eg-Pod.yaml 
apiVersion: v1
kind: Pod
metadata:
  name: pod-empty
  namespace: default
  labels:
    app: myapp
    name: nginx
spec:
  containers:
  - name: nginx
    image: docker.io/library/nginx:latest
    imagePullPolicy: IfNotPresent
    volumeMounts:
    - mountPath: /usr/share/nginx/html
      name: nginx
  volumes:
  - name: nginx
    emptyDir: {}

应用/更新清单文件Eg-Pod.yaml:

root@k8s-master:~/K8sStudy/Chapter2-10# kubectl apply -f Eg-Pod.yaml 
pod/pod-empty created
root@k8s-master:~/K8sStudy/Chapter2-10#

​K8s持久化存储,pv?pvc?_持久化存储_13

可以看到pod正常被创建并正常运行,所以yaml文件没有问题。

查看本机临时目录存在的位置,可用如下方法:

root@k8s-master:~/K8sStudy/Chapter2-10# kubectl get pods pod-empty -o yaml | grep uid
  uid: a732f3a6-9942-4708-8913-9a73dcd5a815
root@k8s-master:~/K8sStudy/Chapter2-10#

在node节点的/var/lib/kubelet/pods/a732f3a6-9942-4708-8913-9a73dcd5a815即为临时目录的路径。

root@k8s-node:/var/lib/kubelet/pods/a732f3a6-9942-4708-8913-9a73dcd5a815# tree ./
./
├── containers
│   └── nginx
│       └── 169e73d7
├── etc-hosts
├── plugins
│   └── kubernetes.io~empty-dir
│       ├── nginx
│       │   └── ready
│       └── wrapped_kube-api-access-zdv5g
│           └── ready
└── volumes
    ├── kubernetes.io~empty-dir
    │   └── nginx
    └── kubernetes.io~projected
        └── kube-api-access-zdv5g
            ├── ca.crt -> ..data/ca.crt
            ├── namespace -> ..data/namespace
            └── token -> ..data/token

11 directories, 7 files
root@k8s-node:/var/lib/kubelet/pods/a732f3a6-9942-4708-8913-9a73dcd5a815# cd volumes/kubernetes.io~empty-dir/nginx/
root@k8s-node:/var/lib/kubelet/pods/a732f3a6-9942-4708-8913-9a73dcd5a815/volumes/kubernetes.io~empty-dir/nginx# echo aaa > index.html
root@k8s-node:/var/lib/kubelet/pods/a732f3a6-9942-4708-8913-9a73dcd5a815/volumes/kubernetes.io~empty-dir/nginx#

在node节点上找到了临时目录的路径/var/lib/kubelet/pods/a732f3a6-9942-4708-8913-9a73dcd5a815/volumes/kubernetes.io~empty-dir/nginx。用echo命令创建了一个内容为aaa的index.html文件,也就是Nginx默认网页。

再次访问pod的80端口,可以看到刚才在node节点上创建的内容为aaa的网页内容了。

​K8s持久化存储,pv?pvc?_hostPath_14

3.2、k8s持久化存储:hostPath

hostPath Volume是指Pod挂载宿主机上的目录或文件。 hostPath Volume使得容器可以使用宿主机的文件系统进行存储,hostpath(宿主机路径):节点级别的存储卷,在pod被删除,这个存储卷还是存在的,不会被删除,所以只要同一个pod被调度到同一个节点上来,在pod被删除重新被调度到这个节点之后,对应的数据依然是存在的。

1、查看hostPath存储卷的用法:kubectl explain pod.spec.volumes.hostPaht

​K8s持久化存储,pv?pvc?_pv_15


#把tomcat.tar.gz上传到xianchaonode2和xianchaonode1上,手动解压:

[root@xianchaonode2 ~]# ctr -n=k8s.io images import  tomcat.tar.gz

[root@xianchaonode1 ~]# ctr -n=k8s.io images import  tomcat.tar.gz

2、创建一个pod,挂载hostPath存储卷

查看yaml清单文件Eg-Pod-hostPath.yaml

root@k8s-master:~/K8sStudy/Chapter2-10# cat Eg-Pod-hostPath.yaml 
apiVersion: v1
kind: Pod
metadata:
  name: pod-volumes-hostpath
  namespace: default
  labels:
    app: mynginx
    name: volumes-hostpath
spec:
  containers:
  - name: nginx
    image: docker.io/library/nginx:latest
    imagePullPolicy: IfNotPresent
    volumeMounts:
    - mountPath: /usr/share/nginx/html			# 将存储卷挂载在pod中的挂载点
      name: nginx		# 挂载的卷为nginx,和下方定义的存储卷的名称一致,表示挂载下方的定义的卷
  volumes:
  - name: nginx				# 创建的卷的名称为nginx
    hostPath:					# 存储方式为hostPath
      path: /mnt/nginx			# hostPath在宿主机的路径,绝对路径
      type: DirectoryOrCreate		# 存储路径为目录,调度的节点上有则直接用,不存在则创建

注意:DirectoryOrCreate表示本地有/data1目录,就用本地的,本地没有就会在pod调度到的节点自动创建一个。

应用/更新清单文件Eg-Pod-hostPath.yaml

root@k8s-master:~/K8sStudy/Chapter2-10# kubectl apply -f Eg-Pod-hostPath.yaml 
pod/pod-volumes-hostpath created
root@k8s-master:~/K8sStudy/Chapter2-10# 

查看创建的pod的状态:

​K8s持久化存储,pv?pvc?_nfs_16

请求pod中的web站点

​K8s持久化存储,pv?pvc?_持久化存储_17

发现站点报403错误,原因是我们挂载的 /mnt/nginx是个空目录,挂载到pod中的/usr/share/nginx/html,将默认的网站页面index.html给覆盖掉了,nginx找不到网页页面则报403错误,我们可以再node节点的/mnt/nginx目录中创建一个index.html文件,内容为pod-volumes-hostPath。然后再请求pod中的web站点,看看是否能得到网页结果内容是pod-volumes-hostPath。

​K8s持久化存储,pv?pvc?_hostPath_18

通过上述结果,可确认,node节点的/mnt/nginx被创建出来了,默认/mnt下是没有nginx这个目录的,而且该路径确实是挂载到了pod中的/usr/share/nginx/html。

模拟删除pod,看看新生成的pod的web站点的请求结果是否还是pod-volumes-hostPath。如果是的话,就能证明hostPath的存储方式在pod被删除情况下,数据是不会删除的。

​K8s持久化存储,pv?pvc?_hostPath_19

结果证明hostPath的存储方式在pod被删除情况下,数据是不会删除的。

hostpath存储卷缺点:

  • 单节点
  • pod删除之后重新创建必须调度到同一个node节点,数据才不会丢失

3.3、k8s持久化存储:nfs

NFS:网络文件系统,英文Network File System(NFS),是由SUN公司研制的UNIX表示层协议(presentation layer protocol),能使使用者访问网络上别处的文件就像在使用自己的计算机一样。

1、搭建nfs服务

以k8s的控制节点作为NFS服务端,我这里是Ubuntu系统

# 安装nfs服务
root@k8s-master:~/K8sStudy/Chapter2-10# apt-get -y install nfs-kernel-server 
# 创建nfs数据目录
root@k8s-master:~/K8sStudy/Chapter2-10# mkdir -pv /data/volumes-nfs
mkdir: created directory '/data'
mkdir: created directory '/data/volumes-nfs'
# 配置创建的数据目录为nfs的共享目录
root@k8s-master:~/K8sStudy/Chapter2-10# cat /etc/exports
# /etc/exports: the access control list for filesystems which may be exported
#               to NFS clients.  See exports(5).
#
# Example for NFSv2 and NFSv3:
# /srv/homes       hostname1(rw,sync,no_subtree_check) hostname2(ro,sync,no_subtree_check)
#
# Example for NFSv4:
# /srv/nfs4        gss/krb5i(rw,sync,fsid=0,crossmnt,no_subtree_check)
# /srv/nfs4/homes  gss/krb5i(rw,sync,no_subtree_check)
#
/data/volumes-nfs *(rw,no_root_squash)
# 重启nfs服务,并设置开机启动
root@k8s-master:~/K8sStudy/Chapter2-10# systemctl enable nfs-server.service --now
# 查看nfs服务状态
root@k8s-master:~/K8sStudy/Chapter2-10# 
root@k8s-master:~/K8sStudy/Chapter2-10# systemctl status nfs-server.service 
● nfs-server.service - NFS server and services
     Loaded: loaded (/lib/systemd/system/nfs-server.service; enabled; vendor preset: enabled)
    Drop-In: /run/systemd/generator/nfs-server.service.d
             └─order-with-mounts.conf
     Active: active (exited) since Mon 2024-05-20 11:31:03 CST; 4s ago
    Process: 180437 ExecStartPre=/usr/sbin/exportfs -r (code=exited, status=0/SUCCESS)
    Process: 180438 ExecStart=/usr/sbin/rpc.nfsd (code=exited, status=0/SUCCESS)
   Main PID: 180438 (code=exited, status=0/SUCCESS)
        CPU: 7ms

May 20 11:31:03 k8s-master systemd[1]: Starting NFS server and services...
May 20 11:31:03 k8s-master exportfs[180437]: exportfs: /etc/exports [1]: Neither 'subtree_check' or 'no_subtree_check' specified for export "*:/data/volu>
May 20 11:31:03 k8s-master exportfs[180437]:   Assuming default behaviour ('no_subtree_check').
May 20 11:31:03 k8s-master exportfs[180437]:   NOTE: this default has changed since nfs-utils version 1.0.x
May 20 11:31:03 k8s-master systemd[1]: Finished NFS server and services.
root@k8s-master:~/K8sStudy/Chapter2-10# 

nfs服务配置文件注解:

rw 该主机对该共享目录有读写权限;no_root_squash 登入 NFS 主机使用分享目录的使用者,如果是 root 的话,那么对于这个分享的目录来说,他就具有 root 的权限。

查看nfs配置是否生效:showmount -e 192.168.60.140,  192.168.60.140是nfs服务的服务器地址,这里是master的物理ip地址。另外有一条命令是nfs的配置生效 exportfs -arv

root@k8s-master:~/K8sStudy/Chapter2-10# showmount -e 192.168.60.140
Export list for 192.168.60.140:
/data/volumes-nfs *					# 可以看到配置的共享目录
root@k8s-master:~/K8sStudy/Chapter2-10# 

在node节点上尝试挂载nfs共享目录,验证nfs服务是否正常可用。

# 安装nfs客户端
root@k8s-node:~# apt-get -y install nfs-kernel-server 
# 挂载nfs共享目录
root@k8s-node:~# mount 192.168.60.140:/data/volumes-nfs /mnt
# 确认是否挂载
root@k8s-node:~# df -h |grep 'volumes-nfs'
192.168.60.140:/data/volumes-nfs    14G  9.3G  3.8G  72% /mnt				# 表示挂载成功
# 取消挂载
root@k8s-node:~# umount /mnt
root@k8s-node:~# df -h |grep 'volumes-nfs'	
root@k8s-node:~# 			# 取消挂载成功

挂载成功。nfs服务可用。

2、创建Pod,挂载NFS共享出来的目录

查看Pod使用nfs存储卷的用法:kubectl explain pod.spec.volumes.nfs

​K8s持久化存储,pv?pvc?_pvc_20

查看yaml资源清单文件:Eg-Pod-nfs.yaml 

root@k8s-master:~/K8sStudy/Chapter2-10# cat Eg-Pod-nfs.yaml 
apiVersion: v1
kind: Pod
metadata:
  name: pod-volumes-nfs
  namespace: default
  labels:
    app: mynginx
    name: volumes-nfs
spec:
  containers:
  - name: nginx
    image: docker.io/library/nginx:latest
    imagePullPolicy: IfNotPresent
    volumeMounts:
    - mountPath: /usr/share/nginx/html
      name: nginx
  volumes:
  - name: nginx
    nfs:			# 存储卷方式为nfs
      path: /data/volumes-nfs			# nfs共享目录
      server: 192.168.60.140			# nfs服务器ip地址

注:path:  /data/volumes-nfs #nfs的共享目录

 server:192.168.60.140是master机器的ip,这个是安装nfs服务的地址

更新资源清单文件Eg-Pod-nfs.yaml 

root@k8s-master:~/K8sStudy/Chapter2-10# kubectl apply -f Eg-Pod-nfs.yaml
pod/pod-volumes-nfs created
root@k8s-master:~/K8sStudy/Chapter2-10#

查看pod状态

​K8s持久化存储,pv?pvc?_hostPath_21

请求pod中的web站点

​K8s持久化存储,pv?pvc?_pv_22

发现站点报403错误,原因是我们挂载的192.168.60.140:/data/volumes-nfs是个空目录,挂载到pod中的/usr/share/nginx/html,将默认的网站页面index.html给覆盖掉了,nginx找不到网页页面则报403错误,我们可以在nfs(这里也就是master)的/data/volumes-nfs目录中创建一个index.html文件,内容为pod-volumes-nfs。然后再请求pod中的web站点,看看是否能得到网页结果内容是pod-volumes-nfs。

​K8s持久化存储,pv?pvc?_hostPath_23

通过上述结果,可确认,nfs共享的数据目录确实是挂载到了pod中的/usr/share/nginx/html。

上面说明挂载nfs存储卷成功了,nfs支持多个客户端挂载,可以创建多个pod,挂载同一个nfs服务器共享出来的目录;但是nfs如果宕机了,数据也就丢失了,所以需要使用分布式存储,常见的分布式存储有glusterfs和cephfs

3.4、k8s持久化存储:PVC

3.4.1、k8s PV是什么?

PersistentVolume(PV)是群集中的一块存储,由管理员配置或使用存储类动态配置。 它是集群中的资源,就像pod是k8s集群资源一样。 PV是容量插件,如Volumes,其生命周期独立于使用PV的任何单个pod。

3.4.2、k8s PVC是什么?

PersistentVolumeClaim(PVC)是一个持久化存储卷,我们在创建pod时可以定义这个类型的存储卷。 它类似于一个pod。 Pod消耗节点资源,PVC消耗PV资源。 Pod可以请求特定级别的资源(CPU和内存)。 pvc在申请pv的时候也可以请求特定的大小和访问模式(例如,可以一次读写或多次只读)。

3.4.3、k8s PVC和PV工作原理

PV是群集中的资源。 PVC是对这些资源的请求。

PV和PVC之间的相互作用遵循以下生命周期:

  1. pv的供应方式

可以通过两种方式配置PV:静态或动态。

  • 静态的:集群管理员创建了许多PV。它们包含可供群集用户使用的实际存储的详细信息。它们存在于Kubernetes API中,可供使用。
  • 动态的:当管理员创建的静态PV都不匹配用户的PersistentVolumeClaim时,群集可能会尝试为PVC专门动态配置卷。此配置基于StorageClasses,PVC必须请求存储类,管理员必须创建并配置该类,以便进行动态配置。
  1. 绑定

用户创建pvc并指定需要的资源和访问模式。在找到可用pv之前,pvc会保持未绑定状态

  1. 使用
  • 需要找一个存储服务器,把它划分成多个存储空间;
  • k8s管理员可以把这些存储空间定义成多个pv;
  • 在pod中使用pvc类型的存储卷之前需要先创建pvc,通过定义需要使用的pv的大小和对应的访问模式,找到合适的pv;
  • pvc被创建之后,就可以当成存储卷来使用了,我们在定义pod时就可以使用这个pvc的存储卷
  • pvc和pv它们是一一对应的关系,pv如果被pvc绑定了,就不能被其他pvc使用了;
  • 我们在创建pvc的时候,应该确保和底下的pv能绑定,如果没有合适的pv,那么pvc就会处于pending状态。
  1. 回收策略

当我们创建pod时如果使用pvc做为存储卷,那么它会和pv绑定,当删除pod,pvc和pv绑定就会解除,解除之后和pvc绑定的pv卷里的数据需要怎么处理,目前,卷可以保留,回收或删除:Retain、Recycle (不推荐使用,1.15可能被废弃了)、Delete

  • Retain:当删除pvc的时候,pv仍然存在,处于released状态,但是它不能被其他pvc绑定使用,里面的数据还是存在的,当我们下次再使用的时候,数据还是存在的,这个是默认的回收策略
  • Delete:删除pvc时即会从Kubernetes中移除PV,也会从相关的外部设施中删除存储资产

3.4.4、创建pod,使用pvc作为持久化存储卷

1、创建nfs共享目录

# 在宿主机master创建NFS需要的共享目录/data/volumes/v{1..3}
root@k8s-master:~# mkdir -pv /data/volumes/v{1..3}
mkdir: created directory '/data/volumes/v1'
mkdir: created directory '/data/volumes/v2'
mkdir: created directory '/data/volumes/v3'
# 将创建的共享目录通过nfs共享出去。修改配置文件如下
root@k8s-master:~# cat /etc/exports 
# /etc/exports: the access control list for filesystems which may be exported
#               to NFS clients.  See exports(5).
#
# Example for NFSv2 and NFSv3:
# /srv/homes       hostname1(rw,sync,no_subtree_check) hostname2(ro,sync,no_subtree_check)
#
# Example for NFSv4:
# /srv/nfs4        gss/krb5i(rw,sync,fsid=0,crossmnt,no_subtree_check)
# /srv/nfs4/homes  gss/krb5i(rw,sync,no_subtree_check)
#
/data/volumes-nfs *(rw,no_root_squash)
/data/volumes/v1 *(rw,no_root_squash)
/data/volumes/v2 *(rw,no_root_squash)
/data/volumes/v3 *(rw,no_root_squash)
# 使配置文件生效
root@k8s-master:~# exportfs -arv
exportfs: /etc/exports [1]: Neither 'subtree_check' or 'no_subtree_check' specified for export "*:/data/volumes-nfs".
  Assuming default behaviour ('no_subtree_check').
  NOTE: this default has changed since nfs-utils version 1.0.x

exportfs: /etc/exports [2]: Neither 'subtree_check' or 'no_subtree_check' specified for export "*:/data/volumes/v1".
  Assuming default behaviour ('no_subtree_check').
  NOTE: this default has changed since nfs-utils version 1.0.x

exportfs: /etc/exports [3]: Neither 'subtree_check' or 'no_subtree_check' specified for export "*:/data/volumes/v2".
  Assuming default behaviour ('no_subtree_check').
  NOTE: this default has changed since nfs-utils version 1.0.x

exportfs: /etc/exports [4]: Neither 'subtree_check' or 'no_subtree_check' specified for export "*:/data/volumes/v3".
  Assuming default behaviour ('no_subtree_check').
  NOTE: this default has changed since nfs-utils version 1.0.x

exporting *:/data/volumes/v3
exporting *:/data/volumes/v2
exporting *:/data/volumes/v1
exporting *:/data/volumes-nfs
# 查看nfs服务所有的共享目录
root@k8s-master:~# showmount -e 192.168.60.140
Export list for 192.168.60.140:
/data/volumes/v3  *
/data/volumes/v2  *
/data/volumes/v1  *
/data/volumes-nfs *
root@k8s-master:~#

2、如何编写pv的资源清单文件

查看定义pv需要的字段:kubectl explain pv 

root@k8s-master:~# kubectl explain pv |grep '<*>'
  apiVersion    <string>
  kind  <string>
  metadata      <ObjectMeta>
  spec  <PersistentVolumeSpec>
  status        <PersistentVolumeStatus>

查看定义pv的spec需要哪些字段:kubectl explain pv.spec

root@k8s-master:~/K8sStudy/Chapter2-10# kubectl explain pv.spec |grep '<*>'
FIELD: spec <PersistentVolumeSpec>
  accessModes   <[]string>
  awsElasticBlockStore  <AWSElasticBlockStoreVolumeSource>
  azureDisk     <AzureDiskVolumeSource>
  azureFile     <AzureFilePersistentVolumeSource>
  capacity      <map[string]Quantity>
  cephfs        <CephFSPersistentVolumeSource>
  cinder        <CinderPersistentVolumeSource>
  claimRef      <ObjectReference>
  csi   <CSIPersistentVolumeSource>
  fc    <FCVolumeSource>
  flexVolume    <FlexPersistentVolumeSource>
  flocker       <FlockerVolumeSource>
  gcePersistentDisk     <GCEPersistentDiskVolumeSource>
  glusterfs     <GlusterfsPersistentVolumeSource>
  hostPath      <HostPathVolumeSource>
  iscsi <ISCSIPersistentVolumeSource>
  local <LocalVolumeSource>
  mountOptions  <[]string>
  nfs   <NFSVolumeSource>
  nodeAffinity  <VolumeNodeAffinity>
  persistentVolumeReclaimPolicy <string>
  photonPersistentDisk  <PhotonPersistentDiskVolumeSource>
  portworxVolume        <PortworxVolumeSource>
  quobyte       <QuobyteVolumeSource>
  rbd   <RBDPersistentVolumeSource>
  scaleIO       <ScaleIOPersistentVolumeSource>
  storageClassName      <string>
  storageos     <StorageOSPersistentVolumeSource>
  volumeMode    <string>
  vsphereVolume <VsphereVirtualDiskVolumeSource>

查看定义nfs类型的pv需要的字段:kubectl explain pv.spec.nfs

root@k8s-master:~# kubectl explain pv.spec.nfs |grep '<*>'
FIELD: nfs <NFSVolumeSource>
  path  <string> -required-
  readOnly      <boolean>
  server        <string> -required-

3、创建pv,查看yaml清单文件

root@k8s-master:~/K8sStudy/Chapter2-10# cat Eg-pv-nfs.yaml 
apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv-v1
  namespace: default
  labels:
    app: v1
spec:
  accessModes: ["ReadWriteOnce"]
  nfs:
    path: /data/volumes/v1
    server: 192.168.60.140
  capacity:
    storage: 20M
---
apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv-v2
  namespace: default
  labels:
    app: v2
spec:
  accessModes: ["ReadOnlyMany"]
  nfs:
    path: /data/volumes/v2
    server: 192.168.60.140
  capacity:
    storage: 20M
---
apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv-v3
  namespace: default
  labels:
    app: v3
spec:
  accessModes: ["ReadWriteMany"]
  nfs:
    path: /data/volumes/v3
    server: 192.168.60.140
  capacity:
    storage: 20M

应用/更新yaml资源清单文件:Eg-pv-nfs.yaml 

root@k8s-master:~/K8sStudy/Chapter2-10# kubectl apply -f Eg-pv-nfs.yaml
persistentvolume/pv-v1 created
persistentvolume/pv-v2 created
persistentvolume/pv-v3 created

查看创建的pv:kubectl get pv

​K8s持久化存储,pv?pvc?_pvc_24

注解:RWO:readwariteonce: 单路读写,允许同一个node节点上的pod访问

    ROX: readonlymany:   多路只读,允许不通node节点的pod以只读方式访问

     RWX:readwritemany:  多路读写,允许不同的node节点的pod以读写方式访问

4、创建pvc,和符合条件的pv绑定

查看定义pvc需要的字段:kubectl explain pvc

root@k8s-master:~/K8sStudy/Chapter2-10# kubectl explain pvc |grep '<*>'
  apiVersion    <string>
  kind  <string>
  metadata      <ObjectMeta>
  spec  <PersistentVolumeClaimSpec>
  status        <PersistentVolumeClaimStatus>

查看定义pvc.spec需要的字段:kubectl explain pvc.spec

root@k8s-master:~/K8sStudy/Chapter2-10# kubectl explain pvc.spec |grep '<*>'
FIELD: spec <PersistentVolumeClaimSpec>
  accessModes   <[]string>
  dataSource    <TypedLocalObjectReference>
  dataSourceRef <TypedObjectReference>
  resources     <ResourceRequirements>
  selector      <LabelSelector>
  storageClassName      <string>
  volumeMode    <string>
  volumeName    <string>

创建pv,查看yaml清单文件Eg-pvc.yaml

root@k8s-master:~/K8sStudy/Chapter2-10# cat Eg-pvc.yaml 
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: pvc-v1
  namespace: default
spec:
  accessModes: ["ReadWriteOnce"]
  selector:
    matchLabels:
      app: v1
  resources:
    requests:
      storage: 20M
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: pvc-v2
  namespace: default
spec:
  accessModes: ["ReadOnlyMany"]
  selector:
    matchLabels:
      app: v2
  resources:
    requests:
      storage: 20M
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: pvc-v3
  namespace: default
spec:
  accessModes: ["ReadWriteMany"]
  selector:
    matchLabels:
      app: v3
  resources:
    requests:
      storage: 20M

应用/更新资源清单文件:kubectl apply -f Eg-pvc.yaml

root@k8s-master:~/K8sStudy/Chapter2-10# kubectl apply -f Eg-pvc.yaml 
persistentvolumeclaim/pvc-v1 created
persistentvolumeclaim/pvc-v2 created
persistentvolumeclaim/pvc-v3 created

查看创建的pvc:kubectl get pvc

​K8s持久化存储,pv?pvc?_pv_25

看到pvc的status都是bound状态,就说明pvc跟pv已经绑定了

5、创建pod,挂载pvc

查看pod使用pvc作为存储卷需要的字段:kubectl explain pod.spec.volumes.persistentVolumeClaim

root@k8s-master:~/K8sStudy/Chapter2-10# kubectl explain pod.spec.volumes.persistentVolumeClaim |grep '<*>'
FIELD: persistentVolumeClaim <PersistentVolumeClaimVolumeSource>
  claimName     <string> -required-
  readOnly      <boolean>

查看yaml资源清单文件:Eg-Pod-pvc.yaml

root@k8s-master:~/K8sStudy/Chapter2-10# cat Eg-Pod-pvc.yaml 
apiVersion: apps/v1
kind: Deployment
metadata:
  name: pod-pvc-test
spec:
  replicas: 2
  selector:
    matchLabels:
      storage: pvc
  template: 
    metadata:
      labels:
        storage: pvc
    spec:
      containers:
      - name: nginx
        image: docker.io/library/nginx:latest 
        imagePullPolicy: IfNotPresent
        ports:
        - containerPort: 80
          protocol: TCP
        volumeMounts:
        - name: nginx-html
          mountPath: /usr/share/nginx/html
      volumes:
      - persistentVolumeClaim:
          claimName: pvc-v1
        name: nginx-html

应用/更新yaml资源清单文件:Eg-Pod-pvc.yaml

root@k8s-master:~/K8sStudy/Chapter2-10# kubectl apply -f Eg-Pod-pvc.yaml
deployment.apps/pod-pvc-test created

查看创建的Deployment和pod

​K8s持久化存储,pv?pvc?_持久化存储_26

验证pvc:请求pod中的web服务,也就是Deployment中容器镜像为nginx

​K8s持久化存储,pv?pvc?_pvc_27

发现站点报403错误,原因是我们挂载的pvc绑定的pvc是个空目录,挂载到pod中的/usr/share/nginx/html,将默认的网站页面index.html给覆盖掉了,nginx找不到网页页面则报403错误,我们可以在pvc绑定的pv-1(这里是master主机上/data/volumes/v1)目录中创建一个index.html文件,内容为pod-volumes-pvc-v1。然后再请求pod中的web站点,看看是否能得到网页结果内容是pod-volumes-pvc-v1。

​K8s持久化存储,pv?pvc?_hostPath_28

上述结果证明,pod通过挂载pvc可以将数据存储到pv中。

注:使用pvc和pv的注意事项

1、我们每次创建pvc的时候,需要事先有划分好的pv,这样可能不方便,那么可以在创建pvc的时候直接动态创建一个pv这个存储类,pv事先是不存在的。

2、pvc和pv绑定,如果使用默认的回收策略retain,那么删除pvc之后,pv会处于released状态,我们想要继续使用这个pv,需要手动删除pv,kubectl delete pv pv_name,删除pv,不会删除pv里的数据,当我们重新创建pvc时还会和这个最匹配的pv绑定,数据还是原来数据,不会丢失。

经过测试,如果回收策略是Delete,删除pv,pv后端存储的数据也不会被删除

回收策略:persistentVolumeReclaimPolicy字段

3.4.5、删除pvc的步骤

需要先删除使用pvc的pod,再删除pvc。

演示删除pvc:

直接删除在使用中的pvc,会卡住删不掉。

​K8s持久化存储,pv?pvc?_持久化存储_29

需要先删除使用pvc的pod或者控制器。

​K8s持久化存储,pv?pvc?_pv_30

上述结果证明,删除pvc前需要删除正在使用pvc资源的其他资源如Pod或者其他控制器。