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

我们前边关于POD的所有内容都聚焦在POD以及里边运行的容器上,但是POD中不只有容器实例,其实我们只讲了故事的一半。故事的另一半是,容器在通常情况下,还包含存储数据的的volumes。Volumes允许容器在运行的过程中,将数据持久化下来,因为有些数据可能超越了容器和POD的生命周期,或者数据需要在多个容器之间共享。接下来我们通过几篇文章来详细介绍一下,如何在容器中持久化数据。

POD是个逻辑的概念,如果你仔细分析应用运行起来之后宿主机上的进程列表,你会发现根本没有POD这么个进程,因此我们可以把POD看成一台逻辑上的"微型计算机”,这个机器上只运行单个应用程序。引用程序可以由1个到多个容器进程组成(取决于这些容器今晨的关系),这些容器进程共享计算资源(CPU,内存,网路设备等)。在传统部署方式下,运行在同一台物理机或者虚拟机上的多个应用进程之间共享文件系统,但是容器进程可以做到更细粒度的文件系统隔离,每个容器进程可以有自己专属的文件系统。

当容器启动的时候,容器进程能够看到构建阶段打包进镜像的文件和文件夹,并且处于运行中的容器进程可以修改镜像中的文件,还可以创建新的文件。但是当容器退出重启后,我们对文件系统做的任何修改,都会丢失,原因笔者在上篇文章解释过,容器本质上没有重启的概念,而是通过启动新的容器实例来“替代”,启动新的容器实例的时候,文件系统中的肯定不包含我们在被替代容器中对数据做的修改。从这个角度来看,容器化部署的应用程序,当容器实例实例重启的时候,应用状态会丢失。虽然对哪些没有使用容器的文件系统持久化应用状态的应用来说,问题好像不太大,但是总有一些应用需要写状态到文件系统,并且这个状态要在容器重启后保持,我们需要更好的方案。

Kubernetes对这个问题的解决方案就是Volume(卷),通过给POD增加数据卷Volume,并且启动的时候挂挂载到容器的文件系统中,来解决数据跨容器数据共享,或者跨POD生命周期数据持久化的问题。

我们先从挂载这个概念说起。在Linux操作系统中(其实继承自Unix操作系统),一切皆文件,这和关系型数据库MYSQL中数据既索引,索引既数据有相同的宣传功效。一切皆文件这句话的含义是:所有文件都放置在以根目录为树根的树形目录结构中,并且任何硬件设备都是文件,它们各有自己的一套文件系统(文件目录结构)。当我们要访问一台设备的时候,我们需要让Linux操作系统能够访问设备的文件系统,这个将将Linux本身的文件目录与硬件设备的文件目录合并供操作系统访问的过程,就叫做“挂载”。这里需要注意的是,如果我们只是将设备插到机器而没有挂载,虽然我们通过罗列设备的命令也可以看到设备,但是无法访问设备的数据。如下图所示,我们将存储设备挂载到操作系统的文件树上的特定的位置,然后就可以访问存储设备上的数据了。

《图1.1 挂载数据卷Volume到操作系统文件树》

在Kubernetes对象体系中,Volume(卷)和容器类似,不属于顶层资源对象(POD和NODE属于顶层的对象),而是作为POD的附属资源对象。如下图所示,我们在POD中定义了数据存储卷,并且这个存储卷被挂载到了容器文件系统指定的目录:

《图1.2 POD中定义的数据卷被挂载到指定的容器文件系统目录》

如上图可以看出,数据卷和PDO具有相同的生命周期,并且独立于每个被挂载到的容器进程。基于这个事实,我们就可以用数据卷来在持久化应用的状态,并且这种持久化的方式可以让容器重启后继续从之前的处理点继续处理。这个就很有意思了,接下来,我们来看看,如何通过Volume来在容器重启场景下保持数据状态。

【通过数据卷在容器重启场景下保持状态】

