一、PV和PVC的引入
Volume 提供了非常好的数据持久化方案,不过在可管理性上还有不足。
Pod 通常是由应用的开发人员维护,而 Volume 则通常是由存储系统的管理员维护。开发人员要获得上面的信息:
要么询问管理员。
要么自己就是管理员。
这样就带来一个管理上的问题:应用开发人员和系统管理员的职责耦合在一起了。如果系统规模较小或者对于开发环境这样的情况还可以接受。但当集群规模变大,特别是对于生成环境,考虑到效率和安全性,这就成了必须要解决的问题。
Kubernetes 给出的解决方案是 PersistentVolume 和 PersistentVolumeClaim。
PersistentVolume (PV) 是外部存储系统中的一块存储空间,由管理员创建和维护。与 Volume 一样,PV 具有持久性,生命周期独立于 Pod。
PersistentVolumeClaim (PVC) 是对 PV 的申请 (Claim)。PVC 通常由普通用户创建和维护。需要为 Pod 分配存储资源时,用户可以创建一个 PVC,指明存储资源的容量大小和访问模式(比如只读)等信息,Kubernetes 会查找并提供满足条件的 PV。
有了 PersistentVolumeClaim,用户只需要告诉 Kubernetes 需要什么样的存储资源,而不必关心真正的空间从哪里分配,如何访问等底层细节信息。这些 Storage Provider 的底层信息交给管理员来处理,只有管理员才应该关心创建 PersistentVolume 的细节信息。
为了方便开发人员更加容易的使用存储才出现的概念。通常我们在一个POD中定义使用存储是这样的方式,我们以hostpath类型来说:
apiVersion: v1 kind: Pod metadata: name: mypod spec: containers: - image: nginx name: mynginx volumeMounts: - mountPath: /usr/share/nginx/html name: html volumes: - name: html # 名称 hostPath: # 存储类型 path: /data # 物理节点上的真实路径 type: Directory # 如果该路径不存在讲如何处理,Directory是要求目录必须存在
其实通过上面可以看出来,无论你使用什么类型的存储你都需要手动定义,指明存储类型以及相关配置。这里的hostpath类型还是比较简单的,如果是其他类型的比如分布式存储,那么这对开发人员来说将会是一种挑战,因为毕竟真正的存储是由存储管理员来设置的他们会更加了解,那么有没有一种方式让我们使用存储更加容易,对上层使用人员屏蔽底层细节呢?答案是肯定的,这就是PV、PVC的概念。不过需要注意的是我们在集群中通常不使用hostPath、emptyDir这种类型,除非你只是测试使用。
二、什么是PV
PV全称叫做Persistent Volume,持久化存储卷。它是用来描述或者说用来定义一个存储卷的,这个通常都是有运维或者数据存储工程师来定义。比如下面我们定义一个NFS类型的PV:
apiVersion: v1 kind: PersistentVolume metadata: # PV建立不要加名称空间,因为PV属于集群级别的 name: nfs-pv001 # PV名称 labels: # 这些labels可以不定义 name: nfs-pv001 storetype: nfs spec: # 这里的spec和volumes里面的一样 storageClassName: normal accessModes: # 设置访问模型 - ReadWriteMany - ReadWriteOnce - ReadOnlyMany capacity: # 设置存储空间大小 storage: 500Mi persistentVolumeReclaimPolicy: Retain # 回收策略 nfs: path: /work/volumes/v1 server: stroagesrv01.contoso.com
accessModes:支持三种类型
ReadWriteMany 多路读写,卷能被集群多个节点挂载并读写
ReadWriteOnce 单路读写,卷只能被单一集群节点挂载读写
ReadOnlyMany 多路只读,卷能被多个集群节点挂载且只能读
这里的访问模型总共有三种,但是不同的存储类型支持的访问模型不同,具体支持什么需要查询官网。比如我们这里使用nfs,它支持全部三种。但是ISCI就不支持ReadWriteMany;HostPath就不支持ReadOnlyMany和ReadWriteMany。
persistentVolumeReclaimPolicy:也有三种策略,这个策略是当与之关联的PVC被删除以后,这个PV中的数据如何被处理
Retain
当删除与之绑定的PVC时候,这个PV被标记为released(PVC与PV解绑但还没有执行回收策略)且之前的数据依然保存在该PV上,但是该PV不可用,需要手动来处理这些数据并删除该PV。
Delete
当删除与之绑定的PVC时候
Recycle
这个在1.14版本中以及被废弃,取而代之的是推荐使用动态存储供给策略,它的功能是当删除与该PV关联的PVC时,自动删除该PV中的所有数据
storageClassName 指定 PV 的 class 为 nfs。相当于为 PV 设置了一个分类,PVC 可以指定 class 申请相应 class 的 PV。
注意:PV必须先与POD创建,而且只能是网络存储不能属于任何Node,虽然它支持HostPath类型但由于你不知道POD会被调度到哪个Node上,所以你要定义HostPath类型的PV就要保证所有节点都要有HostPath中指定的路径。
PV生命周期
PV是群集中的资源。PVC是对这些资源的请求,并且还充当对资源的检查。PV和PVC之间的相互作用遵循以下生命周期:
Provisioning ——-> Binding ——–>Using——>Releasing——>Recycling
供应准备Provisioning—通过集群外的存储系统或者云平台来提供存储持久化支持。
静态提供Static:集群管理员创建多个PV。 它们携带可供集群用户使用的真实存储的详细信息。 它们存在于Kubernetes API中,可用于消费
动态提供Dynamic:当管理员创建的静态PV都不匹配用户的PersistentVolumeClaim时,集群可能会尝试为PVC动态配置卷。 此配置基于StorageClasses:PVC必须请求一个类,并且管理员必须已创建并配置该类才能进行动态配置。 要求该类的声明有效地为自己禁用动态配置。
绑定Binding—用户创建pvc并指定需要的资源和访问模式。在找到可用pv之前,pvc会保持未绑定状态。
使用Using—用户可在pod中像volume一样使用pvc。
释放Releasing—用户删除pvc来回收存储资源,pv将变成“released”状态。由于还保留着之前的数据,这些数据需要根据不同的策略来处理,否则这些存储资源无法被其他pvc使用。
回收Recycling—pv可以设置三种回收策略:保留(Retain),回收(Recycle)和删除(Delete)。
保留策略:允许人工处理保留的数据。
删除策略:将删除pv和外部关联的存储资源,需要插件支持。
回收策略:将执行清除操作,之后可以被新的pvc使用,需要插件支持。
注:目前只有NFS和HostPath类型卷支持回收策略,AWS EBS,GCE PD,Azure Disk和Cinder支持删除(Delete)策略。
三、什么是PVC
PVC是用来描述希望使用什么样的或者说是满足什么条件的存储,它的全称是Persistent Volume Claim,也就是持久化存储声明。开发人员使用这个来描述该容器需要一个什么存储。比如下面使用NFS的PVC:
apiVersion: v1 kind: PersistentVolumeClaim metadata: name: nfs-pvc001 namespace: default labels: # 这些labels可以不定义 name: nfs-pvc001 storetype: nfs capacity: 500Mi #指定 PV 的容量为 500Mi spec: storageClassName: normal accessModes: # PVC也需要定义访问模式,不过它的模式一定是和现有PV相同或者是它的子集,否则匹配不到PV - ReadWriteMany resources: # 定义资源要求PV满足这个PVC的要求才会被匹配到 requests: storage: 500Mi # 定义要求有多大空间
accessModes 指定访问模式为 ReadWriteOnce,支持的访问模式有:
ReadWriteOnce – PV 能以 read-write 模式 mount 到单个节点。
ReadOnlyMany – PV 能以 read-only 模式 mount 到多个节点。
ReadWriteMany – PV 能以 read-write 模式 mount 到多个节点。
这个PVC就会和上面的PV进行绑定,为什么呢?它有一些原则:
PV和PVC中的spec关键字段要匹配,比如存储(storage)大小。
PV和PVC中的storageClassName字段必须一致,这个后面再说。
上面的labels中的标签只是增加一些描述,对于PVC和PV的绑定没有关系
应用了上面的PV和PVC,可以看到自动绑定了。
四、在POD中如何使用PVC呢
apiVersion: apps/v1 kind: Deployment metadata: name: tomcat-deploy spec: replicas: 1 selector: matchLabels: appname: myapp template: metadata: name: myapp labels: appname: myapp spec: containers: - name: myapp image: tomcat:8.5.38-jre8 ports: - name: http containerPort: 8080 protocol: TCP volumeMounts: - name: tomcatedata mountPath : "/data" volumes: - name: tomcatedata persistentVolumeClaim: claimName: nfs-pvc001
这里通过volumes来声明使用哪个PVC,可以看到和自己定义持久化卷类似,但是这里更加简单了,直接使用PVC的名字即可。在容器中使用/data目录就会把数据写入到NFS服务器上的目录中。
当我们删除那个PVC的时候,该PV变成Released状态,由于我们的策略是Retain,所以如果想让这个PV变为可用我们就需要手动清理数据并删除这个PV。这里你可能会觉得矛盾,你让这个PV变为可用,为什么还要删除这个PV呢?其实所谓可用就是删除这个PV然后建立一个同名的。
可以看出来PVC就相当于是容器和PV之间的一个接口,使用人员只需要和PVC打交道即可。另外你可能也会想到如果当前环境中没有合适的PV和我的PVC绑定,那么我创建的POD不就失败了么?的确是这样的,不过如果发现这个问题,那么就赶快创建一个合适的PV,那么这时候持久化存储循环控制器会不断的检查PVC和PV,当发现有合适的可以绑定之后它会自动给你绑定上然后被挂起的POD就会自动启动,而不需要你重建POD。
五、什么是持久化存储
我们知道所谓容器挂载卷就是将宿主机的目录挂载到容器中的某个目录。而持久化则意味着这个目录里面的内容不会因为容器被删除而清除,也不会和当前宿主机有什么直接关系,而是一个外部的。这样当POD重建以后或者在其他主机节点上启动后依然可以访问这些内容。不过之前说过hostPath和emptyDir则推荐使用,因为前者和当前宿主机有必然联系而后者就是一个随POD删除而被删除的临时目录。
六、宿主机是如何挂载远程目录的
挂载过程会有不同,这取决于远程存储的类型,它是块设备存储还是文件设备存储。但是不管怎么样POD有这样一个目录==/var/lib/kubelet/pods//volumes/kubernetes.io~/==这个目录是POD被调度到该节点之后,由kubelet为POD创建的。因为它一定会被创建,因为系统中的默认secret就会被挂载到这里。之后就要根据存储设备类型的不同做不同处理。
6.1文件存储设备
以nfs这种文件设备存储来说。我们依然启动之前的容器继续使用之前的PVC。
由于这个POD运行在node01节点,我们登陆node01节点,查看这个目录
==/var/lib/kubelet/pods//volumes/kubernetes.io~/==当你创建POD的时候它由于它被调度到node01节点,所以会创建这个目录,而且根据YAML中的定义就也会在这个目录中创建你在volumesMount中定义的目录,如下图:
通过命令查看在本地宿主机的挂载情况
由于创建了必要的目录,那么kubelet就直接使用mount命令把nfs目录挂载到这个目录上volumes/kubernetes.io~/,注意这时候仅仅是把这个远程存储挂载到宿主机目录上,要想让容器使用还需要做调用相关接口来把这个宿主机上的目录挂载到容器上。所以当准备好之后启动容器的时候就是利用CRI里的mounts参数把这个宿主机的目录挂载到容器中指定的目录上,就相当于执行 docker run -v。
不过需要注意的是由于nfs文件存储不是一个块设备,所以宿主机系统需要扮演的就是nfs客户端角色,kubelet就是调用这个客户端工具来完成挂载的。
6.2块存储设备
块存储设备你可以理解为一个磁盘。这个的处理要稍微复杂一点,就好像你为Linux服务器添加一块磁盘一样,你得先安装然后分区格式化之后挂载到某个目录使用。
==/var/lib/kubelet/pods//volumes/kubernetes.io~/==这个目录依然会创建。当POD被调度到该节点上会做如下操作
首先要安装一个块设备存储到宿主机(不是物理安装,而是通过API来安装),如何安装取决于不同块存储设备的API,很多云厂商有这种块存储设备比如Google的GCE。
格式化磁盘,
把格式化好的磁盘设备挂载到宿主机上的目录
启动容器挂载宿主机上的目录到容器中
相对于文件设备存储来说块设备要稍微复杂一点,不过上面这些过程都是自动的有kubelet来完成。