云计算时代操作系统Kubernetes之存储(中)

我们在POD中定义数据卷的时候,必须指定数据卷的类型。由于存储技术的发展远远早于Kubernetes平台的诞生,并且随着Kubernetes的日益流行,新的存储技术和方案也在日新月异,因此数据卷可以说理所当然的有很多很多类型,有些是通用的类型,而有些需要底层特定存储技术的支持,下边是Kubernetes支持的数据卷类型不完全清单:

- emptyDir类型,emptyDir类型的数据卷允许POD将数据保存到指定的文件夹中,并且数据在POD的整个生命周期中可见。保存数据的文件夹在POD启动前被创建,并且刚开始文件夹为空,这也是叫empty的缘由。

- hostPath类型,从宿主机的文件系统挂载文件到POD中。

- nfs类型,NFS类型的存储卷挂载到POD中。

- cephfs,cinder,fc等,用来支持不同类型的网络存储。

- configMap,secret,downwardAPI,以及projected类型,四种卷类型,用来将POD和Kubernetes的相关信息通过文件暴露给外部,这些卷类型主要用来配置应用程序。这几种类型笔者会在后续的文章中详细介绍。

- persistentVolumeClaim类型(PVC),一种轻量级的集成外部存储能力的方案。在这种类型的数据卷类型中,PersistentVolumeClaim类型的存储对象指向PersistentVolume类型的存储对象,真实的外部存储系统由PersistentVolume这个对象来引用。由于这是Kuberntes强烈建议大家使用的存储类型,因此笔者会在后续的文章中,单独来详细介绍。

- csi类型,一种通过CSI来扩展存储的方式。这种方式允许所有实现了CSI(Container Storage Interface)接口的存储实现能够被POD引用,在POD初始化的过程中,CSI驱动会将存储卷attach到POD上。

上边罗列的只是数量巨大存储卷类型中很小一部分,每种类型都有对应的使用场景。笔者在本篇以及后续的文章中,着重介绍最具代表性的几个类型,来帮助大家理解Kubernetes存储体系。首先我们从最简单的emptyDir类型开始,这种类型的数据卷用来在容器重启场景中保持状态。

【使用emptyDir数据卷在容器重启场景下保持状态】

还记得我们在前边文章中介绍如何在同一个POD中部署两个容器实例的例子吗?当时的做法是通过post-start hook来执行fortune命令产生一个名言警句写入文件中,运行在另外一个容器中的Nginx服务器由于挂载了相同的volume,因此会直接将这个信息返回给客户端请求。这个保存fortune产生的名言警句的文件在容器的文件系统中,这就意味着当容器由于liveness probe三次失败重启后,你会看到不同的名言警句,虽然说看起来问题不大,但是从原理上讲,数据由于容器重启丢失。

我们来验证一下上边的推理是否符合事实,请在自己的本地环境中部署yunpan-fs.yaml,然后执行kubectl port-forward yunpan-fs 1080:80来创建客户端代理,访问服务返回名言警句。然后通过命令让Nginx重新启动,重新访问服务,你可以看到两次返回的数据不一致,这就证明了保存在容器文件系统的数据,在容器重启的场景下,不会保持。在笔者的本地环境输出如下图:

《图1.1 基于容器文件系统的数据存储在重启场景下不保持》

如上图所示,重启容器后会产生新的名言警句,这就意味着容器重启后保存在文件系统中的数据丢失了。如果我们要在这种重启的场景中保持数据状态,那么就必须确保数据被保存在数据卷中,而emptyDir是解决这个问题的完美方案。当emptyDir类型的数据卷被挂载到容器中,应用写到挂载目录的数据文件,在容器重启后,能够继续保持。

emptyDir类型的数据卷可以让容器即便是重启后,可以让写到文件中的数据状态保持;或者容器的文件系统为只读,但是应用在运行的过程中,需要写状态到文件中等场景,我们也可以使用emptyDir类型的数据卷来在同一个POD的多个容器之前,进行数据共享。

废话不多说了,咱直接修改fortune pod来把post-start hook执行fortune命令返回的名言警句写到emptyDir类型的数据卷中,这样当容器重启后,就不会出现数据丢失了。我们其实要修改的地方不多,主要包括:1,给POD增加emptyDir类型的数据卷定义;2,在容器中将这个数据卷挂载到指定的目录。