数据卷在POD被创建的时候生成,并且先于POD中定义的容器启动,并且随着POD关闭退出,数据卷也会被释放。当POD中的容器启动或者因为某种原因重启时,容器实例将POD定义的数据卷挂载到自己文件系统指定的目录,然后就可以从指定的目录中读取数据卷中的文件,或者读写数据卷中的文件,取决于具体的挂载模式。

基于笔者的经验,给POD添加volume大部分情况下是为了在容器重启的时候,能够保持容器的数据状态。你可以想象一下,如果没有Volume数据卷这种机制,由于容器重启本质上是创建一个新的实例来替换“旧”实例的过程,因此新容器实例的文件系统会从镜像重新创建,为镜像提供的多层文件系统是只读,因此重启之前应用写到文件中的数据就丢失了。

有了数据卷Volume,问题就迎刃而解了,如下图所示,在新容器中运行的应用程序实例,就可以访问重启前的状态,即便是Kubernetes已经对应用进行了重启。

《图1.3 数据卷的数据扩容器持久化能力,让重启应用成为可能》

持久化哪些数据到挂载的数据卷来跨容器保持完全取决于应用开发设计人员。基于笔者的经验,你应该只把表示数据运行状态的数据持久化到数据卷中,而不是应用在运行过程中,本地缓存的数据(比如有些应用会通过caffine这样的组件来构建多级缓存体系,caffine本质上就是基于内存的缓存)。持久化缓存数据会让容器实例在重启后,失去了“重新做人”的机会。因为缓存的数据可能是造成应用crash的罪魁祸首,如果我们将应用的本地缓存数据持久化,应用重新启动后再次加载这些数据,那么重启启动就存在把应用推进“无限重启”的深渊。

基于上边描述的原因,笔者建议大家给自己的部署架构增加数据卷的时候,一定要考虑清楚增加的这个额外的数据跨容器实例持久化机制,对横向扩张和容错会带来哪些影响。

实际上,一个POD对象可以定义多个数据卷,并且运行在POD中的容器实例可以挂载0个到多个数据卷,如下图所示:

《图1.4 POD可以定义多个数据卷,容器也可以挂载多个数据卷》

如上图所示,我们可以在POD定义多个数据卷,并且运行在POD中的容器实例可以挂载多个数据卷,基于具体的业务需求。比如数据卷由于使用的具体存储技术不同,我们可以同时关在读写速度高和普通的两类数据卷,基于实际的业务场景来决定具体读写哪个数据卷。

如果数据卷中保存了隐私数据,比如证书的秘钥,用户名和密码等私密信息,那么数据卷只会被挂载到需要的容器上,比如初始化容器,而不是在所有的容器上挂载这类数据卷,来提升系统的安全性。

同一个数据卷也可以被挂载到不同的容器实例上,因此两个运行中的容器实例就可以通过这种方式来共享数据。比如我们要开发一个对外提供信息服务的网站,我们完全可以让Nginx运行在一个容器中,而在另外一个额容器中运行一个内容生成服务。内容生成服务从CMS系统读取模板和数据,并生成静态页面,而这些静态页面会被写入到和Nginx服务器共同挂在的一个数据卷中,这样两个容器就可以相互配合,实现网站设计的功能。如下图所示:

《图1.5 两个容器共同挂在同一个数据卷来实现数据共享》

如上图所示,同一个数据卷可以被挂载到两个容器的不同目录,这取决于应用的实际需求。当内容生产服务从CMS读取到了数据和模板,并且生成了新的静态页面后,会将静态页面写到目录/var/data中,因此我们将数据卷挂载到内容生成容器文件系统的这个目录。

而对于Nginx服务器来说,默认情况下从目录/var/html加载网页资源,因此我们在Nginx容器实例上,将相同的数据卷挂载到这个目录,这样就可以读取到内容生成容器写入的静态网页数据。上图中两个一个非常重要的信息需要强调一下,我们在容器中挂载数据卷的时候,可以指定读写模式。对于内容生成容器来说,由于要生成数据并写入数据卷,因此数据卷需要以读写模式来挂载;而对于Nginx容器实例来说,由于只是给用户提供信息分享服务,对数据只需要有读的权限,因此数据卷以读模式挂载到Nginx容器上。

