龙空技术网

管理 Kubernetes 上的云原生数据:2.管理Kubernetes上的数据存储

启辰8 105

前言:

现时咱们对“mayaubuntu”大致比较注重,小伙伴们都想要剖析一些“mayaubuntu”的相关文章。那么小编同时在网摘上收集了一些关于“mayaubuntu””的相关内容,希望小伙伴们能喜欢,小伙伴们一起来了解一下吧!

“没有无状态的架构这样的东西。所有应用程序都存储状态的某个地方“ - Alex Chircop,StorageOS 首席执行官

在上一章中,我们描绘了在不久的将来,在 Kubernetes 上运行的强大、有状态、数据密集型应用程序。为了实现这一目标,我们将需要用于持久性、流式传输和分析的数据基础设施,为了构建这个基础设施,我们需要利用 Kubernetes 提供的原语来帮助管理云计算的三种商品:计算、网络和存储。在接下来的几章中,我们将开始研究这些原语,从存储开始,以便了解如何将它们组合起来以创建我们需要的数据基础架构。

为了呼应 Alex Chircop 在上面引用中提出的观点,所有应用程序都必须将它们的状态存储在某个地方,这就是为什么我们将在本章中重点介绍 Kubernetes 为与存储交互提供的基本抽象。我们还将研究存储供应商和开源项目提供的新兴创新,这些创新正在为 Kubernetes 创建本身体现云原生原则的存储基础设施。

让我们从管理容器化应用程序中的持久性开始我们的探索,并将其用作我们研究 Kubernetes 数据存储的起点。

Docker、容器和状态

在分布式云原生应用程序中管理状态的问题并非 Kubernetes 所独有。快速搜索将显示有状态工作负载一直是其他容器编排平台(如 Mesos 和 Docker Swarm)关注的领域。其中一部分与容器编排的性质有关,另一部分由容器本身的性质驱动。

首先,让我们考虑容器。容器的关键价值主张之一是它们的短暂性。容器设计为一次性和可更换,因此它们需要快速启动并使用尽可能少的资源进行开销处理。出于这个原因,大多数容器映像都是从包含简化的、基于 Linux 的开源操作系统(如 Ubuntu)的基础映像构建的,这些操作系统可以快速启动,并且仅包含所包含的应用程序或微服务的基本库。顾名思义,容器被设计为自包含的,将其所有依赖项合并到不可变映像中,同时它们的配置和数据被外部化。这些属性使容器具有可移植性,以便我们可以在任何兼容容器运行时可用的位置运行它们。

所示,与传统虚拟机相比,容器需要的开销更少,传统虚拟机为每个虚拟机运行一个来宾操作系统,并具有层来实现对底层主机操作系统的系统调用。

将容器化与虚拟化进行比较

尽管容器使应用程序更具可移植性,但事实证明,使其数据可移植是一个更大的挑战。我们将在第12章中研究可移植数据集的概念。由于容器本身是短暂的,因此根据定义,任何在容器生命周期之后幸存的数据都必须驻留在外部。容器技术的关键功能是提供链接到持久存储的机制,容器编排技术的关键功能是能够以能够有效地访问持久存储的方式调度容器。

在 Docker 中管理状态

让我们来看看最流行的容器技术 Docker,看看容器如何存储数据。Docker 中的关键存储概念是卷。从 Docker 容器的角度来看,卷是可以支持只读或读写访问的目录。Docker 支持将多个不同的数据存储挂载为卷。我们将介绍几个选项,以便稍后可以在 Kubernetes 中注意它们的等效项。

绑定装载

创建卷的最简单方法是将容器中的目录绑定到主机系统上的目录。这称为绑定装载,如图 所示。

使用 Docker 绑定挂载访问主机文件系统

在 Docker 中启动容器时,您可以使用 --volume 或 -v 选项指定绑定挂载,以及要使用的本地文件系统路径和容器路径。例如,可以启动 Nginx Web 服务器的实例,并将本地项目文件夹从开发计算机映射到容器中。如果您安装了 Docker,则可以在自己的环境中测试此命令:

docker run -it --rm -d --name web -v ~/site-content:/usr/share/nginx/html nginx 

如果本地路径目录尚不存在,Docker 运行时将创建它。Docker 允许您创建具有只读或读写权限的绑定挂载。由于卷表示为目录,因此在容器中运行的应用程序可以将任何可以表示为文件的内容放入卷中,甚至是数据库。

绑定挂载对于开发工作非常有用。但是,使用绑定装载不适用于生产环境,因为这会导致容器依赖于特定主机中存在的文件。这对于单机部署可能没问题,但生产部署往往分布在多个主机上。另一个问题是开放从容器到主机文件系统的访问所带来的潜在安全漏洞。出于这些原因,我们需要另一种生产部署方法。

docker volume create site-content

如果未指定名称,Docker 将分配一个随机名称。创建后,生成的卷可以使用 -v 卷名称:容器路径 的形式装入容器中。例如,您可以使用刚刚创建的卷来允许 Nginx 容器读取内容,同时允许另一个容器编辑内容,使用 to 选项:

docker run -it --rm -d --name web -v site-content:/usr/share/nginx/html:ro nginx 
注意

注意:Docker 卷挂载语法

Docker 还支持 --mount 语法,该语法允许您更明确地指定源文件夹和目标文件夹。这种表示法被认为更现代,但也更冗长。上面显示的语法仍然有效,并且是更常用的语法。

如上所述,一个 Docker 卷可以同时挂载到多个容器中,如图 所示。

创建 Docker 卷以在主机上的容器之间共享数据

使用 Docker 卷的优点是 Docker 管理容器的文件系统访问,这使得对容器实施容量和安全限制变得更加简单。

Tmpfs 坐骑

Docker 支持两种特定于主机系统使用的操作系统的挂载类型:tmpfs(或“临时文件系统”)和命名管道。命名管道在 Docker for Windows 上可用,但由于它们通常不在 K8s 中使用,因此我们不会在这里过多考虑它们。

