2020 年,MinIO 为基于 Kubernetes 的 MinIO 存储部署实施了直接持久卷 (DirectPV)。DirectPV 类似于 LocalPV,但动态预配。
在这篇文章中,我将介绍创建 DirectPV 的有趣设计决策。但在深入了解设计细节之前,让我们先快速回顾一下直接持久卷与网络持久卷。
什么是DirectPV?
DirectPV 是用于直连存储的 Kubernetes 容器存储接口 (CSI) 驱动程序。它用于为工作负载置备本地卷。简单来说,使用 DirectPV 的工作负载将直接使用本地磁盘,而不是远程磁盘。与基于 SAN 或 NAS 的 CSI 驱动程序(网络 PV)不同,后者在数据路径中增加了另一层复制/纠删码和额外的网络跃点。这种额外的分解层会导致复杂性增加和性能不佳。
使用 DirectPV,您可以发现、格式化和装载驱动器,使其准备好进行预配。初始化驱动器后,可以使用“directpv-min-io”存储类在驱动器上计划工作负载的卷。
设计中的挑战
“简约是极致的精致。”
除了解释 DirectPV 的功能和用例之外,我还想介绍一些在开发阶段做出的有趣的设计决策。
以下是 DirectPV 设计阶段面临的一些主要挑战。
挑战 1:匹配驱动器的本地状态和远程状态
DirectPV 在 Kubernetes 自定义资源中保存和维护本地驱动器状态。某些状态(如驱动器名称和排序)在节点重新启动或驱动器热插拔期间可能不会持续存在。持续保持本地设备状态与 DirectPV 记住的驱动器状态同步是必要的,因为它处理敏感操作,如格式化、装载等。这是一个关键部分,如果处理不当,可能会导致数据丢失。
早期版本的 DirectPV 通过识别 WWID、驱动器序列号和分区 UUID 等属性来匹配本地驱动器(来自主机)和远程驱动器(Kubernetes 驱动器资源)。
这种方法存在以下问题
(a) 探测这些设备成本更高,费力更大,并且有不良副作用。
我们执行了 IOCTL 调用来读取驱动器的 S.M.A.R.T(自我监控、分析和报告技术)序列号信息。作为副作用,这会适得其反,导致 CHANGE udev 事件过多,导致 DirectPV udev 侦听器中的 CPU 峰值。
(b) 按驱动器的特性进行匹配至关重要
如前所述,需要持续保持驱动器与本地状态同步,因为 DirectPV 执行本地主机级操作,如格式化、挂载等。早期版本的 DirectPV 将驱动器与多个属性(如 WWID、序列号、主要-次要等)相匹配。由于不同驱动器供应商之间存在差异,因此此类状态无法可靠地进行匹配。并非所有驱动器供应商都提供有关驱动器的相同信息。这里的风险是驱动器的格式可能不正确,并且可能没有正确地映射到卷。本期将介绍一个这样的例子。
溶液:
现在,我们已经看到了探测设备以获取信息以及保持本地和远程驱动器状态同步的潜在挑战。我们发现了一种简单的方法来缩小 DirectPV 管理的驱动器的范围。
我们从管理群集中的所有驱动器转变为仅管理提供给 DirectPV 的驱动器。为了实现这一点,设计进行了一些更改。
# 列出集群中的可用驱动器
kubectl directpv drives ls
# 选择 directpv 应管理和格式化的驱动器
kubectl directpv drives format --drives /dev/sd{a...f} --nodes directpv-{1...4}
如上图所示,在旧版本的 DirectPV 中,服务器启动后,它会探测集群中所有节点中的所有驱动器,并在 Kubernetes 中创建相应的驱动器对象(自定义资源)。这些驱动器对象将列出并格式化。如前所述,如果驱动器状态不可靠,则可能会导致格式化错误的驱动器。
# Probe and save drive information to drives.yaml file.
# 探测驱动器信息并保存到 drives.yaml 文件中。
$ kubectl directpv discover
$ kubectl directpv 发现
# Initialize selected drives.
# 初始化选定的驱动器。
$ kubectl directpv init drives.yaml
最新版本的 DirectPV 执行按需探测。当用户执行命令“kubectl directpv discover”时,它会实时探测集群中的驱动器。与传统设计不同,这是无状态的,并且不会在 Kubernetes 中保存任何驱动器状态。用户查看探测驱动器的输出,然后通过发出“kubectl directpv init”命令将其添加到 DirectPV。
作为初始化过程的一部分,驱动器将使用唯一的文件系统 UUID 进行格式化。成功执行后,将使用 UUID 创建一个驱动器对象。此 FSUUID 对于“初始化”驱动器是唯一的,我们可以可靠地使用它进行匹配。它提供了一个强有力的事实和证据,表明如果初始化驱动器的 FSUUID 通过任何外部进程篡改或覆盖主机而发生更改,则无法匹配驱动器,并将被视为损坏的驱动器。除此之外,我们不再需要任何其他更昂贵的探头。
挑战 2:Hitman 风格的声明性格式和重试
根据定义,Kubernetes 中的事件控制器是声明性的,在维护资源的所需状态方面起着至关重要的作用。它会在出现任何错误时重试,直到达到所需状态。这是 Kubernetes 中管理和维护资源状态的成功方法。
DirectPV的旧版本遵循类似的方法。驱动器(自定义资源)事件控制器侦听格式驱动器请求并做出反应。格式化失败时,控制器会以增量回退的方式重试。当驱动器的状态在定期重试期间发生更改时,此方法是不安全的。这就像“派一个杀手去追捕磁盘并不断重试”。
溶液: 我们更改了设计以遵守以下规则:
格式化时不会自动重试
验证驱动器并在格式化前 100% 确定
初始化(格式化)是实时的,没有自动重试。最重要的是,不会对缓存的驱动器状态执行任何关键驱动器操作。
如果初始化成功或失败,将传达错误,用户将重试初始化。
提供给 init 命令的配置文件将具有驱动器唯一的驱动器哈希值。在格式化之前,DirectPV服务器将验证请求中的此哈希值是否与计算的哈希值匹配。如果没有,那么它就会出错,抱怨状态已经改变。
总结
“简单不仅仅是消除杂乱无章。这是为了让复杂性变得清晰......“ - 阿南德·巴布·佩里亚萨米
对于在 Kubernetes 上运行 MinIO 存储的 MinIO 客户来说,DirectPV 是向前迈出的一大步。到达那里并不容易。这篇文章探讨了 MinIO 工程在实施 DirectPV 时面临的两个高级挑战。这些挑战是 Hitman 风格的重试,并将本地状态与驱动器的远程状态相匹配。
如果你有任何问题,请务必与我们联系!