从安全的角度来看,笔者建议大家在实际项目中,严格遵守最小安全集策略,这样可以减小攻击面,提升应用的安全性。比如在上边的例子中,如果我们给Nginx容器读写权限,虽然从功能实现的角度,没有任何区别,但是从安全的角度,就提供了黑客可以讲恶意代码写入到Nginx容器文件系统的机会,进一步讲,如果这个恶意的代码被写到了共享数据卷。由于这个数据卷也被挂载到了内容生成容器上,如果这个恶意代码被触发,那么就有可能从CMS中读取到不应该被访问的数据,造成数据泄露。

通过数据卷在容器之间共享数据这种方案在基于Kubernetes容器化部署的场景中有广泛的应用,除了上边生成网页的案例,我们还可以通过这种方式来收集应用的日志,并把日志归集到类似于阿里巴巴日志服务这种的统一日志收集,分析,查询和管理平台;以及通过初始化容器来下载应用的配置文件,然后应用在启动的时候,就可以直接从共享的存储卷中加载配置信息,加速应用的启动时间,等等场景。这种模式在容器化部署场景中非常实用,大家可以在自己实际的项目中尝试使用。介绍完如何在不同容器之间通过数据卷共享数据之后,接下来我们来看看,如何在跨POD生命周期的场景下,来持久化保持应用状态。

【跨POD生命周期的数据保持】

文章开头我们强调过,数据卷的生命周期和POD的生命周期一致,有点绕啊,这句话具体什么意思呢?其实大白话来说就是POD被删除了,数据卷也会被“释放”,请注意这里我使用了引号来描述释放,因为释放在不同的数据卷类型下,具有不同的功效。具体来说,有些数据卷类型,完全支持写入的数据可以被挂载到另外一个POD对象的容器实例上。

如下图所示,POD中定义的数据卷映射到POD外的存储系统中,由于POD中容器在运行过程中写入数据到外部的类似于NAS的介质中,因此这种类型的存储卷中写入的数据,生命周期就超过POD的声明周期,即便是POD运行结束退出,写入到NAS存储介质中的数据会继续保持。我们甚至可以在另外一台工作节点上重新启动应用程序,并加载基于NAS存储的数据,然后开始继续应用程序的运行。

《图1.6 POD定义的数据卷,取决于具体的技术实现,生命周期可能超过POD》

如果POD被删除并且创新了一个新的POD来替换,那么基于NAS技术的数据卷就可以被挂载到POD的容器实例上,这样前一个POD持久化的数据就对新创建POD可见。

如笔者反复强调,基于数据卷采用的存储技术(NAS),我们甚至可以将同一个数据卷同时挂载到多个POD上,允许跨POD数据共享。如下图所示,三个POD各自定义了存储卷,并且三个存储卷都映射到相同的外部持久化数据卷上:

《图1.7 通过数据卷在多个POD之间共享数据》

在简单场景下,外部持久化卷可以是工作节点文件系统的文件目录,用来在POD之间共享数据。这种类型的持久化卷要求三个POD必须运行在相同的工作节点上,这样才能通过宿主机上的文件目录来共享文件数据。

如果外部持久化卷是network-attached storage类型(NAS),那么灵活性就更好了,三个POD都可以不用在同一台宿主机上。如笔者前边强调,这取决于底层的存储技术是否支持将相同的network volume同时attach到多台机器上。

对于NAS这样的存储技术来讲,支持将同一个Volume以读写模式挂载到多台机器上,但是并不是所有的存储技术支持这种模式,大家需要结合自己的Kubernetes具体情况来配置。

注:对于笔者运行的本地minikbe环境来说,只有通过共享宿主机文件夹这种机制,因此如果你想验证NAS这种存储技术,首先需要确定你的集群到底支持哪些类型的存储,笔者会在后续的文章中详细介绍PV和PVC等概念,稍安勿躁!

好了,今天的这篇文章就这么多了,接下来我们会继续在介绍数据卷类型,以及如何使用不同数据卷在容器和POD之间共享数据,敬请期待!

©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容