Tmpfs 挂载在 Linux 上运行 Docker 时可用。tmpfs 挂载仅在容器的生命周期内存中存在,因此内容永远不会存在于磁盘上,如图 2-4 所示。Tmpfs 挂载对于为保存相对少量数据而编写的应用程序非常有用,尤其是您不希望写入主机文件系统的敏感数据。由于数据存储在内存中,因此访问速度更快还有一个附带好处。

使用 Docker tmpfs 创建临时卷

要创建 tmpfs 挂载,请使用 docker run --tmpfs 选项。例如,您可以使用这样的命令来指定一个 tmpfs 卷来存储处理敏感数据的 Web 服务器的 Nginx 日志:

docker run -it --rm -d --name web -tmpfs /var/log/nginx nginx 

--mount 选项也可用于对可配置选项进行更多控制。

卷驱动程序

Docker 引擎具有可扩展的架构,允许您通过插件添加自定义行为,以实现网络、存储和授权等功能。第三方可用于多个开源和商业提供商,包括公共云和各种网络文件系统。利用这些优势涉及使用 Docker 引擎安装插件,然后在使用该存储启动 Docker 容器时指定关联的卷驱动程序,如图 2-5 所示。

使用 Docker 卷驱动程序访问网络存储

有关使用 Docker 中支持的各种类型的卷的更多信息,请参阅 文档以及 命令的文档。

边栏:文件、块和对象存储

在我们云架构的现代时代,传统上向应用程序提供存储的三种主要格式是文件、块和对象。其中每个都以不同的方式存储和提供对数据的访问。

文件存储将数据表示为文件夹的层次结构,每个文件夹都可以包含文件。该文件是存储和检索的基本访问单元。容器要访问的根目录挂载到容器文件系统中,使其看起来像任何其他目录。每个公共云都提供自己的文件存储,例如 Google Cloud Filestore 或 Amazon Elastic Filestore。是一个开源的分布式文件系统。其中许多系统与网络文件系统(NFS)兼容,是Sun Microsystems发明的一种分布式文件系统协议,可追溯到1984年,至今仍在广泛使用。块存储将数据组织在区块中,并在一组托管卷中分配这些区块。当您向块存储系统提供数据时,它会将其划分为不同大小的块,并分发这些块,以便最有效地使用底层卷。查询块存储系统时,它会从块的不同位置检索块,并将数据返回给您。当您有一组异构存储设备可用时,这种灵活性使块存储成为一种很好的解决方案。块存储不提供大量元数据处理,这可能会给应用程序带来更多负担。对象存储以称为对象的单元组织数据。每个对象都由唯一标识符或“键”引用,并且可以支持启用搜索的丰富元数据标记。对象按存储桶进行组织。这种扁平化、无层级的组织使对象存储易于扩展。亚马逊的简单存储服务(S3)是对象存储的典型示例,大多数对象存储产品都声称与S3 API兼容。

如果您的任务是构建或选择数据基础结构,则需要了解每种模式的优缺点。

用于数据存储的 Kubernetes 资源

现在您已经了解了容器和云存储的基本概念,让我们看看 Kubernetes 带来了什么。在本节中,我们将介绍 API 中的一些关键 Kubernetes 概念或“资源”,用于将存储附加到容器化应用程序。即使你已经对这些资源有些熟悉,你也需要继续关注,因为我们将特别关注每个资源与有状态数据的关系。

容器和卷

新用户首先遇到的 Kubernetes 资源之一是 pod。Pod 是 Kubernetes 工作负载部署的基本单元。Pod 提供运行容器的环境,Kubernetes 控制平面负责将 Pod 部署到 Kubernetes 工作节点。Kubelet 是 的一个组件,它在每个工作节点上运行。它负责在节点上运行 Pod,以及监控这些 Pod 及其内部容器的运行状况。这些元素总结在 中。

虽然一个 Pod 可以包含多个容器,但最佳做法是让 Pod 包含一个应用程序容器,以及可选的附加帮助程序容器,如图所示。这些帮助程序容器可能包括在主应用程序容器之前运行的 init 容器,以便执行配置任务,或者与主应用程序容器一起运行的挎斗容器,以提供可观测性或管理等帮助程序服务。在以后的章节中,我们将演示数据基础结构部署如何利用这些体系结构模式。

在 Kubernetes Pod 中使用卷

现在,让我们考虑一下在此 Pod 架构中如何支持持久性。与 Docker 一样,当容器崩溃时,容器中的“磁盘上”数据会丢失。kubelet 负责重新启动容器,但这个新容器实际上是原始容器的替代品——它将具有独特的身份,并以全新的状态开始。

在 Kubernetes 中, 用于表示对 Pod 中存储的访问。通过使用卷,容器能够持久保存比容器(也可能是 pod,我们稍后会看到)寿命更长的数据。一个卷可以由 Pod 中的多个容器访问。每个容器在 Pod 中都有自己的 指定了它应挂载到的目录,从而允许容器之间的挂载点不同。

在多种情况下,您可能希望在 Pod 中的多个容器之间共享数据:

init 容器为应用程序容器挂载的特定环境创建自定义配置文件,以获取配置值。应用程序 Pod 写入日志,挎斗 Pod 读取这些日志以识别报告给外部监视工具的警报条件。

但是,您可能希望避免多个容器写入同一卷的情况,因为您必须确保多个写入器不会发生冲突 - Kubernetes 不会为您执行此操作。

注意

注意:准备运行示例代码

本章(以及本书其余部分)中的示例假设您可以访问正在运行的 Kubernetes 集群。对于本章中的示例,本地计算机上的开发集群(如 Kind、K3s 或 Docker Desktop)应该就足够了。本节中使用的源代码位于 。

apiVersion: v1kind: Podmetadata:  name: my-podspec:  containers:  - name: my-app    image: nginx    volumeMounts:    - name: web-data      mountPath: /app/config volumes: - name: web-data

