PV PVC这种设计,是否有种过度设计的嫌疑
我们完全可以自我维护一套NFS或者Ceph服务器,不必学习Kubernetes,而开发人员,可以直接在Pod中声明Volumes,而不是PVC
看起来可以,但是如果Kubernetes内置的20种持久化数据卷实现,都没法满足这个容器存储的需求时候,该怎么办,而且,我们需要更换实际上提供PV的StorageClass,该怎么办?
而且,现在来说,我们使用远程存储服务,不如直接使用宿主机上的本地磁盘目录,而不是依赖于远程存储服务,来提供持久化的Volume
毕竟一个本地的SSD磁盘来提供持久化,可比远程存储快多了
所以,Kubernetes在v1.10之后.提供了一个Local Persistent Volume
当然,这个Local Persistent Volume不适合于所有的应用,适用的范围非常固定,高优先级的系统应用,对IO敏感,分布式数据存储 MongoDB Cassandra 分布式文件存储 Ceph,因为Local Persistent Volume节点宕机会导致数据的丢失,所以最好有数据备份或者同步的能力
那么,看起来比较容易实现的Local Persistent Volume,在我看来,就是hostPath 加上指定Node的NodeAffinity嘛
但是,不应该将一个宿主机上的目录当做PV使用,因为这种本地的存储其实并不可控,所在的磁盘随时都可能被应用写满,甚至导致宿主机宕机,因为并不具有物理上的隔离机制
一个Local Persistent Volume对应的存储,应该是一个额外挂载的宿主机上的磁盘或者块设备,额外的意思是指一个PV一个对应磁盘的含义
第二个难点在于,调度器如何保证Pod始终保证Pod能被正确的调度到所请求的Local Persistent Volume节点上?
造成这个问题是,Kubernetes是先调度Pod到某个节点上,然后在二阶段的处理这个机器上的Volume目录,完成Volume目录和容器的绑定挂载
可是,对于Local PV来说,磁盘是绑定在特定节点上的
对于这种情况,就需要调度器知道节点和Local Persistent Volume对应的磁盘关系,然后根据这个信息来调度Pod
这个原则,就是调度时候考虑Volume分布,在Kubernetes的调度器里,有一个VolumeBindingChecker的过滤条件来负责这个事情,有一个叫做VolumeBindingChecker的过滤条件来负责这件事
但是在实际使用这个Local Persistent Volume之前,首先在集群中配置好磁盘或者块设备
也就是额外挂载一个磁盘
我们可以先创建一个挂载点,用来模拟磁盘
然后为本地磁盘来创建对应的PV了
apiVersion: v1
kind: PersistentVolume metadata: name: example-pv spec: capacity: storage: 5Gi volumeMode: Filesystem accessModes: – ReadWriteOnce persistentVolumeReclaimPolicy: Delete storageClassName: local-storage local: path: /mnt/disks/vol1 nodeAffinity: required: nodeSelectorTerms: – matchExpressions: – key: kubernetes.io/hostname operator: In values: – node-1 |
这个配置中,指定了这是一个Local Persisten Volume,指定了本地的路径 /mnt/disks/vo1
然后在nodeAffinity中指定了绑定的Pod,比如就是node-1,这样调度器在调度Pod的时候,就需要知道一个PV和节点对应的关系,做出正确的选择,这就是kubernetes需要在调度的时候考虑Volume分布的主要方法
然后我们看这个PV创建后,进入了Available状态
那么这样,我们在创建一个对应的PVC,来将PV进行绑定,为了方便绑定操作,我们还声明了一个StorageClass来描述这个PV
kind: StorageClass
apiVersion: storage.k8s.io/v1 metadata: name: local-storage provisioner: kubernetes.io/no-provisioner volumeBindingMode: WaitForFirstConsumer |
这个StorageClass指定的Provisioner是no-provisioner,这是Local Persistent Volume目前不支持Dynamic Provisioning,所以没法在创建PVC的时候,自动创建出对应的PV,也就是说创建PV的操作,是不可以省略的
除此外,StorageClass还定义了一个voluemBindingMode=WaitForFirstConsumer的属性,是Local Persistent Volume中的一个重要特性,延迟绑定
只有提交了PV和PVC的YAML文件,Kubernetes才能根据对应的属性,以及制定的StorageClass来进行绑定,绑定成功后,Pod才能通过声明这个PVC来使用PV
但是这个流程在Local 上并不好使,因为在Kubernetes集群中,有两个属性相同的Local类型的PV,其中第一个pv的名字是pv-1,对应的磁盘所在节点是node-1,第二个pv的名字叫做pv-2,所在节点是node-2
所以Kuberentes的Volume控制循环中,检查到了一个Pod使用的是pvc1 pvc-1和pv-1在node-1上进行绑定,这样Pod会被调度到
但是,Pod假如不被允许运行在node-1上的话,最后结果会导致这个Pod的调度失败了
那么,我们就需要延迟,PV和PVC绑定的时机
仍然是Pod先部署在某个节点,但是不会出现PV和PVC一旦符合就绑定的情况,而是在Pod部署在某个节点后,在进行绑定
也就可以认为,StorageClass 中的 volumeBindingMode=waitForFirstConsumer的含义,就是告诉Kubernetes中的Volume控制循环,虽然已经发现这个StorageClass关联的PVC和PV可以绑定在一起,但是不要现在就进行绑定
要等到第一个使用这个PVC的Pod出现在调度器之后,调度器综合考虑所有的调度规则,来决定这个PVC到底和那个PV绑定
也就是上面的Pod所使用的的pvc-1并不会和pv-1绑定,而是等Pod部署在了Node2之后,和pv-2绑定,并且Pod会被调度到node-2上
所以,这个延迟绑定机制,就是讲原本实时进行的绑定过程,延迟到了Pod第一次调度时候在调度器中进行,保证了绑定结果不会影响Pod的正常调度
而且,调度器还维护了一个和Volume Controller类似的控制循环,为这些声明了延迟绑定的PV和PVC进行绑定,确保了不拖累主绑定的性能,而且,一旦一个Pod的PVC没法完成绑定,则会将其放入调度队列,等待下一个周期
假如我们创建了这个StorageClass,这个StorageClassName是Local-storage,所以不会立刻进行绑定操作
然后可以考虑创建一个PVC来查看是否会绑定
$ kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
example-local-claim Pending local-storage 7s
这样,PVC处于了Pending的状态,还是等待绑定
然后我们编写一个Pod来声明使用这个PVC
kind: Pod
apiVersion: v1 metadata: name: example-pv-pod spec: volumes: – name: example-pv-storage persistentVolumeClaim: claimName: example-local-claim containers: – name: example-pv-container image: nginx ports: – containerPort: 80 name: “http-server” volumeMounts: – mountPath: “/usr/share/nginx/html” name: example-pv-storage |
这个Pod声明使用我们之前定义的PVC了
立刻编程Bound状态,与前面的PV绑定在一起
所以我们的Pod进入调度器,绑定操作才开始进行
这就说明,像是Kubernetes这样构建出来的,基于本地存储的Volume,可以提供容器存储的功能
而且,删除PV的时候,需要按照如下的流程进行操作
1.删除使用这个PV的Pod
2.从宿主机移除本地磁盘
3.删除PVC
4,删除PV
不按照这个流程删除,这个PV就会删除失败
在本章中,我们介绍了一个Kubernetes中Local Persistent Volume实现方式
正是通过PV和PVC,以及StorageClass这套存储体系,来解耦对现有用户的影响,作为上层的系统,只需要编写对应的Pod文件和PVC文件,并不关心对应的PV和StorageClass如何实现的
这种解耦的考虑,都是考虑为开发者提供更多的可扩展性
延迟绑定的特性导致了Local Persistent Volume不支持Dynamic Provisioning,为何?
一般来说,Dynamic Provision机制是通过PVC来创建PV的,而Local Persistent Volume先创建PV,然后创建PVC,只不过Pod创建的时候才会真正的绑定,这就导致和Pod声明的Node可能冲突,还需要重新创建一个PV,如果Dynamic Provisioning能够推迟PV创建,考虑Pod的调度条件和node信息,才能实现dynamic provisioning