Kubernetes 学习12 kubernetes 存储卷
一、概述
1、我们此前讲过根据应用本身是否需要持久存储数据以及某一次请求和之前的请求是否有联系,可以分为四类应用
a、有状态,要存储
b、有状态,无持久存储
c、无状态,要存储
d、无状态,无持久存储
其中,大多数和数据存储服务相关的应用和有状态应用几乎都是需要持久存储数据的。在docker中说过,容器有生命周期,为了使容器将来终结以后可以把其删除,甚至是编排到其它节点上去运行,意味着我们数据不能放在容器本地,不能放在容器自己的名称空间中。注意这是两套逻辑,以k8s为例,pod运行时应该是运行在某个节点上的,只要不出故障他就一直在这个节点上运行,节点故障它才会重新调度,只要节点不故障他就不会走,就一直重启重启,因此只要pod故障被删除,那么这个pod的生命周期也就结束了,其上面的数据也就随着结束一起消失,为了突破pod生命周期受限的现状,我们需要在pod自由文件系统之外的地方来存放数据。我们使用存储卷,相当于把pod内部某一目录与我们节点上某一目录建立关联关系,随后在容器内该目录存放的数据都被存放在节点上了,所以我们删除容器再重建容器只要卷不受影响那么数据都是没问题的,在一定程度上拥有了持久功能,但是在k8s中当pod生命周期结束后下一次启动有可能不会在相同的节点上,因此我们需要使用脱离节点而存在的存储设备,共享存储设备。为此,我们k8s就提供了能应付各种类型的存储卷来让我们使用或非持久,或半持久或一直持久的功能。
2、对k8s中的pod我们知道有这样一个特定,对同一个pod内的各个容器可共享访问同一组卷,因为对k8s来讲,存储卷不属于容器而属于pod,所以如果两个容器都挂载了就相当于两个容器共享数据了,而pod为什么能有存储卷和名称空间呢,我们此前讲过,pod底层有个基础容器但是默认不启动而且是靠一个基础镜像来创建的,我们创建Pod时所有的名称空间都是分配给它的,我们在pod中运行的主容器是共享它的名称空间的,容器挂载存储卷也是复制了它的存储卷而已,因此其叫基础架构容器。对于pod来讲所有容器共享pause这个镜像的容器的网络名称空间,所以这就是为什么同一个pod的所有容器他们能使用同一个地址,共享同一个TCP/IP协议栈,使用同一组主机名的原因,他们的IPC,NAT和UTS名称空间是共享的。
[root@k8smaster ~]# docker images|grep pause k8s.gcr.io/pause 3.1 da86e6ba6ca1 18 months ago 742kB
3、如果我们在pod上使用存储卷,其实也就是这个pod的基础架构容器用了存储卷,而我们说过容器存储卷就是容器与宿主机目录建立关联关系而已,而宿主机目录是宿主机本地的,它随着宿主机的终结而终结,因此宿主机这个目录为了真正实现持久性,那么这个卷应该也不是宿主机本地的,而是宿主机外部挂载的存储卷。因此如果我们想跨节点持久一般而言我们只有使用脱离节点本地的网络存储设备,比如NFS,GFS,ceph等等,因此我们需要保证如果我们每一个节点要能使用这种存储设备的话要能够驱动相应的存储设备才行。在节点级先能够访问上来才可以。
4、那么在k8s上有哪些存储卷可以用呢
a、emptyDir:只在节点本地使用,我们启动一个pod,给这个pod分配一个存储卷,这个存储卷只在本地使用。并且这个pod删掉本地的存储卷也一并清除。这不是为了持久数据而设计,就是作为临时目录的。或者当缓存使用,因为emptyDir背后关联的宿主机的目录可以是宿主机的内存,我们把宿主机的内存剔出来一块当硬盘用,直接挂在到这个目录上,然后再给pod中容器建立关联关系,这样pod可以把这份空间当缓存空间使用。这种没有任何持久性。
b、hostPath: 顾名思义,主机路径,直接在宿主机上找一个目录与容器建立关联关系,他们都不具有真正意义上的持久性,要具有真正意义上的持久性我们需要使用网络连接的存储。
c、网络连接的存储大概可以分为三类
1)、传统意义上的存储设备,也就是我们本地的SAN(iSCSI,FC...),或者NAS(nfs,cifs)网络共享存储
2)、分布式存储:一般为文件系统级别或块存储级别。比较常见的就是glusterfs,rbd[ceph的块级别的存储],cephfs等等。
3)、云端存储:比如亚马逊的EBS【elastic block storage】的弹性块存储,Azure Disk(这种类型只适用于一种场景就是你的k8s是托管在它的云上的)
二、k8s支持的存储
1、那么我们k8s支持哪些存储呢
[root@k8smaster ~]# kubectl explain pods.spec.volumes KIND: Pod VERSION: v1 RESOURCE: volumes <[]Object> DESCRIPTION: List of volumes that can be mounted by containers belonging to the pod. More info: https://kubernetes.io/docs/concepts/storage/volumes Volume represents a named volume in a pod that may be accessed by any container in the pod. FIELDS: awsElasticBlockStore <Object> #亚马逊EBS AWSElasticBlockStore represents an AWS Disk resource that is attached to a kubelet's host machine and then exposed to the pod. More info: https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore azureDisk <Object> AzureDisk represents an Azure Data Disk mount on the host and bind mount to the pod. azureFile <Object> AzureFile represents an Azure File Service mount on the host and bind mount to the pod. cephfs <Object> CephFS represents a Ceph FS mount on the host that shares a pod's lifetime cinder <Object> #openstack上的云存储 Cinder represents a cinder volume attached and mounted on kubelets host machine More info: https://releases.k8s.io/HEAD/examples/mysql-cinder-pd/README.md configMap <Object> ConfigMap represents a configMap that should populate this volume downwardAPI <Object> DownwardAPI represents downward API about the pod that should populate this volume emptyDir <Object> EmptyDir represents a temporary directory that shares a pod's lifetime. More info: https://kubernetes.io/docs/concepts/storage/volumes#emptydir fc <Object> FC represents a Fibre Channel resource that is attached to a kubelet's host machine and then exposed to the pod. flexVolume <Object> FlexVolume represents a generic volume resource that is provisioned/attached using an exec based plugin. flocker <Object> Flocker represents a Flocker volume attached to a kubelet's host machine. This depends on the Flocker control service being running gcePersistentDisk <Object> #谷歌云 GCEPersistentDisk represents a GCE Disk resource that is attached to a kubelet's host machine and then exposed to the pod. More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk gitRepo <Object> GitRepo represents a git repository at a particular revision. DEPRECATED: GitRepo is deprecated. To provision a container with a git repo, mount an EmptyDir into an InitContainer that clones the repo using git, then mount the EmptyDir into the Pod's container. glusterfs <Object> Glusterfs represents a Glusterfs mount on the host that shares a pod's lifetime. More info: https://releases.k8s.io/HEAD/examples/volumes/glusterfs/README.md hostPath <Object> HostPath represents a pre-existing file or directory on the host machine that is directly exposed to the container. This is generally used for system agents or other privileged things that are allowed to see the host machine. Most containers will NOT need this. More info: https://kubernetes.io/docs/concepts/storage/volumes#hostpath iscsi <Object> ISCSI represents an ISCSI Disk resource that is attached to a kubelet's host machine and then exposed to the pod. More info: https://releases.k8s.io/HEAD/examples/volumes/iscsi/README.md name <string> -required- #卷名称 Volume's name. Must be a DNS_LABEL and unique within the pod. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names nfs <Object> NFS represents an NFS mount on the host that shares a pod's lifetime More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs persistentVolumeClaim <Object> #持久存储卷请求,简称PVC PersistentVolumeClaimVolumeSource represents a reference to a PersistentVolumeClaim in the same namespace. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#persistentvolumeclaims photonPersistentDisk <Object> PhotonPersistentDisk represents a PhotonController persistent disk attached and mounted on kubelets host machine portworxVolume <Object> PortworxVolume represents a portworx volume attached and mounted on kubelets host machine projected <Object> Items for all in one resources secrets, configmaps, and downward API quobyte <Object> Quobyte represents a Quobyte mount on the host that shares a pod's lifetime rbd <Object> RBD represents a Rados Block Device mount on the host that shares a pod's lifetime. More info: https://releases.k8s.io/HEAD/examples/volumes/rbd/README.md scaleIO <Object> ScaleIO represents a ScaleIO persistent volume attached and mounted on Kubernetes nodes. secret <Object> Secret represents a secret that should populate this volume. More info: https://kubernetes.io/docs/concepts/storage/volumes#secret storageos <Object> StorageOS represents a StorageOS volume attached and mounted on Kubernetes nodes. vsphereVolume <Object> VsphereVolume represents a vSphere volume attached and mounted on kubelets host machine
2、persistentVolumeClaim(存储卷创建申请) ,当我们作为一个用户去使用k8s功能,在集群中创建pod时,为了实现持久能力,我们得在pod中定义存储卷,如果直接定义某个类型的存储卷那么需要对目标存储卷的功能特别了解才可以,那么这样会阻断一大部分用户。那么怎么样可以让各种类型的存储使用起来特别简单呢,这个时候就需要用到pvc,当我们需要多大的存储卷时直接告诉他我需要多大的空间,不用管这个存储系统到底放在哪个系统上,这就是所谓的存储即服务。让pod创建与底层存储解耦,耦合打散,而pvc关键是要与pvc建立关联关系,而pvc关键是要与pv建立关联关系,而pv关键是要与存储系统建立关联关系。什么意思呢?比如我们有一个节点,某一个pod创建正好调度到这个节点上了,那么我们需要在这个pod上定义的时候定义一个pvc,它是一种存储卷类型,
a、这个pvc需要关联到当前这个pod所在名称空间,需要在当前的名称空间中找一个真正的pvc存在的资源才行。
b、这个pvc只是一个申请,它的申请要与pv建立关联关系。pvc也只是一个形式,这个申请不是由pod申请的,或者pod一申请我们就需要在pod所在的名称空间中创建一个pvc,而这个pvc真正的存储要与pv建立关联关系,pv才是真正的系统之上的一段存储空间。虽然看起来步骤不少但是结果就是,以后我们pod的创建者只需要创建一个pvc就行了(需要什么存储卷,需要多大),怎么创建就由懂这一行的人创建就可以了。但是我们并不知道pod什么时候会创建,也不知道在创建pod时候会用多大存储空间,因此这个pvc去请求和哪个pv建立绑定关系时,怎么可能正好有个pv放那儿好好等着你去用呢,要做到这一步只有这种可能性,用户在创建之前只有先提需求,然后我们后端的工程师或k8s管理员提前创建好,随后当其创建pod时激活pvc,绑定pv就能使用了。
c、但是如果是公有云呢?有很多租户,你压根就不知道他什么时候会创建pv,因此如果要做到按需创建还有一个办法,我们pv也不建了,我们把所有存储空间抽象出来抽象成一个抽象层叫存储类,当用户创建pvc需要用到pv时,能够向存储类申请说你帮我创建出来,这样存储类就能自动帮它生成刚好符合用户请求大小的pv来。这种pv由用户的请求触发而动态生成,我们称为pv的动态供给,而这儿有一个前提是我们要定义好存储类,什么叫存储类呢?我们存储按照其综合服务质量分为好几个级别,又慢又笨的叫铜牌存储(Bronze Storage Class),大部分人能接受的叫银牌存储(Silver Storage Class),有些性能非常快的比如ssd组成的叫金牌存储(Gold Storage Class)
d、存储卷只是在pod中定义的,容器中还需要挂载才行。
3、所以我们要想使用存储卷我们需要定义以下几步
a、在pod上定义volume,而这个volume要指明他关联到哪个存储设备上去的
b、在容器中使用volumeMounts然后才能使用存储卷
三、存储卷制作
1、用emptyDir做一个拥有存储卷的自主式pod
[root@k8smaster volumes]# cat pod-vol-demo.yaml apiVersion: v1 kind: Pod metadata: name: pod-demo namespace: default labels: #也可以在此处写上{app:myapp,tier:frontend}代替下面两行 app: myapp #应用层级标签 tier: frontend #架构层级标签,在分层架构中属于frontend层 annotations: wohaoshuai.com/created-by: "cluster admin" spec: containers: #是一个列表,具体定义方式如下 - name: myapp image: ikubernetes/myapp:v1 ports: #不暴露端口其实也可以被访问,目的是为了说明启动的端口有哪些 - name: http #service 中可以通过名称来引用端口 containerPort: 80 volumeMounts: #定义挂载的volume卷 - name: html #挂载pod上定义的叫做html的存储卷,即下面volumes定义的存储卷 mountPath: /data/web/html #挂载到容器中/data/web/html目录中 - name: busybox image: busybox:latest imagePullPolicy: IfNotPresent volumeMounts: - name: html mountPath: /data/ command: #也可以写成中括号形式,比如可以在此处写上["/bin/sh","-c","sleep 3600"] - "/bin/sh" - "-c" - "sleep 7200" volumes: - name: html emptyDir: {} #使用空,没有值表示使用默认值,大小不限制,使用磁盘空间就行 [root@k8smaster volumes]# kubectl apply -f pod-vol-demo.yaml pod/pod-demo created [root@k8smaster volumes]# kubectl exec -it pod-demo -c myapp /bin/sh / # echo $(date) >> /data/web/html/index.html / # exit [root@k8smaster volumes]# kubectl exec -it pod-demo -c busybox cat /data/index.html Mon Jun 17 06:55:24 UTC 2019
2、接下来将上面pod改造成第一个pod主容器主要用来向用户提供web服务的,第二个pod叫sedcar,为了不断的帮你生成新网页
[root@k8smaster volumes]# cat pod-vol-demo.yaml apiVersion: v1 kind: Pod metadata: name: pod-demo namespace: default labels: #也可以在此处写上{app:myapp,tier:frontend}代替下面两行 app: myapp #应用层级标签 tier: frontend #架构层级标签,在分层架构中属于frontend层 annotations: wohaoshuai.com/created-by: "cluster admin" spec: containers: #是一个列表,具体定义方式如下 - name: myapp image: ikubernetes/myapp:v1 imagePullPolicy: IfNotPresent #如果本地没有就去仓库拖镜像 ports: #不暴露端口其实也可以被访问,目的是为了说明启动的端口有哪些 - name: http #service 中可以通过名称来引用端口 containerPort: 80 volumeMounts: #定义挂载的volume卷 - name: html #挂载pod上定义的叫做html的存储卷,即下面volumes定义的存储卷 mountPath: /usr/share/nginx/html/ #挂载到容器中/usr/share/nginx/html/目录中 - name: busybox image: busybox:latest imagePullPolicy: IfNotPresent volumeMounts: - name: html mountPath: /data/ command: #也可以写成中括号形式,比如可以在此处写上["/bin/sh","-c","sleep 3600"] - "/bin/sh" - "-c" - "while true; do echo $(date) >> /data/index.html; sleep 2; done" volumes: - name: html emptyDir: {} #使用空,没有值表示使用默认值,大小不限制,使用磁盘空间就行 [root@k8smaster volumes]# kubectl apply -f pod-vol-demo.yaml pod/pod-demo created [root@k8smaster volumes]# kubectl get pods -o wide --show-labels |grep pod-demo pod-demo 2/2 Running 0 5s 10.244.1.93 k8snode1 app=myapp,tier=frontend [root@k8smaster volumes]# curl 10.244.1.93 Mon Jun 17 07:56:24 UTC 2019 Mon Jun 17 07:56:26 UTC 2019 Mon Jun 17 07:56:28 UTC 2019 Mon Jun 17 07:56:30 UTC 2019 Mon Jun 17 07:56:32 UTC 2019 Mon Jun 17 07:56:34 UTC 2019 Mon Jun 17 07:56:36 UTC 2019 Mon Jun 17 07:56:38 UTC 2019 Mon Jun 17 07:56:40 UTC 2019 Mon Jun 17 07:56:42 UTC 2019 Mon Jun 17 07:56:44 UTC 2019 Mon Jun 17 07:56:46 UTC 2019 Mon Jun 17 07:56:48 UTC 2019 Mon Jun 17 07:56:50 UTC 2019 Mon Jun 17 07:56:52 UTC 2019 Mon Jun 17 07:56:55 UTC 2019 Mon Jun 17 07:56:57 UTC 2019
3、gitRepo存储卷,把git 仓库当做存储卷来使用,其并不是我们的pod真正把git仓库当存储卷使用,它只不过是把git仓库中的内容(gitHub或者自己的私有仓库服务器),它把指定的服务器上的仓库,可以把我们的源代码放在仓库中,而后当我们pod创建时,它会自动的连接到这个仓库之上,但是这个连接需要依赖于宿主机上有git命令来完成,因为它是基于宿主机来驱动的,它通过宿主机将git仓库中的整个仓库克隆到本地来,并且把它作为存储卷挂载至pod之上,更有意思的是gitRepo仓库其实是建立在emptyDir之上的。意思是说gitRepo从本质上来讲也是一个emptyDir空目录,对于pod而言就是先建了一个空目录,任然是一个空的存储卷,但是所不同的地方在于它需要把指定的仓库的内容克隆下来并扔到这个空目录中,因此我们主容器就可以把这个目录当做是服务于用户的目录,这样就可以使用这个目录了,并且在其做修改时不会同步到git仓库中去,并且git仓库在pod运行中内容发生改变了pod内的存储卷的内容也不会改变。因为它只是在克隆那一刻是什么就拿下来什么。当然如果我们想要同步数据我们可以同样做一个辅助容器每隔一段时间同步一次。
4、hostPath存储卷:把pod所在的宿主机之上的脱离pod中容器的名称空间之外的宿主机的文件系统的某一目录与pod建立关联关系,在pod被删除时这个存储卷是不会被删除的,所以只要同一个pod能够调度到同一个节点上来在pod被删除以后,对应的数据依然是存在的
[root@k8smaster volumes]# kubectl explain pods.spec.volumes.hostPath KIND: Pod VERSION: v1 RESOURCE: hostPath <Object> DESCRIPTION: HostPath represents a pre-existing file or directory on the host machine that is directly exposed to the container. This is generally used for system agents or other privileged things that are allowed to see the host machine. Most containers will NOT need this. More info: https://kubernetes.io/docs/concepts/storage/volumes#hostpath Represents a host path mapped into a pod. Host path volumes do not support ownership management or SELinux relabeling. FIELDS: path <string> -required- #如果要使用hostPath就要指明path是谁,指明宿主机路径,万一宿主机上该路径不存在到底要不要新建则取决于类型 Path of the directory on the host. If the path is a symlink, it will follow the link to the real path. More info: https://kubernetes.io/docs/concepts/storage/volumes#hostpath type <string> # Type for HostPath Volume Defaults to "" More info: https://kubernetes.io/docs/concepts/storage/volumes#hostpath
关于type官网有如下值选择
案例1,直接通过本地路径映射:
在node1中
[root@k8snode1 ~]# mkdir -p /data/pod/volume1 && echo "node1.wohaoshuai.com" >> /data/pod/volume1/index.html
在node2中
[root@k8snode2 ~]# mkdir -p /data/pod/volume1 && echo "node2.wohaoshuai.com" >> /data/pod/volume1/index.html
在master中
[root@k8smaster volumes]# cat pod-hostPath-vol.yaml apiVersion: v1 kind: Pod metadata: name: pod-vol-hostpath namespace: default spec: containers: - name: myapp image: ikubernetes/myapp:v1 volumeMounts: - name: html mountPath: /usr/share/nginx/html/ volumes: - name: html hostPath: path: /data/pod/volume1 type: DirectoryOrCreate [root@k8smaster volumes]# kubectl apply -f pod-hostPath-vol.yaml pod/pod-vol-hostpath created [root@k8smaster volumes]# kubectl get pods -o wide --show-labels |grep pod-vol-hostpath pod-vol-hostpath 1/1 Running 0 23s 10.244.1.95 k8snode1 <none> [root@k8smaster volumes]# curl 10.244.1.95 node1.wohaoshuai.com
5、nfs存储类型:通过NFS映射
首先在节点192.168.10.13创建nfs共享目录/data/volumes 并共享给192.168.10.0网段都可以使用,并且root用户也可压缩使用
[root@localhost ~]# yum install -y nfs-utils [root@localhost ~]# mkdir -p /data/volumes [root@localhost ~]# cat /etc/exports /data/volumes 192.168.10.0/24(rw,no_root_squash) [root@localhost ~]# systemctl start nfs [root@localhost ~]# netstat -anpt|grep 2049 tcp 0 0 0.0.0.0:2049 0.0.0.0:* LISTEN - tcp6 0 0 :::2049 :::* LISTEN -
在节点192.168.10.13共享目录中创建一个文件
echo "<h1>NFS stor01<h1>" >> /data/volumes/index.html
在master节点创建pod并挂载nfs
[root@k8smaster volumes]# kubectl explain pods.spec.volumes.nfs KIND: Pod VERSION: v1 RESOURCE: nfs <Object> DESCRIPTION: NFS represents an NFS mount on the host that shares a pod's lifetime More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs Represents an NFS mount that lasts the lifetime of a pod. NFS volumes do not support ownership management or SELinux relabeling. FIELDS: path <string> -required- #nfs服务器上是哪个路径 Path that is exported by the NFS server. More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs readOnly <boolean> #要不要挂载为只读,默认为false ReadOnly here will force the NFS export to be mounted with read-only permissions. Defaults to false. More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs server <string> -required- #服务器地址 Server is the hostname or IP address of the NFS server. More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs
[root@k8smaster volumes]# cat pod-vol-nfs.yaml apiVersion: v1 kind: Pod metadata: name: pod-vol-nfs namespace: default spec: containers: - name: myapp image: ikubernetes/myapp:v1 volumeMounts: - name: html mountPath: /usr/share/nginx/html/ volumes: - name: html nfs: path: /data/volumes server: 192.168.10.13 [root@k8smaster volumes]# kubectl apply -f pod-vol-nfs.yaml pod/pod-vol-nfs created [root@k8smaster volumes]# kubectl get pods -o wide --show-labels |grep pod-vol-nfs pod-vol-nfs 1/1 Running 0 22s 10.244.2.97 k8snode2 <none> [root@k8smaster volumes]# curl 10.244.2.97 <h1>NFS stor01<h1>
nfs支持多个客户端同时读写,只是性能不是特别好。并且如果nfs down了数据也就挂了,因此最好使用分布式存储。分布式存储目前常用的就是glusterfs和ceph,ceph直接就是restful 风格的接口可以直接被k8s使用,但是glusterfs不行,因此glusterfs还要额外的去部署一个接口以便用于提供所谓的restful接口。
四、pvc使用
1、概述,我们可以这样来组织我们的存储,在pod中我们只需要定义一个存储卷并且定义的时候我只需要说明我需要多大的存储卷就行了,这个存储卷叫pvc类型的存储卷,而pvc类型的存储卷必须与当前名称空间中的pvc建立直接绑定关系,而pvc必须与pv建立绑定关系,而pv应该是真正某个存储设备上的存储空间,所以pv和pvc是k8s系统之上的抽象的但也算是标准的资源,pvc是一种资源,pv也是一种资源,他的创建方式和我们此前创建其它资源方式是一样的,但是用户需要怎么做呢?存储工程师把每个存储空间先划割好,k8s管理员需要把每一个存储空间映射到系统上做成pv,用户就自己定义pod,在pod中定义使用pvc就可以了,但是pvc也需要创建,也需要我们k8s工程师把pvc也做好,但是pv和pvc之间,在pvc不被调用时是空载没有用的,当有pod调用pvc的时候他需要与某个pv绑定起来,相当于这个pvc上的数据是放在哪个pv上的,pvc要绑定哪个pv取决于pod创建者用户在创建中定义我要请求使用多大的存储空间。比如我要使用一个5G的,于是在当前系统上我们要找一个pvc与容纳5G的pv建立绑定关系,2G的就不符合条件。并且我们还可以指明说我创建使用这个pv必须只能有一个人挂载和读写使用。
2、pvc
[root@k8smaster ~]# kubectl explain pods.spec.volumes.persistentVolumeClaim KIND: Pod VERSION: v1 RESOURCE: persistentVolumeClaim <Object> DESCRIPTION: PersistentVolumeClaimVolumeSource represents a reference to a PersistentVolumeClaim in the same namespace. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#persistentvolumeclaims PersistentVolumeClaimVolumeSource references the user's PVC in the same namespace. This volume finds the bound PV and mounts that volume for the pod. A PersistentVolumeClaimVolumeSource is, essentially, a wrapper around another type of volume that is owned by someone else (the system). FIELDS: claimName <string> -required- #需要使用的pvc名称 ClaimName is the name of a PersistentVolumeClaim in the same namespace as the pod using this volume. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#persistentvolumeclaims readOnly <boolean> #当前名称空间中已经有一个用户做好pvc了 Will force the ReadOnly setting in VolumeMounts. Default false.
[root@k8smaster ~]# kubectl explain pvc.spec KIND: PersistentVolumeClaim VERSION: v1 RESOURCE: spec <Object> DESCRIPTION: Spec defines the desired characteristics of a volume requested by a pod author. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#persistentvolumeclaims PersistentVolumeClaimSpec describes the common attributes of storage devices and allows a Source for provider-specific attributes FIELDS: accessModes <[]string> #访问模型, AccessModes contains the desired access modes the volume should have. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#access-modes-1 resources <Object> #资源限制,至少多少,如果设置为10G的那么至少需要10G的pv Resources represents the minimum resources the volume should have. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#resources selector <Object> #标签选择器,表示可以必须选择哪个pv建立关联关系,不加标签就在所有里面找最佳匹配 A label query over volumes to consider for binding. storageClassName <string> #存储类名称 Name of the StorageClass required by the claim. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#class-1 volumeMode <string> #后端存储卷模式 volumeMode defines what type of volume is required by the claim. Value of Filesystem is implied when not included in claim spec. This is an alpha feature and may change in the future. volumeName <string> #卷名称,后端pv的名称,精确选择时候匹配 VolumeName is the binding reference to the PersistentVolume backing this claim.
创建完pvc后就会找系统上合适的pv绑定,如果有就绑定,如果没有就pvc pending(挂起),表示没有合适的pv被这个pvc所claim,pvc此时被阻塞,直到有合适的pv被此pvc所调用为止。
首先我们系统上应该是要有存储设备,存储管理员在上面划割好了很多很多可被独立使用的存储空间,然后我们的集群管理工程师把这每一个空间都引入到集群中把它定义成pv,因为我们集群管理员或存储工程师才更了解这些存储设备的参数,他们必须要了解存储设备怎么被创建,并且知道这些参数怎么被调用,随后我们的k8s使用者创建pod时要先创建一个pvc,意思是说我们要在当前的k8s集群中找一个符合我们条件的存储空间来用,于是就定义pvc的使用标准,而后我们系统就在里面找哪个最合适,然后申请。pv和pvc是一一对应关系,也就是说如果某个pv被某个pvc占用了意味着他就不能再被其它pvc所占用了,如果pvc被占用了会显示这个状态叫binding,意思说被绑定在使用了,就不可能再被其它pv所占用了,但是一个pvc创建以后这个pvc就相当于是一个存储卷了,这个存储卷可以被多个pod所访问,假如多个pod挂载同一个存储卷就叫多路访问,因此我们创建pvc时最好确保下面有pv存在,如果不存在那么就绑定不了。
3、实践。我们在nfs上先准备几个空间,然后将他们做成pv,然后用户才能使用pvc去申请绑定和使用他们
a、在nfs服务器创建测试目录
[root@localhost volumes]# mkdir -p /data/volumes/v{1..5} && ls /data/volumes/ v1 v2 v3 v4 v5
[root@localhost volumes]# cat /etc/exports /data/volumes/v1 192.168.10.0/24(rw,no_root_squash) /data/volumes/v2 192.168.10.0/24(rw,no_root_squash) /data/volumes/v3 192.168.10.0/24(rw,no_root_squash) /data/volumes/v4 192.168.10.0/24(rw,no_root_squash) /data/volumes/v5 192.168.10.0/24(rw,no_root_squash) [root@localhost volumes]# exportfs -arv #重载配置文件使生效 exporting 192.168.10.0/24:/data/volumes/v5 exporting 192.168.10.0/24:/data/volumes/v4 exporting 192.168.10.0/24:/data/volumes/v3 exporting 192.168.10.0/24:/data/volumes/v2 exporting 192.168.10.0/24:/data/volumes/v1 [root@localhost volumes]# showmount -e #客户端查看 Export list for localhost.localdomain: /data/volumes/v5 192.168.10.0/24 /data/volumes/v4 192.168.10.0/24 /data/volumes/v3 192.168.10.0/24 /data/volumes/v2 192.168.10.0/24 /data/volumes/v1 192.168.10.0/24
b、定义pv,定义pv时一定不要加名称空间,因为其是集群级别的不属于名称空间,所有名称空间都可以用,但是pvc是属于名称空间级别的。集群级别的就表示不能定义在名称空间中,名称空间级别的才能定义在名称空间中。
[root@k8smaster volumes]# cat pv-demo.yaml apiVersion: v1 kind: PersistentVolume metadata: name: pv001 labels: name: pv001 spec: nfs: path: /data/volumes/v1 server: 192.168.10.13 accessModes: ["ReadWriteMany","ReadWriteOnce"] #多路读写,多路只读和单路读写 capacity: storage: 2Gi --- apiVersion: v1 kind: PersistentVolume metadata: name: pv002 labels: name: pv002 spec: nfs: path: /data/volumes/v2 server: 192.168.10.13 accessModes: ["ReadWriteOnce"] #多路读写,多路只读和单路读写 capacity: storage: 5Gi --- apiVersion: v1 kind: PersistentVolume metadata: name: pv003 labels: name: pv003 spec: nfs: path: /data/volumes/v3 server: 192.168.10.13 accessModes: ["ReadWriteMany","ReadWriteOnce"] #多路读写,多路只读和单路读写 capacity: storage: 20Gi --- apiVersion: v1 kind: PersistentVolume metadata: name: pv004 labels: name: pv004 spec: nfs: path: /data/volumes/v4 server: 192.168.10.13 accessModes: ["ReadWriteMany","ReadWriteOnce"] #多路读写,多路只读和单路读写 capacity: storage: 10Gi --- apiVersion: v1 kind: PersistentVolume metadata: name: pv005 labels: name: pv005 spec: nfs: path: /data/volumes/v5 server: 192.168.10.13 accessModes: ["ReadWriteMany","ReadWriteOnce"] #多路读写,多路只读和单路读写 capacity: storage: 10Gi --- [root@k8smaster volumes]# kubectl apply -f pv-demo.yaml persistentvolume/pv001 created persistentvolume/pv002 created persistentvolume/pv003 created persistentvolume/pv004 created persistentvolume/pv005 created [root@k8smaster volumes]# kubectl get pv -o wide --show-labels NAME CAPACITY ACCESS MODES RECLAIM POLICY(回收策略) STATUS CLAIM STORAGECLASS REASON AGE LABELS pv001 2Gi RWO,RWX Retain(保留) Available 22s name=pv001 pv002 5Gi RWO Retain Available 22s name=pv002 pv003 20Gi RWO,RWX Retain Available 22s name=pv003 pv004 10Gi RWO,RWX Retain Available 22s name=pv004 pv005 10Gi RWO,RWX Retain Available 22s name=pv005
回收策略是指,如果某个pvc绑定这个pv,在里面存数据了,但是后面这个pvc又释放了,我把pvc删了这个绑定就不存在了,一旦绑定不存在时里面存在数据的pv怎么办呢? Retain表示保留着,recover表示回收,表示把里面数据全删了,把pv置于空闲状态让其它pvc绑。还有一种叫delete,用完直接删除。一般来讲我们默认使用Retain。
c、定义pvc
[root@k8smaster volumes]# kubectl explain pvc.spec KIND: PersistentVolumeClaim VERSION: v1 RESOURCE: spec <Object> DESCRIPTION: Spec defines the desired characteristics of a volume requested by a pod author. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#persistentvolumeclaims PersistentVolumeClaimSpec describes the common attributes of storage devices and allows a Source for provider-specific attributes FIELDS: accessModes <[]string> #他要求的访问模式一定是现存的某个pv的子集 AccessModes contains the desired access modes the volume should have. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#access-modes-1 resources <Object> #资源要求,一旦给了以后pv一定要大于等于这个值才能被使用 Resources represents the minimum resources the volume should have. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#resources selector <Object> A label query over volumes to consider for binding. storageClassName <string> Name of the StorageClass required by the claim. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#class-1 volumeMode <string> volumeMode defines what type of volume is required by the claim. Value of Filesystem is implied when not included in claim spec. This is an alpha feature and may change in the future. volumeName <string> VolumeName is the binding reference to the PersistentVolume backing this claim.
[root@k8smaster volumes]# cat pod-vol-pvc.yaml apiVersion: v1 kind: PersistentVolumeClaim metadata: name: mypvc namespace: default spec: accessModes: ["ReadWriteMany"] resources: requests: 6Gi #要求至少6G空间 --- apiVersion: v1 kind: Pod metadata: name: pod-vol-pvc namespace: default spec: containers: - name: myapp image: ikubernetes/myapp:v1 volumeMounts: - name: html mountPath: /usr/share/nginx/html/ volumes: - name: html persistentVolumeClaim: #使用pvc claimName: mypvc #pvc名字 [root@k8smaster volumes]# kubectl apply -f pod-vol-pvc.yaml persistentvolumeclaim/mypvc created pod/pod-vol-pvc created [root@k8smaster volumes]# kubectl get pv NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE pv001 2Gi RWO,RWX Retain Available 26m pv002 5Gi RWO Retain Available 26m pv003 20Gi RWO,RWX Retain Available 26m pv004 10Gi RWO,RWX Retain Bound default/mypvc 26m pv005 10Gi RWO,RWX Retain Available 26m
[root@k8smaster volumes]# kubectl get pvc -o wide --show-labels
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE LABELS
mypvc Bound pv004 10Gi RWO,RWX 1h <none>
在nfs服务器上创建index.html
[root@localhost /]# echo "test" >> index.html /data/volumes/v4/index.html
在pod中对应路径中查看
[root@k8smaster volumes]# kubectl exec -it pod-vol-pvc cat /usr/share/nginx/html/index.html test
4、只要pvc不删,pv无论是什么回收策略一般而言都不会有问题,但如果手动删除了pvc,这个pv上的数据是否还在则取决于我们这个pv的回收策略,一般来讲有上述三种回收策略,早期的k8s版本还有一个特点,可以手动delete pv,1.9之前的版本都是可以的,1.10才修复这个大bug,现在版本要求只要pv还被pvc绑定着就不支持删除。
5、我们最好使用动态供给,我们定义好存储类,用户创建pvc时动态生成一个pv。