请注意配置的两个部分:卷在 spec.volumes 下定义,卷的使用在 spec.containers.volumeMounts 下定义。首先,在 volumeMounts 下引用卷的名称,并且要挂载它的目录由 mountPath 指定。声明 Pod 规范时,卷和卷挂载是在一起的。要使配置有效,必须在引用卷之前声明卷,并且卷必须由 Pod 中的至少一个容器使用。

您可能还注意到,该卷只有一个名称。您尚未指定任何其他信息。你认为这会做什么?你可以通过使用示例源代码文件 nginx-pod.yaml 或将上面的配置剪切并粘贴到具有该名称的文件中,然后对配置的 Kubernetes 集群执行 kubectl 命令来亲自尝试:

kubectl apply -f nginx-pod.yaml

您可以获取有关使用 kubectl get pod 命令创建的 pod 的更多信息,例如:

kubectl get pod my-pod -o yaml |格雷普-A 5 英寸卷:”

结果可能如下所示:

  volumes:  - emptyDir: {}    name: web-data  - name: default-token-2fp89    secret:      defaultMode: 420

如您所见,Kubernetes 在创建请求的卷时提供了一些额外的信息,默认为一种 emptyDir 。其他默认属性可能会有所不同,具体取决于您使用的 Kubernetes 引擎,我们不会在这里进一步讨论它们。

有几种不同类型的卷可以挂载到容器中,让我们看一下。

临时卷

您会记得上面讨论 Docker 卷中的 tmpfs 卷,它们为单个容器的生命周期提供临时存储。Kubernetes 提供了的概念,这是类似的,但在 Pod 的范围内。上面示例中引入的 emptyDir 是一种临时卷。

临时卷对于想要创建缓存以进行快速访问的数据基础结构或其他应用程序非常有用。尽管它们不会持久化超过容器的生命周期,但它们仍然可以表现出其他卷的一些典型属性,以实现长期持久性,例如快照功能。临时卷比 PersistentVolumes 更容易设置,因为它们在 Pod 定义中完全内联声明,而不引用其他 Kubernetes 资源。正如您将在下面看到的,创建和使用 PersistentVolumes 需要更多。

注意

注意:其他临时存储提供程序

我们将在下面讨论的一些树内和 CSI 存储驱动程序,它们提供 PersistentVolumes 也提供了一个临时卷选项。您需要检查特定提供程序的文档,以查看哪些选项可用。

配置卷

Kubernetes 提供了几种构造,用于将配置数据作为卷注入到 Pod 中。这些卷类型也被认为是临时的,因为它们不提供允许应用程序保留其自己的数据的机制。

这些卷类型与本书中的探索相关,因为它们提供了一种配置在 Kubernetes 上运行的应用程序和数据基础设施的有用方法。我们将简要介绍它们中的每一个:

配置映射卷

ConfigMap 是一种 Kubernetes 资源,用于将应用程序外部的配置值存储为一组名称-值对。例如,应用程序可能需要基础数据库的连接详细信息,例如 IP 地址和端口号。在 ConfigMap 中定义这些信息是从应用程序外部化此信息的好方法。生成的配置数据可以作为卷装载到应用程序中,并在其中显示为目录。每个配置值都表示为一个文件,其中文件名是键,文件的内容包含值。请参阅 Kubernetes 文档,了解有关将 的更多信息。

秘密卷

密钥类似于配置映射,只是它旨在保护对需要保护的敏感数据的访问。例如,您可能希望创建一个包含数据库访问凭据(如用户名和密码)的密钥。配置和访问密钥类似于使用 ConfigMap,还有一个额外的好处,即 Kubernetes 有助于在 Pod 内访问时解密密钥。有关将的更多信息,请参阅 Kubernetes 文档。

向下的 API 卷

Kubernetes 向下 API 将有关 Pod 和容器的元数据公开为环境变量或卷。这与 kubectl 和其他客户端使用的元数据相同。

可用的容器元数据包括容器的名称、ID、命名空间、标签和注释。容器化应用程序可能希望使用 Pod 信息进行日志记录和指标报告,或者确定数据库或表名称。

可用的容器元数据包括请求的资源量和最大资源量,例如 CPU、内存和临时存储。容器化应用程序可能希望使用此信息来限制其自身的资源使用。有关将 的示例,请参阅 Kubernetes 文档

主机路径卷

卷将文件或目录从运行它的 Kubernetes 工作节点挂载到 Pod 中。这类似于上面讨论的 Docker 中的绑定挂载概念。与空目录卷相比,使用 hostPath 卷有一个优势:数据将在 Pod 重新启动后继续存在。

但是,使用 hostPath 卷有一些缺点。首先,为了使替换 Pod 能够访问原始 Pod 的数据,需要在同一工作节点上重新启动它。虽然 Kubernetes 确实使您能够使用亲和力控制将 Pod 放置在哪个节点上,但这往往会限制 Kubernetes 调度程序对 Pod 的最佳放置,如果节点由于某种原因关闭,hostPath 卷中的数据将丢失。其次,与 Docker 绑定挂载类似,hostPath 卷在允许访问本地文件系统方面存在安全问题。出于这些原因,仅建议将 hostPath 卷用于开发部署。

云卷

可以创建引用存储位置的 Kubernetes 卷,而不仅仅是运行 Pod 的工作节点,如图 2-7 所示。这些可以分为由指定云提供商提供的卷类型,以及尝试提供更通用接口的卷类型。

Kubernetes Pod 直接挂载云提供商存储

其中包括以下内容:

卷类型用于在 Amazon Web Services (AWS) Elastic Block Store (EBS) 上挂载卷。许多数据库使用块存储作为其底层存储层。卷类型用于挂载Google Compute Engine(GCE)永久磁盘(PD),这是块存储的另一个示例。Microsoft Azure 支持两种类型的卷:Azure 磁盘卷和 Azure 文件卷的对于 OpenStack 部署,煤渣卷类型可用于访问 OpenStack 卷