另外我们对命令的执行进行了一点点优化,post-start hook会在每次容器启动后都会执行,因此我们需要防止重启后对fortune命令输出对已经存在文件的覆盖,因此我们对post-start命令脚本也做了优化,如下图所示:

《图1.2 给POD增加emptyDir类型的存储卷,并挂载到容器实例》

注:post-start hook脚本被更新成"ls /usr/share/nginx/html/quote || (apk add fortune && fortune > /usr/share/nginx/html/quote)",如果读者对Linux shell脚本不是很熟悉,这句肯定看的云里雾里,我们来稍微解释一下。首先ls命令先执行,我们这里用ls来检查quote文件是否存在,你有所不值得是,当ls后边给的文件存在的时候,命令返回0,而如果不存在,就返回非0。由于我们使用||将两个表达式进行了组合,因此当左边的ls quote执行成功,那么右边的语句就压根不会执行。通过这种方式,如果quote文件存在,那么咱就直接跳过了。而当文件不存在,才需要执行右边的一串命令,安装fortune和执行fortune来产生名言警句。这句脚本确保名言警句只被生成并写入一次,也就是只在容器第一次启动的时候。

如上图所示,我们定义了emptyDir类型的数据卷content,并挂载到nginx容器指定目录/usr/share/nginx/html(这个是Nginx服务器默认用来扫描静态资源的目录)。在POD中配置volume需要提供配置参数,接下来我们详细聊聊如何配置emptyDir类型的数据卷。

对于emptyDir类型的存储卷,Kubernetes要求配置如下两个属性:

- medium,文件夹的存储介质,如果留空不配置,那么默认就是宿主机的(工作节点)磁盘。除了磁盘之外,我们还可以配置Memory,这会导致数据卷使用tmpfs文件系统,这是一个在内存文件系统。

- sizeLimit,文件夹需要的磁盘空间大小,比如我们如果需要限制这个文件夹中文件的大小为10M,那么就可以设置为10Mi。

注:我们上边的例子中,emptyDir类型的数据卷content未显示的定义任何字段,取默认值,大括号非常明确的表达了这一点,但是并不是必须的。

在POD中定义完数据卷只完成了工作的一半,工作的另一半就是将数据卷挂载到容器实例中,这通过在容器spec.containers域通过volumeMounts来引用。volumeMounts除了要制定name之外,还需要包含mountPath字段,来指定数据卷被具体挂载到容器文件系统的文件目录树的那个路径。笔者上边提供的例子中,emptyDir类型的数据卷被挂载到了/usr/share/ngxin/html目录,因为这也是post-start hook将名言警句写到文件的路径。

由于使用了emptyDir类型的数据卷之后,名言警句被写入到了宿主机的文件系统,因此数据在POD的整个生命周期都会保持,因此我们无论重启nginx容器多少次,返回的数据(名言警句)都不应该有任何变化。

接下来,我们将这个新版本基于fortune命令的名言警句网站部署到Kubernetes集群,并人为的让nginx容器重启,你会发现无论我们重启多少次,quote接口返回的内容都一样。背后的原理是,因为我们只在容器第一次启动的时候,才创建quote文件,并且当容器重启重新挂载数据卷后,这个quote文件仍然存在。你可能会问,这个文件到底在宿主机的啥地方啊,可以运行kubectl exec yunpan-emptydir -- mount --list | grep nginx/html来发现,如下图所示:

《图1.3 新版本基于fortune命令的网站输出》

如上图所示,通过使用emptyDir类型的数据局content,我们成功让容器重启之后,保持数据状态。接下来,我们继续看另外一个例子,如何通过数据卷在两个容器时间共享数据。

【使用emptyDir类型的数据卷在两个容器之间共享数据】

如笔者前边多次提到,我们也可以使用emptyDir类型的数据卷来在同一个POD中的两个容器之间共享数据,这里需要注意的是,我们无法通过emptyDir类型的数据卷在不同PDO中不同的容器间共享数据,请继续阅读。

我们基于fortune的名言警句网站目前略显无趣,因为每次都返回相同的谚语,我们希望这个行为能够增强,比如每30分钟更换一次。为了实现这个功能,我们需要将post-start hook替换成容器,并且在容器中,fortune命令每30秒运行一次。为了使大家学习更加容易,笔者已经构建好了需要的容器,并上传到Docker Hub,大家可以自行通过命令 docker pull qigaopan/yunpan-fortune:v1.0拉取。

