volume设计解读
在Kubernetes中,volume的使用方式类似于虚拟机的磁盘,需要给pod(即一个逻辑上的虚拟机)挂一个磁盘,然后该pod里的进程(容器)才能通过volumeMounts的方式使用挂载磁盘。pod容器内的进程能够看到的文件系统由两部分组成:一部分是Docker像文件系统,另一部分是零或多个volume。每个容器都会单独指定每个volume在其内部的挂载点,即pod资源文件的volumeMounts属性.这也印证了nod内的容器是共享这个volume的。
kubernetes的volume机制特点:
- Kubernetes中,volume的生命周期与pod相同,volume会随着pod的销毁而销毁。然而volume并不会因为pod内某个容器的重启而销毁。所以说Kubernetes的volume的生命周期大于pod内容器,而等于pod。
- Kubernetes对volume的用途进和场景行了细化,实现了许多特殊用途的volume,比如persistent volume类型,可以不随pod的销毁而销毁,并且支持GCEPersistentDisk以及AWSElasticBlockStore等第三方存储;secret可以用来在pod之前传递诸如用户名密码之类的敏感信息;configmap可以用来在pod之间传递应用的配置信息。
下面介绍常用的19种不同功能的volume,它们的主要功能如表所示。
volume实现原理分析
在Kubernetes中,volume是作为kubelet的插件的形式而存在的,在kubelet启动的时候,需要传入一个VolumePlugin数组,VolumePlugin的主要功能功能如下:
- 对插件本身的初始化操作,比如在empty_ dir类型的volume中,初始化操作就是设定plugins实例的host的名称为这个volume所在的host。
- 检查该volume插件是否支持创建volume的配置文件中所指定的volume类型。
- 返回一系列实现各种接口的实例,用以支持对具体类型的volume的操作。
可以看到,VolumePlugin所暴露出来的方法主要方便kubelet管理各种类型的volume。而为了实现对具体类型的volume的操作,如挂载、删除等,需要实现一些列如Builder, Cleaner之类的其他接口,这些接口都扩展了一个共同的Volume接口。
Volume接口定义很简单,只有两个函数,一个是得到volume要挂载的目录的路径,另一个是得到当前volume的度量值(即volume已经使用的空间大小,底层文件系统的总空间的大小,以及volume可以用的底层文件系统的容量大小)。在Volume这个基本接口的基础上,又扩展出了一系列的接口,用于进行特定volume类型的操作,比如Builder接口和Cleaner接口用于mount或者unmont一类具体的volume; Attacher与Detacher接口用于将一类特定volume分配到node上,或者解除绑定,等等。
下面我们以empty-dir类型(创建volume时候默认的类型)的volume为例,看看这些接口是如何实现的。
在empty_dir的package中,定义了一个名为emptyDirPlugin的结构体,这个结构体实现了上文所说的VolumePlugin的全部方法,在kubelet启动的时候,emptyDirPlugin的实例会被转化为VolumePlugin接口,并被注册到kubelet的参数中。
kubeler可以使用emptyDirPlugin的方法获取实现了各种接口的实例,这些实例就是上文所说的实现了Volume接口的struct所生成的。在empty-dir中,名为emptyDir的结构体实现了上文所说的接口,比如Builder, Deleter等,用于执行具体的挂载以及删除的操作。
volume使用案例
- EmptyDir
EmptyDir类型的volume创建于pod被调度到某个宿主机上的时候,而同一个pod内的容器都能读写EmptyDir中的同一个文件。一旦这个pod离开了这个宿主机,EmptyDir中的数据就会被永久删除。所以目前EmptyDir类型的volume主要用作临时空间,比如Web服务器写日志或者tmp文件需要的临时目录。
使用步骤如下:
首先,需要在pod内声明了一个名称为redis-data的volume。
代码语言:txt复制 volumes:
- name: redis-data
emptyDir:{}
然后,才能在容器中挂在这个volume。
代码语言:txt复制 volumeMounts:
# mountPath即volume在容器内的挂载点路径
- mountPath: /redis-master-data
# name字段必须与下面的volume名匹配
name: redis-data
按照以上的配置文件创建了pod之后,可以在宿主机上的/var/lib/kubelet/pods/<pod uid>/volumes/kubernetes.io~empty-dir目录下查看到新生成的名为redis-data的目录。如果登录到该pod创建的docker容器中,也可以看到名为/redis-master-data的目录,这个目录与宿主机上的redis-data目录是同一个。
- HostDir
HostDir属性的volume使得对应的容器能够访问当前宿主机上的指定目录。例如,需要运行一个访问Docker系统目录的容器,那么就使用/var/lib/docke:目录作为一个HostDir类型的volume;或者要在一个容器内部运行cAdvisor,那么就使用/dev/cgroups目录作为一个HostDir类型的volume。一旦这个pod离开了这个宿主机,HostDir中的数据虽然不会被永久删除,但数据也不会随pod迁移到其他宿主机上。因此,需要注意的是,由于各个宿主机上的文件系统结构和内容并不一定完全相同,所以相同pod的HostDir可能会在不同的宿主机上表现出不同的行为。
- NFS
NFS类型的volume允许一块现有的网络硬盘在同一个pod内的容器间共享。先来看下面这个在一个pod中使用NFS volume的例子。
在这个例子中,可以看到一个名为myshare的volume挂载到容器testpd文件系统的/var/www/html/mount-test路径上。该volume被定义为NFS类型,非只读类型,来自于IP为172.17.0.2的NFS服务器,该NFS服务器对外暴露/tmp作为共享目录。
persistent volume
pv(PersistentVolume)与Kubernetes中nfs等其他volume类型不同,具有与pod独立的生命周期,并有单独的管理API。pvc(PersistentVolumeClaim)是用户对于某存储资源的请求,它充当了pod和pv之间的“中介”。在创建pod时,用户可以指定volume的来源为pvc,并在其中指定希望绑定的pv资源的规格,如存储空间大小、访问模式等。之后,Kubernetes会在创建pod时根据pvc的需求,将之与可选的pv匹配,匹配成功后pod就可以使用pv所代表的volume资源。
用户使用pvc和pv的流程一般包含如下几个阶段:
- pv创建阶段:在这个阶段中,集群的管理员可以通过Kubernetes所提供的API提前创建好多种规格的pv资源,将来供pod使用。
- pvc与pv绑定阶段:master节点会不断对状态为pending的pvc进行遍历,所以一旦用户创建了新的pvc对象,master就会从所有的volume中找出最合适的pv,选择的过程会参考pvc列出的storage和accessmode两方面的参数。一旦匹配成功,pv以及pvc的状态都会变为Bound,该pv不可以再被其他的pvc所使用。
- pod使用pvc阶段:当Kubernetes启动pod时,可以通过pod使用的pvc中所持有的pv信息,找到对应的pv并挂载到pod中。
- 释放pv阶段:当用户使用完pv之后,可以通过删除pvc来释放其所持有的pv资源。
- 回收阶段:Kubernetes会回收被释放了的pv。目前支持的回收策略包括:Retained(资源保持)、Recycled(资源回收)、Deleted(资源删除)几种不同的方式。