这些类型的使用通常需要在云提供商上进行配置,并且来自 Kubernetes 集群的访问通常仅限于同一云区域和帐户中的存储。有关更多详细信息,请查看云提供商的文档。

其他卷提供程序

还有许多其他卷提供程序,其提供的存储类型各不相同。以下是一些示例:

光纤通道卷类型可用于实施光纤通道协议的 SAN 解决方案。gluster 卷类型用于使用上面引用的 分布式文件系统访问文件存储iscsi 卷将现有的 iSCSI(基于 IP 的 SCSI)卷挂载到 Pod 中。nfs 卷允许将现有的 NFS(网络文件系统)共享挂载到 Pod 中

我们将在下面研究更多实现模式的卷提供程序。

提供了我们到目前为止介绍的 Docker 和 Kubernetes 存储概念的比较。

存储类型

Docker

Kubernetes

从各种提供商访问持久存储

卷(通过卷驱动程序访问)

卷(通过树内或 CSI 驱动程序访问)

访问主机文件系统(不建议用于生产)

绑定挂载

主机路径卷

容器(或 Pod)运行时可用的临时存储

TMPFS

空目录和其他临时卷

配置和环境数据(只读)

(无直接等价物)

ConfigMap, Secret, Downward API

侧边栏:您如何选择 Kubernetes 存储解决方案?

考虑到可用存储选项的数量,尝试确定应该为应用程序使用哪种存储肯定是一项艰巨的任务。除了确定是否需要文件存储、块存储或对象存储外,您还需要考虑延迟和吞吐量要求以及预期的存储卷。例如,如果您的读取延迟要求非常苛刻,则很可能需要一个存储解决方案,将数据保存在访问数据的同一数据中心。

接下来,您需要考虑您拥有的任何现有承诺或资源。也许您的组织对使用首选云提供商的服务有强制要求或偏见。云提供商通常会为使用其服务提供成本激励,但您需要将其与锁定特定服务的风险进行权衡。或者,您可能需要对本地数据中心中的存储解决方案进行投资,以便加以利用。

总体而言,成本往往是选择存储解决方案的首要因素,尤其是从长远来看。确保您的建模不仅包括物理存储和任何托管服务的成本,还包括管理所选解决方案所涉及的运营成本。

在本节中,我们讨论了如何使用卷来提供可由同一 Pod 中的多个容器共享的存储。虽然这对于某些用例来说已经足够了,但有些需求并不能解决。卷不提供在 Pod 之间共享存储资源的功能。特定存储位置的定义与 Pod 的定义相关联。随着 Kubernetes 集群中部署的 Pod 数量的增加,管理单个 Pod 的存储无法很好地扩展。

值得庆幸的是,Kubernetes 提供了额外的原语,有助于简化为单个 Pod 和相关 Pod 组配置和挂载存储卷的过程。我们将在接下来的几节中研究这些概念。

持久卷

Kubernetes 开发人员为管理存储而引入的关键创新是子系统。该子系统由三个协同工作的附加 Kubernetes 资源组成:PersistentVolumes、PersistentVolumeClaims 和 StorageClasses。这允许您将存储的定义和生命周期与 Pod 的使用方式分开,如图 所示:

群集管理员可以显式定义持久卷,也可以通过创建可以动态置备新持久卷的存储类来定义持久卷。应用程序开发人员创建描述其应用程序的存储资源需求的 PersistentVolumeClaims,这些 PersistentVolumeClaims 可以作为 Pod 中的卷定义的一部分进行引用。Kubernetes 控制平面管理 PersistentVolumeClaims 与 PersistentVolumes 的绑定。

持久卷、持久卷声明和存储类

让我们首先看一下 PersistentVolume 资源(通常缩写为 PV),它定义了对特定位置存储的访问。持久卷通常由群集管理员定义,供应用程序开发人员使用。每个 PV 可以表示上一节中讨论的相同类型的存储,例如云提供商提供的存储、网络存储或直接在工作节点上的存储,如图 2-9 所示。由于它们绑定到特定的存储位置,因此 PersistentVolumes 无法在 Kubernetes 集群之间移植。

Kubernetes PersistentVolumes 的类型本地持久卷

该图还介绍了一种称为 local 的 PersistentVolume 类型,它表示直接挂载在 Kubernetes 工作节点(如磁盘或分区)上的存储。与 hostPath 卷一样,本地卷也可以表示目录。本地卷和 hostPath 卷之间的主要区别在于,当使用本地卷的 Pod 重新启动时,Kubernetes 调度程序确保在同一节点上重新调度 Pod,以便它可以附加到相同的持久状态。出于这个原因,本地卷经常被用作管理其自身复制的数据基础架构的后备存储,我们将在第 4 章中看到。

定义 PersistentVolume 的语法看起来很熟悉,因为它类似于在 Pod 中定义卷。例如,下面是定义本地持久卷()的 YAML 配置文件:

apiVersion: v1kind: PersistentVolumemetadata:  name: my-volumespec:  capacity:    storage: 3Gi  accessModes:    - ReadWriteOnce  local:    path: /app/data  nodeAffinity:    required:      nodeSelectorTerms:      - matchExpressions:        - key: kubernetes.io/hostname          operator: In          values:          - node1

如您所见,此代码在工作节点节点 node1 上定义了一个名为 my-volume 的本地卷,大小为 3 GB,访问模式为 ReadWriteOnce 。持久卷支持以下:

ReadWriteOnce 访问允许一次由单个 Pod 挂载卷以进行读取和写入,并且不能由其他 Pod 挂载只读多访问意味着卷可以由多个 Pod 同时挂载以供读取ReadWriteMany 访问允许装入卷,以便多个节点同时读取和写入注意

注意:选择卷访问模式

给定卷的正确访问模式将由工作负载类型驱动。例如,许多分布式数据库将为每个 Pod 配置专用存储,这使得 ReadWriteOnce 成为一个不错的选择。

