构建Kubernetes有状态应用程序的不同方法


【编者的话】这篇文章是一个为期三个月的系列文章的一部分,该系列研究了2020年Kubernetes面临的挑战。本周,我们将研究在Kubernetes上运行有状态(stateful)工作负载的挑战。

系列文章列表:


Kubernetes是计算历史上发展最快的IT基础设施项目之一。在短短的五年里,它已经达到了一个成熟的水平,并成为现代基础设施的基础。从公有云中托管的容器即服务(Container as a Service - CaaS)到数据中心里的企业级平台即服务(Platform as a Service - PaaS),再到边缘计算(edge),Kubernetes正变得无处不在。

在Kubernetes的早期,它主要被认为是运行Web规模的、无状态(stateless)服务的平台。有状态服务,如数据库和分析工作负载,要么是在虚拟机中运行,要么是在基于云的托管服务中运行。但是随着Kubernetes成为最受青睐的基础设施层,它的生态系统努力使有状态的应用程序成为Kubernetes领域的一等公民。

在Kubernetes中运行有状态应用程序有多种技术,而且每种技术都有其优缺点。

本文试图重点介绍在Kubernetes中运行有状态应用程序的关键方法、可选项以及每种方法对应的工作负载类型。在此我假设读者们都比较熟悉Kubernetes存储架构中的关键组成部分,如持久卷(Persistent Volumes)、持久卷声明(Persistent Volume Claims)和存储类(Storage Classes)。

集群的共享存储

第一种方法是通过Samba、NFS或GlusterFS将Kubernetes集群与传统的存储设施进行集成。这种方法可以很容易地扩展到基于云的共享文件系统,如Amazon EFS、Azure Files和Google Cloud Filestore。

在这种架构中,存储层与Kubernetes所管理的计算层完全解耦。在Kubernetes的Pod中有两种方式来使用共享存储:
  1. 本地配置(Native Provisioning):幸运的是,大多数的共享文件系统都有内置到上游Kubernetes发行版中的卷插件,或者具有一个容器存储接口(Container Storage Interface - CSI)驱动程序。这使得集群管理员能够使用特定于共享文件系统或托管服务的参数,以声明的方式来定义持久卷(Persistent Volumes)。
  2. 基于主机的配置(Host-based Provisioning):在这种方法里,启动脚本在每个负责挂载共享存储的节点(Node)上运行。Kubernetes集群中的每个节点都有一个暴露给工作负载的挂载点,且该挂载点是一致的、众所周知的。持久卷(Persistent Volume)会通过hostPath或Local PV指向该主机目录。


s2.png

由于耐久性和持久性是由底层存储来负责,因此工作负载与之完全解耦。这使得Pod可以在任何节点上调度,而且不需要定义节点关联,从而能确保Pod总是在选定好的节点上调度。

然而,当遇到需要高I/O吞吐量的有状态负载的时候这种方法就不是一个理想的选择了。因为共享文件系统的设计目的并不是为了满足那些带IOPS的需求,例如关系型数据库、NoSQL数据库和其他写密集型负载所需的IOPS。

可供选择的存储:GlusterFS、Samba、NFS、Amazon EFS、Azure Files、Google Cloud Filestore。

典型的工作负载:内容管理系统(Content Management Systems)、机器学习培训/推理作业(Machine Learning Training/Inference Jobs)和数字资产管理系统(Digital Asset Management Systems)。

StatefulSets

Kubernetes通过控制器维护所需的配置状态。Deployment、ReplicaSet、DaemonSet和StatefulSet就是一些常用的控制器。

StatefulSet是一种特殊类型的控制器,它可以使Kubernetes中运行集群工作负载变得很容易。集群工作负载通常有一个或多个主服务器(Masters)和多个从服务器(Slaves)。大多数数据库都以集群模式设计的,这样可以提供高可用性和容错能力。

有状态集群工作负载持续地在Masters和Slaves之间复制数据。为此,集群基础设施寄期望于参与的实体(Masters和Slaves)有一致且众所周知的Endpoints,以可靠地同步状态。但在Kubernetes中,Pod的设计寿命很短,且不会保证拥有相同的名称和IP地址。

有状态集群工作负载的另一个需求是持久的后端存储,它具有容错能力,以及能够处理IOPS。

为了方便在Kubernetes中运行有状态集群工作负载,引入了StatefulSets。StatefulSet里的Pod可以保证有稳定且唯一的标识符。它们遵循一种可预测的命名规则,并且还支持有序、顺畅的部署和扩展。

参与StatefulSet的每个Pod都有一个相应的Persistent Volume Claim(PVC),该声明遵循类似的命名规则。当一个Pod终止并在不同的Node上重新调度时,Kubernetes控制器将确保该Pod与同一个PVC相关联,以确保状态是完整的。

由于StatefulSet中的每个Pod都有专用的PVC和PV,所以没有使用共享存储的硬性规则。但还是期望StatefulSet是由快速、可靠、持久的存储层(如基于SSD的块存储设备)支持。在确保将写操作完全提交到磁盘之后,可以在块存储设备中进行常规备份和快照。

可供选择的存储:SSD、块存储设备,例如Amazon EBS、Azure Disks、GCE PD。

典型的工作负载:Apache ZooKeeper、Apache Kafka、Percona Server for MySQL、PostgreSQL Automatic Failover以及JupyterHub。

云原生存储

Kubernetes的崛起创造了与云原生计算相关的新细分市场。由于存储是云原生基础设施的关键构件之一,云原生存储市场的一个新领域在最近几年已经迅速发展起来。

云原生存储将传统的存储原理和工作流带到Kubernetes。与其他服务一样,它也是从底层硬件和操作系统中抽象出来的。从供应到销毁的工作流,同样遵循着Kubernetes里典型的生命周期。云原生存储是以应用程序为中心的,这意味着它理解工作负载的上下文,而不是作为集群之外的独立层。与其他资源一样,云原生存储可以根据工作负载的条件和特征进行扩展和收缩。它能够将连接到每个节点的磁盘池化,并将它们作为单个的、统一的逻辑卷公开给Kubernetes Pods。

从存储集群的安装到卷大小的调整,云原生存储让Kubernetes管理员能够使用强大的kubectl CLI管理他们熟悉的YAML工件(artifacts)。云原生存储也提供了动态配置,支持多个文件系统、快照、本地和远程备份、动态卷大小调整等。

云原生存储平台唯一被寄于期望的是集群中原始存储的可用性,这些原始存储可以聚合到一个逻辑卷中。原始存储可以是本地集群的直连存储(Direct Attached Storage – DAS),也可以是运行在公有云中的托管集群的块存储。
s3.png

云原生存储对于容器就像块存储对于虚拟机一样。两者都是从底层物理存储中分割出来的逻辑存储块。当块存储被挂载到VM时,云原生存储可以通过容器使用的持久卷(PV)来使用。

多数云本地存储平台都会带有自定义的调度器,用来支持存储和计算的超融合。自定义的调度器与Kubernetes的内置调度器一起工作,以确保Pod始终位于具有数据的同一节点上。

可供选择的存储:NetApp Trident、Maya Data、Portworx、Azure Disks、GCE PD NetApp Trident、Maya Data、Portworx、Reduxio、Red Hat OpenShift Container Storage、Robin Systems、Rook、StorageOS。

典型的工作负载:任何需要持久性和持久性的工作负载。

原文链接:Different Approaches for Building Stateful Kubernetes Applications(翻译:伊海峰)

0 个评论

要回复文章请先登录注册