随着像Kubernetes这样的容器编排工具的大火,应用程序的开发与部署方式正经历着一场巨大的变革。微服务体系结构的兴起,以及从开发人员的角度,将基础架构与应用程序逻辑间相互解耦,使得开发人员越来越关注于构建软件和交付价值。
Kubernetes能够将它所管理的物理机抽象出来,借此,开发人员可以通过描述所需的内存数量和计算能力,获取相应的资源,而不必考虑底层基础设施。
在管理Docker映像时,Kubernetes还能够为应用程序提供可移植性。一旦使用Kubernetes的容器架构开发应用程序,它们就可以部署到任何地方——公共云、混合云、本地——而且不需要对底层代码进行任何更改。
虽然Kubernetes在许多方面非常有优势,比如可伸缩性、可移植性和管理能力,但它也存在一个问题,就是不支持状态存储。几乎所有的生产应用都是有状态的,即需要某种外部存储。
而Kubernetes的架构是动态的,容器的创建和销毁取决于负载以及开发人员规范,Pod和容器可以自我修复和复制。本质上来说,它们的生命是短暂的。
然而,持久存储解决方案无法承受这种动态行为,持久存储不能被绑定到动态创建和销毁的规则上。
当需要将有状态的应用程序部署到另一个基础设施(可能是另一个云服务提供商、本地或混合云)上时,它们在可移植性上面临着挑战。持久存储解决方案会被捆绑到特定的云提供商上。
此外,云原生应用程序的存储环境并不容易理解。Kubernetes的存储术语可能会令人纲到困惑,因为许多术语都有复杂的含义和微妙的变化。此外,在原生Kubernetes、开源框架和托管或付费服务之间有许多选项,开发人员在做出决定之前必须考虑这些选项。
下面是CNCF(云原生计算基金会)公布的云原生存储解决方案一览图(其中一部分,具体可点击链接查看):
可能大家首先想到的是在Kubernetes中部署数据库:选择满足你需要的数据库解决方案,将其容器化以在本地磁盘上运行,并将其作为另一个工作负载部署到集群中。然而,由于数据库的固有属性,这并不能很好地工作。
容器是基于无状态原则构建的,这使得容器的spin up和spin down更容易。由于没有要保存和迁移的数据,所以集群不需要处理磁盘读写这种通常来说非常密集的工作。
对于数据库,状态往往需要被保存。如果以容器方式部署在集群上的数据库没有迁移,或者没有频繁地spin up,那么数据存储的物理特性就会发挥作用。理想情况下,使用数据的容器应该与数据库位于同一个Pod中。
这并不是说在容器中部署数据库是一个坏主意——在某些用例中,这种方法就足够了。在测试环境中,或者对于那些不需要生产级别的数据量的任务,集群中的数据库是有意义的,因为所保存的数据规模很小。
在生产环境中,开发人员通常比较依赖外部存储。
Kubernetes如何与存储通信?使用控制平面接口。这些接口将Kubernetes与外部存储连接起来。这些连接到Kubernetes的外部存储解决方案称为卷插件(Volume Plugin),卷插件支持抽象存储并赋予存储可移植性。
以前,卷插件是与核心的Kubernetes代码库一起构建、链接、编译和发布的。这大大限制了开发人员的灵活性,并带来了额外的维护成本。添加新的存储选项需要更改Kubernetes代码库。
随着CSI和Flexvolume的引入,卷插件可以部署在集群上,而无需更改代码库。
原生Kubernetes及存储
原生Kubernetes如何处理存储?Kubernetes提供了一些管理存储的解决方案:临时选项、持久卷的持久存储、持久卷声明、存储类或状态集……等等。
持久卷(PV)是由管理员提供的存储单元,它们独立于任何单个Pod,这样可以将它们从Pod短暂的生命周期中解放出来。
另外,持久卷声明(PVC)是对存储的请求。使用PVC可以将存储绑定到特定节点,使该节点能够使用存储。
处理存储的方法有两种:静态或动态。
通过静态配置,管理员提供了他们认为Pod在发出实际请求之前可能需要的PV,并且这些PV通过显式PVC手动绑定到特定的Pod。
在实践中,静态定义的PV与Kubernetes的可移植结构不兼容,因为所使用的存储可能与环境相关,比如AWS EBS或GCE持久磁盘。手动绑定需要更改YAML文件以指向特定于提供商的存储解决方案。
在开发人员如何考虑资源方面,静态配置也违背了Kubernetes的思想:CPU和内存不是预先分配的,而是绑定到Pod或容器中,它们是动态授予的。
动态配置是通过存储类完成的。集群管理员不需要预先手动创建PV,而是创建多个存储配置文件,就像模板一样。当开发人员创建PVC时,根据请求的要求,其中一个模板在请求时创建,并附加到Pod。
以上只是对外部存储一般如何使用原生Kubernetes进行处理的一个非常宽泛的概述,除此之外,还有许多其他选择需要考虑。
容器存储接口
首先介绍一下容器存储接口(Container Storage Interface,CSI),CSI是由CNCF存储工作组进行的统一工作,旨在定义一个标准的容器存储接口,该接口可以使存储驱动程序在任何容器编排器上工作。
CSI规范已经被应用到Kubernetes中,许多驱动程序插件可以部署在Kubernetes集群上。开发人员可以在Kubernetes上访问CSI兼容的卷驱动程序与CSI卷类型公开的存储。
随着CSI的引入,存储可以作为另一个工作负载进行容器化,并部署在Kubernetes集群上。
开源项目
围绕云原生技术的工具和项目正在大量涌现。作为生产中最突出的问题之一,有相当一部分开源项目致力于解决“在云原生架构上处理存储”这个问题。
目前最受欢迎的存储项目是Ceph和Rook。
Ceph是一个动态管理的、水平可伸缩的分布式存储集群。Ceph提供了对存储资源的逻辑抽象。它被设计成不存在单点故障、可自我管理和基于软件的。Ceph同时为相同的存储集群提供块、对象或文件系统接口。
Ceph的架构非常复杂,有许多底层技术,如RADOS、librados、RADOSGW、RDB,它的CRUSH 算法和监视器、OSD和MDS等组件。这里不深入解读其架构,关键在于,Ceph是一个分布式存储集群,它可提供更高的可伸缩性,在不牺牲性能的情况下消除了单点故障,并提供了对对象、块和文件的访问的统一存储。
很自然地,Ceph已经适应了云原生环境。有许多方法可以部署Ceph集群,例如使用Ansible。你可以使用CSI和PVC部署Ceph集群,并在Kubernetes集群中获得一个接口。
Ceph架构
另一个有趣且非常受欢迎的项目是Rook,这是一个旨在聚合Kubernetes和Ceph的工具——将计算和存储放在一个集群中。
Rook是一个云原生存储编排器,它扩展了Kubernetes的功能。Rook本质上允许将Ceph放入容器中,并提供集群管理逻辑,使得在Kubernetes上能够可靠地运行Ceph。Rook能够自动化部署、引导、配置、伸缩、再平衡,即集群管理员会做的一系列工作。
Rook允许从YAML部署Ceph集群,像Kubernetes一样。YAML文件用作集群管理员希望在集群中实现的高级声明。Rook会启动集群,并开始积极监视。Rook充当控制器,确保YAML文件中声明的所需状态是支持的。Rook运行在一个协调循环中,该循环会观察状态并根据检测到的差异进行操作。
Rook没有自己的持久状态,无需管理,可见它确实是按照Kubernetes的原则建立的。
Rook将Ceph和Kubernetes结合在一起,是最受欢迎的云原生存储解决方案之一,在Github上拥有近4000颗星,1630万次下载,以及100多名贡献者。
作为被CNCF接受的首个存储项目,Rook近期已进入孵化阶段。
最后,对于应用程序中的任何问题,重要的是确定需求,并相应地设计系统或选择工具。云原生环境中的存储也不例外。虽然问题相当复杂,但是有很多工具和方法。随着云计算的发展,无疑也会不断出现新的解决方案。
来源:Software Engineering Daily 作者:Gokhan Simsek