除了和访问模式之外,持久卷的其他属性包括:

卷模式 ,默认为文件系统,但可能会被覆盖为块。回收策略定义了当 Pod 在此持久卷上释放其声明时会发生什么情况。合法值为 保留 、 回收 和 删除 。持久卷可以具有节点关联,该关联性指定哪些工作节点可以访问此卷。对于大多数类型,这是可选的,但对于本地卷类型是必需的。class 属性将此 PV 绑定到特定的 StorageClass,这是我们将在下面介绍的概念。某些持久卷类型公开特定于该类型的装载选项。警告

警告:音量选项的差异

不同卷类型的选项不同。例如,并非每种 PersistentVolume 类型都可以访问每种访问模式或回收策略,因此请参阅所选类型的文档以获取更多详细信息。

您可以使用 kubectl describe persistentvolume 命令(或简称 kubectl describe pv)来查看 PersistentVolume 的状态:

$ kubectl describe pv my-volumeName:              my-volumeLabels:            <none>Annotations:       <none>Finalizers:        [kubernetes.io/pv-protection]StorageClass:Status:            AvailableClaim:Reclaim Policy:    RetainAccess Modes:      RWOVolumeMode:        FilesystemCapacity:          3GiNode Affinity:  Required Terms:    Term 0:        kubernetes.io/hostname in [node1]Message:Source:    Type:  LocalVolume (a persistent volume backed by local storage on a node)    Path:  /app/dataEvents:    <none>

持久卷在首次创建时的状态为“可用”。持久卷可以有多个不同的状态值:

“可用”表示持久卷是免费的,尚未绑定到声明。Bound 表示 PersistentVolume 绑定到 PersistentVolumeClaim,该声明在描述输出中的其他位置列出“已发布”表示已删除持久卷上的现有声明,但资源尚未回收,因此该资源尚不可用失败表示卷的自动回收失败

现在您已经了解了如何在 Kubernetes 中定义存储资源,下一步是学习如何在应用程序中使用该存储。

持久卷声明

如上所述,Kubernetes 将存储的定义与其使用分开。这些任务通常由不同的角色执行:群集管理员定义存储,而应用程序开发人员使用存储。持久卷通常由特定于该群集的管理员和引用存储位置定义。然后,开发人员可以使用 Kubernetes 用于将 Pod 与满足指定条件的 PersistentVolume 相关联的 PersistentVolume(PVC) 来指定其应用程序的存储需求。如图 2-10 所示,PersistentVolumeClaim 用于引用我们之前介绍的各种卷类型,包括本地 PersistentVolumes 或云或网络存储供应商提供的外部存储。

使用 PersistentVolumeClaims 访问 PersistentVolumes

从应用程序开发人员的角度来看,这是该过程的样子。首先,您将创建一个表示所需存储条件的 PVC。例如,这里有一个声明,要求 1GB 的存储空间具有独占读/写访问权限():

apiVersion: v1kind: PersistentVolumeClaimmetadata:  name: my-claimspec:  storageClassName: ""  accessModes:  - ReadWriteOnce  resources:    requests:      storage: 1Gi

关于此声明,您可能已经注意到的一件有趣的事情是 storageClassName 设置为空字符串。我们将在下面讨论存储类时解释这一点的重要性。您可以在 Pod 的定义中引用声明,如下所示():

apiVersion: v1kind: Podmetadata:  name: my-podspec:  containers:  - name: nginx    image: nginx    volumeMounts:    - mountPath: "/app/data"      name: my-volume  volumes:  - name: my-volume    persistentVolumeClaim:      claimName: my-claim 

如您所见,持久卷在 Pod 中表示为卷。为卷指定名称和对声明的引用。这被认为是 persistentVolumeClaim 类型的卷。与其他卷一样,卷被挂载到特定挂载点的容器中,在本例中,挂载到路径 / 的主应用程序 Nginx 容器中。

PVC 还具有一个状态,如果检索该状态,则可以看到该状态:

Name:          my-claimNamespace:     defaultStorageClass:Status:        BoundVolume:        my-volumeLabels:        <none>Annotations:   pv.kubernetes.io/bind-completed: yes               pv.kubernetes.io/bound-by-controller: yesFinalizers:    [kubernetes.io/pvc-protection]Capacity:      3GiAccess Modes:  RWOVolumeMode:    FilesystemMounted By:    <none>Events:        <none>

PVC 具有以下两个状态值之一:“已绑定”,表示它已绑定到卷(如上所述)或“挂起”,表示它尚未绑定到卷。通常,“挂起”状态表示不存在与声明匹配的 PV。

以下是幕后发生的事情。Kubernetes 使用作为 Pod 中的卷引用的 PVC,并在调度 Pod 时考虑这些 PVC。Kubernetes 识别与声明关联的属性匹配的 PersistentVolumes,并将最小的可用模块绑定到声明。这些属性可能包括标签或节点关联,正如我们上面看到的本地卷。

启动 Pod 时,Kubernetes 控制平面确保 PersistentVolumes 挂载到工作节点。然后,每个请求的存储卷都将挂载到指定挂载点的 Pod 中。

存储类

上面显示的示例演示了 Kubernetes 如何将 PVC 绑定到已经存在的 PersistentVolumes。在 Kubernetes 集群中显式创建 PersistentVolumes 的这种模型称为静态配置。Kubernetes 持久卷子系统还支持使用 StorageClasses(通常缩写为 SC)动态配置卷。StorageClass 负责根据群集中运行的应用程序的需要预配(和取消预配)持久卷,如图 所示。

存储类支持动态调配卷

根据您使用的 Kubernetes 集群,可能已经至少有一个可用的 StorageClass。您可以使用命令 kubectl get sc 来验证这一点。如果你在本地机器上运行一个简单的 Kubernetes 发行版,并且没有看到任何 StorageClasses,你可以使用以下命令从 Rancher 安装开源本地存储提供程序:

kubectl apply -f 

