PV的全称是: PersistentVolume (持久化卷),是对底层的共享存储的一种抽象,PV由管理员进行建立和配置,它和具体的底层的共享存储技术的实现方式有关,好比Ceph、GlusterFS、NFS等,都是经过插件机制完成与共享存储的对接.html
PVC的全称是: PersistenVolumeClaim (持久化卷声明),PVC是用户存储的一种声明,PVC和Pod比较类型,Pod是消耗节点,PVC消耗的是PV资源,Pod能够请求CPU的内存,而PVC能够请求特定的存储空间和访问模式。对于真正存储的用户不须要关心底层的存储实现细节,只须要直接使用PVC便可.node
可是经过PVC请求必定的存储空间也颇有可能不足以知足对于存储设备的各类需求,并且不一样的应用程序对于存储性能的要求也能也不尽相同,好比读写速度、并发性能等,为了解决这一问题,Kubernetes又为咱们引入了一个新的资源对象: StorageClass,经过StorageClass的定义,管理员能够将存储资源定义为某种类型的资源,好比快速存储、慢速存储等,用户根据StorageClass的描述就能够很是直观的知道各类存储资源特性了,这样就能够根据应用的特性去申请合适的存储资源了.python
PV能够看做可用的存储资源,PVC则是对存储资源的需求,PV和PVC的互相关系遵循以下图nginx
Kubernetes支持两种资源的供应模式:静态模式(Staic)和动态模式(Dynamic)。资源供应的结果就是建立好的PV.git
静态模式: 管理员手工建立许多PV,在定义PV时须要将后端存储的特性进行设置vim
动态模式: 管理员无需手动建立PV,而是经过StorageClass的设置对后端存储进行描述,标记为某种"类型(Class)",此时要求PVC对存储的类型进行声明,系统将自动完成PV的建立及PVC的绑定,PVC能够声明为Class为"",说明该PVC禁止使用动态模式后端
在用户定义好PVC后,系统将根据PVC对存储资源的请求(存储空间和访问模式)在已存在的PV中选择一个知足PVC要求的PV,一旦找到,就将PV与用户定义的PVC进行绑定,而后用户的应用就可使用这个PVC了。若是系统中没有知足PVC要求的PV,PVC则会无限期处于Pending状态,直到等到系统管理员建立了一个符合要求的PV。PV一旦绑定在某个PVC上,就被这个PVC独占,不能再与其余PVC进行绑定了。在这种状况下,当PVC申请的存储空间比PV的少时,整个PV的空间都可以为PVC所用,可能会形成资源的浪费。若是资源供应使用的是动态模式,则系统在PVC找到合适的StorageClass后,将会自动建立PV并完成PVC的绑定api
Pod 使用volume的定义,将PVC挂载到容器内的某个路径进行使用。volume的类型为persistentVoulumeClaim,在容器应用挂载了一个PVC后,就能被持续独占使用。不过,多个Pod能够挂载同一个PVC,应用程序须要考虑多个实例共同访问一块存储空间的问题服务器
当用户对存储资源使用哪一个完毕后,用户能够删除PVC,与该PVC绑定的PV将会被标记为已释放,但还不能马上与其余PVC进行绑定。经过以前PVC写入的数据可能还留在存储设备上,只有在清除以后该PV才能继续使用.网络
对于PV,管理员能够设定回收策略(Reclaim Policy)用于设置与之绑定的PVC释放资源以后,对于遗留数据如何处理。只有PV的存储空间完成回收,才能供新的PVC绑定和使用。
1 . 静态资源下,经过PV和PVC完成绑定,并供Pod使用的存储管理机制
2 . 动态资源下,经过StorageClass和PVC完成资源动态绑定(系统自动生成PV,并供Pod使用的存储管理机制
因为容器自己是非持久化的,所以须要解决在容器中运行应用程序遇到的一些问题。首先,当容器崩溃时,kubelet将从新启动容器,可是写入容器的文件将会丢失,容器将会以镜像的初始状态从新开始;第二,在经过一个Pod中一块儿运行的容器,一般须要共享容器之间一些文件。Kubernetes经过存储卷解决上述的两个问题。
在Docker有存储卷的概念卷,但Docker中存储卷只是磁盘的或另外一个容器中的目录,并无对其生命周期进行管理。Kubernetes的存储卷有本身的生命周期,它的生命周期与使用的它Pod生命周期一致。所以,相比于在Pod中运行的容器来讲,存储卷的存在时间会比的其中的任何容器都长,而且在容器从新启动时会保留数据。固然,当Pod中止存在时,存储卷也将再也不存在。在Kubernetes支持多种类型的卷,而Pod能够同时使用各类类型和任意数量的存储卷。在Pod中经过指定下面的字段来使用存储卷:
spec.volumes: 经过此字段提供指定的存储卷
spec.containers.volumeMounts: 经过此字段将存储卷挂载到容器中
当前Kubernetes支持以下所列的存储卷类型,并以hostPath、nfs和persistentVolumeClaim类型的存储卷为例,介绍如何定义存储卷,以及如何在Pod中被使用.
* awsElasticBlockStore * azureDisk * azureFile * cephfs * configMap * csi * downwardAPI * emptyDir * fc (fibre channel) * flocker * gcePersistentDisk * gitRepo * glusterfs * hostPath * iscsi * local * nfs * persistentVolumeClaim * projected * portworxVolume * quobyte * rbd * scaleIO * secret * storageos * vsphereVolume
EmptyDir是一个空目录,他的生命周期和所属的Pod是彻底一致的,他用处是把同一Pod内的不一样容器之间共享工做过程产生的文件,
mkdir /storage cd /storage mkdir volumes cat pod_volume.demo1.yaml apiVersion: v1 kind: Pod metadata: name: pod-demo namespace: default labels: app: myapp tier: frontend annotations: youmen.com/created-by: "youmen admin" spec: containers: - name: myapp image: ikubernetes/myapp:v1 imagePullPolicy: IfNotPresent ports: - name: http containerPort: 80 volumeMounts: - name: html mountPath: /usr/share/nginx/html/ - name: busybox image: busybox:latest imagePullPolicy: IfNotPresent volumeMounts: - name: html mountPath: /data/ command: ['/bin/sh', '-c'] args: - 'while true; do echo $(date) >> /data/index.html; sleep 3; done' volumes: - name: html emptyDir: {} kubectl apply -f pod_volume.demo1.yaml curl 10.244.3.34 -s Tue Dec 24 15:37:09 UTC 2019 Tue Dec 24 15:37:12 UTC 2019 Tue Dec 24 15:37:15 UTC 2019 Tue Dec 24 15:37:18 UTC 2019 Tue Dec 24 15:37:21 UTC 2019 Tue Dec 24 15:37:24 UTC 2019
依赖于node,这种会把宿主机的指定卷加载到容器之中,实现数据持久,可是若是Pod发生跨主机的重建,内容很难保证,或者Node节点宕机了
这种卷通常和DaemonSet搭配使用,用来操做主机文件,例如进行日志采集的FLK的FluentD就采用这种方式,加载主机的容器日志目录,达到收集本机全部日志的目的
# hostPath # 将Pod里面的目录内容绑定到宿主机目录,Pod删除并不到致使宿主机删除. # 若是指定宿主机目录不存在要不要先建立取决于type mkdir /data/pod/volume1 -p cat pod-volhost.demo.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 kubectl apply -f pod-volhost-demo.yaml echo youmen >> /data/pod/volume1/index.html kubectl get pods -o wide NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES pod-vol-hostpath 1/1 Running 0 3m49s 10.244.3.35 node1 <none> <none> curl 10.244.3.35 youmen # 咱们删除Pod,能够到Node主机查看目录,依然存在. [root@node1 ~]# cat /data/pod/volume1/index.html youmen
# 建议找集群外的一台机器作NFS hostnamectl set-hostname stor01 echo 172.19.0.18 stor01 >> /etc/hosts yum -y install nfs-utils mkdir -pv /data/volumes -p chmod 755 /data/volumes vim /etc/exports /data/volumes *(rw,no_root_squash,sync) # 存储目录,*容许全部人链接,rw读写权限,sync文件同时写入硬盘及内存,no_root_squash 使用者root用户自动修改成普通用户 systemctl start nfs && systemctl enable nfs
# 查看nfs目录挂载权限 cat /var/lib/nfs/etab /data/volumes *(rw,sync,wdelay,hide,nocrossmnt,secure,no_root_squash,no_all_squash,no_subtree_check,secure_locks,acl,no_pnfs,anonuid=65534,anongid=65534,sec=sys,rw,secure,no_root_squash,no_all_squash) # 检查下挂载目录是否正常 showmount -e localhost Export list for localhost: /data/volumes *
# 切换到k8s集群内的节点挂载存储卷 yum -y install nfs-utils echo 172.19.0.18 stor01 >> /etc/hosts # 挂载测试如下,而后umount掉,待会建立Pod自动会挂载 mount -t nfs stor01:/data/volumes /mnt # 验证一下 df -h |grep nfs1 nfs1:/data/volumes 17G 1.4G 16G 9% /mnt cat pod-volnfs.demo.yaml apiVersion: v1 kind: Pod metadata: name: pod-volnfs-demo 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: stor01 kubectl get pods -o wide NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES pod-volnfs-demo 1/1 Running 0 20s 10.244.1.49 node2 <none> <none> curl 10.244.1.49 nfs # 能够删除Pod再建立数据依然存在,哪怕节点宕掉依然数据不会丢失,可是当挂载的数据,Pod多了,效果就不尽人意了 # 接下来咱们经过pv,pvc来使用这个nfs存储
Pv和Pvc是K8s的一种标准资源,Pvc被Pv调用后就会被绑定起来,取决于用户怎么绑,
由于pvc属于集群资源级别的不能定义在名成空间
# 这里使用nfs类型后端存储, 1g存储空间,访问模式为ReadWriteOnce,回收策略为Recyle cat nfs_demo1.yaml apiVersion: v1 kind: PersistentVolume metadata: name: pv1 #pv名称 spec: capacity: #存储能力,一个pv对象都要指定一个存储能力, 目前仅支持存储空间的设置 storage: 1Gi #存储空间 accessModes: - ReadWriteOnce #访问模式 persistentVolumeReclaimPolicy: Recycle #回收策略 nfs: #服务模式 (nfs、ceph、hostpath等) path: /data/volumes #共享数据目录挂载点 server: nfs1 #nfs服务器地址 kubectl apply -f nfs_demo1.yaml persistentvolume/pv1 created [root@master storage]# kubectl get pv NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE pv1 1Gi RWO Recycle Available 5s # pv1: 名称 # 1Gi: 存储空间大小 # RWO: 访问模式(ReadWriteOnce缩写) # Recycle: 回收策略 # Available: PV状态 # persistentVolumeReclaimPolicy回收策略 # Retain (保留) 保留数据,须要管理员手动清理 # Recycle (回收) 清除PV中的数据,效果至关于执行删除命令 # Delete (删除) 与PV相连的后端存储完成volume的删除操做,常见于云服务商的存储服务 # 不过须要注意的是,目前只有NFS和HostPath两类支持回收策略,通常设置Retain比较保险 # 状态有如下几种 # Available: PV状态,表示可用状态,还未被任何PVC绑定 # Bound: 已绑定,已经绑定到某个PVC # Released: 已释放,对应的pvc已经删除,但资源尚未被集群收回 # Failed: PV自动回收失败
Capacity 存储能力
经过PV的capacity属性来设置存储空间,目前仅支持storage=数据大小,将来可能会加入IOPS、吞吐量等指标配置
AccessModes访问模式
AccessModes 是用来对PV进行访问模式的设置,用于描述用户应用对存储资源的访问权限
ReadWriteOnce (RWO):读写权限,可是只能被单个节点挂载
ReadOnlyMany (ROX):只读权限,可能被多个节点挂载
ReadWriteMany (RWX):读写权限,能够被多个节点挂载
注意: 一些pv可能支持多种访问模式,但挂载时候只可使用一种访问模式,多种访问模式不奏效
PV实际上没有存储,至关于咱们node同样,还须要建立Pod进行消费,接下来咱们进行PVC的建立与配置
# 建立一个数据卷声明 apiVersion: v1 kind: PersistentVolumeClaim metadata: name: pvc-nfs spec: accessModes: - ReadWriteOnce resources: requests: storage: 1Gi kubectl apply -f pvc-nfs-demo1.yaml kubectl get pvc NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE pvc-nfs Bound pv1 1Gi RWO kubectl get pv NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE pv1 1Gi RWO Recycle Bound default/pvc-nfs 10m # 当咱们建立pvc以后,pv的状态变成了Bound绑定状态,而且和pvc的状态相同。 # 而且能够看到pvc已经绑定到名称为pv1的volume上, # 同时在pv上能够看到绑定到名称为pvc-nfs的pvc中
[root@master storage]# kubectl describe pv pv1 Name: pv1 Labels: <none> Annotations: pv.kubernetes.io/bound-by-controller: yes Finalizers: [kubernetes.io/pv-protection] StorageClass: Status: Bound Claim: default/pvc-nfs Reclaim Policy: Recycle Access Modes: RWO VolumeMode: Filesystem Capacity: 1Gi Node Affinity: <none> Message: Source: Type: NFS (an NFS mount that lasts the lifetime of a pod) Server: nfs1 Path: /data/volumes ReadOnly: false Events: <none> [root@master storage]# kubectl describe pvc pvc-nfs Name: pvc-nfs Namespace: default StorageClass: Status: Bound Volume: pv1 Labels: <none> Annotations: pv.kubernetes.io/bind-completed: yes pv.kubernetes.io/bound-by-controller: yes Finalizers: [kubernetes.io/pvc-protection] Capacity: 1Gi Access Modes: RWO VolumeMode: Filesystem Mounted By: <none> Events: <none> # 在Kubernetes中会自动帮咱们查看pv状态为Available而且根据声明pvc容量storage的大小进行筛选匹配, # 同时还会根据AccessMode进行匹配。若是pvc匹配不到pv会一直处于pending状态。
同时,pv与pvc中间还能够经过label标签进行匹配,配置以下
# 记得下面配置文件修改一下名字,不能够重复 apiVersion: v1 kind: PersistentVolume metadata: name: pv2 labels: #这里将pv设置一个labels app: nfs spec: capacity: storage: 1Gi accessModes: - ReadWriteOnce persistentVolumeReclaimPolicy: Recycle nfs: path: /data/volumes server: nfs1 --- apiVersion: v1 kind: PersistentVolumeClaim metadata: name: pvc2-nfs spec: accessModes: - ReadWriteOnce resources: requests: storage: 1Gi selector: # pvc匹配标签为app=nfs的pv matchLabels: app: nfs [root@master storage]# kubectl apply -f nfs_demo2.yaml [root@master storage]# kubectl get pv,pvc NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE persistentvolume/pv1 1Gi RWO Recycle Bound default/pvc-nfs 28m persistentvolume/pv2 1Gi RWO Recycle Bound default/pvc2-nfs 94s NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE persistentvolumeclaim/pvc-nfs Bound pv1 1Gi RWO 17m persistentvolumeclaim/pvc2-nfs Bound pv2 1Gi RWO 94s # 有一点须要注意,当咱们pvc申请的容量小于咱们pv的容量是能够进行绑定的 # 当咱们申请pvc的容量大于pv的容量是没法进行绑定的。 这里须要注意
[root@master storage]# cat deployment-nfs1.yaml apiVersion: apps/v1 kind: Deployment metadata: name: pv-nfs-nginx spec: replicas: 3 selector: matchLabels: app: pv-nfs-nginx template: metadata: labels: app: pv-nfs-nginx spec: containers: - name: pv-nfs-nginx image: nginx ports: - containerPort: 80 volumeMounts: #挂载,首先添加须要挂载的目录 - name: pv-nginx #挂载点的名称 mountPath: /usr/share/nginx/html #挂载点的路径 volumes: #绑定 - name: pv-nginx persistentVolumeClaim: #将镜像中的nginx目录挂载到下面名称的pvc中 claimName: pvc-nfs #pvc名称 --- apiVersion: v1 kind: Service metadata: name: nfs-pvc labels: app: pv-nfs-nginx spec: type: NodePort ports: - port: 80 targetPort: 80 selector: app: pv-nfs-nginx [root@master storage]# kubectl apply -f deployment-nfs1.yaml
[root@master storage]# kubectl get pods,svc |grep pv pod/pv-nfs-nginx-6b4759b5b8-95vhk 1/1 Running 0 74s pod/pv-nfs-nginx-6b4759b5b8-mjwhn 1/1 Running 0 58s pod/pv-nfs-nginx-6b4759b5b8-p4jhn 1/1 Running 0 11s service/nfs-pvc NodePort 10.0.0.57 <none> 80:31850/TCP 4m23s # 这里咱们能够看到pod已经正常启动,而且svc也已经暴露端口了。 # 若是须要更改显示内容,更改以前nfs存储里面的那个目录文件就能够了
因为咱们的index.html直接挂在到了/data1/k8s目录下面,若是有不少个pod都使用pvc进行挂载,会形成咱们数据目录的文件比较乱
这里咱们添加一个
subpath
subPath的目的是为了在单一Pod中屡次使用同一个volume而设计的。
[root@master storage]# cat deployment-nfs1.yaml apiVersion: apps/v1 kind: Deployment metadata: name: pv-nfs-nginx spec: replicas: 3 selector: matchLabels: app: pv-nfs-nginx template: metadata: labels: app: pv-nfs-nginx spec: containers: - name: pv-nfs-nginx image: nginx ports: - containerPort: 80 volumeMounts: #挂载,首先添加须要挂载的目录 - name: pv-nginx #挂载点的名称 mountPath: /usr/share/nginx/html #挂载点的路径 subPath: nginx-pvc volumes: #绑定 - name: pv-nginx persistentVolumeClaim: #将镜像中的nginx目录挂载到下面名称的pvc中 claimName: pvc-nfs #pvc名称 --- apiVersion: v1 kind: Service metadata: name: nfs-pvc labels: app: pv-nfs-nginx spec: type: NodePort ports: - port: 80 targetPort: 80 selector: app: pv-nfs-nginx [root@master storage]# kubectl apply -f deployment-nfs1.yaml # 当咱们更新完pod以后,等pod正常启动。 # 就能够看到在咱们nfs存储目录下面单首创建了一个名称为nginx-pvc的目录, # 这个目录实际上就是咱们subpath后面指定的名称 [root@nfs1 ~]# ls /data/volumes/ index.html nginx-pvc [root@nfs1 ~]# ls /data/volumes/nginx-pvc/ [root@nfs1 ~]# mv /data/volumes/index.html /data/volumes/nginx-pvc/ # 这个目录下面也是没有任何文件的,咱们须要将原来index.html拷贝过去便可 # 如今咱们删除deployment,下面的数据并不会删除。这样使用pv和pvc持久化就完成 # 若是咱们直接删除或者有pod在使用pv或者pvc是没法直接删除的, # 当咱们使用Recycle模式时,删除全部pv和pvc后, # 数据也会进行删除。因此删除pv和pvc请谨慎操做 [root@nfs1 ~]# cat /data/volumes/nginx-pvc/index.html Pvc Persistence
NFS存储的缺点
不支持动态建立持久卷,只能手工建立
先手工建立PV,再经过PV手工建立PVC,PVC就是真正可用的持久卷PVC是和PV进行绑定的: PVC会根据本身需求空间的大小自动选择合适的PV,好比须要一个5G的PVC,PV分别为2G,7G和10G,那么PVC会自动选择7G的,可是剩余的空间不是浪费了么?缘由以下: 一个被绑定的PV只能用于一个PVC,他们是一对一绑定的,若是PVC大小只须要5G,可是所选的PV有7G,那么剩余的2G是没办法使用的,若是不想这样浪费空间只能使用动态建立的方式.