好了,我们已经把需要的容器镜像都准备好了,接下来我们来编写POD的YAML文件,如下图所示:

《图1.4 新版本fortune命令的名言警句网站YAML定义》

如上图所示,emptyDir类型的数据卷被两个容器共享(共同挂载),容器fortune将数据写到content数据卷,在nginx容器中,相同的数据卷被以read-only的模式被挂载到nginx的默认目录。

注:我们在前边文章中反复强调过一个事实,同一个POD中的多个容器几乎是同时启动的,因此可能存在微小的一段时间,ngxin服务器已经成功运行起来,但是quote文件尚未生成。聪明的你可能想到了,要避免这种场景,我们可以使用初始化容器。

接着,我们将fortune POD部署到Kubernetes集群中,两个容器几乎同时开始运行。fortune容器每30秒更新一次谚语(名言警句),nginx容器基于相同的数据文件服务客户端请求,当POD中的两个容器都Ready后,可以验证一下输出,是否每30秒后,quote请求对应的谚语的返回会更新。

《图1.5 验证新版本fortune提供的功能是否符合预期》

由于在fortune例子中emptyDir类型的数据卷会在宿主机的磁盘上创建共享目录,因此数据读写的性能,完全取决于工作节点上硬件的类型。如果我们的应用需要高性能的IO操作,那么磁盘可能不是最合适的存储介质。

Kubernetes允许我们使用tmpfs文件系统来创建数据卷,而tmpfs将数据保存在内存中,我们只需要在POD的YAML文件中,把emptyDir的字段meidum设置为Memory。

其实Memory类型的数据卷除了提供较高的IO之外,数据安全性也比磁盘高。由于数据并没有落盘,因此数据不容易被恶意攻击者窃取,因此建议大家可以在自己的项目上考虑这种数据卷类型。另外我们也可以通过参数sizeLimit来约束数据卷的size,特别对于Memory类型的数据卷来说,请务必设置sizeLimit,以防内存被耗尽。

在前边的内容中,我们将目光主要集中在如何在POD中定义数据卷,而没有详细介绍volume是如何挂载到容器中的,接下来我们来看看在容器中挂载数据卷具体需要设置哪些参数。如下图所示,是我们在新版本的fortune POD定义中关于content数据卷挂载的配置:

《图1.6 在容器中挂载数据卷》

从上图可以看出,挂载数据卷到容器中,我们需要至少配置两个字段:name和mountPath,其中name字段是我们在POD定义的数据卷的名字,而mountPath字段指定了数据卷应该挂载到容器文件系统的文件数的那个目录。

除了这两个必须提供的参数之外,我们还有一些可选的参数可以配置,详细的可配置参数清单如下:

- name字段,如笔者上边的介绍,name字段就是我们在POD中挂载的数据卷的name

- mountPath字段,前文应介绍,不累述

- readOnly字段,是否以只读的模式挂载数据卷,默认是false,也就是以读写的方式挂载数据卷。

- mountPropagation字段,设置如果在数据卷内部挂载额外的文件系统会发生什么。有几个选项,默认是none,指如果宿主机在数据卷中挂在了额外的文件系统,容器不会收到任何通知,反之亦然;还有两个选项HostToContainer和Bidirectional,具体含义如命名,如果要了解详情,可以参考官方文档。

- subPath字段,默认为“”,意味着整个数据卷都被挂载到mountPath指定的目录,当设置为非空的字符串后,只有subPath指定的文件路径被挂载到容器中

- subPathExpr字段,使用类似于shell提供的$(ENV_VAR_NAME)语句,只能使用环境变量。

在大部分场景下,我们只需要设置name和mountPath就可以了,顶多额外多配置参数readOnly。mountPropagation参数只有在一些复杂配置的场景下才会用到,当我们用一个数据卷来提供不同的文件夹给不同的容器的时候,subPath和subPathExpr非常有用。另外这两个参数也可以用作多个PDO共享一个数据卷的场景。

好了,这篇文章的内容就这么多了,下篇文章我们继续介绍存储,看看如何访问宿主机文件系统中的数据文件,敬请期待!

你可能感兴趣的:(云计算时代操作系统Kubernetes之存储(中))