This storage provider comes pre-installed in K3s, a desktop distribution also provided by Rancher. If you take a look at the YAML configuration referenced in that statement, you’ll see the following definition of a StorageClass (source code):

apiVersion: storage.k8s.io/v1kind: StorageClassmetadata:  name: local-pathprovisioner: rancher.io/local-pathvolumeBindingMode: WaitForFirstConsumerreclaimPolicy: Delete

从定义中可以看出,存储类由几个关键属性定义:

配置器与基础存储提供程序(如公有云或存储系统)进行交互,以便分配实际存储。预配器可以是 Kubernetes 内置预配器之一(称为“树内”,因为它们是 Kubernetes 源代码的一部分),也可以是符合容器存储接口 (CSI) 的预配器,我们将在下面进行研究。参数是传递给预配程序的存储提供程序的特定配置选项。常见选项包括文件系统类型、加密设置和 IOPS 吞吐量。有关更多详细信息,请查看存储提供程序的文档。回收策略描述在删除持久卷时是否回收存储。默认值为“删除”,但可以覆盖为“保留”,在这种情况下,存储管理员将负责与存储提供程序一起管理该存储的未来状态。虽然上面的例子中没有显示它,但还有一个可选的 allowVolumeExpansion 标志。这指示存储类是否支持扩展卷的功能。如果为 true,则可以通过增加 PersistentVolumeClaim 的 storage.request 字段的大小来扩展卷。此值默认为 false。控制何时预配和绑定存储。如果值为 Immediate ,则只要 PersistentVolumeClaim 引用创建的 StorageClass ,就会立即置备持久卷,并且该声明将绑定到 PersistentVolume,而不管该声明是否在 Pod 中引用。许多存储插件还支持第二种模式,称为 等待第一消费者 ,在这种情况下,在创建引用声明的 pod 之前,不会预配 PersistentVolume。这种行为被认为是可取的,因为它为 Kubernetes 调度程序提供了更大的灵活性。注意

注意:动态置备限制

本地不能由存储类动态预配,因此您必须自己手动创建它们。

应用程序开发人员可以在创建 PVC 时引用特定的 StorageClass,方法是将 storageClass 属性添加到定义中。例如,下面是引用本地路径存储类()的 PVC 的 YAML 配置:

apiVersion: v1kind: PersistentVolumeClaimmetadata:  name: my-local-path-claimspec:  storageClassName: local-path  accessModes:  - ReadWriteOnce  resources:    requests:      storage: 1Gi

如果在声明中未指定 storageClass,则使用默认的 StorageClass。默认存储类可由群集管理员设置。如上文“持久卷”部分所示,您可以使用空字符串选择不使用 StorageClasses,这表示您使用的是静态预配的存储。

存储类提供了一个有用的抽象,群集管理员和应用程序开发人员可以将其用作协定:管理员定义存储类,开发人员按名称引用存储类。底层 StorageClass 实现的细节可能因 Kubernetes 平台提供商而异,从而促进了应用程序的可移植性。

这种灵活性允许管理员创建表示各种不同存储选项的存储类,例如,在吞吐量或延迟方面区分不同的服务质量保证。此概念在其他存储系统中称为“配置文件”。查看(侧边栏),了解有关如何以创新方式利用 StorageClass 的更多想法。

Kubernetes Storage Architecture

在前面的部分中,我们已经讨论了 Kubernetes 通过其 支持的各种存储资源。在本章的其余部分,我们将看看这些解决方案是如何构建的,因为它们可以为我们提供一些有关如何构建云原生数据解决方案的宝贵见解。

注意

注意:定义云原生存储

我们在本章中讨论的大多数存储技术都是作为列出的“云原生存储”解决方案的一部分捕获的。是一个有用的资源,它定义了云原生存储的关键术语和概念。这两个资源都会定期更新。

弹性卷

最初,Kubernetes 代码库包含多个“树内”存储插件,即与其他 Kubernetes 代码包含在同一个 GitHub 存储库中。这样做的好处是,它有助于标准化连接到不同存储平台的代码,但也有一些缺点。首先,许多 Kubernetes 开发人员在广泛的存储提供商方面的专业知识有限。更重要的是,升级存储插件的能力与 Kubernetes 发布周期有关,这意味着如果您需要对存储插件进行修复或增强,则必须等到 Kubernetes 版本接受它。这减缓了K8s存储技术的成熟,因此,采用速度也放缓了。

Kubernetes 社区创建了 Flexvolume 规范,以允许开发可以独立开发的插件,即从 Kubernetes 源代码树中开发,而不受 Kubernetes 发布周期的束缚。大约在同一时间,其他容器编排系统的存储插件标准正在出现,来自这些社区的开发人员开始质疑开发多个标准来解决相同的基本问题是否明智。

注意

注意:未来弹性卷支持

虽然新功能开发在 FlexVolume 上已经暂停,但许多部署仍然依赖于这些插件,并且从 Kubernetes 1.21 版本开始,没有弃用该功能的积极计划。

容器存储接口 (CSI)

容器存储接口 (CSI) 计划是作为容器化应用程序存储的行业标准而建立的。CSI 是一个开放标准,用于定义跨容器编排系统(包括 Kubernetes、Mesos 和 Cloud Foundry)工作的插件。正如 Google 工程师兼 Kubernetes 存储 主席 Saad Ali 在 The New Stack 文章 in Kubernetes 中指出的那样:“容器存储接口允许 Kubernetes 直接与任意存储系统交互。

CSI 规范可在 上找到。Kubernetes 中对 CSI 的支持始于 1.x 版本,并在 。Kubernetes 继续跟踪 CSI 规范的更新。

一旦 CSI 实现部署在 Kubernetes 集群上,其功能就会通过标准的 Kubernetes 存储资源(如 PVC、PV 和 SC)进行访问。在后端,每个 CSI 实现必须提供两个插件:节点插件和控制器插件。CSI 规范使用 gRPC 定义了这些插件所需的接口,但没有确切指定如何部署插件。

让我们简要看一下这些服务中的每一个的角色,如图 2-12 所示:

控制器插件支持对卷的操作,例如创建、删除、列出、发布/取消发布、跟踪和扩展卷容量。它还跟踪卷状态,包括每个卷连接到的节点。控制器插件还负责拍摄和管理快照,以及使用快照克隆卷。控制器插件可以在任何节点上运行 - 它是一个标准的 Kubernetes 控制器。节点插件在每个将附加预置卷的 Kubernetes 工作节点上运行。节点插件负责本地存储,以及将卷挂载和卸载到节点上。Kubernetes 控制平面指示插件在需要卷的节点上计划任何 Pod 之前挂载卷。

映射到 Kubernetes 的容器存储接口注意

注意:其他 CSI 资源:

为有兴趣开发符合 CSI 的驱动程序的开发人员和存储提供商提供指导。该站点还提供了非常有用。此列表通常比 Kubernetes 文档站点上提供的列表更新。

侧边栏:CSI 迁移

Kubernetes 社区一直非常注重保持版本之间的向前和向后兼容性,从树内存储插件到 CSI 的过渡也不例外。Kubernetes 中的功能通常作为 Alpha 功能引入,并进入 Beta,然后作为正式发布 (GA) 发布。引入新的 API(如 CSI)带来了更复杂的挑战,因为它涉及引入新的 API 以及弃用旧的 API。

引入 方法是为了促进存储插件用户的一致体验。当等效的符合 CSI 的驱动程序可用时,每个相应的树内插件的实现将更改为外观。对树内插件的调用将委托给底层符合 CSI 的驱动程序。迁移功能本身就是可以在 Kubernetes 集群上启用的功能。

这允许分阶段采用过程,可以在现有集群更新到较新的 Kubernetes 版本时使用。每个应用程序都可以独立更新,以使用符合 CSI 的驱动程序,而不是树内驱动程序。这种成熟和替换 API 的方法是一种有用的模式,可以提高整个平台的稳定性,并为管理员提供对迁移到新 API 的控制。

容器附加存储

虽然 CSI 是跨容器业务流程协调程序标准化存储管理的重要一步,但它并未提供有关存储软件运行方式或位置的实施指导。一些 CSI 实现基本上是在 Kubernetes 集群之外运行的传统存储管理软件的精简包装。虽然这种对现有存储资产的重用肯定有好处,但许多开发人员表示希望存储管理解决方案能够完全在 Kubernetes 中与其应用程序一起运行。

容器附加存储是一种设计模式,它提供了一种更云原生的方法来管理存储。管理存储操作(例如将卷附加到应用程序)的业务逻辑本身由容器中运行的微服务组成。这允许存储层具有与部署在 Kubernetes 上的其他应用程序相同的属性,并减少管理员必须跟踪的不同管理界面的数量。存储层变成了另一个 Kubernetes 应用程序。

正如 Evan Powell 在 CNCF 博客《容器附加存储:入门》一文中指出的那样,“反映了更广泛的解决方案趋势,这些解决方案通过基于 Kubernetes 和微服务构建,并为基于 Kubernetes 的微服务环境提供功能,从而重塑特定类别或创建新类别。例如,在云原生生态系统中出现了安全、DNS、网络、网络策略管理、消息传递、跟踪、日志记录等方面的新项目。

有几个项目和产品体现了 CAS 的存储方法。让我们来看看一些开源选项。

OpenEBS

OpenEBS是由MayaData创建并捐赠给CNCF的项目,并于2019年成为CNCF的沙盒项目。这个名字是亚马逊Elastic Block Store上的一个游戏,OpenEBS试图提供与这种流行的托管服务相当的开源服务。OpenEBS 提供用于管理本地卷和 NVMe 持久卷的存储引擎。

OpenEBS 提供了一个部署到 Kubernetes 上的符合 CSI 的实现的一个很好的例子,如图 2-13 所示。控制平面包括实现 CSI 控制器接口的 OpenEBS 配置程序和 OpenEBS API 服务器,后者为客户端提供配置接口并与 Kubernetes 控制平面的其余部分进行交互。

开放式 EBS 数据平面由节点磁盘管理器 (NDM) 以及每个持久卷的专用 Pod 组成。NDM 在每个将访问存储的 Kubernetes worker 上运行。它实现了 CSI 节点接口,并提供了自动检测连接到工作节点的块存储设备的有用功能。

开放式EBS架构

OpenEBS 为每个卷创建多个 Pod。将创建一个控制器 Pod 作为主要副本,并在其他 Kubernetes 工作节点上创建其他副本 Pod,以实现高可用性。每个 Pod 都包含公开指标收集和管理的接口的挎斗,这允许控制平面监视和管理数据平面。

长角牛

是 Kubernetes 的开源分布式块存储系统。它最初由Rancher开发,并于2019年成为CNCF沙盒项目。Longhorn专注于提供云供应商存储和昂贵的外部存储阵列的替代方案。Longhorn 支持提供到 NFS 或 AWS S3 兼容存储的增量备份,以及实时复制到单独的 Kubernetes 集群以进行灾难恢复。

Longhorn 使用的架构与 OpenEBS 类似的架构;根据文档,“Longhorn 为每个块设备卷创建一个专用的存储控制器,并在存储在多个节点上的多个副本之间同步复制该卷。存储控制器和副本本身是使用 Kubernetes 编排的。Longhorn 还提供集成的用户界面以简化操作。

鲁克和塞夫

根据其网站,“Rook是一个开源云原生存储编排器,为各种存储解决方案提供平台,框架和支持,以与云原生环境进行本机集成。Rook 最初是作为 Ceph 的容器化版本创建的,可以部署在 Kubernetes 中。 是一个开源分布式存储框架,提供块、文件和对象存储。Rook是CNCF接受的第一个存储项目,现在被认为是CNCF毕业项目。

Rook 是一个真正的 Kubernetes 原生实现,因为它利用了 Kubernetes 自定义资源 (CRD) 和称为运算符的自定义控制器。Rook 为 Ceph、Apache Cassandra 和网络文件系统 (NFS) 提供运算符。我们将在第 4 章中了解有关自定义资源和运算符的更多信息。

还有一些针对 Kubernetes 的商业解决方案体现了 CAS 模式。其中包括(OpenEBS的创建者),的, 和这些公司提供块和文件格式的原始存储,以及用于简化其他数据基础架构(如数据库和流媒体解决方案)部署的集成。

容器对象存储接口 (COSI)

CSI 提供对文件和块存储的支持,但对象存储 API 需要不同的语义,并且不太适合挂载卷的 CSI 范例。2020 年秋季,由 领导的一组公司开始开发用于容器编排平台中对象存储的新 API:容器对象存储接口 (COSI)。COSI 提供了一个 Kubernetes ,更适合预置和访问对象存储、定义存储桶自定义资源,并包括创建存储桶和管理存储桶访问的操作。COSI 控制平面和数据平面的设计以 CSI 为模型。COSI 是一个新兴标准,具有良好的开端,并有可能在 Kubernetes 社区中广泛采用,并可能超越。

边栏:开发人员如何推动 KUBERNETES 存储的未来

与 Kiran Mova,MayaData 的联合创始人兼首席技术官,Kubernetes 成员

许多组织才刚刚开始他们的容器化之旅。Kubernetes 是一个闪亮的对象,每个人都想在 Kubernetes 中运行所有内容。但并非所有团队都为 Kubernetes 做好了准备,更不用说在 Kubernetes 上管理有状态工作负载了。

应用程序开发人员是推动 Kubernetes 上有状态工作负载的人。这些开发人员开始使用可用的云资源,甚至是单节点 Kubernetes 集群,并假设他们已经准备好在生产环境中运行它。开发人员正在“Kubernetic化”他们的内部应用程序,对存储的需求与支持他们的平台团队所习惯的完全不同。

微服务和 Kubernetes 改变了存储卷的配置方式。平台团队习惯于根据具有所需吞吐量或容量的预置卷来考虑数据。在旧方式中,平台团队将与应用程序团队会面,估计数据大小,进行一个月的规划,预配 2-3 TB 的卷,并将其装载到 VM 或裸机服务器中,这将为下一年提供足够的存储容量。

有了 Kubernetes,配置变得更加容易和临时。通过采用 Kubernetes,你可以以非常经济高效和敏捷的方式运行事物。但许多平台团队仍在努力迎头赶上。一些团队只专注于正确调配存储,而另一些团队则开始专注于“第 2 天”操作,例如自动资源调配、扩展卷或断开连接和销毁卷。

平台团队还没有一种万无一失的方式来在 Kubernetes 中运行有状态工作负载,因此他们经常将持久性卸载到公共云提供商。公共云为其托管服务提供了强有力的理由,声称他们拥有运行存储系统所需的一切,但是一旦您开始使用托管服务进行状态,您可能会依赖这些云提供商并陷入困境。

同时,存储技术的创新也在并行发生:

格局正在超融合和解聚之间来回切换。这种重新架构正在堆栈的所有层进行,它不仅仅是软件,还包括流程和使用数据的人。

硬件趋势正在推动低延迟解决方案的发展,包括NVMe和DPDK / SPDK,以及对Linux内核的更改,如io_uring利用更快的硬件。

容器附加存储将帮助我们更有效地管理存储。例如,能够在工作负载缩小时回收存储空间。对于分布在多个节点中的数据,这可能是一个难题。我们需要更好的逻辑将数据重新定位到现有节点上。

为合规性和运营带来更多自动化的技术也正在出现。

有了所有这些创新,了解大局并确定如何利用这项技术获得最大利益可能会有点不知所措。平台 SRE 需要了解 Kubernetes、声明式部署、GitOps 原则、新的卷类型,甚至数据库概念,如最终一致性。

我们设想未来,应用程序开发人员将根据所需的服务质量(例如每秒 I/O 操作数 (IOPS) 和吞吐量)来指定其 Kubernetes 存储需求。开发人员应该能够以更人性化的术语为其工作负载指定不同的存储需求。例如,平台团队可以为“快速存储”与“慢速存储”定义 StorageClass,或者为“元数据存储”与“数据存储”定义 StorageClass。这些存储类将做出不同的成本/性能权衡,并提供特定的服务级别协议 (SLA)。我们甚至可能看到这些新的存储类开始出现一些标准定义。

理想情况下,应用程序团队不应选择选择哪些存储解决方案。应用程序开发人员唯一应该关心的是为其应用程序指定 PersistentVolumeClaims,以及他们需要的 StorageClasses。管理存储的其他细节应该隐藏,当然存储子系统会通过标准 Kubernetes 机制报告错误,包括状态和日志。此功能将使应用程序开发人员的工作变得更加简单,无论他们是部署数据库还是其他一些有状态工作负载。

这些创新将引导我们在 Kubernetes 上找到一个更理想的存储位置。今天,我们所处的位置很容易部署基础设施。让我们共同努力,轻松部署正确的基础架构。

如您所见,Kubernetes 上的存储是一个有很多创新的领域,包括多个开源项目和商业供应商竞相提供最有用、最具成本效益和性能的解决方案。CNCF 景观的提供了存储提供程序和相关工具的有用列表,包括本章中引用的技术以及更多内容。

总结

在本章中,我们探讨了如何在容器系统(如 Docker)和容器编排系统(如 Kubernetes)中管理持久性。您已经了解了可用于管理有状态工作负载的各种 Kubernetes 资源,包括 Volumes、PersistentVolumes、PersistentVolumeClaims、StorageClasses。我们已经了解了容器存储接口和容器附加存储模式如何为管理存储的更多云原生方法指明了方向。现在,你已准备好学习如何使用这些构建基块和设计原则来管理有状态工作负载,包括数据库、流数据等。

标签: #mayaubuntu