精通-Ceph-第二版-全-

精通 Ceph 第二版(全)

原文:annas-archive.org/md5/fa7e0f21a9c29ccb30ce68b7f5b45dc6

译者:飞龙

协议:CC BY-NC-SA 4.0

前言

Ceph 是一个统一的、高度弹性的分布式存储系统,提供块存储、对象存储和文件访问,近年来受到了广泛关注和使用。由于开源的优势,Ceph 得到了开发者和终端用户的快速采用,多个知名企业也参与了该项目。每个新版本的发布都进一步增强了其性能和功能,不断提升 Ceph 的地位。

随着对日益增长的数据存储需求和传统基于 RAID 的系统面临的挑战,Ceph 正好能为这些问题提供解决方案。随着世界逐步采纳新型云技术和基于对象的存储,Ceph 已准备好成为推动新一代存储技术的核心力量。

在本书中,我们将涵盖各种各样的话题,从安装和管理 Ceph 集群,到如何在遇到灾难时进行恢复,帮助你应对突发情况。对于那些有兴趣将应用程序与 Ceph 直接对接的读者,本书还将教你如何开发使用 Ceph 库的应用程序,甚至如何通过将自定义代码插入 Ceph 来执行分布式计算。通过本书的学习,你将顺利迈向掌握 Ceph 的道路。

《Mastering Ceph》第二版现已包含关于 Luminous 版本中新引入的 BlueStore 技术的更新,提供了帮助你理解 BlueStore 并升级现有集群以利用该技术的指南。

本书适合谁阅读

如果你是存储专业人员、系统管理员或云工程师,并且正在寻找构建强大存储系统的解决方案,适用于云环境和本地基础设施,那么这本书适合你。

本书内容概述

第一章,Ceph 规划,介绍了 Ceph 的基本原理、基本架构,并探讨了一些良好的应用案例。它还讨论了在实施 Ceph 之前你应该采取的规划步骤,包括设定设计目标、开发概念验证并进行基础设施设计。

第二章,使用容器部署 Ceph,是一个直截了当、按步骤讲解如何设置 Ceph 集群的章节。本章介绍了用于测试的 ceph-deploy,并进一步介绍了 Ansible。最后,我们还探讨了 Rook 项目,展示如何在 Kubernetes 集群上部署 Ceph 集群。章节还包括一个关于变更管理的部分,解释了变更管理对于大型 Ceph 集群稳定性的重要性。本章还为读者提供了一个共同的平台,帮助你构建本书后续使用的示例。

第三章,BlueStore,解释了 Ceph 必须能够提供数据和元数据的原子操作,并介绍了 FileStore 如何在标准文件系统之上构建,以提供这些保证。我们还将讨论这种方法所面临的问题。

本章接着介绍了 BlueStore,解释了它的工作原理及其解决的问题。将介绍其组件以及它们如何与不同类型的存储设备交互。我们将探索一些关键值存储的概览,包括 BlueStore 使用的 RocksDB。本章还将讨论一些 BlueStore 的设置以及它们如何与不同硬件配置交互。

本章最后讨论了将集群升级到 BlueStore 的方法,并通过一个升级示例指导读者完成升级过程。

第四章,Ceph 与非本地协议,讨论了 Ceph 为本地 Ceph 客户端提供的存储功能,并突出说明了在一些尚未广泛采用的传统存储部署中存在的问题。接着本章探讨了如何通过 NFS 和 iSCSI 将 Ceph 导出到非本地 Ceph 客户端,并通过实例演示配置。

第五章,RADOS 池和客户端访问,探讨了 Ceph 如何通过三种主要协议(块、文件和对象)提供存储。本章讨论了每种协议的使用场景以及用于提供每种协议的 Ceph 组件。章节还讲解了复制池和纠删码池之间的区别,并深入探讨了纠删码池的操作。

第六章,使用 Librados 开发,解释了如何使用 librados 构建可以直接与 Ceph 集群交互的应用程序。接着通过不同语言中的多个使用 librados 的示例,帮助读者了解如何使用 librados,包括在原子事务中的应用。

第七章,使用 Ceph RADOS 类进行分布式计算,讨论了将处理过程直接移至 OSD 进行有效的分布式计算的好处。然后介绍了如何通过构建简单的 Lua RADOS 类来开始使用 RADOS 类。接着探讨了如何将自己的 C++ RADOS 类构建到 Ceph 源代码树中,并对比在客户端和 OSD 上执行处理的基准测试。

第八章,Ceph 监控,首先描述了监控为何重要,并讨论了警报和监控之间的区别。接着本章讲解了如何从所有 Ceph 组件中获取性能计数器,解释了其中一些关键计数器的含义,以及如何将它们转化为可用的数值。

一个使用 Graphite 的示例将展示能够操作捕获数据并以图形形式提供更有意义输出的价值。本书还介绍了 Ceph Mimic 版本中新推出的 Ceph Dashboard,并提供了一个逐步的示例,说明如何在运行中的 Ceph 集群上启用它。

第九章,调优 Ceph,首先简要概述了如何调整 Ceph 和操作系统。它还介绍了避免调优非瓶颈部分的基本概念。本章还涵盖了你可能希望调优的领域,并建立如何衡量调优成功的方法。接下来,本章展示了如何对 Ceph 进行基准测试并进行基准测量,以确保所获得的任何结果都有意义。最后,讨论了不同的工具及基准测试如何与实际性能相关联。

第十章,使用 Ceph 进行分层,解释了 Ceph 中的 RADOS 分层是如何工作的,在哪些情况下应使用以及它的缺点。本章将带领读者逐步了解如何在 Ceph 集群上配置分层,并最终介绍调优选项,以便从分层中提取最佳性能。

第十一章,故障排除,概述了虽然 Ceph 在很大程度上是自动化的,能够自我管理并从故障场景中恢复,但在某些情况下仍需要人工干预。本章将探讨常见的错误和故障场景,并通过故障排除帮助 Ceph 恢复健康状态。

第十二章,灾难恢复,详细说明了当 Ceph 处于完全丧失服务或数据丢失的状态时,如何使用不太常见的恢复技术来恢复集群的访问权限,并希望能够恢复数据。本章为你提供在这些场景中尝试恢复所需的知识。

为了充分利用本书

本书假设读者对 Linux 操作系统有中等水平的熟练度,并且具备存储技术和网络基础知识。虽然本书会介绍 Ceph 集群的简单多节点设置,但建议读者在使用 Ceph 前有一些相关经验。尽管本书使用的是 Virtual Box,但你也可以自由选择其他实验环境,如 VMware Workstation 或其他工具。

本书要求你拥有足够的资源来支持整个 Ceph 实验环境。最低硬件或虚拟要求如下所示:

  • CPU:2 核

  • 内存:8 GB RAM(推荐 16 GB)

  • 磁盘空间:40 GB

跟随本书操作,你将需要以下软件:

  • VirtualBox

  • vagrant

需要互联网连接,以便安装每章示例中的必要软件包。

下载示例代码文件

你可以从 www.packt.com 的帐户中下载本书的示例代码文件。如果你在其他地方购买了本书,可以访问 www.packt.com/support,注册后直接将文件发送到你的邮箱。

你可以按照以下步骤下载代码文件:

  1. 登录或注册 www.packt.com

  2. 选择 SUPPORT 标签。

  3. 点击代码下载 & 勘误。

  4. 在搜索框中输入书名并按照屏幕上的指示操作。

一旦文件下载完成,请确保使用最新版本的工具解压或提取文件夹:

  • 适用于 Windows 的 WinRAR/7-Zip

  • 适用于 Mac 的 Zipeg/iZip/UnRarX

  • 适用于 Linux 的 7-Zip/PeaZip

本书的代码包也托管在 GitHub 上,地址为 github.com/PacktPublishing/Mastering-Ceph-Second-Edition。如果代码有更新,将会在现有的 GitHub 仓库中更新。

我们的丰富书籍和视频目录中也有其他代码包,欢迎访问 github.com/PacktPublishing/。赶快去看看!

下载彩色图片

我们还提供了一个 PDF 文件,其中包含本书中使用的截图/图表的彩色图片。你可以在此下载:www.packtpub.com/sites/default/files/downloads/9781789610703_ColorImages.pdf

使用的约定

本书中使用了许多文本约定。

CodeInText:表示文本中的代码词汇、数据库表名、文件夹名、文件名、文件扩展名、路径名、虚拟 URL、用户输入和 Twitter 用户名。例如:“使用以下代码安装 corosyncpacemakercmrsh 工具集:”

代码块的设置如下:

Vagrant.configure("2") do |config|
nodes.each do |node|
config.vm.define node[:hostname] do |nodeconfig|
nodeconfig.vm.box = "bento/ubuntu-16.04" 

当我们希望引起你注意某一特定代码块时,相关的行或项目会以粗体显示:

Vagrant.configure("2") do |config|
nodes.each do |node|
config.vm.define node[:hostname] do |nodeconfig|
nodeconfig.vm.box = "bento/ubuntu-16.04" 

任何命令行输入或输出均按如下方式书写:

 yum install *.rpm 

粗体:表示新术语、重要词汇或屏幕上显示的文字。例如,菜单或对话框中的文字会这样显示。这里有一个例子:“点击 Repo URL 链接,将带你到仓库目录树。”

警告或重要说明如下所示。

小贴士和技巧如下所示。

联系我们

我们总是欢迎读者的反馈。

一般反馈:如果你对本书的任何部分有疑问,请在邮件主题中提到书名,并通过 customercare@packtpub.com 给我们发送邮件。

勘误:虽然我们已经尽最大努力确保内容的准确性,但错误还是难免发生。如果您在本书中发现任何错误,我们将非常感激您能向我们报告。请访问 www.packt.com/submit-errata,选择您的书籍,点击勘误提交表格链接并填写详细信息。

盗版:如果您在互联网上遇到任何我们作品的非法复制品,无论何种形式,我们将非常感激您能提供该位置地址或网站名称。请通过 copyright@packt.com 与我们联系,并附上该材料的链接。

如果您有兴趣成为作者:如果您在某个领域具有专业知识并且有兴趣撰写或为书籍作贡献,请访问 authors.packtpub.com

评论

请留下评论。在您阅读并使用本书后,不妨在您购买本书的网站上留下评论。潜在读者可以看到并参考您的公正意见做出购买决策,我们在 Packt 可以了解您对我们产品的看法,而我们的作者也能看到您对他们书籍的反馈。谢谢!

有关 Packt 的更多信息,请访问 packt.com

第一部分:规划与部署

在本节中,读者将了解在生产环境中部署 Ceph 时的最佳实践。

本节包含以下章节:

  • 第一章,Ceph 规划

  • 第二章,使用容器部署 Ceph

  • 第三章,BlueStore

  • 第四章,Ceph 与非原生协议

第一章:Ceph 的规划

本书的第一章涵盖了在部署 Ceph 集群时需要考虑的所有领域,从初步规划阶段到硬件选择。我们将讨论的主题包括以下内容:

  • 什么是 Ceph,以及它是如何工作的

  • Ceph 的良好使用案例及重要的考虑因素

  • 基础设施设计建议和最佳实践

  • Ceph 项目规划的思路

什么是 Ceph?

Ceph 是一个开源的、分布式的、可扩展的、软件定义的存储系统,可以提供块存储、对象存储和文件存储。通过使用可控复制下的可扩展哈希CRUSH)算法,Ceph 消除了对集中式元数据的需求,并能够将负载分散到集群中的所有节点。由于 CRUSH 是一个算法,数据的位置是通过计算得出的,而不是基于表查找,并且可以扩展到数百 PB 而不会有瓶颈的风险及相关的单点故障。客户端还直接与所需的 OSD 建立连接,这也消除了任何单点故障成为瓶颈的可能性。

Ceph 提供三种主要类型的存储:通过RADOS 块设备RBD)提供的块存储,通过 CephFS 提供的文件存储,以及通过 RADOS 网关提供的对象存储,支持 S3 和 Swift 兼容的存储。

Ceph 是一个纯粹的 SDS 解决方案,这意味着你可以在任何符合 Ceph 要求的硬件上运行它。这是存储行业的一项重大进展,因为该行业通常面临严格的厂商锁定问题。

值得注意的是,根据 CAP 定理,Ceph 偏向于一致性,并会在分区发生时不惜一切代价将保护数据作为高于可用性的优先级。

Ceph 是如何工作的

Ceph 的核心存储层是可靠自治分布式对象存储RADOS),正如其名称所示,它提供了一个对象存储,基于此构建了更高层的存储协议。Ceph 中的 RADOS 层由多个对象存储守护进程OSD)组成。每个 OSD 是完全独立的,并通过对等关系形成集群。每个 OSD 通常映射到一个单独的磁盘,这与传统方法不同,后者通过 RAID 控制器将多个磁盘组合成一个设备供操作系统使用。

Ceph 集群的另一个关键组件是监视器。它们通过使用 Paxos 来负责形成集群法定人数。监视器不直接参与数据路径,因此不具备 OSD 相同的性能要求。它们主要用于通过各种集群映射提供已知的集群状态,包括成员信息。这些集群映射由 Ceph 集群组件和客户端共同使用,用于描述集群拓扑结构,并确保数据能够安全地存储在正确的位置。还有一个最终的核心组件——管理器,它负责配置和统计。由于 Ceph 旨在运行的规模,可以理解追踪集群中每个单独对象的状态会变得非常计算密集。Ceph 通过哈希底层对象名称,将对象分配到多个放置组,从而解决了这个问题。然后,使用一种叫做 CRUSH 的算法将放置组分配到 OSD。这样一来,跟踪数百万个对象的任务就转变为跟踪一个更易管理的放置组数量,通常这个数量是以千为单位来衡量的。

Librados 是一个 Ceph 库,可用于构建直接与 RADOS 集群交互的应用程序,用于存储和检索对象。

若想了解 Ceph 内部如何运作,强烈建议阅读官方的 Ceph 文档,以及 Sage Weil 编写的论文,他是 Ceph 的创建者和主要架构师。

Ceph 使用案例

在深入了解具体的使用案例之前,让我们先看看以下几个关键点,这些点应该在考虑部署 Ceph 集群之前理解和考虑:

  • Ceph 不是存储阵列:Ceph 不应与传统的扩展型存储阵列进行比较;它在本质上是不同的,试图用现有的知识、基础设施和预期将 Ceph 强行纳入这一角色会导致失望。Ceph 是一种软件定义的存储,内部数据流动通过 TCP/IP 网络操作,相比传统存储阵列背后简单的 SAS 电缆,它引入了更多的技术层次和复杂性。Ceph 项目正在继续扩展其在当前由传统存储阵列主导的领域中的应用,支持 iSCSI 和 NFS,每一次发布,Ceph 都更接近实现更好的互操作性。

  • 性能:由于 Ceph 采用非集中式的方式,它能提供比扩展型存储阵列更强大的性能,后者通常必须通过一对控制器头来传输所有 I/O。尽管技术发展意味着更快的 CPU 和更快的网络速度不断出现,但仅依赖一对存储控制器,性能仍然有限。随着闪存技术的最新进展,加上 NVMe 等新接口的出现,带来了前所未有的性能水平,Ceph 的扩展性使得每增加一个 OSD 节点,CPU 和网络资源会线性增加。然而,我们也应该考虑 Ceph 在性能方面不适合的地方。主要是针对那些需要极低延迟的应用场景。使 Ceph 成为扩展型解决方案的原因,也意味着低延迟性能会受到影响。在软件中执行大部分处理以及额外的网络跳数所带来的开销,意味着延迟通常是传统存储阵列的两倍,至少是本地存储的十倍。因此,应该慎重选择最适合特定性能需求的技术。尽管如此,一个设计良好并经过调优的 Ceph 集群应该能够满足除了最极端的情况之外的性能需求。需要记住的是,任何采用广泛条带化的存储系统——数据被分布到系统中的所有磁盘——速度通常会受到集群中最慢组件的限制。因此,确保集群中的每个节点具有相似的性能是非常重要的。随着 NVMe 和 NVDIMMS 的新发展,存储访问的延迟仍在不断降低。

Ceph 的工作正在进行中,旨在消除瓶颈,以利用这些新技术,但应考虑如何平衡延迟需求与分布式存储系统的优势之间的关系。

  • 可靠性:Ceph 设计为通过其组件的扩展性质提供一个高度容错的存储系统。虽然单个组件的高可用性较差,但当它们聚集在一起时,任何组件的故障都不应导致无法服务客户端请求。事实上,随着 Ceph 集群的增长,个别组件的故障应当被视为正常现象,并成为正常操作条件的一部分。然而,Ceph 提供一个强大集群的能力并不意味着可以在硬件或设计选择上妥协,若如此做,很可能会导致失败。Ceph 假设硬件能够满足几个要求,稍后在本章中将讨论这些要求。与 RAID 不同,RAID 中,使用更大容量的硬盘时,磁盘重建的时间可能需要几周,而 Ceph 通常能在数小时内恢复单个磁盘故障。随着大容量硬盘的日益普及,Ceph 在可靠性和降级性能方面相比传统存储阵列具有许多优势。

  • 使用普通硬件:Ceph 设计为运行在普通硬件上,这使我们能够设计和构建一个集群,而无需支付传统一线存储和服务器供应商要求的高昂费用。这既是一个优势,也是一个挑战。能够选择自己的硬件使你能够精确匹配 Ceph 组件的需求。然而,品牌硬件所提供的一个优势是兼容性测试。我们并不陌生于发现奇怪的固件问题,这些问题可能导致非常混乱的症状。应考虑你的 IT 团队是否有足够的时间和技能来应对未经测试的硬件解决方案可能带来的任何隐晦问题。使用普通硬件还可以防止传统的叉车升级模式,在这种模式下,升级单个组件往往需要完全替换整个存储阵列。使用 Ceph,你可以以非常细粒度的方式更换单个组件,并且通过自动数据平衡,避免了长时间的数据迁移过程。

具体使用案例

现在我们将讨论一些 Ceph 的常见使用案例,并探讨其中的一些概念。

基于 OpenStack 或 KVM 的虚拟化

Ceph 是为 OpenStack 环境提供存储的完美选择;事实上,Ceph 目前是最受欢迎的选择。2018 年的 OpenStack 调查显示,61% 的受访 OpenStack 用户正在使用 Ceph 为 OpenStack 提供存储。OpenStack Cinder 块驱动程序使用 Ceph RBD 来为虚拟机(VM)提供块存储,而 OpenStack Manila,这款文件即服务FaaS)软件,与 CephFS 集成得很好。以下是 Ceph 成为 OpenStack 优秀解决方案的一些原因:

  • 两者都是开源项目,并且都有商业版本

  • 两者在大规模部署中都有成熟的应用记录

  • Ceph 可以提供块存储、CephFS 和对象存储,OpenStack 都可以使用这些服务。

  • 通过精心规划,完全有可能部署一个超融合集群

如果你不使用 OpenStack,或没有计划使用,Ceph 也与 KVM 虚拟化系统非常兼容。

大容量块存储

由于能够设计和构建具有成本效益的 OSD 节点,Ceph 使你能够构建大型、高性能的存储集群,相比其他选择,这种方案成本效益更高。Luminous 版本带来了对块和文件工作负载的 Erasure 编码支持,这使得 Ceph 在这一任务中的吸引力进一步增加。

对象存储

由于核心的 RADOS 层本身就是一个对象存储,Ceph 在通过 S3 或 Swift 协议提供对象存储方面表现出色。Ceph 目前在兼容 S3 API 方面拥有最好的记录之一。如果成本、延迟或数据安全是使用公共云对象存储解决方案的顾虑,自己运行 Ceph 集群来提供对象存储可能是一个理想的用例。

用于自定义应用的对象存储

使用 librados,你可以让自家应用直接与底层的 Ceph RADOS 层进行交互。这可以极大简化应用的开发,并为你提供对高性能可靠存储的直接访问。librados 的一些更高级功能,例如将多个操作打包为一个原子操作,在现有存储解决方案中也很难实现。

分布式文件系统 – web 群集

一组 web 服务器需要访问相同的文件,以便无论客户端连接到哪一台,它们都能提供相同的内容。传统上,HA NFS 解决方案会用来提供分布式文件访问,但在规模化时可能会遇到多个限制。CephFS 可以提供一个分布式文件系统来存储 web 内容,并允许它在 web 服务器群集中的所有服务器上挂载。

分布式文件系统 – NAS 或文件服务器替代

通过将 Samba 与 CephFS 配合使用,可以将一个高可用的文件系统导出到基于 Windows 的客户端。由于 Samba 和 CephFS 都具有活跃和非活跃的特性,随着 Ceph 集群的扩展,性能也会随之提升。

大数据

大数据是指分析大量无法容纳在传统数据分析系统中的数据,或者这些数据的分析方法过于复杂的概念。大数据通常需要既能存储大量数据又能提供扩展性能的存储系统。Ceph 能够满足这两个要求,因此是为大数据系统提供扩展存储的理想候选者。

基础设施设计

在考虑基础设施设计时,我们需要关注某些组件。接下来我们将简要介绍这些组件。

SSD

SSD 非常出色。在过去的 10 年里,它们的价格已经大幅下降,所有证据表明,这一趋势将继续。它们能够提供比旋转磁盘低几个数量级的访问时间,并且消耗更少的电力。

关于 SSD,有一个重要概念需要理解:虽然它们的读写延迟通常以微秒为单位进行衡量,但要覆盖现有数据,需要先擦除整个闪存块才能进行写入。SSD 中的典型闪存块大小可能为 128k,即使是一个 4 KB 的写入操作,也需要读取整个块,擦除它,然后将现有数据和新写入的数据一起写入。擦除操作可能需要几毫秒,如果没有 SSD 固件中的聪明例程,这会使写入操作变得非常缓慢。为了绕过这个限制,SSD 配备了一个 RAM 缓存,以便它们可以立即确认写入,同时固件会在内部移动数据,优化覆盖写入过程和磨损均衡。然而,RAM 缓存是易失性内存,通常在突然断电的情况下会导致数据丢失和损坏。为了防止这种情况,SSD 可以配备电源丧失保护,通过在板载设置一个大电容器,储存足够的电力以便将所有未完成的写入刷新到闪存中。

近年来最大的趋势之一是不同层次的 SSD 已经变得可以获取。大致来说,这些可以分为以下几类:

  • 消费级:这是你可以购买的最便宜的 SSD,面向普通 PC 用户。它们提供了非常大的存储容量,并且价格非常便宜,性能也相当不错。它们可能没有电源丧失保护,并且在进行同步写入时表现可能会非常差,或者在存储数据完整性上做出虚假承诺。它们的写入耐久性可能非常差,但对于标准使用来说,仍然足够。

  • 专业级:这些比消费级型号稍强,通常提供更好的性能,并具有更高的写入耐久性,尽管仍远不及企业级 SSD。

在讨论企业级模型之前,值得一提的是,为什么在任何情况下都不应将前面提到的 SSD 模型用于 Ceph。以下是这些原因:

  • 缺乏适当的电源丧失保护将导致极其差的性能,或者不能确保数据的一致性。

  • 固件的测试不如企业级 SSD 那样严格,因为企业级 SSD 经常会暴露出数据损坏的错误。

  • 低写入耐久性意味着它们会很快磨损,通常会导致突发故障。

  • 由于高磨损和故障率,它们的初期成本优势很快就会消失。

  • 使用消费级 SSD 运行 Ceph 会导致性能低下,并增加灾难性数据丢失的风险。

企业级 SSD

消费级 SSD 和企业级 SSD 之间的最大区别在于,企业级 SSD 应该提供保证,当它向主机系统响应确认数据已安全存储时,数据实际上已经永久写入闪存。也就是说,如果系统突然断电,操作系统认为已经写入磁盘的数据将安全地存储在闪存中。此外,企业级 SSD 还应该配备超级电容器,以便在断电情况下提供足够的电力,将 SSD 的 RAM 缓存刷新到闪存中,从而加速写入同时保证数据安全。

企业级 SSD 通常有多种不同的版本,以提供广泛的每 GB 成本选项,同时平衡写入耐久性。

企业级 – 读密集型

读密集型 SSD 是一种市场营销术语,因为所有 SSD 都能够轻松处理读取操作。这个名字指的是较低的写入耐久性。然而,它们将提供最佳的每 GB 成本。这些 SSD 通常仅具有大约 0.3-1 驱动写入每一天的写入耐久性,且持续五年。这就是说,你可以每天对一个 400 GB 的 SSD 写入 400 GB 数据,并预期它在五年后仍能正常工作。如果你每天写入 800 GB 数据,那么它只能保证两年半的使用寿命。一般来说,对于大多数 Ceph 工作负载,这种类型的 SSD 通常被认为写入耐久性不足。

企业级 – 一般用途

一般用途 SSD 通常提供三到五 DWPD,是成本和写入耐久性的良好平衡。对于 Ceph 使用,它们通常是 SSD 基于 OSD 的不错选择,前提是 Ceph 集群上的工作负载没有计划过度写入。它们也非常适合在混合 HDD/SSD 集群中存储 BlueStore 数据库分区。

企业级 – 写密集型

写密集型 SSD 是最昂贵的类型。它们通常提供高达 10 DWPD 及以上的写入耐久性。如果计划进行非常重的写入工作负载,它们应当用于纯 SSD OSD。如果您的集群仍在使用已弃用的 filestore 对象存储,那么也建议为日志使用高写入耐久性 SSD。

对于任何新的 Ceph 部署,BlueStore 是推荐的默认对象存储。以下信息仅适用于仍在运行 filestore 的集群。有关 filestore 的工作原理以及为何被替代的详细信息,稍后会在第三章中介绍,BlueStore

为了理解在运行 filestore 时选择合适 SSD 的重要性,我们必须明白,由于普通 POSIX 文件系统的限制,为了提供在写入过程中发生的原子事务,需要一个日志,以便在事务未能完全完成时能够回滚。如果没有为日志使用独立的 SSD,则会为其创建一个单独的分区。OSD 处理的每次写入将首先写入日志,然后再刷新到磁盘的主存储区。这就是为什么建议为旋转磁盘使用 SSD 作为日志的主要原因。双重写入严重影响旋转磁盘的性能,主要是由于磁盘头在日志和数据区之间随机移动的特性。

同样,使用 filestore 的 SSD OSD 仍然需要一个日志,因此它将经历大约双倍的写入次数,从而提供一半的预期客户端性能。

如今可以看到,并非所有型号的 SSD 都是相同的,Ceph 的需求可能使选择正确的 SSD 成为一项艰巨的任务。幸运的是,可以通过一个快速测试来确定 SSD 是否适合用作 Ceph 日志。

内存

对于 BlueStore OSD,每个 HDD OSD 推荐使用 3 GB 内存,每个 SSD OSD 推荐使用 5 GB 内存。实际上,存在许多变量导致这一推荐,但可以说,绝不能让 OSD 内存不足的情况发生,任何多余的内存都将被用来提升性能。

除了 OSD 的基本内存使用量外,影响内存使用量的主要变量是 OSD 上运行的 PG 数量。虽然总数据量对内存使用量有影响,但其影响远不及 PG 数量的影响。一个在每个 OSD 上运行 200 个 PG 的健康集群,可能每个 OSD 使用的内存不足 4 GB。

然而,在一个 PG 数量高于最佳实践的集群中,内存使用量会更高。同样需要注意的是,当 OSD 从集群中移除时,额外的 PG 会被放置到剩余的 OSD 上,以重新平衡集群。这也会增加内存使用量以及恢复操作本身。如果没有足够的内存,内存使用量的激增有时会导致级联故障。为了减少 Linux 内存不足时随机杀死 OSD 进程的风险,应该始终为 SSD 配置一个较大的交换分区。

至少,目标是为 HDD 分配每个 OSD 大约 4 GB,为 SSD 分配每个 OSD 大约 5 GB;这应视为最低要求,理想的每个 OSD 内存应为 5 GB/6 GB(分别对应 HDD/SSD)。无论是使用 BlueStore 还是 filestore,服务器上安装的任何额外内存都可以用于缓存数据,从而减少客户端操作的读取延迟。Filestore 使用 Linux 页缓存,因此会自动利用 RAM。对于 BlueStore,我们需要手动调整内存限制,分配额外的内存作为缓存;这将在第三章中进一步详细讲解,BlueStore

如果您的集群仍在使用 filestore,根据工作负载和用于 Ceph OSD 的旋转磁盘大小,可能需要额外的内存,以确保操作系统能够充分缓存文件系统中的目录条目和文件节点,这些文件系统用于存储 Ceph 对象。这可能会影响您希望为节点配置的 RAM 量,相关内容将在本书的调优部分进行更详细的讨论。

无论配置的内存大小如何,都应始终使用 ECC 内存。

CPU

Ceph 的官方推荐是每个 OSD 提供 1 GHz 的 CPU 性能。不幸的是,现实中情况并非如此简单。官方推荐没有提到的是,每个 I/O 需要一定的 CPU 性能;这并非一个静态数值。从这个角度考虑,这其实是有道理的:只有当有任务时,CPU 才会被使用。如果没有 I/O 操作,CPU 就不需要。而这个关系是按比例增加的:I/O 越多,所需的 CPU 就越多。官方的推荐值适用于基于旋转磁盘的 OSD。配备快速 SSD 的 OSD 节点往往会消耗远超该推荐值的 CPU。更复杂的是,CPU 的需求还会根据 I/O 大小的不同而变化,较大的 I/O 需要更多的 CPU。

如果 OSD 节点开始出现 CPU 资源紧张的情况,可能会导致 OSD 超时并从集群中被标记为下线,通常会在几秒钟后重新加入。持续的丢失与恢复会加剧对已有限的 CPU 资源的压力,进而导致连锁故障。

一个理想的目标是每个 I/O 约 1-10 MHz,分别对应 4 kb 至 4 MB 的 I/O。如同往常一样,在上线之前需要进行测试,以确认在正常和高负载 I/O 条件下,CPU 的需求是否得到了满足。此外,在 BlueStore 中使用压缩和校验和将会增加每个 I/O 所需的 CPU,升级自使用 filestore 的 Ceph 集群时,这一点需要在任何计算中加以考虑。擦除编码池的 CPU 消耗也会高于复制池。CPU 的使用量会随着擦除编码类型和配置文件的不同而变化,因此必须进行测试,以更好地了解需求。

另一个影响 Ceph 性能的关键 CPU 选择因素是核心的时钟速度。Ceph 中大部分 I/O 路径是单线程的,因此时钟速度更快的核心会更快地运行该代码路径,从而减少延迟。由于大多数 CPU 的热设计有限,通常随着核心数的增加,时钟速度会有所折衷。高核心数和高时钟速度的 CPU 往往价格较高,因此在选择最佳 CPU 时,了解你的 I/O 和延迟要求非常有帮助。

进行了一项小实验,旨在找出 CPU 时钟速度对写入延迟的影响。一个运行 Ceph 的 Linux 工作站通过用户空间调控器手动调整了其 CPU 时钟。以下结果清晰地显示了高时钟速度 CPU 的优势:

CPU MHz 4 KB 写入 I/O 平均延迟(微秒)
1600 797 1250
2000 815 1222
2400 1161 857
2800 1227 812
3300 1320 755
4300 1548 644

如果低延迟,特别是低写入延迟很重要,那么请选择时钟速度最高的 CPU,理想情况下至少要超过 3 GHz。这可能需要在只使用 SSD 的节点上做出一些妥协,涉及可用核心数,以及每个节点能够支持多少 SSD。对于具有 12 个旋转磁盘和 SSD 日志的节点,单插槽四核处理器是一个极好的选择,因为它们通常具有非常高的时钟速度,并且价格非常具有竞争力。

在低延迟不是特别重要的情况下——例如,在对象工作负载中——可以考虑选择具有良好核心数量和平衡时钟速度的入门级处理器。

另一个关于 CPU 和主板选择的考虑因素是插槽数量。在双插槽设计中,内存、磁盘控制器和网络接口卡(NIC)是在插槽之间共享的。当一个 CPU 需要从另一个 CPU 所在的资源中获取数据时,就需要一个插槽,这个插槽必须穿越两个 CPU 之间的互联总线。现代 CPU 具有高速互联,但这确实会引入性能损失,因此应该考虑是否可以实现单插槽设计。关于如何规避这些可能的性能损失,调优部分提供了一些选项。

磁盘

在选择用于构建 Ceph 集群的磁盘时,总是会有倾向选择最大容量磁盘的诱惑,因为从总体拥有成本的角度来看,数字看起来很好。然而,实际上这通常不是一个理想选择。尽管磁盘在过去 20 年里容量大幅增加,但它们的性能并没有显著提升。首先,你应该忽略任何顺序的 MB/s 数据,因为在企业级工作负载中,你永远看不到这样的数据;总会有某些因素使 I/O 模式变得足够非顺序,以至于它实际上可以被视为完全随机。其次,记住以下数据:

  • 7.2k 磁盘 = 70–80 4k IOPS

  • 10k 磁盘 = 120–150 4k IOPS

  • 15k 磁盘 = 应使用 SSD

一般而言,如果你正在设计一个将提供活跃工作负载而不是大量非活跃/归档存储的集群,那么应该根据所需的 IOPS 进行设计,而不是容量。如果你的集群主要包含旋转磁盘,并且目的是为活跃工作负载提供存储,那么应优先考虑增加小容量磁盘的数量,而不是使用大容量磁盘。随着 SSD 容量成本的下降,应该认真考虑在集群中使用 SSD,作为缓存层或甚至是完全的 SSD 集群。SSD 已经在除少数特殊工作负载外取代了 15k 磁盘;到本世纪末,10k 磁盘可能也会走上相同的道路。存储领域可能会变成“两马竞争”的局面,缓慢的大容量磁盘将承担大宗存储角色,而基于闪存的设备将承担活跃 I/O 角色。

还应考虑将 SSD 用作 Ceph 文件存储的日志,或在使用 BlueStore 时用于存储数据库和 WAL。当使用 SSD 日志时,文件存储性能显著提升,除非集群设计用于处理非常冷的数据,否则不建议使用 SSD 日志。

选择用于存储 BlueStore 数据库的 SSD 时,重要的是正确选择 SSD 的尺寸,以便大部分或理想情况下所有元数据都能存储在 SSD 上。官方建议,RocksDB 数据库的大小应约为 HDD 大小的 4%。在实践中,实际使用中很少见到这种水平的消耗,4% 只是一个保守估计。随着 10 TB 及更大容量的磁盘逐渐普及,为这样一个磁盘专门配备 400 GB 的 SSD 并不具备成本效益。如果 RocksDB 的大小超过了分配给 SSD 的空间,那么元数据将溢出到 HDD 上。因此,虽然 OSD 仍然可以运行,但需要从 HDD 中读取元数据的请求将比所有元数据都存储在 SSD 上时更慢。在用于 RBD 工作负载的集群的实际测试中,数据库的使用通常在 0.5% 区域。有关 RocksDB 中存储的数据及所需空间的更多细节,请参见本书的第三章,BlueStore

由于用于存储 BlueStore 数据库的 SSD 并不用于存储数据,因此它们的写入耐久性不像过去那样关键,当时 SSD 是与文件存储一起使用的。

还需要注意的是,默认的复制级别 3 意味着每个客户端写入 I/O 会在后端磁盘上生成至少三倍的 I/O。使用文件存储时,由于 Ceph 内部机制的原因,在大多数情况下,这个数字可能会超过六倍的写入放大效应。

虽然与传统的 RAID 阵列相比,Ceph 能够快速从故障磁盘中恢复,但这是因为 Ceph 在恢复过程中涉及到大量更多的磁盘。然而,更大的磁盘仍然存在挑战,特别是当您需要从受影响的多个磁盘中恢复节点故障时。在由十个 1TB 磁盘组成的集群中,每个磁盘 50%占用时,如果发生磁盘故障,剩余的磁盘将需要在它们之间恢复 500GB 的数据,大约每个磁盘需要恢复约 55GB。以平均 20MB/s 的恢复速度,预计恢复时间约为 45 分钟。一个有一百个 1TB 磁盘的集群仍然需要恢复 500GB 的数据,但这次任务由 99 个磁盘共享,每个磁盘需要恢复约 5GB;理论上,较大的集群从单个磁盘故障中恢复需要约 4 分钟。实际上,这些恢复时间会更长,因为有额外的机制增加了恢复时间。在较小的集群中,选择磁盘容量时,恢复时间应该是一个关键因素。

网络

网络是 Ceph 集群中一个关键但常被忽视的组件。设计不良的网络往往会导致多种问题,这些问题以奇特的方式显现,并导致令人困惑的故障排除过程。

10 G 需求

建议使用 10G 网络来构建 Ceph 集群;虽然 1G 网络可以工作,但延迟量几乎无法接受,并且将限制您可以部署的节点大小。在选择恢复时,还应考虑大量数据需要在集群中移动的情况。1G 网络不仅无法为此提供足够的性能,而且会影响正常的 I/O 流量。在最糟糕的情况下,这可能导致 OSD 超时,从而引起集群不稳定。

如前所述,10G 网络的主要好处之一是较低的延迟。通常,集群不会产生足够的流量来充分利用 10G 带宽;然而,无论集群负载如何,都将实现延迟改善。在本书调优部分中将了解到,延迟直接影响存储系统的性能,特别是在执行直接或同步 I/O 时。

近年来,下一代网络硬件已经推出,支持从 25G 到 100G 不等的速度。如果在部署 Ceph 集群时实施新的网络,强烈建议考虑部署这种下一代硬件。

如果你的 OSD 节点配备了双网卡,你应该仔细设计一个网络,使其能够同时活跃地进行传输和接收。将 10G 链路置于被动状态是浪费,而且在负载下有助于降低延迟。

网络设计

良好的网络设计是将 Ceph 集群投入使用的重要步骤。如果你的网络由其他团队管理,请确保他们参与到设计的所有阶段,因为现有网络往往并未设计用于满足 Ceph 的需求,这可能导致 Ceph 性能差,并影响现有系统。

推荐将每个 Ceph 节点通过冗余链路连接到两个独立的交换机,这样在交换机故障的情况下,Ceph 节点仍然可访问。如果可能,应该避免堆叠交换机,因为它们可能引入单点故障,并且在某些情况下,为了进行固件升级,两个交换机都需要下线。

如果你的 Ceph 集群仅包含在一组交换机中,那么可以跳过接下来的部分。

传统网络主要围绕北–南访问路径设计,在这种路径中,北部的客户端通过网络访问南部的服务器。如果一台服务器连接到一个接入交换机,而这个交换机需要与连接到另一个接入交换机的另一台服务器进行通信,那么流量将通过核心交换机进行路由。由于这种访问模式,接入层和汇聚层在设计时并没有考虑到处理大量服务器间流量的问题,这对于它们原本设计的环境是足够的。服务器间流量被称为东–西流量,随着应用程序变得更加紧密互联并需要多个其他服务器的数据,这种流量在现代数据中心中变得越来越普遍。

Ceph 产生大量东–西流量,这不仅来自集群内部的复制流量,还来自其他服务器访问 Ceph 存储。在大型环境中,传统的核心、汇聚和接入层设计可能无法应对,因为大量流量会通过核心交换机进行路由。可以购买更快速的交换机并添加更快或额外的上联链路;然而,根本问题是你试图在一个扩展网络设计上运行一个扩展存储系统。各层的布局如下图所示:

传统网络拓扑

在数据中心中,一种越来越受欢迎的设计是叶脊设计。这种方法完全摒弃了传统模型,用两层交换机——脊层和叶层——取而代之。核心概念是每个叶交换机连接到每个脊交换机,因此任何叶交换机与其他叶交换机之间最多只需一次跳跃。这提供了稳定的跳跃延迟和带宽。如下图所示:

一种叶脊拓扑结构

叶层是服务器连接的地方,通常由大量 10 G 端口和少数 40 G 或更高速的上联端口组成,用于连接脊层。

脊层通常不会直接连接到服务器,除非有某些特殊要求,它将仅作为所有叶交换机的汇聚点。脊层通常具有更高的端口速度,以减少从叶交换机出来的流量可能发生的任何竞争。

叶脊网络通常不再采用纯粹的第 2 层拓扑结构,其中第 2 层域终止在叶交换机上,且第 3 层路由在叶交换机和脊交换机之间进行。建议你使用动态路由协议,如 BGP 或 OSPF,来建立跨越整个网络的路由。与大型第 2 层网络相比,这带来了许多优势。生成树通常用于第 2 层网络以阻止交换环路,方法是阻塞上联链路。在使用 40 G 上联时,这样丢失的带宽非常可观。通过在第 3 层设计中使用动态路由协议,ECMP(等成本多路径)可以用于公平地分配数据到所有上联链路,以最大化可用带宽。在一个叶交换机通过 40 G 上联连接到两个脊交换机的例子中,整个拓扑中任何其他叶交换机的带宽都会达到 80 G,无论它位于哪里。

一些网络设计更进一步,将第 3 层边界推到服务器,通过在服务器上运行这些路由协议,使得 ECMP 可以用于简化服务器两个网卡的活动/活动使用方式。这被称为主机上的路由。

OSD 节点大小

在为 Ceph 设计节点时,一个常见的方法是选择一个大容量服务器,该服务器包含大量磁盘插槽。在某些场景下,这可能是一个不错的选择,但通常来说,Ceph 更倾向于使用较小的节点。为了决定每个节点应包含多少磁盘,你需要考虑多个因素,正如我们将在后续章节中描述的那样。

故障域

如果你的集群节点少于 10 个,那么将故障域作为主要关注点应该是你此时的重点。

在传统的扩展存储中,硬件预期要达到 100%的可靠性;所有组件都是冗余的,像系统板或磁盘 JBOD 这样的完整组件故障很可能会导致停机。因此,无法真正了解这样的故障如何影响系统的运行,只能寄希望于它不会发生!而在 Ceph 中,有一个基本假设,即你的基础设施某一部分(无论是磁盘、节点,甚至机架)完全故障应该被视为正常现象,并且不应该使你的集群不可用。

假设有两个 Ceph 集群,都是由 240 个磁盘组成。集群 A 由 20 个 12 磁盘节点组成,集群 B 由 4 个 60 磁盘节点组成。现在假设某种原因导致一个 Ceph OSD 节点下线。这可能是计划维护或意外故障,但该节点现在已下线,所有数据不可用。Ceph 设计上能够处理这种情况,并且如果需要,甚至可以在保持完整数据访问的同时恢复。

在集群 A 的情况下,我们现在已经失去了 5% 的磁盘,如果发生永久丢失,必须重建 72 TB 的数据。集群 B 失去了 25% 的磁盘,必须重建 360 TB 的数据。后者将严重影响集群性能,并且在数据重建期间,性能降级的时间可能持续好几天。

即使决定覆盖自动修复并将 Ceph 保持在降级状态,直到修复或对节点进行维护,在 4 x 60 磁盘的例子中,将一个节点下线也会导致集群的 I/O 性能下降 25%,这可能意味着客户端应用程序会受到影响。

很明显,在较小规模的集群中,这种非常大的高密度节点并不是一个好主意。如果你希望减少节点故障的影响,10 节点的 Ceph 集群可能是最小的规模,因此在 60 驱动器的 JBOD 情况下,至少需要一个以 PB 级别为单位的集群。

价格

想要选择大型、高密度节点的一个常被引用的原因是尝试降低硬件购买的成本。但这通常是一个错误的经济决策,因为高密度节点往往需要高端部件,而这些部件每 GB 的成本往往比低密度节点更高。

例如,一个 12 磁盘的 HDD 节点可能只需要一个四核处理器就能为 OSD 提供足够的 CPU 资源。而 60 托架的机箱可能需要双 10 核处理器或更强的处理器,这些处理器每 GHz 的成本要贵得多。你可能还需要更大的 DIMM,这些 DIMM 的成本也较高,甚至可能需要更多 10G 或更快的网络接口卡。

硬件的大部分成本将由 CPU、内存、网络和磁盘组成。正如我们所看到的,这些硬件资源的需求会随着磁盘数量和大小的增加而线性扩展。较大的节点可能具有优势的唯一因素是,它们需要更少的主板和电源,但这在整体成本中占的比重不大。

当查看仅使用 SSD 的集群时,SSD 的更高性能要求使用更强大的 CPU,并大大增加带宽需求。显然,不建议部署一个单节点 60 SSD 的集群,因为所需的 CPU 资源可能无法提供,或者成本过于昂贵。使用 1U 和 2U 服务器,配备 10 个或 24 个磁盘托架,可能会在成本、性能或容量之间找到一个平衡点,这取决于集群的使用场景。

电源

服务器可以配置为单个或双重冗余电源供应。传统的工作负载通常需要双重电源供应,以防止电源或电源馈电失败时发生停机。如果您的 Ceph 集群足够大,那么您可能可以考虑在 OSD 节点中使用单一 PSU,并让 Ceph 在发生电源故障时提供可用性。应考虑运行单一电源供应与在数据中心整个电源馈电故障时的最坏情况之间的利弊。

如果您的 Ceph 节点使用带写回缓存的 RAID 控制器,则应通过电池或闪存备份设备保护它们。在完全电源故障的情况下,缓存的内容将在电力恢复之前保持安全。如果 RAID 控制器的缓存以写穿模式运行,则不需要缓存备份。

如何规划成功的 Ceph 实施

以下是部署成功 Ceph 集群的一些通用规则:

  • 至少使用 10 G 网络

  • 进行研究并测试您希望使用的正确大小的硬件

  • 不要在 filestore 上使用 no barrier 挂载选项

  • 不要将池配置为大小为二或 min_size 为一

  • 不要使用消费级 SSD

  • 在没有电池保护的情况下,切勿使用写回模式的 RAID 控制器

  • 不要使用不理解的配置选项

  • 必须实施某种形式的变更管理

  • 必须进行电力丧失测试

  • 必须有一个约定的备份和恢复计划

理解您的需求以及它们与 Ceph 的关系

正如我们所讨论的,Ceph 并不总是适用于每个存储需求。希望本章为您提供了帮助,帮助您识别需求并将其与 Ceph 的能力相匹配。希望 Ceph 适合您的用例,您可以继续进行项目。

需要谨慎理解项目的要求,包括以下内容:

  • 知道项目的关键利益相关者是谁。他们很可能是能够详细说明如何使用 Ceph 的人。

  • 收集 Ceph 需要与之交互的系统的详细信息。如果发现,例如,预期使用不受支持的操作系统与 Ceph 一起使用,那么需要在早期阶段标出这一点。

定义目标,以便您可以衡量项目是否成功

每个项目都应有一系列目标,帮助判断项目是否成功。一些目标可能包括:

  • 它的成本不应超过 X 数量

  • 它应该提供 X IOPS 或 MB/s 的性能

  • 它应能承受某些故障场景

  • 它应该将存储的拥有成本降低 X

这些目标需要在项目生命周期中不断回顾,以确保项目保持在正确的轨道上。

加入 Ceph 社区

无论是通过加入 Ceph 邮件列表、IRC 频道,还是参加社区活动,成为 Ceph 社区的一部分都是强烈推荐的。你不仅可以在许多可能有相似使用场景的人之间运行提议的硬件和集群配置,而且如果你在遇到困难时,社区提供的支持和指导也非常出色。

成为社区的一员,你还将深入了解 Ceph 项目的开发过程,并在功能正式发布之前看到它们的形成。对于那些更具冒险精神的人,可以考虑积极参与项目。这可能包括在邮件列表中帮助他人、报告 Bug、更新文档,甚至提交代码改进。

选择硬件

本章的基础设施部分已经为你提供了 Ceph 硬件需求的良好理解,以及选择正确硬件的理论背景。Ceph 集群停机的第二大原因来自于不当的硬件选择,因此在设计阶段早期做出正确的选择至关重要。

如果可能,向你的硬件供应商询问是否有任何参考设计;这些设计通常经过 Red Hat 认证,可以减轻你确定硬件选择有效性的工作量。你还可以请 Red Hat 或你选择的 Ceph 支持供应商验证你的硬件,他们会有之前的经验,并能够解答你可能有的任何问题。

最后,如果你计划完全在内部部署并运行 Ceph 集群,而不依赖任何第三方的参与或支持,建议考虑联系 Ceph 社区。Ceph 用户的邮件列表由来自全球各地的不同背景的个人贡献。很有可能有人正在做类似的事情,他们能够为你提供有关硬件选择的建议。

培训你自己和你的团队使用 Ceph

与所有技术一样,Ceph 管理员接受一定的培训至关重要。一旦 Ceph 集群上线并成为业务依赖,缺乏经验的管理员将成为稳定性的风险。根据你对第三方支持的依赖程度,可能需要不同层次的培训,并且这也可能决定你是应该寻找培训课程,还是自学。

运行 PoC 来确定 Ceph 是否满足需求

应该部署一个概念验证PoC)集群,以测试设计并在进行大规模硬件采购之前尽早发现问题。这应该作为项目中的一个决策点;如果发现任何严重问题,不要害怕重新审视目标或重新开始设计。如果你有类似规格的现有硬件,使用它进行 PoC 测试是可以的,但目标应该是尽量使用与计划构建生产集群的硬件尽可能相似的设备,以便充分测试设计。

除了测试稳定性外,PoC 集群还应当用于预测你为项目设定的目标是否可能实现。尽管在 PoC 阶段可能很难直接复制工作负载的需求,但应该尽量使测试与预期的生产工作负载匹配。

PoC 阶段也是加深你对 Ceph 知识理解、练习日常操作并测试功能的好时机。这对后续工作会有帮助。你还应该借此机会尽可能地对 PoC 集群进行“折磨”。随机拔出磁盘、关闭节点电源以及断开网络连接。如果设计得当,Ceph 应该能够承受这些事件。现在进行这些测试将使你更有信心在更大规模下操作 Ceph,并且如果需要,也能更轻松地了解如何进行故障排除。

遵循最佳实践来部署你的集群

在部署集群时,你应该专注于理解过程,而不是单纯跟随示例。这将帮助你更好地了解组成 Ceph 的各种组件,如果在部署或操作过程中遇到错误,你也将能更好地解决问题。本书的下一章将详细介绍 Ceph 的部署,包括使用编排工具。

最初,建议使用操作系统和 Ceph 的默认选项。如果在部署和初步测试过程中出现任何问题,最好从已知状态开始。

使用副本的 RADOS 池应将其副本级别保持在默认的三副本,并将最小副本级别设为二副本。这分别对应池变量中的 sizemin_size。除非有充分的理解和理由来降低这些值的影响,否则不建议更改它们。副本大小决定了集群中将存储多少份数据副本,降低副本大小的效果在数据丢失保护方面应该是显而易见的。而 min_size 在数据丢失中的影响较少被理解,但却是常见的原因。擦除编码池应类似地进行配置,以确保有至少两个擦除编码块用于恢复。例如,k=4 m=2;这将提供与 size=3 副本池相同的耐用性,但可用容量是其两倍。

min_size 变量控制集群必须写入多少副本才能确认写入操作已回传给客户端。min_size2 意味着集群至少必须写入两份数据副本才能确认写入;这意味着,在严重退化的集群情况下,如果 PG 只剩下一个副本,写入操作将会被阻塞,直到 PG 恢复到有两个副本为止。正因如此,你可能会想将 min_size 降到 1,这样,在这种情况下,集群操作仍然可以继续,如果可用性比一致性更重要,那么这可以是一个有效的决策。然而,当 min_size1 时,数据可能只会写入一个 OSD,并且无法保证所需副本的数量很快就能满足。在此期间,任何额外的组件故障可能会导致数据丢失,尤其是在退化状态下写入的数据。总之,停机时间是坏事,而数据丢失通常更糟,而这两个设置可能会对数据丢失的概率产生最大的影响。

在极小的集群中,min_size 设置为 1 的唯一场景是永久使用这种设置,因为这些集群中没有足够的 OSD 可供设置更高的值,尽管在这种规模下,是否选择 Ceph 作为正确的存储平台仍有争议。

定义变更管理过程

Ceph 集群数据丢失和停机的最大原因通常是人为错误,无论是误执行了错误的命令,还是更改了配置选项,这些操作可能会产生意外后果。随着管理 Ceph 的团队人数增加,这些事件可能会变得更加频繁。减少人为错误导致服务中断或数据丢失的风险的一个好方法是实施某种形式的变更控制。下一章将更详细地讨论这一点。

创建备份和恢复计划

Ceph 具有高度冗余性,并且在设计得当的情况下,应该没有单点故障,能够抵抗多种硬件故障。然而,一百万分之一的情况确实会发生,正如我们所讨论的,人的错误也是不可预测的。在这两种情况下,Ceph 集群可能会进入不可用状态,或者发生数据丢失。在许多情况下,可能可以恢复部分或全部数据,并使集群恢复正常运行。

然而,在所有情况下,在将任何实时数据放入 Ceph 集群之前,应该讨论完整的备份和恢复计划。许多公司因长时间停机并丧失关键数据而倒闭,或者失去客户的信任。当外界得知不仅发生了长时间的停机,还丢失了关键数据时,往往会对公司产生致命打击。可能在讨论后,大家同意不需要备份和恢复计划,这也是可以的。只要风险和可能的结果都已经讨论并达成一致,这就是最重要的。

总结

本章中,你学习了成功规划和实施 Ceph 项目所需的所有步骤。你还了解了可用的硬件选择,它们如何与 Ceph 的需求相关,以及它们如何影响 Ceph 的性能和可靠性。最后,你还了解到确保 Ceph 集群健康运行所需的流程和程序的重要性。

本书接下来的章节将在本章所学知识的基础上,帮助你将其应用于实际,最终实现 Ceph 存储的部署、管理和使用。

问题

  1. RADOS 代表什么?

  2. CRUSH 代表什么?

  3. 消费级 SSD 和企业级 SSD 有什么区别?

  4. Ceph 更倾向于保证数据的一致性还是可用性?

  5. 自 Luminous 版本发布以来,默认的存储技术是什么?

  6. 谁创建了 Ceph?

  7. 在 Ceph 上创建的块设备叫什么?

  8. 实际存储数据的 Ceph 组件叫什么?

第二章:使用容器部署 Ceph

一旦你规划好 Ceph 项目,并准备好部署测试或生产集群,你需要考虑部署和维护的方式。本章将演示如何通过使用 Vagrant 快速部署用于测试和开发的测试环境。同时,也会解释为何你可能希望使用编排工具来部署 Ceph,而不是使用 Ceph 自带的工具。作为一个流行的编排工具,本书将使用 Ansible 展示如何快速可靠地部署 Ceph 集群,并说明使用它的优势。

在本章中,我们将学习以下内容:

  • 如何使用 Vagrant 和 VirtualBox 准备测试环境

  • Ceph 的部署和编排工具之间的区别

  • 相较于使用编排工具的优势

  • 如何安装和使用 Ansible

  • 如何配置 Ceph Ansible 模块

  • 如何使用 Vagrant 和 Ansible 部署测试集群

  • 如何管理 Ceph 配置的想法

  • Rook 项目是什么,它使 Ceph 操作员能做什么

  • 如何部署基本的 Kubernetes 集群

  • 如何使用 Rook 在 Kubernetes 上部署 Ceph

技术要求

为了能够运行本章后续描述的 Ceph 环境,确保计算机满足一定的要求是很重要的,以便为虚拟机提供足够的资源。具体要求如下:

  • 兼容 Vagrant 和 VirtualBox 的操作系统,包括 Linux、macOS 和 Windows

  • 2 核 CPU

  • 8 GB 内存

  • 在 BIOS 中启用虚拟化指令

使用 Vagrant 和 VirtualBox 准备你的环境

尽管可以在任何硬件或虚拟机上部署测试集群,但本书将使用 Vagrant 和 VirtualBox 的组合。这将快速提供虚拟机并确保环境的一致性。

VirtualBox 是一个免费的开源虚拟机管理程序,目前由 Oracle 开发;虽然其性能和功能可能不如高端虚拟机管理程序,但其轻量级的方式和多操作系统支持使其成为测试的理想选择。

Vagrant 可以帮助快速高效地创建可能包含多台机器的环境。它基于 box 的概念,box 是为虚拟机管理程序预定义的模板,而 Vagrantfile 则定义了要构建的环境。它支持多个虚拟机管理程序,并允许 Vagrantfile 在它们之间进行移植。

如何安装 VirtualBox

请参考 VirtualBox 网站,以获取适用于你操作系统的 VirtualBox 安装方法:www.virtualbox.org/wiki/Downloads

如何设置 Vagrant

请按照 Vagrant 网站上的安装说明,在您选择的操作系统上安装 Vagrant:www.vagrantup.com/downloads.html

  1. 为您的 Vagrant 项目创建一个新目录,例如 ceph-ansible

  2. 切换到此目录并运行以下命令:

vagrant plugin install vagrant-hostmanager      

vagrant box add bento/ubuntu-16.04

现在创建一个名为 Vagrantfile 的空文件,并将以下内容放入其中:

nodes = [ 
  { :hostname => 'ansible', :ip => '192.168.0.40', :box => 'xenial64' }, 
  { :hostname => 'mon1', :ip => '192.168.0.41', :box => 'xenial64' }, 
  { :hostname => 'mon2', :ip => '192.168.0.42', :box => 'xenial64' }, 
  { :hostname => 'mon3', :ip => '192.168.0.43', :box => 'xenial64' }, 
  { :hostname => 'osd1',  :ip => '192.168.0.51', :box => 'xenial64', :ram => 1024, :osd => 'yes' }, 
  { :hostname => 'osd2',  :ip => '192.168.0.52', :box => 'xenial64', :ram => 1024, :osd => 'yes' }, 
  { :hostname => 'osd3',  :ip => '192.168.0.53', :box => 'xenial64', :ram => 1024, :osd => 'yes' } 
] 

Vagrant.configure("2") do |config| 
  nodes.each do |node| 
    config.vm.define node[:hostname] do |nodeconfig| 
      nodeconfig.vm.box = "bento/ubuntu-16.04" 
      nodeconfig.vm.hostname = node[:hostname] 
      nodeconfig.vm.network :private_network, ip: node[:ip] 

      memory = node[:ram] ? node[:ram] : 512; 
      nodeconfig.vm.provider :virtualbox do |vb| 
        vb.customize [ 
          "modifyvm", :id, 
          "--memory", memory.to_s, 
        ] 
        if node[:osd] == "yes"         
          vb.customize [ "createhd", "--filename", "disk_osd-#{node[:hostname]}", "--size", "10000" ] 
          vb.customize [ "storageattach", :id, "--storagectl", "SATA Controller", "--port", 3, "--device", 0, "--type", "hdd", "--medium", "disk_osd-#{node[:hostname]}.vdi" ] 
        end 
      end 
    end 
    config.hostmanager.enabled = true 
    config.hostmanager.manage_guest = true 
  end 
end 

运行 vagrant up 来启动 Vagrantfile 中定义的虚拟机:

现在,让我们 ssh 连接到其中一台虚拟机:

vagrant ssh ansible

如果您在 Windows 上运行 vagrantssh 命令会提醒您需要使用您选择的 SSH 客户端,并提供相关的使用信息。Putty 是一个不错的 SSH 客户端建议。在 Linux 上,该命令会直接连接到虚拟机。

用户名和密码都是 vagrant。登录后,您应该看到 ansible vm 的 bash 提示符:

只需输入 exit 即可返回到您的主机。

恭喜您,您已经成功部署了三个 Ceph 监视器服务器,三个 Ceph OSD 服务器,以及一台 Ansible 服务器。Vagrantfile 还可以包含一些额外步骤,用于在服务器上执行命令进行配置,但现在我们先关闭这些服务器;当本章后续的示例需要时,我们可以重新启动它们:

vagrant destroy --force

Ceph-deploy

Ceph-deploy 是官方的 Ceph 集群部署工具。它的工作原理是通过一个管理员节点,通过免密码 SSH 访问所有 Ceph 集群中的机器;并且该管理员节点还保存一份 Ceph 配置文件。每次执行部署操作时,Ceph-deploy 工具会通过 SSH 连接到 Ceph 节点,执行必要的步骤。虽然 Ceph-deploy 工具是一个完全支持的方法,可以确保 Ceph 集群功能正常,但 Ceph 的后续管理并不会像预期的那样简便。

如果使用 Ceph-deploy 来管理大规模的 Ceph 集群,会增加很多管理开销。因此,建议将 Ceph-deploy 限制在测试或小规模的生产集群中,尽管正如您所看到的,编排工具可以快速部署 Ceph,并且可能更适合用于测试环境,尤其是当您需要不断构建新的 Ceph 集群时。

编排

让安装和管理 Ceph 更容易的一个解决方案是使用编排工具。现在有多种可用的工具,如 Puppet、Chef、Salt 和 Ansible,这些工具都有 Ceph 模块可用。如果你在环境中已经使用了编排工具,建议继续使用该工具。本书将使用 Ansible,原因如下:

  • 这是 Red Hat 偏爱的部署方法,Red Hat 是 Ceph 和 Ansible 项目的拥有者。

  • 它拥有一套成熟且完备的 Ceph 角色和剧本。

  • 如果你以前从未使用过编排工具,Ansible 往往更容易学习。

  • 它不需要设置中央服务器,这意味着演示更专注于使用工具,而不是安装工具。

所有工具遵循相同的原则,即提供主机清单和要在主机上执行的任务集。这些任务通常引用变量,允许在运行时定制任务。编排工具设计为按计划运行,这样,如果由于某种原因主机的状态或配置发生变化,在下一次运行时将会正确地恢复到预定状态。

使用编排工具的另一个优点是文档管理。虽然它们不能替代良好的文档,但它们清晰地描述了你的环境,包括角色和配置选项,这意味着你的环境开始自带文档功能。如果确保通过编排工具执行任何安装或更改,编排工具的配置文件将清晰地描述你环境的当前状态。如果将其与 Git 仓库等存储编排配置的工具结合使用,你就能拥有一个变更控制系统。本章稍后将更详细地介绍这一点。唯一的缺点是需要花费额外的时间来完成工具的初始设置和配置。

因此,通过使用编排工具,你不仅能够实现更快速、出错率更低的部署,还能免费获得文档和变更管理。如果你现在还没有意识到这一点,这正是你应该关注的内容。

Ansible

如前所述,Ansible 将是本书首选的编排工具,让我们更详细地了解它。

Ansible 是一个无代理的编排工具,用 Python 编写,通过 SSH 在远程节点上执行配置任务。它首次发布于 2012 年,已广泛采用,且因其易于采用和学习曲线低而著名。Red Hat 在 2015 年收购了 Ansible 公司,因此为部署 Ceph 提供了一个高度开发且紧密集成的方案。

在 Ansible 中,名为 playbooks 的文件用于描述要在指定的主机或主机组上执行的命令、操作和配置,并存储在 yaml 文件格式中。为了避免出现大型且难以管理的 playbooks,可以创建 Ansible 角色,以便让 playbook 包含单个任务,该任务可以执行与该角色相关的多个操作。

使用 SSH 连接到远程节点并执行 playbooks 意味着它非常轻量,不需要代理或中央服务器。

为了测试,Ansible 还可以与 Vagrant 很好地集成;可以将 Ansible playbook 作为 Vagrant 配置的一部分指定,并且 Vagrant 会自动生成一个来自已创建虚拟机的清单文件,并在服务器启动后运行 playbook。这允许通过一个简单的命令部署一个包含其操作系统的 Ceph 集群。

安装 Ansible

你将把之前创建的 Vagrant 环境恢复,并通过 SSH 登录到 Ansible 服务器。对于这个示例,只需要 ansiblemon1osd1

Vagrant up ansible mon1 osd1  
  • 添加 Ansible PPA:
$ sudo apt-add-repository ppa:ansible/ansible-2.6     

  • 更新 apt-get 源并安装 Ansible:
$ sudo apt-get update && sudo apt-get install ansible -y    

创建你的清单文件

Ansible 清单文件用于 Ansible 引用所有已知主机并指定它们所属的组。通过将组名放入方括号中来定义一个组;组可以通过使用 children 定义在其他组内进行嵌套。

在我们将主机添加到清单文件之前,我们首先需要配置远程节点以实现无密码 SSH,否则每次 Ansible 尝试连接到远程机器时,我们都需要输入密码,如下所示:

  1. 生成 SSH 密钥:
$ ssh-keygen

  1. 将密钥复制到远程主机:
$ ssh-copy-id mon1

这需要对每个主机重复执行。通常,你会在 Vagrant 配置阶段包括此步骤,但手动执行这些任务几次是有用的,这样你可以理解整个过程。

现在尝试通过 ssh mon1 登录到机器:

输入 exit 返回到 Ansible 虚拟机。现在让我们创建 Ansible 清单文件。编辑 /etc/ansible 中名为 hosts 的文件:

$ sudo nano /etc/ansible/hosts

创建三个组,分别命名为 osdsmgrsmons,然后创建一个第四个组,命名为 ceph。这个第四组将包含 osdsmons 作为子组。

在正确的组下输入主机列表:

[mons] 
mon1 
mon2 
mon3

[mgrs]
mon1 

[osds] 
osd1 
osd2 
osd3 

[ceph:children] 
mons 
osds
mgrs 

变量

大多数 playbooks 和角色将使用变量,这些变量可以通过多种方式覆盖。最简单的方法是创建 host_varsgroups_vars 文件夹中的文件;这些文件允许你基于主机或组成员身份分别覆盖变量。

创建一个/etc/ansible/group_vars目录。在group_vars中创建一个名为mons的文件,并将以下内容放入其中:

a_variable: "foo"

group_vars中创建一个名为osds的文件,并将以下内容放入其中:

a_variable: "bar"

变量遵循优先级顺序;你还可以创建一个all文件,该文件将应用于所有组。然而,如果同名的变量出现在一个更具体的匹配组中,它将覆盖该all文件中的变量。Ceph Ansible 模块利用这一点,使你能够拥有一组默认变量,并为特定角色指定不同的值。

测试

为了验证 Ansible 是否正常工作,并确保我们能够成功远程连接并执行命令,让我们使用 ping 命令与 Ansible 检查我们的一个主机。注意:这与网络 ping 不同;Ansible 的 ping 命令确认它可以通过 SSH 通信并远程执行命令:

$ ansible mon1 -m ping  

很棒,成功了。现在让我们运行一个简单的远程命令来展示 Ansible 的功能。以下命令将检索指定远程节点当前运行的内核版本:

$ ansible mon1 -a 'uname -r'  

一个非常简单的 playbook

为了展示 playbook 是如何工作的,以下示例将展示一个小的 playbook,它也使用了我们之前配置的变量:

- hosts: mon1 osd1
 tasks:
 - name: Echo Variables
 debug: msg="I am a {{ a_variable }}"

然后运行 playbook。请注意,运行 playbook 的命令与运行临时 Ansible 命令不同:

$ ansible-playbook /etc/ansible/playbook.yml

输出显示了在mon1osd1上执行的 playbook,它们属于组的成员,这些组是父组 Ceph 的子组。同时注意输出中两个服务器的不同之处,因为它们会根据你之前在group_vars目录中设置的变量来处理。

最后,最后几行显示了 playbook 运行的整体状态。现在你可以再次销毁你的Vagrant环境,为下一部分做准备:

Vagrant destroy --force

这就是 Ansible 介绍的结束,但这并不意味着它是一本完整的指南。建议在将其用于生产环境之前,先深入了解其他资源,以获得对 Ansible 的更全面的理解。

添加 Ceph Ansible 模块

我们可以使用 Git 来克隆 Ceph Ansible 仓库,如下所示:

git clone https://github.com/ceph/ceph-ansible.git
git checkout stable-3.2 sudo cp -a ceph-ansible/* /etc/ansible/

我们还需要安装一些ceph-ansible所需要的额外软件包:

sudo apt-get install python-pip

sudo pip install notario netaddr

让我们还探讨一下 Git 仓库中的一些关键文件夹:

  • group_vars:我们已经介绍了这里存放的内容,并将在稍后更详细地探讨可能的配置选项

  • infrastructure-playbooks:此目录包含预先编写的 playbook,用于执行一些标准任务,例如部署集群或向现有集群中添加 OSD。playbook 顶部的注释很好地说明了它们的作用。

  • roles:此目录包含构成 Ceph Ansible 模块的所有角色。你会看到每个 Ceph 组件都有一个角色;这些角色通过 playbook 被调用,以便安装、配置和维护 Ceph。

为了能够使用 Ansible 部署 Ceph 集群,需要在 group_vars 目录中设置若干关键变量。以下变量是必需设置的;另外,建议你修改它们的默认值。对于其余变量,建议你阅读变量文件中的注释。关键的全局变量包括以下内容:

 #mon_group_name: mons
    #osd_group_name: osds
    #rgw_group_name: rgws
    #mds_group_name: mdss
    #nfs_group_name: nfss
    ...
    #iscsi_group_name: iscsigws 

这些控制模块使用什么组名称来识别 Ceph 主机类型。如果你将在更广泛的环境中使用 Ansible,建议在组名前加上 ceph-,以明确这些组与 Ceph 相关:

#ceph_origin: 'upstream' # or 'distro' or 'local'  

使用 'upstream' 设置来使用 Ceph 团队生成的包,或使用 distro 设置来使用分发版维护者生成的包。如果你希望能够独立于你的分发版升级 Ceph,建议使用前者:

#fsid: "{{ cluster_uuid.stdout }}"
#generate_fsid: true  

默认情况下,fsid 会为你的集群生成并存储在一个文件中,以便以后可以再次引用。除非你希望控制 fsid 或将 fsid 硬编码到组变量文件中,否则你不需要修改它:

#monitor_interface: interface
#monitor_address: 0.0.0.0  

应指定上述命令之一。如果你在 group_vars 中使用了变量,那么你可能希望使用 monitor_interface,这是 Linux 中的接口名称,通常在所有 mons 中是相同的。否则,如果你在 host_vars 中指定了 monitor_address,你可以指定接口的 IP,显然,在三个或更多 mons 中它们会有所不同:

#ceph_conf_overrides: {}  

并非每个 Ceph 变量都由 Ansible 直接管理,但提供了上述变量,以便你可以将任何额外的变量传递给 ceph.conf 文件及其对应的部分。以下是如何实现的示例(请注意缩进):

 ceph_conf_overrides:
      global:
        variable1: value
      mon:
        variable2: value
      osd:
        variable3: value 

OSD 变量文件中的关键变量如下:

 #copy_admin_key: false 

如果你希望能够从 OSD 节点而不仅仅是从监视节点管理你的集群,将此设置为 true,这将把管理员密钥复制到你的 OSD 节点:

 #devices: []
 #osd_auto_discovery: false
 #journal_collocation: false
 #raw_multi_journal: false
 #raw_journal_devices: [] 

这些可能是整个 Ansible 配置中最关键的一组变量。它们控制哪些磁盘被用作 OSD 以及日志的位置。你可以手动指定你希望用作 OSD 的设备,或者使用自动发现。本书中的示例使用的是静态设备配置。

journal_collocation 变量设置是否希望将日志存储在与 OSD 数据相同的磁盘上;将为其创建一个单独的分区。

raw_journal_devices允许你指定希望用作日志的设备。通常,一个单独的 SSD 会作为多个 OSD 的日志;在这种情况下,启用raw_multi_journal,并简单地多次指定日志设备;如果你希望 Ansible 指示 ceph-disk 为你创建它们,则不需要指定分区号。

这些是你应该考虑的主要变量;建议你阅读变量文件中的注释,查看是否有其他变量需要根据你的环境进行修改。

使用 Ansible 部署测试集群

网上有多个示例,包含完全配置的Vagrantfile和相关的 Ansible 剧本;这允许你只用一个命令启动一个完全功能的 Ceph 环境。虽然这非常方便,但它并没有帮助你学习如何正确配置和使用 Ceph 的 Ansible 模块,就像你在生产环境中部署 Ceph 集群时那样。因此,本书将从头开始指导你配置 Ansible,即使它运行在 Vagrant 配置的服务器上。需要特别注意的是,像 Ceph 本身一样,Ansible 剧本也在不断变化,因此建议你查看ceph-ansible文档以了解是否有任何重大变更。

到此为止,你的 Vagrant 环境应该已经启动,并且 Ansible 应该能够连接到所有六个 Ceph 服务器。你还应该有一个 Ceph Ansible 模块的克隆副本。

创建一个名为/etc/ansible/group_vars/ceph的文件:

ceph_origin: 'repository'
ceph_repository: 'community'
ceph_mirror: http://download.ceph.com
ceph_stable: true # use ceph stable branch
ceph_stable_key: https://download.ceph.com/keys/release.asc
ceph_stable_release: mimic # ceph stable release
ceph_stable_repo: "{{ ceph_mirror }}/debian-{{ ceph_stable_release }}"
monitor_interface: enp0s8 #Check ifconfig
public_network: 192.168.0.0/24

创建一个名为/etc/ansible/group_vars/osds的文件:

osd_scenario: lvm
lvm_volumes:
- data: /dev/sdb

创建一个fetch文件夹,并将所有者更改为vagrant用户:

sudo mkdir /etc/ansible/fetch
sudo chown vagrant /etc/ansible/fetch

运行 Ceph 集群部署剧本:

cd /etc/ansible
sudo mv site.yml.sample site.yml
ansible-playbook -K site.yml

K参数告诉 Ansible 应该询问你输入sudo密码。现在请放松并观看 Ansible 部署你的集群:

一旦完成,并且假设 Ansible 没有错误地完成,ssh进入mon1并运行:

vagrant@mon1:~$ sudo ceph -s

这就是通过 Ansible 部署一个完全功能的 Ceph 集群的全部过程。

如果你想能够停止 Vagrant Ceph 集群而不丢失到目前为止的工作,可以运行以下命令:

vagrant suspend

要暂停所有虚拟机的当前状态,请运行以下命令:

vagrant resume

这将启动虚拟机;它们将恢复到你离开时的状态。

变更与配置管理

如果你通过 Ansible 等编排工具部署基础设施,管理 Ansible playbook 就变得很重要。正如我们所看到的,Ansible 允许你快速部署初始的 Ceph 集群,也能在后续进行配置更新。必须认识到,如果部署了错误的配置或操作,这种强大的能力也可能产生毁灭性的影响。通过实施某种形式的配置管理,Ceph 管理员将能够在运行 Ansible playbook 之前清晰地看到已做出的更改。

一种推荐的方法是将 Ceph 的 Ansible 配置存储在 Git 仓库中;这样可以跟踪更改,并通过监控 Git 提交或强制用户向主分支提交合并请求来实现某种形式的变更控制。

Ceph 容器化

我们之前已经看到,通过使用 Ansible 等编排工具,我们可以减少部署、管理和维护 Ceph 集群所需的工作量。我们还看到这些工具如何帮助你发现可用的硬件资源,并将 Ceph 部署到这些资源上。

然而,使用 Ansible 配置裸金属服务器仍然会导致非常静态的部署,可能不太适合今天更动态的工作负载。设计 Ansible playbook 时还需要考虑多个不同的 Linux 发行版,以及可能在不同版本之间发生的任何变化;systemd 就是一个很好的例子。此外,许多编排工具的开发需要定制化,以便处理发现、部署和管理 Ceph。这是 Ceph 开发人员思考过的一个常见主题;通过使用 Linux 容器及其相关的编排平台,他们希望改善 Ceph 的部署体验。

一种被选为首选方案的方法是与一个名为 Rook 的项目合作。Rook 与容器管理平台 Kubernetes 配合使用,自动化 Ceph 存储的部署、配置和使用。如果你列出一个自定义 Ceph 编排和管理框架所需实现的需求和功能,你很可能会设计出一个与 Kubernetes 类似的框架。所以,在已经成熟的 Kubernetes 项目上构建功能是合乎逻辑的,而 Rook 正是这样做的。

在容器中运行 Ceph 的一个主要好处是它允许将服务部署在相同的硬件上。传统上,在 Ceph 集群中,Ceph 监视器需要运行在专用硬件上;而使用容器时,这一要求被去除了。对于较小的集群,这可以在运行和购买服务器的成本上节省大量开支。如果资源允许,其他基于容器的工作负载也可以在 Ceph 硬件上运行,从而进一步提高硬件采购的投资回报率。使用 Docker 容器可以预留所需的硬件资源,以避免不同工作负载之间互相影响。

为了更好地理解这两种技术如何与 Ceph 配合使用,我们首先需要更详细地了解 Kubernetes 和容器本身。

容器

尽管容器技术作为一种新兴技术已经出现不久,但将一组进程相互隔离的原则已经存在很长时间。当前的技术增强了隔离的完整性。以往的技术可能仅仅隔离了文件系统的某些部分,而最新的容器技术则还隔离了操作系统的多个区域,并且还可以为硬件资源提供配额。尤其是 Docker 技术,它已成为讨论容器时最受欢迎的技术,以至于这两个词经常被交替使用。容器一词描述了一种执行操作系统级虚拟化的技术。Docker 是一个控制主要是 Linux 特性的软体产品,例如控制组(cgroups)和命名空间(namespaces),用于隔离一组 Linux 进程。

需要注意的是,与 VMware、Hyper-V 和 KVM 等完整的虚拟化解决方案不同,后者提供虚拟化的硬件并需要一个单独的操作系统实例,容器利用宿主机的操作系统。虚拟机的完整操作系统需求可能导致存储空间浪费数十 GB 用于操作系统安装,并可能浪费数 GB 的 RAM。而容器通常只消耗以 MB 为单位的存储和 RAM 开销,这意味着与完全虚拟化技术相比,更多的容器可以被部署在相同的硬件上。

由于容器可以完全从主机系统配置,因此它们的编排也更加容易;再加上它们可以在毫秒内启动的能力,这意味着它们非常适合动态变化的环境。特别是在 DevOps 环境中,当基础设施和应用程序之间的界限开始变得模糊时,它们变得非常受欢迎。基础设施管理往往比应用程序开发的速度慢,这意味着在敏捷开发环境中,基础设施团队通常总是在追赶进度。有了 DevOps 和容器,基础设施团队可以集中精力提供一个坚实的基础,而开发人员则可以打包他们的应用程序以及运行所需的操作系统和中间件。

Kubernetes

能够在几秒钟内快速高效地启动数十个容器很快让你意识到,如果虚拟机的泛滥已经够糟糕了,那么使用容器,问题很容易会变得更加严重。随着 Docker 在现代 IT 基础设施中的出现,管理所有这些容器的需求应运而生。这就是 Kubernetes 的出现。

尽管有多种容器编排技术可供选择,但 Kubernetes 已经取得了广泛的成功,并且作为 Rook 构建的产品,本书将重点介绍它。

Kubernetes 是一个用于自动化部署、扩展和管理容器化应用程序的开源容器编排系统。它最初是在 Google 开发用于运行其内部系统的,但后来已开源并且其受欢迎程度不断增长。

尽管本章将涵盖部署一个非常简单的 Kubernetes 集群来部署一个带有 Rook 的 Ceph 集群,但它并不是一个完整的教程,建议读者查找其他资源以了解更多关于 Kubernetes 的信息。

使用 Rook 部署 Ceph 集群

要使用 Vagrant 创建三个虚拟机来运行 Kubernetes 集群,以部署带有 Rook 的 Ceph 集群。

你将完成的第一个任务是通过 Vagrant 部署三个虚拟机。如果你已经按照本章节开始时的步骤,并使用 Vagrant 构建了 Ansible 环境,那么你应该拥有部署 Kubernetes 集群所需的一切。

以下是用于启动三个虚拟机的Vagrantfile;和之前一样,将内容放入名为Vagrantfile的新目录中,然后运行vagrant up

nodes = [
  { :hostname => 'kube1',  :ip => '192.168.0.51', :box => 'xenial64', :ram => 2048, :osd => 'yes' },
  { :hostname => 'kube2',  :ip => '192.168.0.52', :box => 'xenial64', :ram => 2048, :osd => 'yes' },
  { :hostname => 'kube3',  :ip => '192.168.0.53', :box => 'xenial64', :ram => 2048, :osd => 'yes' }
]

Vagrant.configure("2") do |config|
  nodes.each do |node|
    config.vm.define node[:hostname] do |nodeconfig|
      nodeconfig.vm.box = "bento/ubuntu-16.04"
      nodeconfig.vm.hostname = node[:hostname]
      nodeconfig.vm.network :private_network, ip: node[:ip]

      memory = node[:ram] ? node[:ram] : 4096;
      nodeconfig.vm.provider :virtualbox do |vb|
        vb.customize [
          "modifyvm", :id,
          "--memory", memory.to_s,
        ]
        if node[:osd] == "yes"        
          vb.customize [ "createhd", "--filename", "disk_osd-#{node[:hostname]}", "--size", "10000" ]
          vb.customize [ "storageattach", :id, "--storagectl", "SATA Controller", "--port", 3, "--device", 0, "--type", "hdd", "--medium", "disk_osd-#{node[:hostname]}.vdi" ]
        end
      end
    end
    config.hostmanager.enabled = true
    config.hostmanager.manage_guest = true
  end
end

SSH 到第一个虚拟机Kube1

将内核更新到更新版本;这对于 Rook 中某些 Ceph 功能的正常运行是必需的:

安装 Docker,如下所示:

sudo apt-get install docker.io

启用并启动 Docker 服务,如下所示:

sudo systemctl start docker
sudo systemctl enable docker

通过编辑 /etc/fstab 并注释掉交换分区行,禁用未来启动时的交换分区:

现在也禁用交换分区,如下所示:

sudo swapoff -a

添加 Kubernetes 仓库,如下所示:

sudo add-apt-repository “deb http://apt.kubernetes.io/ kubernetes-xenial main”

添加 Kubernetes GPG 密钥,如下所示:

sudo curl -s https://packages.cloud.google.com/apt/doc/apt-key.gpg | sudo apt-key add

安装 Kubernetes,如下所示:

sudo apt-get update && sudo apt-get install -y kubeadm kubelet kubectl

kube2kube3 虚拟机上重复 Docker 和 Kubernetes 的安装步骤。

一旦所有虚拟机都成功安装了 Docker 和 Kubernetes,我们就可以初始化 Kubernetes 集群:

sudo kubeadm init --apiserver-advertise-address=192.168.0.51 --pod-network-cidr=10.1.0.0/16 --ignore-preflight-errors=NumCPU

过程结束时会输出一个命令字符串,请记下它,因为它用于将额外的节点加入到集群中。示例如下:

现在我们已经在所有节点上安装了 Docker 和 Kubernetes,并初始化了主节点,接下来让我们将剩下的两个节点添加到集群中。记得之前让你记下的那串文本吗?现在我们可以在剩余的两个节点上运行它:

sudo kubeadm join 192.168.0.51:6443 --token c68o8u.92pvgestk26za6md --discovery-token-ca-cert-hash sha256:3954fad0089dcf72d0d828b440888b6e97465f783bde403868f098af67e8f073

mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config

现在我们可以安装一些额外的容器网络支持。Flannel,一个简单的 Kubernetes 网络插件,使用 VXLAN 作为覆盖层来实现容器之间的网络连接。首先从 GitHub 下载 yaml 文件:

wget https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml

在安装 Flannel 网络组件之前,我们需要对 YAML 规范文件进行一些更改:

nano kube-flannel.yml

请不要使用制表符缩进,要使用空格。

我们需要找到以下几行并进行必要的更改,如下所示:

  • 第 76 行:"Network": "10.1.0.0/16"

  • 第 126 行:- --iface=eth1

现在我们可以发出相关的 Kubernetes 命令来应用规范文件并安装 Flannel 网络:

kubectl apply -f kube-flannel.yml

网络安装完成后,我们可以确认一切正常,并且我们的 Kubernetes 工作节点已准备好运行工作负载:

$ kubectl get nodes

现在让我们检查所有支持 Kubernetes 内部服务的容器是否都在运行:

$ kubectl get pods --all-namespaces –o wide

请注意,我们在前一步安装的容器网络服务(Flannel)已经自动部署到了所有三个节点上。此时,我们已经拥有了一个完全功能正常的 Kubernetes 集群,准备运行任何我们希望在其上运行的容器。

现在我们可以将 Rook 部署到 Kubernetes 集群中。首先,从 GitHub 克隆 Rook 项目:

$ git clone https://github.com/rook/rook.git

切换到 examples 目录,如下所示:

$ cd rook/cluster/examples/kubernetes/ceph/

最后,通过运行以下两个命令创建 Rook 驱动的 Ceph 集群:

$ kubectl create -f operator.yaml

$ kubectl create -f cluster.yaml

为了确认我们的 Rook 集群现在正在工作,让我们检查 Rook 命名空间下的运行容器:

$ kubectl get pods --all-namespaces -o wide

您将看到 Rook 部署了一些 mons,并且还启动了一些发现容器。这些发现容器运行一个发现脚本来定位附加到 Kubernetes 物理主机的存储设备。一旦发现过程首次完成,Kubernetes 将运行一个一次性容器,通过格式化磁盘并将 OSD 添加到集群中来准备 OSD。如果您等待几分钟并重新运行 get pods 命令,您应该能看到 Rook 已经检测到连接到 kube2kube3 的两个磁盘,并为它们创建了 osd 容器:

为了与集群交互,让我们部署 toolbox 容器;这是一个包含 Ceph 安装和必要集群密钥的简单容器:

$ kubectl create -f toolbox.yaml

现在在 toolbox 容器中执行 bash

kubectl -n rook-ceph exec -it $(kubectl -n rook-ceph get pod -l "app=rook-ceph-tools" -o jsonpath='{.items[0].metadata.name}') bash

这将为您提供一个在 Ceph toolbox 容器中运行的 root shell,在这里我们可以通过运行 ceph –s 来检查 Ceph 集群的状态,并通过 ceph osd tree 查看当前的 OSD:

您会注意到,虽然我们构建了三个虚拟机,Rook 仅在 kube2kube3 上部署了 OSD。这是因为默认情况下 Kubernetes 不会将容器调度到主节点上;在生产集群中,这是期望的行为,但为了测试,我们可以移除这一限制。

退出回到主 Kubernetes 节点,并运行以下命令:

kubectl taint nodes $(hostname) node-role.kubernetes.io/master:NoSchedule-

您会注意到 Kubernetes 将会在 kube1 上部署一些新容器,但不会部署新的 OSD;这是由于目前的限制,rook-ceph-operator 组件只在首次启动时部署新的 OSD。为了检测新可用的磁盘并将它们准备为 OSD,需要删除 rook-ceph-operator 容器。

运行以下命令,但将容器名称替换为 get pods 命令中列出的名称:

kubectl -n rook-ceph-system delete pods rook-ceph-operator-7dd46f4549-68tnk

Kubernetes 现在将自动启动一个新的 rook-ceph-operator 容器,并通过此操作启动新 osd 的部署;可以通过再次查看正在运行的容器列表来确认这一点:

您可以看到 kube1 运行了一个 rook-discover 容器,一个 rook-ceph-osd-prepare 容器,最后是一个 rook-ceph-osd 容器,在这个例子中是 osd 编号 2

我们还可以通过使用我们的工具箱容器来检查,新的osd是否已经成功加入集群:

现在,Rook 已经部署了我们的完整测试 Ceph 集群,我们需要利用它并创建一些 RADOS 池,同时通过客户端容器使用一些存储。为了演示这个过程,我们将部署一个 CephFS 文件系统。

在我们直接开始部署文件系统之前,先看看我们将要部署的示例yaml文件。确保你仍然在~/rook/cluster/examples/kubernetes/ceph目录下,并使用文本编辑器查看filesystem.yaml文件:

你可以看到文件内容描述了将要创建的 RADOS 池以及文件系统所需的 MDS 实例。在这个例子中,将部署三个池,其中两个为复制池,一个为纠删码池用于实际数据存储。将部署两个 MDS 服务器,一个作为活动实例,另一个作为备用回放实例。

退出文本编辑器,现在部署yaml文件中的 CephFS 配置:

$ kubectl create -f filesystem.yaml

现在让我们回到工具箱容器,检查状态,看看已经创建了什么:

我们可以看到已经创建了两个池,一个用于 CephFS 元数据,另一个用于实际存储在 CephFS 文件系统中的数据。

为了举例说明 Rook 如何被应用容器使用,我们现在将部署一个小型的 NGINX Web 服务器容器,将其 HTML 内容存储在 CephFS 文件系统上。

将以下内容放入名为nginx.yaml的文件中:

apiVersion: v1
kind: Pod
metadata:
 name: nginx
spec:
 containers:
 - name: nginx
 image: nginx:1.7.9
 ports:
 - containerPort: 80
 volumeMounts:
 - name: www
 mountPath: /usr/share/nginx/html
 volumes:
 - name: www
 flexVolume:
 driver: ceph.rook.io/rook
 fsType: ceph
 options:
 fsName: myfs
 clusterNamespace: rook-ceph

现在使用kubectl命令来创建pod/nginx

稍等一会儿,容器将启动并进入运行状态;使用get pods命令来验证这一点:

我们现在可以在这个容器上启动一个快速的 Bash shell 来确认 CephFS 挂载是否成功:

$ kubectl exec -it nginx bash

我们可以看到 CephFS 文件系统已被挂载到/usr/share/nginx/html。这一切都在没有在容器中安装任何 Ceph 组件、没有任何配置或密钥环复制的情况下完成。Rook 在后台处理了所有这些;一旦理解并欣赏这一点,Rook 的真正强大之处便显现出来。如果这个简单的 NGINX pod 示例扩展成一个基于负载自动扩展的服务,自动启动多个容器,那么 Rook 和 Ceph 所提供的灵活性,即无需额外配置便能自动在 web 集群中呈现相同的共享存储,显得尤为有用。

总结

在本章中,你学习了 Ceph 的各种部署方法及其之间的差异。你现在也会对 Ansible 的工作原理以及如何用它部署 Ceph 集群有一个基本的理解。此时,建议你继续研究并实践使用 Ansible 部署和配置 Ceph,以便你能够自信地在生产环境中使用它。本书的其余部分也将假设你已经完全理解了本章的内容,以便能够操作 Ceph 的配置。

你还学习了在 Kubernetes 平台上运行的容器中部署 Ceph 的一些令人兴奋的新进展。尽管 Rook 项目仍处于开发的初期阶段,但显然它已经是一个非常强大的工具,能够让 Ceph 发挥其最佳功能,同时简化所需的部署和管理。随着 Kubernetes 成为推荐的容器管理平台并持续获得成功,将 Ceph 与 Rook 集成将会是技术上的完美匹配。

强烈建议读者继续深入学习 Kubernetes,因为本章仅仅触及了它提供的功能表面。行业内的强烈迹象表明,容器化将成为部署和管理应用程序的主要技术,因此,理解 Kubernetes 以及 Ceph 如何与 Rook 集成是非常有必要的。

问题

  1. 哪个软件可以用来快速部署测试环境?

  2. 是否应该使用 vagrant 来部署生产环境?

  3. 哪个项目使得可以在 Kubernetes 上部署 Ceph?

  4. 什么是 Docker?

  5. 用于执行一系列命令的 Ansible 文件叫什么?

第三章:BlueStore

在本章中,你将了解 BlueStore,这个新对象存储设计用于取代现有的 filestore。它提高的性能和增强的功能集旨在使 Ceph 能够继续发展,并为未来提供一个具有弹性和高性能的分布式存储系统。自 Luminous 版本以来,BlueStore 已成为推荐并默认的对象存储,在创建新的 OSD 时使用。本章将介绍 BlueStore 的工作原理,以及它为何比 Filestore 更适合 Ceph 的需求。然后,通过逐步教程,你将学习如何将 Ceph 集群升级到 BlueStore。

在本章中,你将学习以下内容:

  • 什么是 BlueStore?

  • Filestore 的限制

  • BlueStore 克服了哪些问题

  • BlueStore 的组成部分及其工作原理

  • ceph-volume 介绍

  • 如何部署 BlueStore OSD

  • 从 filestore 升级到 BlueStore 的方法

什么是 BlueStore?

BlueStore 是一个 Ceph 对象存储,主要旨在解决 filestore 的限制问题。在 Luminous 版本发布之前,filestore 是默认的对象存储。最初,一个名为 NewStore 的新对象存储正在开发中,以取代 filestore。NewStore 是 RocksDB 和标准的 可移植操作系统接口POSIX)文件系统的结合,RocksDB 用于存储元数据,而 POSIX 文件系统用于存储实际的对象。然而,很快就显现出,使用 POSIX 文件系统会带来高开销和限制,这也是最初想要摆脱 filestore 的关键原因之一。

因此,BlueStore 应运而生。通过将原始块设备与 RocksDB 结合,解决了困扰 NewStore 的多个问题。BlueStore 这个名字反映了 Block 和 NewStore 这两个词的结合:

Block + NewStore = BlewStore = BlueStore

BlueStore 旨在消除与 filestore 相关的双重写入惩罚,并提高同一硬件上可获得的性能。此外,通过对对象在磁盘上存储方式的更多控制,新的功能(如校验和和压缩)可以得以实现。

为什么需要它?

Ceph 之前的对象存储 filestore 存在许多限制,这些限制已经开始限制 Ceph 能够运行的规模,以及它能够提供的功能。以下是 BlueStore 需要出现的一些主要原因。

Ceph 的需求

在 Ceph 中,一个对象除了其数据外,还包含一些相关的元数据,确保数据和元数据的原子性更新至关重要。如果元数据或数据单独更新,而没有同步更新,Ceph 的一致性模型就会面临风险。为了确保这些更新是原子性的,它们需要在一个事务中完成。

Filestore 限制

Filestore 最初是作为对象存储设计的,以便开发者可以在本地机器上测试 Ceph。由于其稳定性,它迅速成为标准的对象存储,并且在全球的生产集群中得到广泛使用。

最初,filestore 的设计理念是,未来的B 树文件系统btrfs)将提供事务支持,从而允许 Ceph 将原子性要求委托给 btrfs。事务将允许应用程序将一系列请求发送到 btrfs,并且只有在所有请求都被提交到稳定存储后,才会收到确认。没有事务支持的情况下,如果 Ceph 写操作在中途中断,那么数据或元数据可能会丢失,或者它们之间会出现不一致。

不幸的是,依赖 btrfs 来解决这些问题最终证明是一个错误的希望,并且发现了多个限制。虽然 btrfs 仍然可以与 filestore 一起使用,但存在许多已知问题,可能会影响 Ceph 的稳定性。

最终,事实证明 XFS 是与 filestore 一起使用的最佳选择,但 XFS 有一个主要的限制,它不支持事务,这意味着 Ceph 无法保证其写操作的原子性。解决这个问题的方法是写前日志。所有写操作,包括数据和元数据,都会首先写入日志,该日志位于原始块设备上。一旦包含数据和元数据的文件系统确认所有数据已经安全刷新到磁盘,日志条目就可以被刷新。这一解决方案的一个有益副作用是,当使用 SSD 来存放旋转磁盘的日志时,它就像一个写回缓存,降低了写入的延迟,达到了 SSD 的速度;然而,如果 filestore 日志与数据分区位于同一个存储设备上,那么吞吐量至少会减半。

在旋转磁盘 OSD 的情况下,这可能导致非常差的性能,因为磁头会不断在磁盘的两个区域之间移动,即使是顺序操作也是如此。尽管基于 SSD 的 OSD 上的 filestore 几乎不会遭受相同的性能损失,但由于 filestore 日志的存在,它们的吞吐量仍然有效减半,因为需要写入的数据量是原来的两倍。无论是哪种情况,性能损失都是非常不希望出现的,并且在闪存驱动器的情况下,这会加速设备的磨损,要求使用更昂贵的闪存版本,称为写入耐久性闪存。下图展示了 filestore 及其日志如何与块设备交互。你可以看到,所有数据操作都必须通过 filestore 日志和文件系统日志:

在文件存储(filestore)中,开发人员试图控制底层 POSIX 文件系统的行为,以使其按照 Ceph 所需的方式执行,这带来了额外的挑战。多年来,文件系统开发人员进行了大量的工作,试图使文件系统变得智能,并预测应用程序如何提交 I/O 请求。对于 Ceph 来说,这些优化中的许多与它试图指示文件系统执行的操作相冲突,从而需要更多的解决方法和复杂性。

对象元数据存储在文件系统属性的组合中,这些属性被称为扩展属性XATTRs),并存储在LevelDB键值存储中,该存储也位于 OSD 磁盘上。由于 RocksDB 当时不可用,LevelDB 更符合 Ceph 的许多要求,因此在文件存储创建时选择了 LevelDB,而非 RocksDB。

Ceph 被设计成能够扩展到 PB 级数据并存储数十亿个对象。然而,由于在一个目录中合理存储的文件数量有限,进一步的解决方法被引入以帮助限制这一点。对象存储在一个哈希目录名称的层次结构中;当这些文件夹中的文件数量达到设定的限制时,目录会被拆分为另一个级别,并且对象会被移动。

然而,提高对象枚举速度是有权衡的:当这些目录拆分发生时,它们会影响性能,因为对象被移动到正确的目录中。在更大的磁盘上,增加的目录数量给 VFS 缓存带来额外的压力,并可能导致访问不频繁的对象出现额外的性能损失。

正如本书在性能调优章节中将要讨论的那样,文件存储中的一个主要性能瓶颈是,当 XFS 开始查找那些当前不在 RAM 中缓存的 inode 和目录条目时。在每个 OSD 存储大量对象的场景下,目前还没有真正解决此问题的方法,Ceph 集群随着填充的逐渐增多而变得越来越慢是很常见的现象。

摆脱在 POSIX 文件系统上存储对象实际上是解决大多数这些问题的唯一方法。

为什么 BlueStore 是解决方案?

BlueStore 的设计旨在解决这些限制。在 NewStore 开发之后,很明显,试图将 POSIX 文件系统作为底层存储层的一种方式,会引入许多与文件存储中存在的类似问题。为了使 Ceph 能够实现期望的底层存储性能,它还需要对存储设备具有直接的块级访问,而不是额外的 Linux 文件系统开销。通过将元数据存储在 RocksDB 中,而将实际的对象数据直接存储在块设备上,Ceph 可以更好地控制底层存储,同时提供更好的性能。

BlueStore 的工作原理

以下图示显示了 BlueStore 如何与块设备交互。与 filestore 不同,数据直接写入块设备,元数据操作由 RocksDB 处理:

块设备被划分为 RocksDB 数据存储和 Ceph 中实际存储的用户数据。每个对象都作为若干个从块设备中分配的 blob 存储。RocksDB 包含每个对象的元数据,并跟踪数据 blob 的使用情况和分配信息。

RocksDB

RocksDB 是一个高性能的键值存储系统,最初是从 LevelDB 分支出来的,但在开发之后,Facebook 对其进行了显著的性能改进,适用于具有低延迟存储设备的多处理器服务器。它还进行了许多功能增强,其中一些在 BlueStore 中得到了应用。

RocksDB 用于存储关于存储对象的元数据,之前这些元数据是通过将 LevelDB 和 XATTRs 结合在 filestore 中处理的。

RocksDB 的一个关键特点是数据在数据库各个级别中的写入方式。它得益于其源自 LevelDB 的特性。新数据被写入一个基于内存的表,并可选地记录在持久化存储的事务日志中,即 WAL;当这个基于内存的表填满时,数据会通过称为压缩的过程移动到数据库的下一个级别。当该级别填满时,数据会再次迁移到更低级别,以此类推。所有这些级别都存储在 RocksDB 称之为 SST 文件的文件中。在 Ceph 中,每个级别的大小配置为前一个级别的 10 倍,如果你尝试在混合的 HDD–SSD 布局中将整个 RocksDB 存储在 SSD 上,那么这会带来一些有趣的因素。

所有新数据都会写入基于内存的表和 WAL,内存表被称为 level 0。BlueStore 将 level 0 配置为 256 MB。各级之间的默认大小倍增因子为十,这意味着 level 1 也是 256 MB,level 2 为 2.56 GB,level 3 为 25.6 GB,level 4 为 256 GB。对于大多数 Ceph 用例,每个 OSD 的平均元数据大小应该在 20-30GB 之间,热数据集通常小于此。希望 level 0、1 和 2 包含大部分热数据,以便写入,因此将 SSD 分区大小至少配置为 3 GB 应能确保这些级别存储在 SSD 上。写入性能应该良好,因为写入的元数据会直接存储到 SSD 上;然而,在读取元数据时——例如在客户端读取请求期间——可能会遇到元数据位于 level 3 或 4 的情况,这时元数据需要从旋转硬盘读取,这将对延迟产生负面影响,并增加磁盘负载。

因此,显而易见的解决方案是以某种方式计算你认为 BlueStore 元数据在你的数据集中的增长大小,并调整 RocksDB 存储的大小,以确保它能够全部存储在 SSD 上。实现这一目标有两个难点。

首先,根据实际数据的大小,预先计算元数据的大小是非常困难的。根据客户端模型(RBD、CephFS 或 RGW),将存储不同数量的元数据。此外,快照和你是否使用复制池或纠删码池也会导致元数据大小的不同。

接下来的挑战是正确配置你的闪存设备,以确保所有元数据都能适配。如前所述,RocksDB 会将数据压缩到数据库的各个级别。当 BlueStore 为 RocksDB 创建文件时,它仅会将某一特定级别放到闪存设备上,前提是该级别的全部内容可以适配。因此,每个级别都有最低的大小要求,以确保该级别确实位于闪存上。例如,为了确保 DB 的 2.56 GB 的级别 2 部分能够适配闪存,你需要至少拥有一个 4-5 GB 的 SSD 分区。这是因为级别 0、级别 1 和级别 2 都需要适配,并且还要有少量的开销。为了确保级别 3 能够完整适配,你需要稍多于 30 GB 的空间;如果空间小于此,级别 2 之上的额外空间将不会被使用。为了确保级别 4 能够适配,你可能需要超过 300 GB 的闪存空间。

如果你使用的是闪存存储实际数据,并且需要进一步提高性能,建议将 WAL 存储在更快的存储设备上,这有助于降低 RocksDB 操作的延迟。如果你使用的是旋转磁盘,将 WAL 移到专用设备上可能不会带来显著的提升。有多种可能的存储布局配置,WAL、DB 和数据可以放置在不同的存储设备上。以下是三种此类配置的示例:

  • WAL、DB 和数据都在旋转磁盘或闪存上

  • WAL 和 DB 在 SSD 上,数据在旋转磁盘上

  • WAL 在 NVMe 上,DB 在 SSD 上,数据在旋转磁盘上

压缩

BlueStore 引入的另一个便捷功能是它支持在子对象级别对数据进行压缩,即 BlueStore 内部的 blob。这意味着,无论客户端访问模型如何,写入 Ceph 的数据都可以受益于此功能。压缩是按池启用的,但默认是禁用的。

除了按池启用压缩外,还有许多额外的选项可以控制压缩行为,如下所示:

  • compression_algorithm: 这个选项控制使用哪个压缩库来压缩数据。默认是 snappy,这是由 Google 编写的压缩库。虽然它的压缩比率不是最佳的,但它具有非常高的性能,除非你有特定的容量需求,否则最好还是使用 snappy。其他选项包括zlibzstd

  • compression_mode: 该设置控制每个存储池的压缩操作状态。可以设置为nonepassiveaggressiveforcepassive设置启用压缩,但仅会压缩从更高层级标记为需要压缩的对象。aggressive设置会尝试压缩所有对象,除非明确告知不压缩。force设置则始终会尝试压缩数据。

  • compress_required_ratio: 默认情况下,设置为 87.5%。已压缩的对象必须压缩到低于此值才被认为值得压缩;否则,该对象将以未压缩格式存储。

尽管压缩确实需要额外的 CPU 资源,但 Snappy 非常高效,Ceph 的分布式特性使其非常适合此任务,因为压缩任务会分摊到集群中大量的 CPU 上。相比之下,传统的存储阵列则必须更多地使用其宝贵的、有限的双控制器 CPU 资源。

使用压缩的一个额外优势是,相较于节省的存储空间,还能提高读写大块数据时的 I/O 性能。由于数据被压缩,磁盘或闪存设备需要读取或写入的数据量减少,这意味着响应时间更快。此外,闪存设备可能因写入数据量减少而遭遇更少的写入磨损。

校验和

为了增强存储数据的保护,BlueStore 会计算并存储所有写入数据的校验和。每次读取请求时,BlueStore 会读取校验和并与从设备读取的数据进行比较。如果发现不匹配,BlueStore 会报告读取错误并修复损坏。Ceph 随后会从另一个持有该对象的 OSD 重新尝试读取。尽管现代硬件具有复杂的校验和和错误检测功能,但在 BlueStore 中引入额外的检查层,能大大降低静默数据损坏的风险。默认情况下,BlueStore 使用 crc32 算法创建校验和,这很可能捕捉到任何静默数据损坏;不过,若有需要,也可以选择其他算法。

BlueStore 缓存调优

与 filestore 不同,filestore 中任何 OSD 节点中的空闲 RAM 都会被用作页面缓存,而在 BlueStore 中,RAM 必须在启动时静态分配给 OSD。对于旋转磁盘 OSD,分配的内存为 1 GB;而基于闪存的 SSD 则分配 3 GB 的内存。该内存用于多个不同的内部缓存:RocksDB 缓存、BlueStore 元数据缓存和 BlueStore 数据缓存。前两个缓存负责确保在查找重要元数据时,BlueStore 内部的平稳运行;默认设置已被调整为提供良好的性能,进一步增加内存会带来递减的回报。最后的 BlueStore 数据缓存将缓存存储在 Ceph 集群中的用户数据。与某些 filestore OSD 存储在页面缓存中的数据相比,默认情况下该缓存相对较低;这是为了避免 BlueStore 在默认情况下产生过高的内存消耗。

如果您的 OSD 节点在所有 OSD 运行并存储数据后仍有大量空闲内存,那么可以增加分配给每个 OSD 的内存量,并决定如何在不同的缓存之间进行分配。

Ceph 的最新版本包含了一个在 BlueStore 中自动调节内存分配的功能,旨在优化 BlueStore 中不同缓存之间的内存分配。默认情况下,OSD 会尝试消耗大约 4 GB 的内存,并通过持续分析内存使用情况来调整每个缓存的分配。自动调节带来的主要改进是,不同的工作负载以不同方式使用 BlueStore 中的缓存,而试图通过静态变量预分配内存是一项非常困难的任务。除了可能会微调目标内存阈值外,其余的自动调节过程基本是自动化的,并且对 Ceph 管理员来说是隐藏的。

如果禁用自动调节,BlueStore 将退回到手动缓存分配行为。接下来的部分详细描述了可以通过手动模式控制的各种 BlueStore 缓存。在此模式下,有两个基于 OSD 的设置控制分配给每个 OSD 的内存,分别是bluestore_cache_size_hddbluestore_cache_size_ssd。顾名思义,可以调整这两个设置中的任何一个,控制分配给 HDD 或 SSD 的内存量。然而,我们不仅可以更改分配给 OSD 的总内存量;还有多个其他设置可以控制三个缓存之间的内存分配,如下所示:

  • 默认情况下,bluestore_cache_kv_ratio设置为 0.5,这意味着会将 50%的内存分配给 RocksDB 缓存。此缓存由 RocksDB 内部使用,并且并不直接由 Ceph 管理。目前认为,这种方式在决定内存分配时能够提供最佳的性能回报。

  • bluestore_cache_meta_ratio 设置,默认值为 0.5,将把 50% 的可用内存分配给缓存 BlueStore 元数据。请注意,根据可用内存和 bluestore_cache_kv_min 的值,可能会分配不到 50% 的内存用于缓存元数据。BlueStore 元数据缓存包含原始的元数据,尚未存储在 RocksDB 中。

  • bluestore_cache_kv_min 设置默认值为 512 MB,确保至少使用 512 MB 内存作为 RocksDB 缓存。超过该值的部分将与 BlueStore 元数据缓存共享,比例为 50:50。

最后,前两个比例剩余的内存将用于缓存实际数据。默认情况下,由于 kvmeta_ratios,这将是 0%。大多数 Ceph 客户端会有自己的本地读取缓存,这有助于将极热的数据缓存起来;然而,在没有自己本地缓存的客户端的情况下,可能需要研究是否调整缓存比例,预留少量缓存用于数据缓存能够带来改善。

默认情况下,BlueStore 的自动调优应该提供最佳的内存使用平衡并提供最佳性能,不建议您改为手动方法。

延迟写入

与文件存储不同,在文件存储中,每次写入都会完整地写入日志并最终写入磁盘,而在 BlueStore 中,写入操作的数据部分在大多数情况下直接写入块设备。这消除了双重写入的惩罚,并且在纯旋转磁盘 OSD 上,显著提高了性能并降低了 SSD 的磨损。然而,如前所述,当旋转磁盘与 SSD 日志结合使用时,双重写入有一个积极的副作用,可以降低写入延迟。BlueStore 还可以使用基于闪存的存储设备通过延迟写入来降低写入延迟,首先将数据写入 RocksDB WAL,然后稍后将这些条目刷新到磁盘。与文件存储不同,并非每个写入都会写入 WAL;配置参数决定了延迟写入的 I/O 大小的截止值。配置参数如下所示:

bluestore_prefer_deferred_size

这控制了将首先写入 WAL 的 I/O 大小。对于旋转磁盘,默认为 32 KB,而 SSD 默认情况下不延迟写入。如果写入延迟很重要并且您的 SSD 足够快,那么通过增加这个值,您可以增加希望延迟到 WAL 的 I/O 大小。

BlueFS

尽管开发 BlueStore 的主要动机是为了避免使用底层文件系统,BlueStore 仍然需要一种方法来存储 RocksDB 和 OSD 磁盘上的数据。为满足这一需求,开发了 BlueFS,它是一个极其简化的文件系统,仅提供 BlueStore 所需的最基本功能。这也意味着它被设计为在 Ceph 提交的简化操作下可靠地运行。同时,它去除了使用标准 POSIX 文件系统时可能存在的双重文件系统日志写入开销。

与文件存储不同,由于 BlueFS 不是本地 Linux 文件系统,因此你无法简单地浏览文件夹结构并手动查看对象;然而,可以使用 ceph-objectstore-tool 挂载 BlueFS 文件系统,从而启用浏览或手动修正错误。这个内容将在灾难恢复部分进一步介绍。

ceph-volume

尽管严格来说不属于 BlueStore 的一部分,ceph-volume 工具与 BlueStore 几乎同时发布,并且是建议用于配置 Bluestore OSD 的工具。它是 ceph-disk 工具的直接替代,后者存在一系列关于竞争条件和 OSD 正确列举及启动可预测性的问题。ceph-disk 工具使用 udev 来识别 OSD,然后挂载并激活它们。ceph-disk 工具现在已经被弃用,所有新的 OSD 应该使用 ceph-volume 创建。

虽然 ceph-volume 可以在简单模式下运行,但推荐的方式是使用 lvm 模式。顾名思义,它利用 Linux 逻辑卷管理器来存储与 OSD 相关的信息并管理块设备。此外,作为 lvm 的一部分,dm-cache 可以用于在 OSD 下提供块级缓存。

ceph-volume 工具还具有批处理模式,旨在根据块设备列表智能地配置 OSD。应特别小心使用 --report 模式,以确保其预期操作与您的预期一致。否则,建议您手动分区并创建 OSD。

如何使用 BlueStore

要使用 ceph-volume 创建 BlueStore OSD,您需要运行以下命令,并指定数据和 RocksDB 存储的设备。如前所述,您可以根据需要将 RocksDB 的 DB 和 WAL 部分分开:

ceph-volume create --bluestore /dev/sda --block.wal /dev/sdb --block.db /dev/sdc (--dmcrypt)

方括号中显示的是加密选项。建议对所有新创建的 OSD 进行加密,除非有特别的原因不这样做。现代 CPU 的加密操作几乎不产生额外开销,并且简化了在更换磁盘时往往被忽视的安全措施。随着诸如欧洲 GDPR 等新数据保护法律的出台,建议对静态数据进行加密。

上述代码假设你的数据磁盘是 /dev/sda。假设你使用的是机械硬盘,并且有一块更快的设备,比如 SSD(/dev/sdb)和一块非常快速的 NVMe 设备(/dev/sdc)。ceph-volume 工具将在数据磁盘上创建两个分区:一个用于存储实际的 Ceph 对象,另一个小的 XFS 分区用于存储 OSD 的详细信息。然后,它会将 SSD 的链接放到 RocksDB 上,NVMe 设备的链接放到 WAL 上。你可以通过对设备进行分区,或使用 lvm 从中切割逻辑卷,来创建多个共享同一 SSD 的 OSD,用于 DB 和 WAL。

然而,正如我们在第二章《使用容器部署 Ceph》中发现的那样,使用适当的部署工具可以帮助减少 Ceph 集群的部署时间,并确保整个集群的配置一致性。虽然 Ceph Ansible 模块也支持部署 BlueStore OSD,但在撰写本文时,它目前不支持自动在单一设备上创建多个 DB 和 WAL 分区。

现在你已经了解如何创建 BlueStore OSD,接下来的话题是讨论如何升级现有集群。

升级现有集群至 BlueStore 的策略

很可能有些读者正在运行使用 filestore 的现有 Ceph 集群。这些读者可能会想知道是否应该升级到 BlueStore,如果是的话,最佳的升级方法是什么。

应该理解,尽管 filestore 仍然得到支持,但它已经接近生命周期的尽头,除了可能需要的关键 bug 修复外,不会再有其他的开发工作。因此,强烈建议你规划将集群升级到 BlueStore,以便利用任何当前和未来的增强功能,并继续运行受支持的 Ceph 版本。未来版本对 filestore 的支持路径尚未公布,但最好从 Nautilus 版本后的 Ceph 版本开始,确保运行 BlueStore OSD。

升级 OSD 至 BlueStore 并没有特别的迁移路径;过程就是简单地销毁 OSD,重建为 BlueStore,然后让 Ceph 在新创建的 OSD 上恢复数据。由于 filestore 日志与 BlueStore 的 RocksDB 之间存在不同的大小要求,修改分区大小很可能需要一次销毁多个 OSD。因此,可能需要考虑是否应在此时进行操作系统重建。

升级过程有两种主要方法,主要取决于 Ceph 运维人员对风险的承受能力和备用容量的可用性,具体如下:

  • 降级升级:降级升级会销毁当前的 OSD,而不会将其内容重新分布到其余的 OSD 上。一旦 OSD 作为 BlueStore OSD 重新上线,丢失的数据副本将被重建。在集群恢复到完全健康状态之前,Ceph 集群中的一部分数据将处于降级状态,尽管会保留多个副本,但如果集群发生某种故障,它们将面临更高的风险。恢复时间将取决于需要恢复的 OSD 数量以及每个 OSD 上存储的数据大小。由于很可能会同时升级多个 OSD,因此预计恢复时间会比单个 OSD 升级时更长。还请注意,使用默认池设置 size=3min_size=2 时,如果发生额外的磁盘故障,某些 PG 将只保留一个副本,而由于副本数小于 min_size,I/O 将被暂停,直到恢复过程中重新创建第二个副本。执行降级升级的好处是,恢复期间只需等待集群重新平衡一次,并且不需要额外的空间,这意味着对于几乎已满的集群而言,这可能是唯一的选择。

  • 进出升级:如果你希望防止数据丢失或不可用的任何可能性,并且有足够的空间在集群中重新分配待升级 OSD 的内容,那么推荐使用进出升级方法。通过将待升级的 OSD 标记为 out,Ceph 将重新平衡 PG 到其他 OSD 上。一旦这个过程完成,可以停止并销毁 OSD,而不会影响数据的持久性或可用性。当 BlueStore OSD 重新引入时,PG 将重新流回,在此期间,数据副本的数量不会减少。无论哪种方法,最终都会得到完全相同的配置,因此最终选择取决于个人偏好。如果集群中有大量 OSD,可能需要某种自动化手段来减轻操作员的负担;然而,如果你想自动化这个过程,则在销毁 filestore OSD 步骤时需要格外小心,因为一个错误很容易擦除超出预期的 OSD。一个折衷方法是创建一个小脚本,自动化清除、分区和创建步骤。然后可以手动在每个 OSD 节点上运行该脚本。

在你的测试集群中升级 OSD

为了演示 BlueStore 的基础,我们将使用ceph-volume无干扰地手动将一个在线的 Ceph 集群的 OSD 从 filestore 升级到 BlueStore。如果您希望实际执行此过程,可以参考第二章《使用容器部署 Ceph》中的Ansible部分,部署一个具有 filestore OSD 的集群,然后按照以下步骤进行升级。OSD 将按降级方法进行升级,其中 OSD 在仍包含数据的情况下被移除。

通过执行ceph -s命令确保您的 Ceph 集群完全健康,如下代码所示。我们将通过首先将 OSD 从集群中移除,然后让 Ceph 将数据恢复到新的 BlueStore OSD 上来升级 OSD,因此在开始之前,我们需要确保 Ceph 有足够有效的数据副本。利用 Ceph 的热维护功能,您可以在集群中的所有 OSD 上重复此过程,而无需停机:

现在我们需要停止所有 OSD 的运行,卸载磁盘,然后按照以下步骤清除它们:

  1. 使用以下命令停止 OSD 服务:
 sudo systemctl stop ceph-osd@*

上述命令给出的输出如下所示:

我们可以通过再次使用ceph -s命令确认 OSD 已停止,并且 Ceph 仍在正常运行,如下图所示:

  1. 现在,卸载 XFS 分区;可以忽略错误:
sudo umount /dev/sd* 

  1. 卸载文件系统意味着磁盘不再被锁定,我们可以使用以下代码清除磁盘:
sudo ceph-volume lvm zap /dev/sd<x>

  1. 现在,我们还可以编辑闪存设备上的分区表,以删除 filestore 日志并重新创建适合 BlueStore 的 RocksDB 大小,如以下代码所示。在此示例中,闪存设备为 NVMe:
sudo fdisk /dev/sd<x>

使用d命令删除每个 Ceph 日志分区,如下所示:

现在为 BlueStore 创建所有新分区,如下图所示:

为每个打算创建的 OSD 添加一个分区。完成后,您的分区表应如下所示:

使用w命令将新的分区表写入磁盘,如下图所示。执行此操作后,系统会提示您新的分区表当前未在使用中,因此我们需要运行sudo partprobe将分区表加载到内核中:

  1. 回到你的监视器之一。首先,确认我们将要移除的 OSD,并使用以下purge命令移除 OSD:
sudo ceph osd tree

上述命令输出如下内容:

现在,从 Ceph 集群中移除逻辑 OSD 条目——在此示例中,移除 OSD 36:

sudo ceph osd purge x --yes-i-really-mean-it

  1. 使用ceph -s命令检查 Ceph 集群的状态。你现在应该能看到 OSD 已被移除,如下图所示:

请注意,OSD 的数量已经减少,而且由于 OSD 已从 CRUSH 图中移除,Ceph 现在已开始尝试将丢失的数据恢复到剩余的 OSD 上。最好不要让 Ceph 长时间处于这种状态,以避免不必要的数据移动。

  1. 现在发出ceph-volume命令,使用以下代码创建bluestore OSD。在这个示例中,我们将把数据库存储在单独的闪存设备上,因此需要指定此选项。同时,按照本书的建议,OSD 将进行加密:
sudo ceph-volume lvm create --bluestore --data /dev/sd<x> --block.db /dev/sda<ssd> --dmcrypt

上述命令会输出大量信息,但如果成功,我们最终会看到如下内容:

  1. 再次使用ceph-s命令检查ceph的状态,以确保新的 OSD 已添加并且 Ceph 正在将数据恢复到这些 OSD 上,如下图所示:

请注意,由于新的 OSD 在升级前已经被放置在 CRUSH 图中的相同位置,现有的错位对象数量几乎为零。Ceph 现在只需要恢复数据,而不是重新分布数据布局。

如果需要进一步升级节点,等待回填过程完成并且 Ceph 状态恢复为HEALTH_OK。然后可以继续下一个节点的工作。

如你所见,整体过程非常简单,与替换故障磁盘所需的步骤完全相同。

总结

本章我们学习了 Ceph 中的新对象存储——BlueStore。希望你能更好地理解为什么它是必须的,以及现有文件存储设计的局限性。你也应该对 BlueStore 的内部工作有基本了解,并对如何将 OSD 升级到 BlueStore 感到有信心。

在下一章中,我们将探讨如何通过常用存储协议导出 Ceph 存储,以便非 Linux 客户端可以使用 Ceph 存储。

问题

  1. 在 Luminous 及更高版本中,创建 OSD 时默认使用的对象存储是什么?

  2. BlueStore 内部使用的是哪种数据库?

  3. 在 BlueStore 的数据库部分,数据在各个层级之间移动的过程叫什么名字?

  4. 将小型写入临时写入 SSD 而不是 HDD 的方法叫什么名字?

  5. 如何挂载 BlueFS 并将其作为标准 Linux 文件系统浏览?

  6. BlueStore 中默认使用的压缩算法是什么?

  7. 在 BlueStore 数据库中上移一层会使大小增加多少倍?

第四章:Ceph 与非原生协议

多年的开发使得 Ceph 构建了一个广泛的功能集,为 Linux 提供了高质量、高性能的存储。然而,不能运行 Linux 的客户端(因此无法与 Ceph 直接通信)在 Ceph 的部署范围上受限。最近,已经开发出一些新功能,允许 Ceph 与一些非 Linux 客户端开始进行通信,比如互联网小型计算机系统接口iSCSI)和网络文件系统NFS)。本章将详细介绍 Ceph 存储如何导出到客户端的不同方法,以及每种方法的优缺点。在所有方法中,都会使用 Linux 服务器作为代理,将这些客户端的 I/O 请求转换为原生 Ceph I/O,因此,了解如何在 Linux 中使用这些协议将是有益的。本章还将介绍如何使这些代理服务器高可用,并探讨相关的困难。

本章将重点讨论两种主要的存储类型:文件存储和块存储,因为这两种存储类型在传统企业工作负载中最为常见。

简要地,我们将在本章中涵盖以下主题:

  • 文件

  • 示例:

    • 通过 iSCSI 导出 Ceph RBD

    • 通过 Samba 导出 CephFS

    • 通过 NFS 导出 CephFS

  • ESXi 虚拟化管理程序

  • 集群

块级存储模拟了最初由硬盘提供的存储类型,后来由存储阵列提供。通常,块存储通过光纤通道或 iSCSI 从存储阵列导出到主机,然后在块设备上格式化本地文件系统。在某些情况下,这个文件系统可能是集群类型的,允许在多个主机上同时呈现块设备。需要注意的是,尽管基于块的存储允许你将其呈现给多个主机,但只有在文件系统支持的情况下才应这样做,否则文件系统极有可能发生损坏。

近年来,块存储在虚拟化技术中的应用有了巨大的扩展。块存储通常被呈现给格式化了文件系统的虚拟化管理程序。然后,一个或多个虚拟机以文件的形式存储在这个文件系统上。这与使用 KVM 作为虚拟化管理程序时的原生 Ceph 方式大相径庭;因为 KVM 直接支持 Ceph RADOS 块设备RBD),它将每个虚拟机的磁盘直接存储为 RBD,从而消除了虚拟化管理程序文件系统相关的复杂性和开销。

Ceph RBD 是块存储的一种类型,可以通过 iSCSI 导出,允许支持 iSCSI 的客户端使用 Ceph 存储。自 Mimic 版本发布以来,Ceph 对 RBD 映像的 iSCSI 导出配置提供了基本支持。Ceph 的 iSCSI 支持的配置全部通过 Ansible 管理,Ansible 不仅安装所需的软件,还导出 iSCSI 设备。

在写作本文时,读者应注意目前仍然存在一些限制,主要涉及高可用性HA)功能。这些问题主要影响 ESXi 和集群解决方案,其中多个主机试图同时访问块设备。在撰写本文时,不推荐您在这两种用例中使用 Ceph 的 iSCSI 支持。对于有兴趣进一步探索当前兼容性的用户,建议他们查阅上游 Ceph 文档和邮件列表。

文件

如其名称所示,文件存储由某种形式的文件系统支持,文件系统用于存储文件和目录。在传统的存储场景中,文件存储通常通过充当文件服务器的服务器提供,或者通过使用网络附加存储NAS)实现。文件存储可以通过多种协议提供,并可以存放在不同类型的文件系统上。

两种最常见的文件访问协议是 SMB 和 NFS,它们广泛被许多客户端支持。SMB 传统上被视为微软协议,是 Windows 中的本地文件共享协议,而 NFS 则被认为是 Unix 基础架构中使用的协议。

正如我们稍后将看到的,Ceph 的 RBD 和其 CephFS 文件系统都可以作为导出文件存储到客户端的基础。RBD 可以挂载在代理服务器上,随后在其上方放置本地文件系统。从这里开始,作为 NFS 或 SMB 导出的过程与任何其他具有本地存储的服务器非常相似。当使用 CephFS 时,作为一个文件系统,它有直接与 NFS 和 SMB 服务器软件的接口,以最小化堆栈中的层级数量。

导出 CephFS 而不是将文件系统置于 RBD 之上的方式有许多优势。这些优势主要集中在简化 I/O 传输的层数和高可用(HA)配置中组件的数量。正如前面所讨论的,大多数本地文件系统一次只能在一个服务器上挂载,否则会发生损坏。因此,在设计涉及 RBD 和本地文件系统的 HA 解决方案时,必须确保集群解决方案不会尝试跨多个节点挂载 RBD 和文件系统。关于这一点,本章后面有关集群部分会详细讨论。

然而,有一个可能的原因是希望导出格式化为本地文件系统的 RBD:Ceph 的 RBD 组件在操作上比 CephFS 更简单,并且比 CephFS 被标记为更稳定的时间更长。虽然 CephFS 已经证明非常稳定,但在考虑解决方案的操作性时,应该确保操作员能够顺利管理 CephFS。

要通过 NFS 导出 CephFS,有两种可能的解决方案。一个是使用 CephFS 内核客户端,将文件系统挂载到操作系统中,然后使用基于内核的 NFS 服务器将其导出到客户端。尽管这种配置应该可以正常工作,但基于内核的 NFS 服务器和 CephFS 客户端通常依赖操作员运行相对较新的内核以支持最新功能。

更好的做法是使用nfs-ganesha,它支持直接与 CephFS 文件系统通信。由于 Ganesha 完全在用户空间中运行,因此不需要特定的内核版本,而且支持的 CephFS 客户端功能可以跟上 Ceph 项目的当前状态。Ganesha 中还有一些内核 NFS 服务器不支持的增强功能。此外,使用 Ganesha 实现 HA NFS 应该比使用内核服务器更容易。

Samba 可以用于将 CephFS 导出为与 Windows 兼容的共享。与 NFS 类似,Samba 也支持直接与 CephFS 通信,因此在大多数情况下,应该不需要先将 CephFS 文件系统挂载到操作系统中。可以使用一个单独的项目 CTDB 来为 CephFS 支持的 Samba 共享提供高可用性(HA)。

最后,值得注意的是,尽管 Linux 客户端可以直接挂载 CephFS,但通过 NFS 或 SMB 导出 CephFS 可能仍然更为可取。我们之所以这么做,是因为,考虑到 CephFS 的工作方式,客户端直接与 Ceph 集群进行通信,在某些情况下,这可能由于安全问题而不可取。通过 NFS 重新导出 CephFS,客户端可以在不直接暴露于 Ceph 集群的情况下使用存储。

示例

以下示例将演示如何将 RBD 导出为 iSCSI 设备,以及如何通过 NFS 和 Samba 导出 CephFS。所有这些示例都假设您已经有一个准备好的可导出的 CephFS 文件系统;如果不是这种情况,请参考第五章,RADOS 池和客户端访问,以获取如何部署 CephFS 的说明。

它们还假设您有一个虚拟机可以作为代理服务器。这可以是一个用于测试目的的 Ceph 监视器虚拟机,但不建议用于生产工作负载。

通过 iSCSI 导出 Ceph RBD

iSCSI 是一种通过 IP 网络导出块设备的技术。随着 10 G 网络的广泛采用,iSCSI 已经变得极为流行,现在成为了块存储领域的主流技术。

导出块存储的设备称为 iSCSI 目标,客户端称为 iSCSI 启动器,二者都由 IQN 名称标识。

在写作本文时,Ceph 中的 iSCSI 支持仅适用于基于 Red Hat 的发行版。尽管底层组件应当能够在任何 Linux 发行版上正常工作,但将它们连接在一起的粘合部分仍然需要进行一系列更新以提升兼容性。因此,本示例将需要一台运行 CentOS 的虚拟机,以便安装 iSCSI 组件。如果你在 第二章 中创建的 Vagrant 和 Ansible 实验室中测试功能,使用容器部署 Ceph,则可以修改 Vagrant 文件,提供一个额外的运行 CentOS 的虚拟机。

iSCSI 组件的官方包仓库仅通过完整的 RHEL 订阅提供。要获取此示例的包,必须从 Ceph 的项目构建服务器下载。

以下链接将引导您到每个包的最新构建:

在每一页上,查看架构列,如下所示的屏幕截图。这是您稍后需要查找包的目录:

点击左侧的最新(或您需要的任何版本)构建号,将带您进入以下页面:

点击 Repo URL 链接,这将带您进入仓库目录树。浏览到之前在架构列中看到的正确架构类型,您将看到可下载的 RPM,如下所示的截图所示:

复制 URL,然后使用 wget 下载该包,如以下截图所示:

对每个之前列出的 URL 都执行此操作。完成后,您应该拥有以下包:

现在,通过运行以下命令安装所有 RPM 包:

yum install *.rpm

现在,基础的 iSCSI 支持已安装,我们还需要使用以下代码安装 Ceph 包:

rpm --import 'https://download.ceph.com/keys/release.asc'

创建一个新的仓库文件,并使用以下代码添加 Ceph RPM 仓库:

nano /etc/yum.repos.d/ceph.repo

现在添加 Fedora EPEL 仓库,并使用以下代码安装和更新 Ceph:

yum install -y https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm
yum update
yum install ceph

如果 Ceph 配置目录尚不存在,请使用以下代码创建:

mkdir /etc/ceph

使用以下代码从 Ceph 监视节点复制ceph.conf

scp mon1:/etc/ceph/ceph.conf /etc/ceph/ceph.conf

使用以下代码复制 Ceph keyring

scp mon1:/etc/ceph/ceph.client.admin.keyring /etc/ceph/ceph.client.admin.keyring

使用以下代码编辑 Ceph iSCSI 网关配置文件:

nano /etc/ceph/iscsi-gateway.cfg

确保它看起来像前面的截图所示的代码。注意底部添加的行,它允许仅使用一个服务器来测试ceph-iscsi。在生产环境中,这一行不需要,因为你很可能会有冗余的 iSCSI 网关。

现在,使用以下代码启用并启动ceph-iscsi守护进程:

systemctl daemon-reload
systemctl enable rbd-target-api
systemctl start rbd-target-api systemctl enable rbd-target-gw
systemctl start rbd-target-gw

请注意,存储在iscsi-gateway.conf中的配置仅用于启动ceph-iscsi服务并连接到 Ceph 集群。实际的 iSCSI 配置集中存储在 RADOS 对象中。

现在,iSCSI 守护进程正在运行,可以使用gwcli工具管理 iSCSI 配置并将 RBDs 呈现为 iSCSI 设备。

一旦gwcli成功启动,我们可以运行ls命令查看ceph-iscsi配置的结构,如下截图所示:

gwcli工具已连接到 Ceph 集群并检索了池列表及其他配置。现在我们可以配置 iSCSI 了。

第一个要配置的项是 iSCSI 网关,使用以下代码:

cd iscsi-target
create iqn.2003-01.com.redhat.iscsi-gw:iscsi-igw

现在,输入已创建的iqn,可以使用以下代码将所有网关的 IP 添加进去:

cd iqn.2003-01.com.redhat.iscsi-gw:iscsi-igw
cd gateways
create ceph-iscsi 10.176.20.38

现在我们可以创建或添加 RBD。如果在运行create命令时 RBD 已经存在,那么ceph-iscsi将简单地添加现有 RBD;如果没有给定名称的 RBD,则会创建一个新的 RBD。一个常见的例子是,当 RBD 包含数据或我们需要将 RBD 数据放置在纠删码池中时,可能需要使用预先存在的 RBD。

对于这个例子,将在 RBD 池中创建一个 100GB 的 RBD,名为iscsi-test,如以下代码所示:

cd /disks
create pool=rbd image=iscsi-test size=100G

现在需要添加发起者iqn并分配 chap 认证,如以下代码所示:

cd /iscsi-target/iqn.2003-01.com.redhat.iscsi-gw:iscsi-igw
cd hosts
create iqn.2018-11.com.test:my-test-client
auth chap=chapuser/chappassword

最后,使用以下代码将磁盘添加到主机作为 LUN。目标的格式是<rados pool>.<RBD name>

disk add rbd.iscsi-test

iSCSI 目标配置现在已完成,可以添加到任何 iSCSI 发起者的目标列表中。添加并重新扫描后,RBD 将作为 LUN 显示,然后可以像普通块设备一样处理,并根据需要格式化为任何文件系统。

通过 Samba 导出 CephFS

Samba 项目最初是为了允许客户端和服务器通过 Microsoft SMB 协议进行通信。它后来发展成可以充当完整的 Windows 域控制器。由于 Samba 可以作为文件服务器,支持通过 SMB 协议与客户端通信,因此它可以用来将 CephFS 导出给 Windows 客户端。

另有一个名为 CTDB 的独立项目,它与 Samba 配合使用,创建一个故障转移集群,以提供高度可用的 SMB 共享。CTDB 使用恢复锁的概念来检测和处理脑裂情况。传统上,CTDB 使用集群文件系统的某个区域来存储恢复锁文件;然而,这种方法在 CephFS 中效果不好,因为恢复序列的时序与 OSD 和 CephFS MDS 故障转移的时序冲突。因此,开发了一种 RADOS 特定的恢复锁,它允许 CTDB 直接将恢复锁信息存储在 RADOS 对象中,从而避免了上述问题。

在此示例中,将使用一个包含两个代理节点的集群,将 CephFS 上的目录导出为可供 Windows 客户端访问的 SMB 共享。CTDB 将用于提供故障转移功能。此共享还将利用 CephFS 快照,以便在 Windows 文件资源管理器中启用上一个版本的功能。

对于这个例子,您需要两台虚拟机,它们需要具备有效的网络连接,并能够访问您的 Ceph 集群。虚拟机可以是手动创建的、通过 Ansible 在您的实验室中部署的,或者可以在 Ceph 监视器上安装,用于测试 Samba 软件。

使用以下代码在两台虚拟机上安装cephctdbsamba包:

sudo apt-get install ceph samba ctdb

使用以下代码将ceph.conf从 Ceph 监视节点复制过来:

scp mon1:/etc/ceph/ceph.conf /etc/ceph/ceph.conf

使用以下代码将 Ceph 密钥环从监视节点复制过来:

scp mon1:/etc/ceph/ceph.client.admin.keyring /etc/ceph/ceph.client.admin.keyring

现在,您的 Samba 网关应该能够作为 Ceph 集群的客户端。这可以通过检查是否能够查询 Ceph 集群的状态来确认。

如前所述,CTDB 有一个 Ceph 插件,可以将恢复锁直接存储在 RADOS 池中。在某些 Linux 发行版中,可能不会随 Samba 和 CTDB 包一起分发此插件;在基于 Debian 的发行版中,目前没有包含该插件。为了解决这个问题并避免手动编译,我们将借用其他发行版中的预编译版本。

使用以下代码从 SUSE 软件库下载 samba-ceph包:

wget http://widehat.opensuse.org/opensuse/update/leap/42.3/oss/x86_64/samba-ceph-4.6.7+git.51.327af8d0a11-6.1.x86_64.rpm

使用以下代码安装一个实用工具,以提取 RPM 包的内容:

apt-get install rpm2cpio

使用以下代码使用rpm2cpio工具提取刚刚下载的 RPM 包的内容:

rpm2cpio samba-ceph-4.6.7+git.51.327af8d0a11-6.1.x86_64.rpm | cpio -i --make-directories

最后,使用以下代码将 CTDB RADOS 助手复制到虚拟机的bin文件夹中:

cp usr/lib64/ctdb/ctdb_mutex_ceph_rados_helper /usr/local/bin/

确保在两个虚拟机上都执行了所有步骤。现在所有必需的软件已经安装完毕,我们可以继续配置 Samba 和 CTDB。CTDB 和 Samba 的配置文件中都包含示例内容。在本示例中,只显示最基本的内容;如果读者希望进一步探索可用的配置选项,可以自行研究:

nano /etc/samba/smb.conf

nano /etc/ctdb/ctdbd.conf

nano /etc/ctdb/nodes

在每一行中,输入参与 CTDB Samba 集群的每个节点的 IP 地址,如以下截图所示:

最后一步是创建一个 Samba 用户,用于访问共享。为此,请使用以下代码:

smbpasswd -a test

再次确保在两个 Samba 节点上都执行此配置。一旦完成,可以启动 CTDB 服务,它应该能够形成选举并启动 Samba。您可以使用以下代码启动 CTDB 服务:

systemctl restart ctdb

几秒钟后,CTDB 将开始标记节点为健康状态;可以通过运行以下代码来确认这一点:

ctdb status

这应该显示一个与以下截图相似的状态:

启动后,状态短时间内不健康是正常的,但如果状态长时间保持这种状态,请检查位于 /var/log/ctdb 的 CTDB 日志,以查看可能的错误原因。

一旦 CTDB 进入健康状态,您应该能够从任何 Windows 客户端访问 CephFS 共享。

为了提供真正的高可用性(HA),您需要使用负载均衡器等机制将客户端引导到活动节点的 IP 地址。这超出了本示例的范围。

通过 NFS 导出 CephFS

NFS 是一个文件共享协议,支持 Linux、Windows 和 ESXi 操作系统。因此,将 CephFS 文件系统作为 NFS 共享进行导出,可以使 CephFS 在多种不同类型的客户端之间使用。

Ganesha 是一个用户空间的 NFS 服务器,具有原生的 CephFS 插件,因此它能够直接与 CephFS 文件系统进行通信,而不需要首先将其挂载到本地服务器。它还支持将其配置和恢复信息直接存储在 RADOS 对象中,这有助于实现无状态运行 NFS 服务器。

按照以下步骤安装和配置通过 Ganesha 导出 CephFS:

  1. 使用以下代码安装 Ganesha 的 PPA(在写作时 Ganesha 2.7 是最新版本):
add-apt-repository ppa:nfs-ganesha/nfs-ganesha-2.7

  1. 使用以下代码安装 libntirpc-1.7 的 PPA,这是 Ganesha 所需的:
add-apt-repository ppa:gluster/libntirpc-1.7

  1. 使用以下代码安装 Ganesha:
apt-get install ceph nfs-ganesha nfs-ganesha-ceph liburcu6

  1. 使用以下代码将 ceph.conf 从 Ceph 监视器节点复制过来:
scp mon1:/etc/ceph/ceph.conf /etc/ceph/ceph.conf
  1. 使用以下代码将 Ceph 密钥环从监视器节点复制过来:
scp mon1:/etc/ceph/ceph.client.admin.keyring /etc/ceph/ceph.client.admin.keyring

现在 Ganesha 已经安装完成,需要配置它以指向你的 CephFS 文件系统。Ganesha 包中提供了一个示例配置文件,你可以使用这个文件作为基础。首先,使用以下代码将示例的 Ganesha Ceph 配置 文件复制为主 Ganesha 配置 文件:

mv /etc/ganesha/ceph.conf /etc/ganesha/ganesha.conf

配置文件中有详细注释,但以下截图展示了一个简化版,其中所有必要的选项都已配置。建议保留默认配置文件,并在需要的地方调整选项,而不是直接覆盖,因为包含的注释非常有助于更好地理解配置选项:

path config 变量需要设置为 CephFS 文件系统的根目录,因为 CephFS 当前不正确地支持通过 NFS 导出子目录。

  1. 现在使用以下代码启用并启动 nfs-ganesha 服务:
systemctl enable nfs-ganesha
systemctl start nfs-ganesha

现在你应该能够将 NFS 共享挂载到任何兼容的客户端。NFS 共享的名称将是 CephFs。

ESXi 虚拟化平台

一个相当频繁的需求是能够导出 Ceph 存储并通过 VMware 的 ESXi 虚拟化平台进行使用。ESXi 支持使用其自身的 VMFS 集群文件系统格式化的 iSCSI 块存储和基于文件的 NFS 存储。两者都完全可用且受支持,意味着通常由用户的偏好决定使用哪一种,或者哪一种存储阵列最受支持。

在将 Ceph 存储导出到 ESXi 时,存在一些额外因素需要考虑,这些因素涉及将 Ceph 用作存储提供商时的使用以及在 iSCSI 和 NFS 之间做出选择时的决策。因此,本章的这一部分专门用于解释在将 Ceph 存储呈现给 ESXi 时应考虑的额外因素。

首先需要考虑的是,ESXi 是在考虑企业存储阵列的前提下开发的,在其开发过程中做出了一些设计决策,专门考虑了这些存储阵列的运行。如在开篇章节中所述,直接附加、光纤通道和 iSCSI 阵列的延迟要比分布式网络存储低得多。对于 Ceph,需要额外的跳数,作为 NFS 或 iSCSI 代理;这通常会导致写入延迟是优秀块存储阵列的几倍。

为了配合存储供应商的 QOS 尝试(暂时忽略 VAAI 加速),ESXi 会将任何克隆或迁移操作拆分为较小的 64 KB I/O,其原因是大量并行的 64 KB I/O 比较容易调度磁盘时间,而大规模的多 MB I/O 会占用磁盘操作更长时间。然而,Ceph 更倾向于使用更大的 I/O 大小,因此在克隆或迁移虚拟机时性能较差。此外,根据导出方式的不同,Ceph 可能不会提供预读功能,从而影响顺序读取性能。

另一个需要注意的方面是管理 Ceph PG 锁的影响。当访问 Ceph 中存储的对象时,包含该对象的 PG 会被锁定以保持数据一致性。所有其他对该 PG 的 I/O 操作必须排队,直到锁被释放。对于大多数场景,这个问题影响较小;但是,当将 Ceph 导出到 ESXi 时,ESXi 的一些操作可能会导致 PG 锁的争用。

如前所述,ESXi 通过将 I/O 提交为 64 KB 来迁移虚拟机。它还试图保持 32 个并行操作的流,以确保性能可接受。这在使用 Ceph 作为底层存储时会导致问题,因为这些 64 KB 的 I/O 中有很大一部分会命中同一个 4 MB 对象,这意味着在 32 个并行请求中,每个请求几乎以串行方式处理。可以使用 RBD 条带化来确保这些高度并行但高度本地化的 I/O 被分布到多个对象上,但实际效果可能会有所不同。VAAI 加速可能对某些迁移和克隆操作有所帮助,但在某些情况下,这些加速并不总是可用,因此 ESXi 会回退到默认方法。

与虚拟机迁移相关,如果您使用的是通过 iSCSI 访问 RBD 配置的 VMFS,您可能会在更新仅存储在磁盘小区域中的 VMFS 元数据时遇到 PG 锁竞争。当扩展精简配置的 VMDK 或写入快照的虚拟机文件时,VMFS 元数据通常会进行大量更新。如果多个虚拟机同时尝试更新 VMFS 元数据,PG 锁竞争可能会限制吞吐量。

在撰写本文时,官方的 Ceph iSCSI 支持禁用了 RBD 缓存。对于某些操作,缺少预读缓存会对 I/O 性能产生负面影响。这种情况在需要顺序读取 VMDK 文件时尤为明显,例如在虚拟机在数据存储之间迁移或删除快照时。

关于 HA 支持,在撰写本文时,官方的 Ceph iSCSI 支持仅使用隐式 ALUA 来管理活动的 iSCSI 路径。如果 ESXi 主机故障切换到另一路径,而同一 vSphere 集群中的其他主机仍保持在原路径,则会导致问题。长期解决方案将是切换到显式 ALUA,这样可以让 iSCSI 发起端控制目标端的活动路径,从而确保所有主机都通过相同的路径通信。目前启用完整 HA 堆栈的唯一变通方法是每个数据存储只运行一个虚拟机。

NFS–XFS–RBD 配置与 iSCSI 配置共享许多 PG 锁争用问题,并且受到 XFS 日志引发的争用影响。XFS 日志是一个小型的环形缓冲区,大小为数十 MB,仅覆盖少数几个底层 RADOS 对象。由于 ESXi 通过 NFS 发送同步写入,XFS 的并行写入排队,等待日志写入完成。因为 XFS 不是分布式文件系统,所以在构建 HA 解决方案时,需要采取额外措施来管理 RBD 和 XFS 文件系统的挂载。

最后,我们有 NFS 和 CephFS 方法。由于 CephFS 是一个文件系统,它可以直接导出,这意味着与其他两种方法相比少了一层。此外,由于 CephFS 是一个分布式文件系统,它可以同时挂载到多个代理节点,这意味着需要跟踪和管理的集群对象减少了两个。

还可能通过 NFS 导出单个 CephFS 文件系统,提供一个大型的 ESXi 数据存储,这意味着不需要像使用 RBD 时那样担心在数据存储之间迁移虚拟机。这大大简化了操作,解决了我们迄今为止讨论的许多限制问题。

尽管 CephFS 仍然需要元数据操作,但这些操作的并行处理远远优于 XFS 或 VMFS 中的元数据操作,因此对性能的影响非常小。CephFS 的元数据池也可以放在闪存存储上,以进一步提高性能。元数据更新的处理方式也大大降低了 PG 锁定的发生频率,这意味着数据存储上的并行性能不受限制。

如前所述,在 NFS 部分,CephFS 可以通过 Ganesha FSAL 直接导出,或者通过 Linux 内核挂载后再导出。出于性能考虑,目前推荐通过内核挂载并导出的方式。

在决定哪种方法最适合您的环境之前,建议您进一步研究每种方法,并确保您能够顺利管理该解决方案。

集群

集群的目标是通过让单点故障跨多个服务器运行,从而提高服务的可靠性。理论上,这听起来相对简单:如果服务器 A 宕机,便在服务器 B 上启动服务。然而,在实践中,有几个需要考虑的因素;否则,存在可用性可能比单台服务器更差,甚至更糟的是可能发生数据损坏的风险。高可用性非常难以实现,但很容易出错。

脑裂

集群中需要解决的第一个问题是节点断开连接且无法互相知晓对方状态的情况。此状态被称为脑裂。在一个两节点的集群中,每个节点无法知道与另一个节点失去通信的原因是因为另一个节点已经宕机,还是因为某种形式的网络中断。在后者的情况下,如果做出错误的假设,并在两个节点上都启动资源,就会导致数据损坏。解决脑裂问题的方法是确保集群总是有奇数个节点;这样,至少两个节点总能形成仲裁并达成共识,确认哪个节点出现了故障。

然而,即使节点已经形成了仲裁,当剩余节点重新启动资源时,仍然不安全。以节点看似离线为例,可能是由于网络分区,或者是服务器负载过高,导致没有及时响应。如果剩余节点自己重新启动服务,如果这个无法响应的节点重新上线,可能会发生什么?为了应对这种情况,我们需要确保集群随时能够 100%确认所有节点和资源的状态。这可以通过围栏机制来实现。

围栏

围栏是一个限制资源运行的过程,前提是集群状态有一致的视图。它还通过控制集群节点的电源状态或其他方法,帮助将集群恢复到已知状态。如前所述,如果集群不能确定集群节点的当前状态,其他节点无法简单地重新启动服务,因为无法知道受影响的节点是否已经真正宕机,或者仍在运行那些资源。除非配置了数据一致性风险,否则集群将无限期地等待,直到它可以确定状态,并且除非受影响的节点自行恢复,否则集群资源将保持离线状态。

解决方案是使用 STONITH 等方法进行隔离,其设计目的是通过操作外部控制机制将集群恢复到正常状态。最流行的方法是使用服务器的内置 IPMI 功能来对节点进行电源循环。由于服务器的 IPMI 是操作系统外部的,并且通常连接到与服务器 LAN 不同的网络,因此几乎不可能受到导致服务器脱机的问题的影响。通过对服务器进行电源循环并从 IPMI 获得确认,集群现在可以完全确定集群资源不再在该节点上运行。然后,集群可以安全地在其他节点上重新启动资源,而无需担心冲突或损坏。

Pacemaker 和 corosync

在 Linux 上最广泛使用的集群解决方案是 pacemaker 和 corosync 的组合。Corosync 负责节点之间的消息传递和确保一致的集群状态,而 pacemaker 则负责在此集群状态的基础上管理资源。pacemaker 提供了大量的资源代理,支持广泛的服务集群化,包括一些常见服务器 IPMI 的 STONITH 代理。

它们都可以由多种不同的客户端工具管理,最常见的是 pcscrmsh。以下教程将重点介绍 crmsh 工具集。

创建由 CephFS 支持的高可用 NFS 共享

在本示例中,需要三个 VM 才能形成集群节点。在所有三个 VM 上执行以下步骤:

  1. 使用以下代码安装 corosyncpacemakercmrsh 工具集:
apt-get install corosync pacemaker crmsh

  1. 编辑 corosync 配置文件,并将绑定地址 (bindnetaddr) 更改为与 VM 上配置的 IP 匹配,使用以下代码:
nano /etc/corosync/corosync.conf

  1. 使用以下代码启用并启动 corosync 服务:

  1. 在所有节点完成这些步骤后,检查集群的状态。您应该看到所有三个节点都已加入集群,如以下截图所示:

注意到显示的是No resources。这是因为,尽管集群正在运行,节点已成为成员,但尚未配置任何资源。需要一个虚拟 IP 资源,这是 NFS 客户端将要连接的资源。同时,还需要一个控制 Ganesha 服务的资源。资源由资源代理管理。资源代理通常是一些脚本,包含一系列标准函数,pacemaker 会调用这些函数来启动、停止和监控资源。标准的 pacemaker 安装包含了大量的资源代理,但如果需要,编写自定义的资源代理并不困难。

  1. 如本节开始时所讨论的,围栏(fencing)和 STONITH 是高可用集群的核心部分;然而,在构建测试环境时,实施 STONITH 可能会很困难。默认情况下,如果没有配置 STONITH 配置,pacemaker 将不允许你启动任何资源。因此,在这个示例中,应该使用以下命令禁用 STONITH:
crm configure property stonith-enabled=false

  1. 现在集群已经准备好创建资源,让我们使用以下代码创建虚拟 IP 资源:
crm configure primitive p_VIP-NFS ocf:heartbeat:IPaddr params ip=192.168.1.1 op monitor interval=10s

从前面的截图中可以看到,虚拟 IP 已经启动,并且现在正在节点 nfs1 上运行。如果节点 nfs1 变得不可用,集群将尝试通过将资源迁移到其他节点来保持资源的运行。

现在,和之前的 NFS 部分一样,让我们通过以下步骤安装 Ganesha 的最新版本:

  1. 使用以下代码安装 Ganesha PPA(ganesha 2.7 是本文撰写时的最新版本):
add-apt-repository ppa:nfs-ganesha/nfs-ganesha-2.7

  1. 使用以下代码安装 Ganesha 所需的 libntirpc-1.7 PPA:
add-apt-repository ppa:gluster/libntirpc-1.7 

  1. 使用以下代码安装 Ganesha:
apt-get install ceph nfs-ganesha nfs-ganesha-ceph liburcu6

  1. 使用以下代码从 Ceph 监视节点复制 ceph.conf
scp mon1:/etc/ceph/ceph.conf /etc/ceph/ceph.conf
  1. 使用以下代码从监视节点复制 Ceph 密钥环:
scp mon1:/etc/ceph/ceph.client.admin.keyring /etc/ceph/ceph.client.admin.keyring
  1. 现在 Ganesha 已经安装完成,可以应用配置。可以使用来自独立 Ganesha 部分的相同配置,如下图所示:

与独立示例不同,我们必须确保 Ganesha 不会单独运行,只有 pacemaker 才能启动它。

现在,所有配置工作已完成,可以使用以下代码将 pacemaker 资源添加到控制 Ganesha 的运行:

crm configure primitive p_ganesha systemd:nfs-ganesha op monitor interval=10s

最后,我们需要确保 Ganesha 服务与虚拟 IP 在同一节点上运行。我们可以通过使用以下代码创建一个组资源来实现这一点。组资源确保所有资源在同一节点上一起运行,并且按定义的顺序启动:

crm configure group g_NFS p_VIP-NFS p_ganesha

现在,如果我们检查集群状态,可以看到 Ganesha 服务正在运行,并且由于分组,它与虚拟 IP 运行在同一节点上,如下图所示:

NFS 客户端现在应该能够连接到虚拟 IP 并映射 NFS 共享。如果集群节点发生故障,虚拟 IP 和 Ganesha 服务将迁移到另一个集群节点,客户端应该只会看到短暂的服务中断。

为了检查故障转移能力,我们可以将运行中的集群节点置为standby模式,强制 Pacemaker 将资源转移到另一个节点上。

在当前示例中,资源正在节点nfs2上运行,命令如下:

crm node standby nfs2

我们现在可以看到节点nfs2已经进入standby模式,资源已迁移并在节点nfs3上运行。

总结

在本章中,你了解了不同的存储协议以及它们如何与 Ceph 的功能相匹配。你还学会了哪些协议最适合某些角色,并且应该能够在选择时做出明智的决策。

完成这些示例后,你应该也能清楚地理解如何通过 iSCSI、NFS 和 SMB 导出 Ceph 存储,使非本地 Ceph 客户端能够使用 Ceph 存储。

最后,你还应该了解设计和构建一个可用的故障转移集群的要求,能够为非本地客户端提供高可用的 Ceph 存储。

在下一章,我们将讨论不同类型的 RADOS 池和可以配置的不同类型的 Ceph 存储。

问题

  1. 本章讨论的三种存储协议是什么?

  2. 通常使用哪种存储协议通过 IP 网络提供块存储?

  3. Windows 客户端主要使用哪种存储协议?

  4. 用户空间的 NFS 服务器叫什么?

  5. 用于构建故障转移集群的两个软件是什么?

  6. 为什么你可能想通过 NFS 将 CephFS 导出到 Linux 客户端?

第二部分:操作与调优

本节结束时,读者将能够对一个基础的 Ceph 安装进行配置,以最好地满足其使用案例的需求。

本节包含以下章节:

  • 第五章,RADOS 池和客户端访问

  • 第六章,使用 Librados 开发

  • 第七章,使用 Ceph RADOS 类进行分布式计算

  • 第八章,监控 Ceph

  • 第九章,Ceph 调优

  • 第十章,使用 Ceph 进行分层存储

第五章:RADOS 池和客户端访问

Ceph 提供了多种不同的池类型和配置。它还支持几种不同的数据存储类型,为客户端提供存储。本章将探讨复制和消除码池之间的区别,并示例创建和维护两者的方法。然后我们将介绍如何为 RADOS 块设备(RBD)、对象和 CephFS 使用这些池。最后,我们将看看如何对不同类型的存储方法进行快照。本章涵盖以下主题:

  • Ceph 存储类型

RADOS 池是 Ceph 集群的核心部分。创建 RADOS 池驱动了放置组的创建和分布,它们本身是 Ceph 的自治部分。可以创建两种类型的池,即复制和消除码,提供不同的可用容量、耐用性和性能。然后,RADOS 池可以用于通过 RBS、CephFS 和 RGW 为客户端提供不同的存储解决方案,或者用于启用层次化性能覆盖其他 RADOS 池。

复制池

在 Ceph 中,复制的 RADOS 池是默认的池类型;数据从客户端接收到主要的 OSD,然后复制到其余的 OSD。复制的逻辑相对简单,只需最少的处理来计算和在 OSD 之间复制数据。然而,由于数据完全复制,写入惩罚较大,因为数据必须多次写入各个 OSD。默认情况下,Ceph 将使用 3 倍的复制因子,因此所有数据将被写入三次;这并不考虑可能存在于 Ceph 堆栈下游的任何其他写入放大。这种写入惩罚有两个主要缺点:显然会给 Ceph 集群增加更多的 I/O 负载,因为需要写入更多数据,在 SSD 的情况下,这些额外的写入将更快地磨损闪存单元。然而,正如我们将在消除码池部分看到的那样,对于较小的 I/O,更简单的复制策略实际上导致所需的总操作更少——无论 I/O 的大小如何,总是存在固定的 3 倍写入惩罚。

还应该注意,尽管在客户端写入操作期间将对象的所有副本都写入,但在读取对象时,只涉及保存对象副本的主要 OSD。客户端也仅将写操作发送到主要 OSD,然后主要 OSD 将操作发送到其余副本。这种行为有多种原因,但主要是为了确保读取的一致性。

如上所述,默认的复制大小为 3,需要至少两个副本以接受客户端 I/O。不推荐减少这些值,增加它们可能对增加数据耐久性几乎没有影响,因为丢失所有共享相同 PG 的三个 OSD 的机会极其罕见。由于 Ceph 将优先恢复副本最少的 PG,这进一步减少了数据丢失的风险,因此,将副本数量增加到四个只有在提高数据可用性时才有益,其中两个共享相同 PG 的 OSD 可以丢失并允许 Ceph 继续为客户端 I/O 提供服务。但是,由于四个副本的存储开销,建议此时考虑使用纠删码。随着 NVMes 的引入,由于其更快的性能可以减少重建时间,使用副本大小为 2 仍然可以提供合理的数据耐久性。

要创建一个复制池,请发出如下示例中的命令:

ceph osd pool create MyPool 128 128 replicated

这将创建一个包含128个放置组的复制池,称为MyPool

纠删码池

Ceph 的默认复制级别通过在不同的 OSD 上存储数据的三个副本,提供了极好的数据丢失保护。然而,存储数据的三个副本大大增加了硬件的购买成本以及相关的运营成本,如电力和冷却。此外,存储副本也意味着对于每个客户端写入,后端存储必须写入三倍的数据量。在某些情况下,这些缺点可能意味着 Ceph 不是一个可行的选择。

纠删码旨在提供解决方案。就像 RAID 5 和 6 相对于 RAID 1 提供了更高的可用存储容量一样,纠删码允许 Ceph 从相同的原始容量提供更多的可用存储。然而,就像基于奇偶校验的 RAID 级别一样,纠删码也带来了自己的一系列缺点。

什么是纠删码?

纠删码允许 Ceph 通过将对象分割为若干部分,然后计算一种类型的循环冗余校验CRC),即纠删码,然后将结果存储在一个或多个额外的部分中,实现更大的可用存储容量或在相同数量的磁盘故障时增加的弹性,相对于标准的复制方法。纠删码通过在单独的 OSD 上存储这些部分来实现这一点。这些部分被称为KM块,其中K指的是数据碎片的数量,M指的是纠删码碎片的数量。与 RAID 一样,这些通常可以表示为K+M,或者例如4+2

如果包含对象碎片的 OSD 发生故障,该碎片是计算擦除码之一,则数据将从存储数据没有影响的其余 OSD 中读取。然而,如果包含对象数据碎片的 OSD 发生故障,Ceph 可以使用擦除码从剩余数据和擦除码碎片的组合中数学上重新创建数据。

K+M

拥有更多的擦除码碎片(erasure code shards),可以容忍更多的 OSD 故障,并且仍然可以成功读取数据。同样,对象分割为KM碎片的比率直接影响每个对象所需的原始存储百分比。

3+1配置将为您提供 75%的可用容量,但仅允许单个 OSD 故障,因此不建议使用。相比之下,三倍复制池只能提供 33%的可用容量。

4+2配置将为您提供 66%的可用容量,并允许两个 OSD 故障。这可能是大多数人使用的良好配置。

另一方面,18+2将为您提供 90%的可用容量,并且仍然允许两个 OSD 故障。表面上,这听起来是一个理想的选择,但更多的碎片总数会带来成本。更多的碎片总数会对性能产生负面影响,并增加 CPU 需求。同样大小的 4 MB 对象在复制池中将作为整个单一对象存储,现在将被分割成 20 个 200 KB 的块,这些块必须被跟踪并写入 20 个不同的 OSD。旋转磁盘将展示更快的带宽,以 MBps 为单位,使用更大的 I/O 大小进行测量,但在更小的 I/O 大小下,带宽急剧下降。这些更小的碎片将生成大量的小型 I/O,并对一些集群增加额外负载。

另外,重要的是不要忘记这些碎片需要根据 CRUSH 映射规则分布在不同的主机上:属于同一对象的任何碎片不能存储在同一主机上的另一个碎片上。某些集群可能没有足够数量的主机来满足此要求。如果无法满足 CRUSH 规则,PGs 将无法变为活动状态,并且任何发送到这些 PG 的 I/O 将被停止,因此了解对集群健康的影响对于进行 CRUSH 修改至关重要。

从这些大块池中读取数据也是一个问题。与副本池不同,在副本池中,Ceph 可以从对象中的任何偏移位置读取请求的数据,而在擦除池中,必须读取来自所有 OSD 的所有分片,才能满足读取请求。在18+2的示例中,这可能大幅增加所需的磁盘读取操作数量,平均延迟也会因此增加。这种行为是一个副作用,通常只会在使用大量分片的池中导致性能影响。在某些情况下,4+2配置相比副本池能获得性能提升,这是因为对象被分割成多个分片。由于数据实际上被条带化存储在多个 OSD 上,每个 OSD 需要写入的数据更少,而且没有二级和三级副本需要写入。

擦除编码也可以用来提高耐久性,而不仅仅是最大化可用存储空间。例如,一个4+4池:它的存储效率为 50%,所以它比 3x 副本池更好,但它可以在没有数据丢失的情况下承受最多四个 OSD 的损失。

擦除编码在 Ceph 中是如何工作的?

与复制一样,Ceph 也有主 OSD 的概念,这在使用擦除编码池时也存在。主 OSD 负责与客户端进行通信,计算擦除分片,并将其发送到 PG 集合中的其余 OSD。下图说明了这一点:

如果集合中的某个 OSD 宕机,主 OSD 可以使用剩余的数据和擦除分片来重建数据,然后将其发送回客户端。在读操作期间,主 OSD 会请求 PG 集合中的所有 OSD 发送它们的分片。主 OSD 使用数据分片中的数据来构建请求的数据,而擦除分片则会被丢弃。擦除池可以启用快速读取选项,允许主 OSD 从擦除分片中重建数据,如果它们比数据分片返回得更快。这可以帮助降低平均延迟,但会稍微增加 CPU 使用率。下图展示了 Ceph 如何从擦除编码池读取数据:

下图展示了当数据分片不可用时,Ceph 如何从擦除池中读取数据。数据通过反向擦除算法进行重建,使用剩余的数据和擦除分片:

算法和配置文件

你可以使用多种不同的擦除插件来创建你的擦除编码池。

Jerasure

Ceph 中的默认擦除插件是jerasure插件,它是一个高度优化的开源擦除编码库。该库有多种不同的技术可用于计算擦除码。默认使用的是Reed-Solomon,这在现代处理器上提供了良好的性能,因为该技术可以加速使用的指令。

还有许多其他的 jerasure 技术可以使用,它们都有固定数量的 M 分片。如果你只打算使用两个 M 分片,它们是很好的候选者,因为它们的固定大小意味着可以进行优化,从而提升性能。每种优化技术除了只支持两个纠删分片外,通常还对总分片数量有一定要求。以下是每种优化技术的简要描述:

  • reed_sol_van: 默认技术,支持 k+m 分片数量的完全灵活性,但也是最慢的。

  • reed_sol_r6_op: 默认技术的优化版,适用于 m=2 的使用场景。尽管它比未优化版本快,但不如其他版本快。然而,k 分片的数量是灵活的。

  • cauchy_orig: 优于默认技术,但最好使用 cauchy_good

  • cauchy_good: 在保持分片配置完全灵活性的同时,表现适中。

  • liberation: 总分片数量必须为质数且 m=2,因此 3+25+29+2 都是不错的候选者,性能极佳。

  • liber8tion: 总分片数量必须为 8m=2,仅 6+2 可用,但性能极佳。

  • blaum_roth: 总分片数量必须比质数少一个且 m=2,因此理想的配置是 4+2,性能极佳。

一如既往,应该在将任何生产数据存储到纠删码池之前进行基准测试,以识别最适合你工作负载的技术。

通常情况下,除非其他配置有明显优势,否则应优先使用 jerasure 配置,因为它提供了均衡的性能,并经过充分测试。

ISA

ISA 库设计用于支持英特尔处理器,并提供增强的性能。它支持 Reed-Solomon 和 Cauchy 技术。

LRC

在分布式存储系统中使用纠删码的一个缺点是,恢复操作可能会对主机间的网络造成很大压力。由于每个分片存储在不同的主机上,恢复操作需要多个主机参与。当 CRUSH 拓扑跨越多个机架时,这会对机架间的网络链路造成压力。本地可修复纠删码 (LRC) 插件增加了一个额外的奇偶校验分片,这个分片是本地存储在每个 OSD 节点的。这使得恢复操作可以保持在 OSD 故障的节点本地进行,而无需其他节点从所有剩余分片持有节点接收数据。

然而,添加这些本地恢复码确实会影响给定磁盘数量的可用存储量。在发生多磁盘故障时,LRC 插件必须转而使用全局恢复,就像 jerasure 插件一样。

SHEC

SHEC 配置文件与 LRC 插件的目标类似,都是为了减少恢复过程中网络带宽的需求。然而,SHEC 并不像 LRC 那样在每个节点上创建额外的奇偶校验碎片,而是通过重叠的方式将碎片分布在多个 OSD 中。插件名称中的shingle部分代表了数据分布方式,类似于房屋屋顶上的瓦片重叠。通过在 OSD 之间重叠奇偶校验碎片,SHEC 插件减少了单盘和多盘故障恢复时的资源需求。

擦除编码池的覆盖写支持

尽管擦除编码池的支持已经在 Ceph 的多个版本中推出,但在 Luminous 版本引入 BlueStore 之前,Ceph 并不支持部分写入。这个限制意味着擦除池无法直接用于 RBD 和 CephFS 工作负载。随着 Luminous 版本中 BlueStore 的引入,为部分写入支持的实现奠定了基础。引入部分写入支持后,擦除池能够支持的 I/O 类型几乎与复制池相匹配,从而使擦除编码池可以直接用于 RBD 和 CephFS 工作负载。这大大降低了这些用例的存储容量成本。

对于完全条带写入,这种情况发生在新对象或者整个对象被重写时,写入惩罚大大减少。一个客户端将一个 4MB 的对象写入一个 4+2 擦除编码池时,只需要写入 6MB 的数据,其中 4MB 是数据块,2MB 是擦除编码块。与在复制池中写入 12MB 数据相比,这大大减少了数据量。然而,需要注意的是,每个擦除条带的块会被写入到不同的 OSD 中。对于较小的擦除配置文件,例如4+2,这通常会显著提升旋转磁盘和 SSD 的性能,因为每个 OSD 写入的数据量较少。然而,对于较大的擦除条带,随着需要写入的 OSD 数量不断增加,这种增加的开销开始抵消减少写入数据量的好处,尤其是在旋转磁盘上,因为其延迟与 I/O 大小并不成线性关系。

Ceph 的用户空间客户端,如librbdlibcephfs,足够智能,能够将较小的 I/O 操作批量合并,并在可能的情况下提交一个完整的条带写入;当应用程序之前提交的是顺序 I/O 操作,但未对齐到 4MB 对象边界时,这可以提供帮助。

部分写入支持允许对对象进行覆盖写入;这会引入一些复杂性,因为当进行部分写入时,擦除块也需要更新,以匹配新的对象内容。这与 RAID 5 和 RAID 6 面临的挑战非常相似,尽管需要在多个 OSD 之间协调这一过程,并保持一致性,增加了复杂性。当进行部分写入时,Ceph 首先从磁盘读取整个现有对象,然后必须在内存中合并新的写入,计算新的擦除编码块,并将所有内容写回磁盘。因此,不仅涉及读写操作,而且每个操作可能会涉及多个磁盘,形成擦除条带。正如你所看到的,一个 I/O 操作最终可能会比复制池的写入惩罚高出几倍。对于一个4+2擦除编码池,一个小的 4 KB 写入可能会将 12 个 I/O 提交到集群中的磁盘,而不考虑任何额外的 Ceph 开销。

创建擦除编码池

让我们再次启动测试集群并切换到 Linux 的超级用户模式,这样就不需要在每个命令前面加上sudo了。

擦除编码池通过使用擦除配置文件来控制;这些配置文件控制每个对象被分割成多少个碎片,包括数据碎片和擦除碎片之间的分配。配置文件还包括确定使用哪个擦除编码插件来计算哈希值的配置。

以下插件可供使用:

  • Jerasure

  • ISA

  • LRC

  • 叠加擦除编码SHEC

要查看擦除配置文件列表,请运行以下命令:

 # ceph osd erasure-code-profile ls

你可以看到,在 Ceph 的全新安装中存在一个default配置文件:

让我们使用以下命令查看它包含的配置选项:

 # ceph osd erasure-code-profile get default

default配置文件指定将使用 jerasure 插件和 Reed-Solomon 错误纠正码,并将对象分割为2个数据碎片和1个擦除碎片:

这对于我们的测试集群几乎是完美的;然而,为了完成这次练习,我们将使用以下命令创建一个新配置文件:

 # ceph osd erasure-code-profile set example_profile k=2 m=1
    plugin=jerasure technique=reed_sol_van # ceph osd erasure-code-profile ls

你可以看到我们的新example_profile已经创建:

现在,让我们使用此配置文件创建擦除编码池:

 # ceph osd pool create ecpool 128 128 erasure example_profile

前面的命令给出了以下输出:

前面的命令指示 Ceph 创建一个名为ecpool的新池,并使用128个 PG。它应该是一个擦除编码池,并且应该使用我们之前创建的example_profile

让我们创建一个包含小文本字符串的对象,然后通过读取它来证明数据已经被存储:

 # echo "I am test data for a test object" | rados --pool
    ecpool put Test1 –
    # rados --pool ecpool get Test1 -

这证明了擦除编码池正在正常工作,但这并不是最令人激动的发现:

让我们检查一下是否能够看到更低层级发生的事情。

首先,找出存储我们刚创建的对象的 PG:

 # ceph osd map ecpool Test1

前面的命令结果告诉我们,在这个示例 Ceph 集群中,PG 3.40 对象被存储在 OSDs 120 中。这是显而易见的,因为我们只有三个 OSD,但在更大的集群中,这是一个非常有用的信息:

PG 在你的测试集群中可能不同,因此请确保 PG 文件夹结构与前面 ceph osd map 命令的输出匹配。

如果你使用的是 BlueStore,默认情况下 OSD 的文件结构无法查看。然而,你可以在停止的 OSD 上使用以下命令,将 BlueStore OSD 挂载为 Linux 文件系统。

我们现在可以查看 OSD 的文件夹结构,使用以下命令查看对象是如何被拆分的:

ceph-objectstore-tool --op fuse --data-path /var/lib/ceph/osd/ceph-0 --mountpoint /mnt

以下示例使用的是 filestore;如果使用的是 BlueStore,请用前面命令中 /mnt 挂载点的内容替换 OSD 路径:

 ls -l /var/lib/ceph/osd/ceph-2/current/1.40s0_head/

前面的命令给出了以下输出:

 # ls -l /var/lib/ceph/osd/ceph-1/current/1.40s1_head/

前面的命令给出了以下输出:

 # ls -l /var/lib/ceph/osd/ceph-0/current/1.40s2_head/ total 4

前面的命令给出了以下输出:

注意,PG 目录名已附加到分片编号上,并且复制池的目录名仅包含 PG 编号。如果你检查对象文件的内容,你会看到我们在创建对象时输入的文本字符串。然而,由于文本字符串的大小较小,Ceph 用空字符填充了第二个分片和擦除分片;因此,它们的内容与第一个分片相同。你可以用一个包含更多文本的新对象重复这个示例,看看 Ceph 是如何将文本拆分成分片并计算擦除码的。

排查 2147483647 错误

本小节包含在擦除编码中,而不是本书的第十一章 故障排除 部分,因为它通常出现在擦除编码池中,因此与本章非常相关。以下屏幕截图显示了运行 ceph health detail 命令时出现的错误示例:

如果你看到2147483647作为擦除编码池的一个 OSD,通常意味着 CRUSH 无法找到足够的 OSD 来完成 PG 对等过程。这通常是由于 K+M 碎片数大于 CRUSH 拓扑中主机数量。然而,在某些情况下,即使主机数量等于或大于碎片数量,这个错误仍然可能发生。在这种情况下,了解 CRUSH 如何选择 OSD 作为数据放置的候选项非常重要。当 CRUSH 用于查找 PG 的候选 OSD 时,它会应用 CRUSH 映射以找到 CRUSH 拓扑中的合适位置。如果结果与之前选定的 OSD 相同,Ceph 会通过向 CRUSH 算法传入略有不同的值来重新生成另一个映射。在某些情况下,如果主机数量与擦除碎片数量相似,CRUSH 可能会在找到正确的 OSD 映射之前耗尽尝试次数。较新的 Ceph 版本已经通过增加 CRUSH 可调参数choose_total_tries大多数解决了这些问题。

复现问题

为了更详细地帮助我们理解问题,接下来的步骤将展示如何创建一个需要比我们三节点集群支持更多碎片的erasure-code-profile

如同本章早些时候所做的那样,创建一个新的擦除配置文件,但将 K/M 参数修改为k=3m=1

 $ ceph osd erasure-code-profile set broken_profile k=3 m=1
    plugin=jerasure technique=reed_sol_van

现在创建一个池:

 $ ceph osd pool create broken_ecpool 128 128 erasure broken_profile

如果我们查看ceph -s的输出,我们将看到该新池的 PG 卡在创建状态:

ceph health detail的输出显示了原因,并且我们看到2147483647错误:

如果你遇到此错误,并且它是由于你的擦除配置文件大于你的主机或机架数量而导致的,根据你设计的 CRUSH 映射,唯一的解决办法是减少碎片数量或增加主机数量。

要创建一个擦除编码池,请发出如下命令:

ceph osd pool create MyECPool 128 128 erasure MyECProfile

这将创建一个具有 128 个放置组的擦除编码池,名为MyECPool,并使用名为MyECProfile的擦除编码配置文件。

尽管部分写入使擦除编码池在支持的功能方面几乎与复制池相媲美,但它们仍然无法存储所有 RBD 所需的数据。因此,在创建 RBD 时,必须将 RBD 头对象放置在一个复制的 RADOS 池中,然后指定该 RBD 的数据对象应存储在擦除编码池中。

清理

为了防止位腐化,Ceph 定期运行名为 scrubbing 的过程,以验证存储在 OSD 上的数据。scrubbing 过程在 PG 级别进行,比对所有参与的 OSD 上每个 PG 的内容,检查每个 OSD 是否具有相同的内容。如果发现某个 OSD 上的对象副本与其他 OSD 不同,甚至缺少该对象,PG 会被标记为不一致。可以通过指示 Ceph 修复 PG 来修复不一致的 PG;这一过程将在第十一章中详细讲解,故障排除

Scrubbing 有两种类型:正常和深度。正常 scrubbing 仅检查对象是否存在及其元数据是否正确;深度 scrubbing 是指对实际数据进行比较。深度 scrubbing 通常比正常 scrubbing 更加 I/O 密集。

尽管 BlueStore 现在支持校验和,但 scrubbing 的需求并没有完全消失。BlueStore 仅比较正在主动读取的数据的校验和,因此对于很少写入的冷数据,可能会发生数据丢失或损坏,只有 scrubbing 过程才能检测到这一点。

有一些 scrubbing 调优选项将在第九章中讲解,调优 Ceph;它们会影响 scrubbing 过程的调度时机以及对客户端 I/O 的影响。

Ceph 存储类型

尽管 Ceph 通过 RADOS 层提供基本的对象存储,但单独使用它并不方便,因为直接使用 RADOS 存储的应用场景极为有限。因此,Ceph 在基础 RADOS 能力的基础上构建,并提供更高级的存储类型,使客户端更容易使用。

RBD

RBD 简称,是 Ceph 存储可以呈现为标准 Linux 块设备的方式。RBD 由多个对象组成,默认每个对象为 4 MB,这些对象被串联在一起。一个 4 GB 的 RBD 默认包含 1,000 个对象。

精简配置

由于 RADOS 的工作方式,RBD 是精简配置的;也就是说,底层对象仅在数据写入对应的逻辑块地址时才会被配置。对此没有任何保护措施;Ceph 会允许你在 1 TB 磁盘上配置一个 1 PB 的块设备,只要你从未在上面存放超过 1 TB 的数据,一切都将按预期工作。如果正确使用,精简配置可以大大增加 Ceph 集群的可用容量,因为虚拟机(通常是 RBD 的主要使用场景之一)可能包含大量的空白空间。然而,应该小心监控 Ceph 集群中数据的增长;如果底层的可用容量已满,Ceph 集群将实际上离线,直到腾出空间。

快照和克隆

RBD 支持对其进行快照操作。快照是 RBD 镜像的只读副本,能够保留其在拍摄时刻的状态。如果需要,可以拍摄多个快照以保存 RBD 随时间变化的历史。拍摄 RBD 快照的过程非常迅速,且不会对读取源 RBD 的性能产生影响。然而,当写入操作首次影响源 RBD 时,快照会为我们克隆对象的现有内容,之后的进一步 I/O 将不会产生额外的影响。这个过程被称为写时复制(copy-on-write),它是存储产品中执行快照的标准方式。需要注意的是,在 BlueStore 中这一过程的加速效果显著,因为不像 filestore 中那样需要完整复制对象,尽管仍然需要小心确保那些经历大量写 I/O 的 RBD 不会长时间保留打开的快照。同时,需要注意那些在写入过程中会消耗额外 I/O 的快照——由于写时复制过程会创建对象的克隆,因此会消耗额外的集群空间——在使用快照时,需注意监控空间的使用情况。

在删除快照的过程中,包含快照对象的 PG 会进入一个快照修剪状态。在此状态下,作为写时复制过程一部分被克隆的对象将被移除。同样,在 BlueStore 中,这一过程对集群负载的影响更小。

RBD 还支持快照分层;这是一种从现有快照创建可写克隆的过程,而该快照本身是现有 RBD 的快照。这个过程通常用于创建主镜像的克隆虚拟机(VM);首先为虚拟机创建一个初始 RBD,并安装操作系统。然后,在主镜像的生命周期中不断拍摄快照,以捕捉变化。这些快照将作为克隆新虚拟机的基础。当最初克隆 RBD 快照时,RBD 中的对象非克隆部分不需要重复创建,因为它们与源对象相同,只需由克隆引用即可。一旦克隆的 RBD 开始写入数据,每个被修改的对象将被写入为属于该克隆的新对象。

这种对象引用的过程意味着,许多共享相同操作系统模板的虚拟机,可能会比每个虚拟机单独部署到新的 RBD 时消耗更少的空间。在某些情况下,可能希望强制进行完全克隆,即复制所有 RBD 对象;在 Ceph 中,这个过程被称为克隆扁平化(flattening a clone)。

首先,在默认 RBD 池中为一个名为 test 的 RBD 镜像创建一个名为 snap1 的快照:

rbd snap create rbd/test@snap1

通过查看 RBD 的所有快照,确认快照已经创建:

rbd snap ls rbd/test

对于要克隆的快照,需要对其进行保护。由于克隆依赖于快照,任何对快照的修改可能会导致克隆损坏:

rbd snap protect rbd/test@snap1

查看快照信息;可以看到该快照现在已经被保护:

rbd info rbd/test@snap1

现在可以创建快照的克隆:

rbd clone rbd/test@snap1 rbd/CloneOfTestSnap1

你可以通过查看克隆的rbd info来确认克隆与快照的关系:

rbd info rbd/CloneOfTestSnap1

或者你可以通过查看快照的子项列表来执行此操作:

rbd children rbd/test@snap1

现在展开克隆;这将使其成为一个完全独立的 RBD 镜像,不再依赖于快照:

rbd flatten rbd/CloneOfTestSnap1

确认克隆现在不再附加到快照;请注意,父字段现在已缺失:

rbd info rbd/CloneOfTestSnap1

取消快照保护:

rbd snap unprotect rbd/test@snap1

最后删除它:

rbd snap rm rbd/test@snap1

对象映射

由于 RBD 支持薄配置,并由大量 4MB 的对象组成,确定 RBD 消耗了多少空间,或克隆 RBD 等任务,将涉及大量的读取请求,以确定 RBD 中某个对象是否存在。为了解决这个问题,RBD 支持对象映射;这些映射指示了 RBD 中哪些逻辑块已被分配,因此大大加速了计算哪些对象存在的过程。对象映射作为一个对象存储在 RADOS 池中,不应直接操作。

独占锁定

为了防止两个客户端同时写入同一个 RBD,独占锁定允许客户端获取锁,以禁止其他客户端写入 RBD。需要注意的是,客户端始终可以请求将锁转移给自己,因此该锁只是用来保护 RBD 设备本身;如果两个客户端尝试挂载非集群化的文件系统,它仍然很可能会被损坏,无论是否启用了独占锁定。

CephFS

CephFS 是一个 POSIX 兼容的文件系统,建立在 RADOS 池之上。POSIX 兼容意味着它应该能够作为任何其他 Linux 文件系统的替代品正常运行,并保持预期功能。CephFS 提供了内核和用户空间客户端,允许在运行的 Linux 系统上挂载该文件系统。虽然内核客户端通常速度更快,但它在支持功能方面往往滞后于用户空间客户端,并且通常需要你运行最新的内核才能利用某些功能和修复。CephFS 文件系统也可以通过 NFS 或 Samba 导出给非 Linux 客户端,这两款软件直接支持与 CephFS 的通信。下一章将详细介绍这一主题。

CephFS 将每个文件存储为一个或多个 RADOS 对象。如果一个对象大于 4 MB,它将被切分到多个对象中。这种切分行为可以通过使用 XATTRs 来控制,XATTRs 可以与文件和目录相关联,并控制对象大小、条带宽度和条带数量。默认的切分策略实际上将多个 4 MB 的对象连接在一起,但通过修改条带数量和宽度,可以实现 RAID 0 风格的切分。

MDS 及其状态

CephFS 需要一个额外的组件来协调客户端访问和元数据;这个组件被称为元数据 服务器,简称MDS。虽然 MDS 用于处理客户端的元数据请求,但实际的数据读取和写入仍然直接通过 OSD 进行。这种方法最小化了 MDS 对文件系统性能的影响,特别是对于大量数据传输,尽管较小的 I/O 密集型操作可能会受到 MDS 性能的限制。目前 MDS 以单线程进程运行,因此建议将 MDS 运行在具有最高时钟频率的硬件上。

MDS 有一个本地缓存,用于存储 CephFS 元数据的热点部分,以减少访问元数据池的 I/O 量;该缓存存储在本地内存中以提高性能,并可以通过调整 MDS 缓存内存限制配置选项来控制,默认值为 1 GB。

CephFS 利用存储在 RADOS 中的日志,主要是出于一致性考虑。该日志存储来自客户端的元数据更新流,然后将其刷新到 CephFS 元数据存储中。如果 MDS 终止,接管活动角色的 MDS 可以回放存储在日志中的元数据事件。回放日志的过程是 MDS 变为活动状态的关键部分,因此该过程会阻塞,直到完成。通过使用一个备用回放 MDS,持续回放日志并准备在较短时间内接管主活动角色,可以加速该过程。如果有多个活动 MDS,纯备用 MDS 可以作为任何活动 MDS 的备用,而备用回放 MDS 必须分配给特定的 MDS 等级。

除了活动和回放状态外,MDS 还可以处于其他几个状态;在操作 Ceph 集群并使用 CephFS 文件系统时,您可能会在 ceph 状态中看到的状态列出如下,供参考。状态分为两部分:冒号左侧的部分表示 MDS 是处于 up 还是 down 状态。冒号右侧的部分表示当前的操作状态:

  • up:active:这是正常期望的状态,只要一个 MDS 处于该状态,客户端就可以访问 CephFS 文件系统。

  • up:standby:只要有一个 MDS 处于 up:active 状态,这可以是一个正常状态。在此状态下,MDS 处于在线状态,但不参与 CephFS 基础设施的任何活动。当活动 MDS 上线时,备用 MDS 会上线并回放 CephFS 日志。

  • up:standby_replay:与 up:standby 状态类似,处于此状态的 MDS 在活动 MDS 下线时可用以变为活动状态。然而,standby_replay MDS 会持续回放其被配置为跟随的 MDS 的日志,这意味着故障转移时间大大减少。需要注意的是,虽然备用 MDS 可以替代任何活动 MDS,但 standby_replay MDS 只能替代它被配置为跟随的 MDS。

  • up:replay:在此状态下,MDS 已开始接管活动角色,并且正在回放存储在 CephFS 日志中的元数据。

  • up:reconnect:如果在活动 MDS 上线时有客户端会话处于活动状态,恢复中的 MDS 将在此状态下尝试重新建立客户端连接,直到客户端超时为止。

虽然 MDS 可以处于其他状态,但在正常操作过程中这些状态不太可能被看到,因此未在此包含。有关所有可用状态的详细信息,请参考官方 Ceph 文档。

创建 CephFS 文件系统

要创建一个 CephFS 文件系统,需要两个 RADOS 池:一个用于存储元数据,另一个用于存储实际的数据对象。尽管技术上可以使用任何现有的 RADOS 池,但强烈建议创建专用池。与数据池相比,元数据池通常只包含较小比例的数据,因此在配置此池时,所需的 PG 数量通常可以设置在 64 到 128 范围内。数据池的配置应类似于 RBD 池,PG 的数量需要根据集群中的 OSD 数量和 CephFS 文件系统将要存储的数据量来计算。

至少需要部署一个 MDS,但建议在任何生产环境部署中,至少部署两个 MDS,其中一个作为备用或备用回放运行。

编辑 /etc/ansible/hosts 文件,并添加将承载 mds 角色的服务器。以下示例使用了来自 第二章 的 mon2 虚拟机,使用容器部署 Ceph

现在重新运行 Ansible playbook,它将部署 mds

一旦 playbook 运行完成,检查 mds 是否正常运行;可以通过 ceph-s 输出查看:

Ansible 应该在部署过程中已配置数据池和元数据池;可以通过在其中一个监视节点上运行以下命令来确认:

sudo ceph osd lspools

从前述截图中,我们可以看到池 6 和池 7 已为 CephFS 创建。如果这些池尚未创建,请按照本章开始时的步骤创建 RADOS 池。虽然数据池可能作为纠删码池创建,但元数据池必须是复制类型的。

创建 CephFS 文件系统的最后一步是指示 Ceph 使用两个已创建的 RADOS 池来构建文件系统。然而,正如前面步骤所示,Ansible 部署应已处理此过程。我们可以通过运行以下命令来确认:

sudo ceph fs status 

如果 CephFS 文件系统已创建并准备好投入使用,则将显示以下内容:

如果 CephFS 文件系统未创建,可以使用以下命令创建它:

sudo ceph fs create <Filesystem Name> <Metadata Pool> <Data Pool>

现在 CephFS 文件系统已激活,它可以挂载到客户端,并像其他 Linux 文件系统一样使用。挂载 CephFS 文件系统时,需要通过挂载命令传递cephx用户密钥。该密钥可以从存储在/etc/ceph/目录中的密钥环中获取。在以下示例中,我们将使用管理员密钥环;在生产环境中,建议创建一个特定的cephx用户:

cat /etc/ceph/ceph.client.admin.keyring

哈希密钥是挂载 CephFS 文件系统所需的:

sudo mount -t ceph 192.168.0.41:6789:/ /mnt -o name=admin,secret=AQC4Q85btsqTCRAAgzaNDpnLeo4q/c/q/0fEpw==

在这个例子中,只有一个监视器被指定;在生产环境中,建议以逗号分隔的格式提供所有三个监视器的地址,以确保故障转移。

这里是确认文件系统已挂载的信息:

数据是如何存储在 CephFS 中的?

为了更好地理解 CephFS 是如何在对象存储之上映射一个 POSIX 兼容的文件系统的,我们可以更仔细地看看 Ceph 如何将文件 inode 映射到对象。

首先,让我们看一下一个名为test的文件,它存储在挂载在/mnt/tmp下的 CephFS 文件系统中。以下命令使用了熟悉的 Unix ls命令,但添加了一些额外的参数以显示更多细节,包括文件的 inode 号:

ls -lhi /mnt/tmp/test

以下截图是前述命令的输出:

输出显示文件的大小为 1 G,inode 号是最左边的长数字。

接下来,通过列出存储在 CephFS 数据池中的对象并 grep 该数字,我们可以找到负责保存该文件文件系统详细信息的对象。然而,在继续之前,我们需要将存储在十进制中的 inode 号转换为十六进制,因为 CephFS 是以对象名称的形式存储 inode 号的:

printf "%x\n" 1099511784612

以下截图是前述命令的输出:

现在我们可以在池中找到该对象;请注意,在数据量大的 CephFS 池上,这可能需要很长时间,因为它将列出后台的每个对象:

rados -p cephfs_data ls | grep 100000264a4 | wc -l

注意,发现了 256 个对象。默认情况下,CephFS 将较大的文件分解为 4 MB 的对象,256 个对象的总大小等于 1 GB 文件的大小。

实际对象存储的与在 CephFS 文件系统中可见的文件完全相同的数据。如果一个文本文件保存在 CephFS 文件系统中,它的内容可以通过将底层对象与 inode 编号匹配,并使用rados命令下载该对象来读取。

cephfs_metadata池存储了 CephFS 文件系统中所有文件的元数据;这些数据包括修改时间、权限、文件名以及文件在目录树中的位置等值。如果没有这些元数据,存储在数据池中的数据对象实际上只是随机命名的对象;数据仍然存在,但对人工操作员来说几乎没有意义。因此,CephFS 元数据的丢失并不会导致实际数据丢失,但仍然会使数据变得或多或少无法读取。因此,应像保护其他 RADOS 池一样保护元数据池。对于元数据丢失,有一些高级恢复步骤可能会有所帮助,具体内容可以参见第十二章,灾难恢复

文件布局

CephFS 允许你通过使用称为文件布局的设置来改变文件在底层对象中的存储方式。文件布局允许你控制条带大小和宽度,并且还可以指定数据对象所在的 RADOS 池。文件布局作为扩展属性存储在文件和目录上。新的文件或目录将继承其父目录的文件布局设置;然而,对父目录布局的进一步更改不会影响已存在的文件。

调整文件条带化通常是出于性能原因,以增加读取大文件时的并行性,因为数据的某个部分将被分布到更多的 OSD 中。默认情况下,不使用条带化,存储在 CephFS 中的大文件将仅跨越多个 4 MB 大小的对象。

文件布局还可以用来改变文件对象存储在哪个数据池中。这对于允许不同目录存储热数据和冷数据可能非常有用,其中热文件可能存储在一个 3 倍冗余的 SSD 池中,而冷文件存储在由旋转磁盘支持的纠删码池中。一个很好的例子是可能有一个名为Archive/的子目录,用户可以将不再预计日常使用的文件复制到该目录。复制到该目录中的任何文件都会存储在纠删码池中。

可以使用setfattrgetfattr工具查看和编辑文件布局:

getfattr -n ceph.file.layout /mnt/test

可以看出,默认的文件布局是将测试文件的数据对象存储在cephfs_data池中。还可以看到,该文件被分割成 4 MB 的对象,并且由于stripe_unit也是 4 MB,且stripe_count等于 1,因此没有使用条带化。

快照

CephFS 还支持到每个目录级别的快照;快照不需要包含整个 CephFS 文件系统。每个 CephFS 文件系统上的目录都包含一个隐藏的 .snap 目录;当在其中创建一个新的子目录时,实际上会创建一个快照,且该子目录中的视图将表示在快照创建时原目录的状态。

可以拍摄多个快照,并可以相互独立浏览,这使得快照能够作为短期归档方案的一部分来使用。当通过 Samba 导出 CephFS 时,其中一个使用方式是通过 Windows Explorer 的“先前版本”标签页来暴露快照功能。

在以下示例中,首先创建了一个测试文件,拍摄了快照,然后修改了该文件。通过检查实时文件和快照中的文件内容,我们可以看到 CephFS 快照的呈现方式:

多 MDS

CephFS 的新功能是支持多个活跃的 MDS。以前,建议只有一个活跃的 MDS 和一个或多个备用 MDS,这对于较小的 CephFS 部署来说足够了。然而,在较大的部署中,单个 MDS 可能开始成为限制,尤其是由于 MDS 的单线程限制。需要注意的是,多个活跃的 MDS 仅用于提高性能,并不提供任何故障转移或高可用性;因此,始终应配置足够的备用 MDS。

当多个活动的 MDS 存在时,CephFS 文件系统会被分配到每个 MDS 上,这样元数据请求就不会全部由单个 MDS 来处理。这个分配过程是在每个目录级别进行的,并且会根据元数据请求负载动态调整。这个分配过程涉及到创建新的 CephFS 排名;每个排名需要一个工作中的 MDS 才能使其激活。

在以下示例中,Ceph 集群中使用了三个活跃的 MDS 服务器。主 MDS 运行排名 0,始终托管 CephFS 根目录。第二个 MDS 为垂直条纹模式的目录提供元数据服务,因为它们的元数据负载相当高。所有其他目录的元数据仍由主 MDS 提供服务,因为它们几乎没有活动,唯一的例外是包含猫咪 GIF 的目录;该目录的元数据请求负载极高,因此有一个单独的排名和 MDS 分配给它,如水平模式所示:

RGW

RADOS 网关 (RGW) 通过 S3 或 Swift 兼容接口呈现 Ceph 原生的对象存储,这是两种最流行的对象 API 用于访问对象存储,S3 因为亚马逊 AWS S3 的成功而成为主流。本书的这一部分将主要关注 S3。

RGW 最近已更名为 Ceph 对象网关,尽管之前的名称仍然广泛使用。

Ceph 的 radosgw 组件负责将 S3 和 swift API 请求转换为 RADOS 请求。尽管它可以与其他组件一起安装,但出于性能考虑,建议将其安装在单独的服务器上。radosgw 组件完全无状态,因此非常适合放置在负载均衡器后面,从而实现横向扩展。

除了存储用户数据外,RGW 还需要一些额外的 RADOS 池来存储附加的元数据。除了索引池之外,这些池大多数使用非常少,因此可以使用较少的 PG(大约 64 通常就足够了)来创建。索引池有助于列出桶的内容,因此强烈建议将索引池放置在 SSD 上。数据池可以存放在旋转硬盘或 SSD 上,具体取决于存储的对象类型,尽管对象存储通常与旋转硬盘匹配得很好。由于客户端通常是远程的,WAN 连接的延迟会抵消 SSD 带来的许多优势。需要注意的是,只有数据池应当放置在擦除编码池上。

幸运的是,RGW 在第一次尝试访问所需池时会自动创建它们,从而简化了一些安装过程。然而,池是以默认设置创建的,可能您希望为数据对象存储创建一个擦除编码池。只要 RGW 服务未进行任何访问,数据池在创建后不应存在,因此可以手动创建为擦除池。只要名称与 RGW 区域的预期池名称匹配,RGW 将在第一次访问时使用该池,而不是尝试创建新的池。

部署 RGW

我们将使用在第二章中部署的 Ansible 实验环境,通过容器部署 Ceph,来部署 RGW。

首先,编辑 /etc/ansible/hosts 文件,并将 rgws 角色添加到 mon3 虚拟机:

我们还需要更新 /etc/ansible/group_vars/ceph 文件,添加 radosgw_address 变量;它将设置为 [::],意味着绑定到所有 IPv4 和 IPv6 接口:

现在再次运行 Ansible 剧本:

ansible-playbook -K site.yml

执行后,您应该能看到它已成功部署 RGW 组件:

从监视节点查看 Ceph 状态时,我们可以检查 RGW 服务是否已在 Ceph 集群中注册并正常运行:

现在 RGW 已经激活,用户帐户是与 S3 API 交互所必需的,可以使用以下所示的 radosgw-admin 工具来创建:

sudo radosgw-admin user create --uid=johnsmith --display-name="John Smith" --email=john@smith.com

注意命令的输出,特别是access_keysecret_key,它们用于与 S3 客户端一起认证 RGW。

要将对象上传到我们的 S3 兼容 Ceph 集群,首先需要创建一个 S3 桶。我们将使用s3cmd工具来完成这项工作,具体如下所示:

sudo apt-get install s3cmd

现在,s3cmd已安装,需要配置指向我们的 RGW 服务器;它有一个内置的配置工具,可用于生成初始配置。在配置向导中,它会提示输入在用户帐户创建时生成的访问密钥和秘密密钥,如下所示:

s3cmd --configure

生成的配置将指向亚马逊的 S3 服务;生成的配置文件需要进行编辑并修改一些选项。编辑 Linux 用户主目录中的.s3cfg文件,并进行以下更改:

nano .s3cfg

注释掉bucket_location变量:

host_basehost_buckets更改为匹配 RGW 的地址:

保存文件并退出回到 shell;s3cmd现在可以用于操作您的s3存储。以下示例将创建一个test桶,您可以在其中上传对象:

s3cmd mb s3://test

现在,您已经拥有了一个完全功能的 S3 兼容存储平台,准备好探索对象存储的世界。

总结

在本章中,您了解了复制池和纠删码池之间的差异,以及它们的优缺点。掌握这些信息后,您现在应该能够在决定使用复制池还是纠删池时做出最佳选择。您还对纠删码池的功能有了更深入的理解,这将有助于规划和操作。

现在,您应该对部署 Ceph 集群以提供块存储、文件存储和对象存储感到自信,并能够展示常规的管理任务。

在下一章中,我们将学习关于 librados 的内容,以及如何使用它来开发直接与 Ceph 通信的自定义应用程序。

问题

  1. 请列出两种不同的纠删编码技术。

  2. 当一个纠删码池对一个对象进行部分写入时,这个过程称为什么?

  3. 为什么您可能选择一个带有两个奇偶校验分片的纠删码配置文件?

  4. 将克隆的快照转换为完整的 RBD 镜像的过程叫什么?

  5. 运行 CephFS 文件系统需要什么 Ceph 守护进程?

  6. 为什么在 CephFS 文件系统中,您可能选择运行多个活动元数据服务器而不是单一的服务器?

  7. 运行 RGW 需要什么 Ceph 守护进程?

  8. Ceph 的 RGW 支持哪两种 API?

第六章:使用 Librados 进行开发

Ceph 通过内建接口提供块存储、文件存储和对象存储,能够满足大量用户的需求。然而,在内部开发应用程序的场景中,通过使用 librados 将应用程序直接与 Ceph 接口对接,可能会带来一些好处。Librados 是 Ceph 提供的一个库,允许应用程序直接读取和写入 Ceph 的 RADOS 层中的对象。

本章将涵盖以下主题:

  • 什么是 librados?

  • 使用 librados 以及它支持的语言

  • 编写一个示例 librados 应用程序

  • 编写一个使用 Python 将图像文件存储到 Ceph 中的 librados 应用程序

  • 使用 C++编写一个使用原子操作的 librados 应用程序

什么是 librados?

Librados 是一个 Ceph 库,你可以将其包含到你的应用程序中,允许你使用原生协议直接与 Ceph 集群进行通信。由于 librados 使用 Ceph 的原生通信协议进行通信,它允许你的应用程序充分利用 Ceph 的强大功能、速度和灵活性,而不必依赖诸如 Amazon S3 等高级协议。它提供了大量的功能,使得你的应用程序可以从简单的对象读取和写入,到需要将多个操作封装在一个事务中或异步执行的高级操作。Librados 支持多种编程语言,包括 C、C++、Python、PHP 和 Java。

如何使用 librados

要开始使用 librados,首先需要一个开发环境。对于本章中的示例,可以使用一个监视节点,既作为开发环境,又作为客户端运行开发的应用程序。本书中的示例假设你使用的是基于 Debian 的发行版:

  1. 安装操作系统的基础构建工具:
$ sudo apt-get install build-essential

前面的命令会输出以下内容:

  1. 安装librados开发库:
$ sudo apt-get install librados-dev

前面的命令会输出以下内容:

  1. 创建一个用 C 语言编写的快速应用程序,以建立与测试 Ceph 集群的连接:
$ mkdir test_app $ cd test_app
  1. 使用你喜欢的文本编辑器创建一个名为test_app.c的文件,并将以下内容放入其中:
       #include <rados/librados.h> 
       #include <stdio.h> 
       #include <stdlib.h> 

       rados_t rados = NULL; 

       int exit_func(); 

       int main(int argc, const char **argv) 
       { 
         int ret = 0; 
         ret = rados_create(&rados, "admin"); // Use the
         client.admin keyring 
         if (ret < 0) { // Check that the rados object was created 
           printf("couldn't initialize rados! error %d\n", ret); 
           ret = EXIT_FAILURE; 
           exit_func; 
         } 
         else 
           printf("RADOS initialized\n"); 

         ret = rados_conf_read_file(rados, "/etc/ceph/ceph.conf"); 
         if (ret < 0) { //Parse the ceph.conf to obtain cluster details 
           printf("failed to parse config options! error %d\n", ret); 
           ret = EXIT_FAILURE; 
           exit_func(); 
         } 
         else 
           printf("Ceph config parsed\n"); 

         ret = rados_connect(rados); //Initiate connection to the
         Ceph cluster 
         if (ret < 0) { 
           printf("couldn't connect to cluster! error %d\n", ret); 
           ret = EXIT_FAILURE; 
           exit_func; 
         } else { 
           printf("Connected to the rados cluster\n"); 
         } 

         exit_func(); //End of example, call exit_func to clean
         up and finish 

       } 

       int exit_func () 
       { 
         rados_shutdown(rados); //Destroy connection to the
         Ceph cluster 
         printf("RADOS connection destroyed\n"); 
         printf("The END\n"); 
         exit(0); 
       } 
  1. 通过运行以下命令编译测试应用程序:
$ gcc test_app.c -o test_app -lrados

需要注意的是,你需要告诉gcc链接到librados库,以便使用其功能。

  1. 通过运行应用程序来测试其是否正常工作。别忘了以 root 身份运行它,或使用sudo,否则你将无法访问 Ceph 密钥环:
sudo ./test_app

前面的命令会输出以下内容:

测试应用程序简单地读取你的ceph.conf配置文件,利用它建立与 Ceph 集群的连接,然后断开连接。它可能不是最激动人心的应用程序,但它测试了基本架构是否到位且正常工作,并为本章其余示例奠定了基础。

示例 librados 应用程序

我们现在将通过一些使用 librados 的示例应用程序,帮助你更好地理解使用该库能够完成的任务。

以下示例将带你完成创建一个应用程序的步骤,该程序在接收到一个图像文件作为参数时,将该图像存储为 Ceph 集群中的一个对象,并将图像文件的各种属性存储为对象属性。该应用程序还允许你检索对象并将其导出为图像文件。这个示例将使用 Python 编写,librados 也支持 Python。以下示例还使用了Python 图像库PIL)来读取图像的大小,并使用命令行解析库读取命令行参数:

  1. 安装 librados Python 绑定和图像处理库:
$ sudo apt-get install python-rados python-imaging

前述命令输出如下:

  1. 创建一个新的 Python 应用程序文件,文件扩展名为 .py,并输入以下内容:
       import rados, sys, argparse 
       from PIL import Image 

       #Argument Parser used to read parameters and generate --help 
       parser = argparse.ArgumentParser(description='Image to RADOS 
       Object Utility') 
       parser.add_argument('--action', dest='action', action='store',
       required=True, help='Either upload or download image to/from 
       Ceph') 
       parser.add_argument('--image-file', dest='imagefile',
       action='store', required=True, help='The image file to
       upload to RADOS') 
       parser.add_argument('--object-name', dest='objectname', 
       action='store', required=True, help='The name of the
       RADOS object') 
       parser.add_argument('--pool', dest='pool', action='store', 
       required=True, help='The name of the RADOS pool to store
       the object') 
       parser.add_argument('--comment', dest='comment', action=
       'store', help='A comment to store with the object') 

       args = parser.parse_args() 

       try: #Read ceph.conf config file to obtain monitors 
         cluster = rados.Rados(conffile='/etc/ceph/ceph.conf') 
       except: 
         print "Error reading Ceph configuration" 
         sys.exit(1) 

       try: #Connect to the Ceph cluster 
         cluster.connect() 
       except: 
         print "Error connecting to Ceph Cluster" 
         sys.exit(1) 

       try: #Open specified RADOS pool 
         ioctx = cluster.open_ioctx(args.pool) 
       except: 
         print "Error opening pool: " + args.pool 
         cluster.shutdown() 
         sys.exit(1) 

       if args.action == 'upload': #If action is to upload 
         try: #Open image file in read binary mode 
           image=open(args.imagefile,'rb') 
           im=Image.open(args.imagefile) 
         except: 
           print "Error opening image file" 
           ioctx.close() 
           cluster.shutdown() 
           sys.exit(1) 
         print "Image size is x=" + str(im.size[0]) + " y=" + 
         str(im.size[1]) 
         try: #Write the contents of image file to object and add 
         attributes 
           ioctx.write_full(args.objectname,image.read()) 
           ioctx.set_xattr(args.objectname,'xres',str(im.size[0]) 
           +"\n") 
           ioctx.set_xattr(args.objectname,'yres',str(im.size[1]) 
           +"\n") 
           im.close() 
           if args.comment: 
             ioctx.set_xattr(args.objectname,'comment',args.comment 
             +"\n") 
         except: 
           print "Error writing object or attributes" 
           ioctx.close() 
           cluster.shutdown() 
           sys.exit(1) 
         image.close() 
       elif args.action == 'download': 
         try: #Open image file in write binary mode 
           image=open(args.imagefile,'wb') 
         except: 
           print "Error opening image file" 
           ioctx.close() 
           cluster.shutdown() 
           sys.exit(1) 
         try: #Write object to image file 
           image.write(ioctx.read(args.objectname)) 
         except: 
           print "Error writing object to image file" 
           ioctx.close() 
           cluster.shutdown() 
           sys.exit(1) 
         image.close() 
       else: 
         print "Please specify --action as either upload or download" 
       ioctx.close() #Close connection to pool 
       cluster.shutdown() #Close connection to Ceph 
       #The End
  1. 测试由 Argument Parser 库生成的 help 功能:
$ sudo python app1.py --help

前述命令输出如下:

  1. 下载 Ceph 标志作为测试图像:
wget http://docs.ceph.com/docs/master/_static/logo.png

前述命令输出如下:

  1. 运行我们的 Python 应用程序以读取图像文件并将其上传到 Ceph 作为对象:
$ sudo python app1.py --action=upload --image-file=test1.png
       --object-name=image_test --pool=rbd --comment="Ceph Logo"

前述命令输出如下:

  1. 验证对象是否已创建:
$ sudo rados -p rbd ls

前述命令输出如下:

  1. 使用 rados 验证属性是否已添加到对象:
$ sudo rados -p rbd listxattr image_test

前述命令输出如下:

  1. 使用 rados 验证属性内容,如下图所示:

使用原子操作的 librados 应用示例

在前面的 librados 应用示例中,首先在 Ceph 集群上创建了一个对象,然后添加了该对象的属性。在大多数情况下,这种两阶段操作是可行的;然而,一些应用程序可能要求对象的创建和其属性的设置是原子的。也就是说,如果服务中断,只有当对象的所有属性都已设置时,才能存在该对象,否则 Ceph 集群应该回滚该事务。以下示例使用 C++ 编写,展示了如何使用 librados 原子操作来确保跨多个操作的事务一致性。该示例将写入一个对象,并询问用户是否希望中止事务。如果用户选择中止,则对象写入操作将回滚;如果用户选择继续,则属性将被写入,整个事务将被提交。请执行以下步骤:

  1. 创建一个新的 .cc 扩展名文件,并将以下内容放入其中:
       #include <cctype> 
       #include <rados/librados.hpp> 
       #include <iostream> 
       #include <string> 

       void exit_func(int ret); 

       librados::Rados rados; 

       int main(int argc, const char **argv) 
       { 
         int ret = 0; 

         // Define variables 
         const char *pool_name = "rbd"; 
         std::string object_string("I am an atomic object\n"); 
         std::string attribute_string("I am an atomic attribute\n"); 
         std::string object_name("atomic_object"); 
         librados::IoCtx io_ctx; 

         // Create the Rados object and initialize it 
         { 
           ret = rados.init("admin"); // Use the default client.admin 
           keyring 
           if (ret < 0) { 
             std::cerr << "Failed to initialize rados! error " << ret 
             << std::endl; 
             ret = EXIT_FAILURE; 
           } 
         } 

         // Read the ceph config file in its default location 
         ret = rados.conf_read_file("/etc/ceph/ceph.conf"); 
         if (ret < 0) { 
           std::cerr << "Failed to parse config file " 
                     << "! Error" << ret << std::endl; 
           ret = EXIT_FAILURE; 
         } 

         // Connect to the Ceph cluster 
         ret = rados.connect(); 
         if (ret < 0) { 
           std::cerr << "Failed to connect to cluster! Error " << ret 
           << std::endl; 
           ret = EXIT_FAILURE; 
         } else { 
           std::cout << "Connected to the Ceph cluster" << std::endl; 
         } 

         // Create connection to the Rados pool 
         ret = rados.ioctx_create(pool_name, io_ctx); 
         if (ret < 0) { 
           std::cerr << "Failed to connect to pool! Error: " << ret << 
           std::endl; 
           ret = EXIT_FAILURE; 
         } else { 
           std::cout << "Connected to pool: " << pool_name <<
           std::endl; 
         } 

         librados::bufferlist object_bl; // Initialize a bufferlist 
         object_bl.append(object_string); // Add our object text
         string to the bufferlist 
         librados::ObjectWriteOperation write_op; // Create a write 
         transaction 
         write_op.write_full(object_bl); // Write our bufferlist to the 
         transaction 
         std::cout << "Object: " << object_name << " has been written 
         to transaction" << std::endl; 
         char c; 
         std::cout << "Would you like to abort transaction? (Y/N)? "; 
         std::cin >> c; 
         if (toupper( c ) == 'Y') { 
           std::cout << "Transaction has been aborted, so object will 
           not actually be written" << std::endl; 
           exit_func(99); 
         } 
         librados::bufferlist attr_bl; // Initialize another bufferlist 
         attr_bl.append(attribute_string); // Add our attribute to the 
         bufferlist 
         write_op.setxattr("atomic_attribute", attr_bl); // Write our 
         attribute to our transaction 
         std::cout << "Attribute has been written to transaction" << 
         std::endl; 
         ret = io_ctx.operate(object_name, &write_op); // Commit the
         transaction 
         if (ret < 0) { 
           std::cerr << "failed to do compound write! error " << ret << 
           std::endl; 
           ret = EXIT_FAILURE; 
         } else { 
           std::cout << "We wrote the transaction containing our object
           and attribute" << object_name << std::endl; 
         } 

       } 

       void exit_func(int ret) 
       { 
         // Clean up and exit 
         rados.shutdown(); 
         exit(ret); 
       } 
  1. 使用 g++ 编译源代码:
g++ atomic.cc -o atomic -lrados -std=c++11
  1. 让我们运行该应用并中止事务:

上面的截图显示,即使我们发送了一个写对象命令,由于事务没有提交,实际上该对象并未写入 Ceph 集群。

  1. 让我们重新运行应用程序,这次让它继续执行事务:

如你所见,这次对象与其属性一同被写入。

使用 watchers 和 notifiers 的 librados 应用示例

以下是用 C 编写的 librados 应用,它向我们展示了如何在 RADOS 中使用 watch 或 notify 功能。Ceph 使客户端能够在对象上创建一个 watcher,并从完全独立的连接到同一集群的客户端接收通知。

watcher 功能是通过回调函数实现的。当你调用 librados 函数创建 watcher 时,其中两个参数是回调函数:一个是接收到通知时要执行的操作,另一个是在 watcher 丧失联系或遇到对象错误时要执行的操作。这些回调函数包含了你希望在通知或错误发生时运行的代码。

这种简单的消息传递形式通常用于通知正在使用 RBD 的客户端希望拍摄快照。希望拍摄快照的客户端会向所有可能正在监视该 RBD 对象的客户端发送通知,以便它能够刷新缓存,并可能确保文件系统处于一致状态。

以下示例在名为 my_object 的对象上创建了一个 watcher 实例,然后等待。当它接收到通知时,它将显示有效载荷,并将收到的消息发送回通知者:

  1. 创建一个新的 .c 扩展名文件,并将以下内容放入其中:
 #include <stdio.h> 
 #include <stdlib.h> 
 #include <string.h> 
 #include <syslog.h> 

 #include <rados/librados.h> 
 #include <rados/rados_types.h> 

 uint64_t cookie; 
 rados_ioctx_t io; 
 rados_t cluster; 
 char cluster_name[] = "ceph"; 
 char user_name[] = "client.admin"; 
 char object[] = "my_object"; 
 char pool[] = "rbd"; 

 /* Watcher callback function - called when watcher receives a
 notification */ 
 void watch_notify2_cb(void *arg, uint64_t notify_id, uint64_t
 cookie, uint64_t notifier_gid, void *data, size_t data_len) 
 { 
 const char *notify_oid = 0; 
 char *temp = (char*)data+4; 
 int ret; 
 printf("Message from Notifier: %s\n",temp); 
 rados_notify_ack(io, object, notify_id, cookie, "Received", 8); 
 } 

 /* Watcher error callback function - called if watcher encounters
 an error */ 
 void watch_notify2_errcb(void *arg, uint64_t cookie, int err) 
 { 
 printf("Removing Watcher on object %s\n",object); 
 err = rados_unwatch2(io,cookie); 
 printf("Creating Watcher on object %s\n",object); 
 err = rados_watch2(io,object,&cookie,watch_notify2_cb,
 watch_notify2_errcb,NULL); 
 if (err < 0) { 
 fprintf(stderr, "Cannot create watcher on %s/%s: %s\n", object,
 pool, strerror(-err)); 
 rados_ioctx_destroy(io); 
 rados_shutdown(cluster); 
 exit(1); 
 } 
 } 

 int main (int argc, char **argv) 
 { 
 int err; 
 uint64_t flags; 

 /* Create Rados object */ 
 err = rados_create2(&cluster, cluster_name, user_name, flags); 
 if (err < 0) { 
 fprintf(stderr, "Couldn't create the cluster object!: %s\n",
 strerror(-err)); 
 exit(EXIT_FAILURE); 
 } else { 
 printf("Created the rados object.\n"); 
 } 

 /* Read a Ceph configuration file to configure the cluster
 handle. */ 
 err = rados_conf_read_file(cluster, "/etc/ceph/ceph.conf"); 
 if (err < 0) { 
 fprintf(stderr, "Cannot read config file: %s\n",
 strerror(-err)); 
 exit(EXIT_FAILURE); 
 } else { 
 printf("Read the config file.\n"); 
 } 
 /* Connect to the cluster */ 
 err = rados_connect(cluster); 
 if (err < 0) { 
 fprintf(stderr, "Cannot connect to cluster: %s\n",
 strerror(-err)); 
 exit(EXIT_FAILURE); 
 } else { 
 printf("\n Connected to the cluster.\n"); 
 } 

 /* Create connection to the Rados pool */ 
 err = rados_ioctx_create(cluster, pool, &io); 
 if (err < 0) { 
 fprintf(stderr, "Cannot open rados pool %s: %s\n", pool,
 strerror(-err)); 
 rados_shutdown(cluster); 
 exit(1); 
 } 

 /* Create the Rados Watcher */ 
 printf("Creating Watcher on object %s/%s\n",pool,object); 
 err = rados_watch2(io,object,&cookie,watch_notify2_cb, 
 watch_notify2_errcb,NULL); 
 if (err < 0) { 
 fprintf(stderr, "Cannot create watcher on object %s/%s: %s\n",
 pool, object, strerror(-err)); 
 rados_ioctx_destroy(io); 
 rados_shutdown(cluster); 
 exit(1); 
 } 

 /* Loop whilst waiting for notifier */ 
 while(1){ 
 sleep(1); 
 } 
 /* Clean up */ 
 rados_ioctx_destroy(io); 
 rados_shutdown(cluster); 
 } 
  1. 编译 watcher 示例代码:
$ gcc watcher.c -o watcher -lrados
  1. 运行 watcher 示例应用:

  1. watcher 现在等待通知。在另一个终端窗口中,使用 rados 向正在被监视的 my_object 对象发送通知:

  1. 你可以看到通知已发送,并且已收到确认通知。如果我们再查看第一个终端窗口,我们可以看到来自通知者的消息:

总结

本章关于使用 librados 开发应用程序的内容到此结束。你现在应该对如何在应用程序中包含 librados 功能以及如何读取和写入对象到 Ceph 集群的基本概念感到熟悉。如果你打算使用 librados 开发应用程序,建议阅读官方的 librados 文档,以便更好地理解可用的所有功能。

在下一章中,我们将学习 RADOS 类以及它们如何被用来加速大型应用程序的处理。

问题

  1. 请列举一个你可能想编写使用原生 librados API 的应用程序的原因。

  2. RADOS watcher 是做什么的?

  3. 请列出 librados 支持的五种编程语言。

第七章:使用 Ceph RADOS 类进行分布式计算

Ceph 的一个常被忽视的功能是将自定义代码直接加载到 OSD 中,然后可以在 librados 应用程序中执行。这使得您能够利用 Ceph 的大规模分布式特性,不仅提供高性能的扩展存储,还能将计算任务分配到 OSD 上,从而实现大规模并行计算。这一能力通过动态加载 RADOS 类到每个 OSD 来实现。

本章将涵盖以下主题:

  • 示例应用程序及使用 RADOS 类的好处

  • 编写简单的 Lua RADOS 类

  • 编写一个模拟分布式计算的 RADOS 类

示例应用程序及使用 RADOS 类的好处

如前所述,通过 RADOS 类,代码可以直接在 OSD 代码库内执行,从而利用所有 OSD 节点的集体计算能力。与典型的客户端应用程序方法不同,客户端需要从 Ceph 集群读取对象、对其进行计算,然后将其写回,这会带来大量的往返开销。使用 RADOS 类显著减少了与 OSD 之间的往返次数,而且可用的计算能力远高于单个客户端所能提供的。因此,将操作直接卸载到 OSD 上,可以使单个客户端显著提高处理速度。

一个简单的使用 RADOS 类的例子是在需要计算 RADOS 池中每个对象的哈希值并将每个对象的哈希值作为属性存储时。让客户端执行此操作将突出显示由于客户端从远程集群执行这些操作而带来的瓶颈和额外延迟。而使用包含所需代码的 RADOS 类来读取对象、计算哈希并将其作为属性存储,客户端只需要向 OSD 发送执行该 RADOS 类的命令。

编写简单的 Lua RADOS 类

从 Kraken 版本开始,Ceph 中的默认 RADOS 类之一是能够运行 Lua 脚本的类。Lua 脚本动态地传递给 Lua RADOS 对象类,然后该类执行脚本的内容。脚本通常以 JSON 格式的字符串传递给对象类。虽然这相较于传统的 RADOS 对象类(需要在使用前进行编译)具有优势,但它也限制了 Lua 脚本的功能复杂性。因此,在选择适合任务的方式时,应该考虑该方法是否合适。

以下 Python 代码示例演示了如何创建并传递 Lua 脚本,供 OSD 执行。Lua 脚本读取指定对象的内容,并将文本字符串以大写形式返回——所有处理都在持有对象的远程 OSD 上完成;原始对象内容不会发送到客户端。

将以下内容放入名为 rados_lua.py 的文件中:

import rados, json, sys

try: #Read ceph.conf config file to obtain monitors
  cluster = rados.Rados(conffile='/etc/ceph/ceph.conf')
except:
  print "Error reading Ceph configuration"
  exit(1)

try: #Connect to the Ceph cluster
  cluster.connect()
except:
  print "Error connecting to Ceph Cluster"
  exit(1)

try: #Open specified RADOS pool
  ioctx = cluster.open_ioctx("rbd")
except:
  print "Error opening pool"
  cluster.shutdown()
  exit(1)

cmd = {
  "script": """
      function upper(input, output)
        size = objclass.stat()
        data = objclass.read(0, size)
        upper_str = string.upper(data:str())
        output:append(upper_str)
      end
      objclass.register(upper)
  """,
  "handler": "upper",
}

ret, data = ioctx.execute(str(sys.argv[1]), 'lua', 'eval_json', json.dumps(cmd))
print data[:ret]

ioctx.close() #Close connection to pool
cluster.shutdown() #Close connection to Ceph

现在,让我们创建一个包含所有小写字母的测试对象:

 echo this string was in lowercase | sudo rados -p rbd put LowerObject –

默认情况下,Lua 对象类不允许 OSD 调用;我们需要在所有 OSD 的 ceph.conf 中添加以下内容:

[osd]
osd class load list = *
osd class default list = *

现在,运行我们的 Python librados 应用程序:

 sudo python rados_lua.py LowerObject

上述命令将产生以下输出:

你应该会看到我们对象中的文本已经转换成了全大写。你可以从前面的 Python 代码看到,我们并没有在本地 Python 代码中做任何转换,所有转换操作都在远程的 OSD 上完成。

编写一个模拟分布式计算的 RADOS 类

如前面示例中提到的,尽管使用 Lua 对象类简化了 RADOS 对象类的使用,但当前仍然存在一些限制。为了编写一个能够执行更高级处理的类,我们需要回退到使用 C 语言编写类。然后,我们需要在 Ceph 源代码中编译这个新类。

为了演示这一点,我们将编写一个新的 RADOS 对象类,计算指定对象的 MD5 哈希值,然后将其作为对象的一个属性存储。此过程将重复 1,000 次,以模拟一个繁忙的环境,并且使得运行时间更容易测量。然后,我们将比较通过对象类执行此操作与在客户端计算 MD5 哈希值的速度。尽管这仍然是一个相对基础的任务,但它将使我们能够生成一个可控且可重复的场景,并比较客户端与直接通过 RADOS 类在 OSD 上执行任务的速度。它还将为我们提供一个良好的基础,帮助我们理解如何构建更复杂的应用程序。

准备构建环境

使用以下命令克隆 Ceph Git 仓库:

 git clone https://github.com/ceph/ceph.git

上述命令将产生以下输出:

一旦我们克隆了 Ceph Git 仓库,我们需要编辑 CMakeLists.txt 文件,并为我们将要编写的新类添加一个部分。

编辑源代码树中的以下文件:

 ~/ceph/src/cls/CMakeLists.txt

另外,将以下内容放入文件中:

# cls_md5
set(cls_md5_srcs md5/cls_md5.cc)
add_library(cls_md5 SHARED ${cls_md5_srcs})
set_target_properties(cls_md5 PROPERTIES
  VERSION "1.0.0"
  SOVERSION "1"
  INSTALL_RPATH "")
install(TARGETS cls_md5 DESTINATION ${cls_dir})
target_link_libraries(cls_md5 crypto)
list(APPEND cls_embedded_srcs ${cls_md5_srcs})

一旦 cmakelist.txt 文件更新完成,我们可以通过运行以下命令来使用 cmake 构建环境:

 do_cmake.sh

上述命令将产生以下输出:

这将会在源代码树中创建一个 build 目录。

为了构建 RADOS 类,我们需要安装包含 make 命令的所需软件包:

 sudo apt-get install build-essentials

Ceph 源代码树中还包含一个 install-deps.sh 文件,运行时会安装所需的其他软件包。

RADOS 类

以下代码示例是一个 RADOS 类,当执行时,它读取对象,计算 MD5 哈希,然后将其作为属性写回该对象,而无需客户端参与。每次调用此类时,它都会在本地对 OSD 重复执行此操作 1,000 次,并且仅在处理结束时通知客户端。我们需要执行以下步骤:

  1. 创建我们新 RADOS 类的目录:
 mkdir ~/ceph/src/cls/md5
  1. 创建 C++源文件:
 ~/ceph/src/cls/md5/cls_md5.cc
  1. 将以下代码放入其中:
 #include "objclass/objclass.h"
 #include <openssl/md5.h>

 CLS_VER(1,0)
 CLS_NAME(md5)

 cls_handle_t h_class;
 cls_method_handle_t h_calc_md5;

 static int calc_md5(cls_method_context_t hctx, bufferlist *in,
 bufferlist *out)
 {
 char md5string[33];

 for(int i = 0; i < 1000; ++i)
 {
 size_t size;
 int ret = cls_cxx_stat(hctx, &size, NULL);
 if (ret < 0)
 return ret;

 bufferlist data;
 ret = cls_cxx_read(hctx, 0, size, &data);
 if (ret < 0)
 return ret;
 unsigned char md5out[16];
 MD5((unsigned char*)data.c_str(), data.length(), md5out);
 for(int i = 0; i < 16; ++i)
 sprintf(&md5string[i*2], "%02x", (unsigned int)md5out[i]);
 CLS_LOG(0,"Loop:%d - %s",i,md5string);
 bufferlist attrbl;
 attrbl.append(md5string);
 ret = cls_cxx_setxattr(hctx, "MD5", &attrbl);
 if (ret < 0)
 {
 CLS_LOG(0, "Error setting attribute");
 return ret;
 }
 }
 out->append((const char*)md5string, sizeof(md5string));
 return 0;
 }

 void __cls_init()
 {
 CLS_LOG(0, "loading cls_md5");
 cls_register("md5", &h_class);
 cls_register_cxx_method(h_class, "calc_md5", CLS_METHOD_RD | 
 CLS_METHOD_WR, calc_md5, &h_calc_md5)
 }
  1. 进入之前创建的build目录,并使用make创建我们的新 RADOS 类:
 cd ~/ceph/build
 make cls_md5

上述命令将给出以下输出:

  1. 将我们的新类复制到集群中的 OSD:
 sudo scp vagrant@ansible:/home/vagrant/ceph/build/lib/libcls_md5.so*
       /usr/lib/rados-classes/

上述命令将给出以下输出:

此外,重启 OSD 以加载该类。现在你将在 Ceph OSD 日志中看到它正在加载我们的新类:

这需要对集群中的所有 OSD 节点重复进行。

客户端 librados 应用程序

如前所述,我们将使用两个 librados 应用程序,一个直接在客户端计算 MD5 哈希,另一个调用我们的 RADOS 类并让其计算 MD5 哈希。这些应用程序都需要从测试集群中的监视器节点运行,但可以在任何节点上编译并在需要时复制过去。为了这个示例,我们将直接在监视器节点上编译这些应用程序。

在我们开始之前,让我们确保监视器节点上存在构建环境:

 apt-get install build-essential librados-dev

在客户端计算 MD5

以下代码示例是 librados 客户端应用程序,它将从 OSD 读取对象,在客户端计算该对象的 MD5 哈希,并将其作为属性写回该对象。这与 RADOS 类中的计算和存储方式相同,唯一的区别是处理的位置不同。

创建一个名为rados_md5.cc的新文件,并将以下内容合并到其中:

#include <cctype>
#include <rados/librados.hpp>
#include <iostream>
#include <string>
#include <openssl/md5.h>

void exit_func(int ret);

librados::Rados rados;

int main(int argc, const char **argv)
{
  int ret = 0;

  // Define variables
  const char *pool_name = "rbd";
  std::string object_name("LowerObject");
  librados::IoCtx io_ctx;

  // Create the Rados object and initialize it
 {
    ret = rados.init("admin"); // Use the default client.admin keyring
    if (ret < 0) {
      std::cerr << "Failed to initialize rados! error " << ret <<
      std::endl;
      ret = EXIT_FAILURE;
    }
  }

  // Read the ceph config file in its default location
  ret = rados.conf_read_file("/etc/ceph/ceph.conf");
  if (ret < 0) {
    std::cerr << "Failed to parse config file "
              << "! Error" << ret << std::endl;
    ret = EXIT_FAILURE;
  }

  // Connect to the Ceph cluster
  ret = rados.connect();
  if (ret < 0) {
    std::cerr << "Failed to connect to cluster! Error " << ret <<
    std::endl;
    ret = EXIT_FAILURE;
  } else {
    std::cout << "Connected to the Ceph cluster" << std::endl;
  }

  // Create connection to the Rados pool
  ret = rados.ioctx_create(pool_name, io_ctx);
  if (ret < 0) {
    std::cerr << "Failed to connect to pool! Error: " << ret <<
    std::endl;
    ret = EXIT_FAILURE;
  } else {
    std::cout << "Connected to pool: " << pool_name << std::endl;
  }
  for(int i = 0; i < 1000; ++i)
  {
    size_t size;
    int ret = io_ctx.stat(object_name, &size, NULL);
    if (ret < 0)
      return ret;

    librados::bufferlist data;
    ret = io_ctx.read(object_name, data, size, 0);
    if (ret < 0)
      return ret;
    unsigned char md5out[16];
    MD5((unsigned char*)data.c_str(), data.length(), md5out);
    char md5string[33];
    for(int i = 0; i < 16; ++i)
      sprintf(&md5string[i*2], "%02x", (unsigned int)md5out[i]);
    librados::bufferlist attrbl;
    attrbl.append(md5string);
    ret = io_ctx.setxattr(object_name, "MD5", attrbl);
    if (ret < 0)
    {
      exit_func(1);
    }
  }
  exit_func(0);
}

void exit_func(int ret)
{
  // Clean up and exit
  rados.shutdown();
  exit(ret);
}

通过 RADOS 类在 OSD 上计算 MD5

最后,最后一个代码示例是 librados 应用程序,它指示 OSD 在本地计算 MD5 哈希,而不将任何数据传输到客户端或从客户端传输。你会注意到,稍后的代码中没有 librados 的读写语句,它完全依赖于exec函数来触发 MD5 哈希的创建。

创建一个名为rados_class_md5.cc的新文件,并将以下内容放入其中:

#include <cctype>
#include <rados/librados.hpp>
#include <iostream>
#include <string>

void exit_func(int ret);

librados::Rados rados;

int main(int argc, const char **argv)
{
 int ret = 0;

 // Define variables
 const char *pool_name = "rbd";
 std::string object_name("LowerObject");
 librados::IoCtx io_ctx;
 // Create the Rados object and initialize it
 {
 ret = rados.init("admin"); // Use the default client.admin keyring
 if (ret < 0) {
 std::cerr << "Failed to initialize rados! error " << ret <<
 std::endl;
 ret = EXIT_FAILURE;
 }
 }

 // Read the ceph config file in its default location
 ret = rados.conf_read_file("/etc/ceph/ceph.conf");
 if (ret < 0) {
 std::cerr << "Failed to parse config file "
 << "! Error" << ret << std::endl;
 ret = EXIT_FAILURE;
 }

 // Connect to the Ceph cluster
 ret = rados.connect();
 if (ret < 0) {
 std::cerr << "Failed to connect to cluster! Error " << ret <<
 std::endl;
 ret = EXIT_FAILURE;
 } else {
 std::cout << "Connected to the Ceph cluster" << std::endl;
 }

 // Create connection to the Rados pool
 ret = rados.ioctx_create(pool_name, io_ctx);
 if (ret < 0) {
 std::cerr << "Failed to connect to pool! Error: " << ret <<
 std::endl;
 ret = EXIT_FAILURE;
 } else {
 std::cout << "Connected to pool: " << pool_name <<
 std::endl;
 }
 librados::bufferlist in, out;
 io_ctx.exec(object_name, "md5", "calc_md5", in, out);
 exit_func(0);

}
void exit_func(int ret)
{
 // Clean up and exit
 rados.shutdown();
 exit(ret);
}

我们现在可以编译这两个应用程序:

如果应用程序成功编译,将不会有任何输出。

测试

我们将使用标准的 Linux time工具运行这两个 librados 应用程序,以测量每次运行所需的时间:

 time sudo ./rados_md5

上述命令将给出以下输出:

让我们确保属性确实已创建:

 sudo rados -p rbd getxattr LowerObject MD5

上述命令将会输出以下内容:

让我们删除对象属性,以便我们可以确认 RADOS 类在运行时确实创建了该属性:

 sudo rados -p rbd rmxattr LowerObject MD5

现在,运行通过 RADOS 类执行 MD5 计算的应用程序:

 time sudo ./rados_class_md5

上述命令将会输出以下内容:

如您所见,使用 RADOS 类方法要快得多,实际上,速度几乎提高了两个数量级。

然而,我们也需要确认该属性已被创建,并且代码确实运行了 1,000 次:

 sudo rados -p rbd getxattr LowerObject MD5

上述命令将会输出以下内容:

由于我们在 RADOS 类中插入了日志,我们还可以检查 OSD 日志,以确认 RADOS 类确实运行了 1,000 次:

在重复执行小任务时,客户端与 OSD 之间的通信开销会逐渐增加。通过将处理过程直接移至 OSD,我们可以消除这一问题。

RADOS 类注意事项

尽管我们已经看到可以通过使用 Ceph 的 RADOS 类来利用强大的功能,但需要注意的是,这一切都是通过从 OSD 内部调用您自定义的代码实现的。因此,必须非常小心,确保您的 RADOS 类没有 bug。RADOS 类有能力修改 Ceph 集群中的任何数据,因此意外的数据损坏是很容易发生的。RADOS 类还可能导致 OSD 进程崩溃。如果该类在大规模集群操作中使用,可能会影响集群中的所有 OSD,因此必须确保错误处理得当,避免错误的发生。

总结

现在您应该了解了什么是 RADOS 类,以及它们如何通过将任务直接移至 OSD 来加速处理。从通过 Lua 构建简单类,到通过 C++ 在 Ceph 源代码树中开发类,您现在应该具备了构建 RADOS 类以解决您所面临问题的知识。基于这个概念,您可以创建一个更大的应用程序,利用 Ceph 集群的横向扩展特性提供大量存储和计算资源。

在下一章中,我们将通过一些示例来探讨监控在 Ceph 中的重要性。

问题

  1. RADOS 类在 Ceph 的哪个组件中执行?

  2. RADOS 类可以用什么语言编写?

  3. RADOS 类带来了什么优势?

  4. RADOS 类带来了什么缺点?

第八章:监控 Ceph

当你在操作一个 Ceph 集群时,监控其健康和性能是很重要的。通过监控 Ceph,你可以确保集群处于良好的健康状态,并能够快速应对可能出现的问题。通过捕获并绘制性能计数器图表,你还将获得调整 Ceph 所需的数据,并观察调整对集群的影响。

在本章中,你将学习以下主题:

  • 为什么监控 Ceph 重要

  • 如何通过使用新的内置仪表板监控 Ceph 的健康状态

  • 应该监控的内容

  • PG 的状态及其含义

  • 如何使用 collectd 捕获 Ceph 的性能计数器

  • 使用 Graphite 的示例图表

为什么监控 Ceph 重要

监控 Ceph 最重要的原因是确保集群处于健康状态。如果 Ceph 没有运行在健康状态,无论是因为硬盘故障还是其他原因,服务或数据丢失的可能性都会增加。虽然 Ceph 在从各种场景中恢复方面高度自动化,但了解正在发生的情况以及何时需要手动干预是至关重要的。

监控不仅仅是检测故障;监控其他指标,如已使用的磁盘空间,与了解磁盘故障发生的时间同样重要。如果你的 Ceph 集群存储满了,它将停止接受 I/O 请求,并且无法从未来的 OSD 故障中恢复。

最后,监控操作系统和 Ceph 性能指标可以帮助你发现性能问题或识别调整机会。

应该监控的内容

简单的答案是:所有内容,或者说,尽可能多地监控。你永远无法预测你的集群可能面临什么样的情况,设置正确的监控和告警系统可能意味着优雅地应对情况与发生大规模宕机之间的差异。应该监控的事项按重要性递减的顺序如下:

Ceph 健康状态

最重要的是捕获 Ceph 的健康状态。主要的报告项是集群的整体健康状态,可能是 HEALTH_OKHEALTH_WARNHEALTH_ERR。通过监控这个状态,你将在 Ceph 认为存在问题时及时收到警报。此外,你可能还需要捕获 PG 的状态和降级对象的数量,因为它们可以提供额外的信息,帮助你了解可能存在的问题,而无需实际登录 Ceph 服务器并使用 Ceph 工具集来检查状态。

操作系统和硬件

强烈建议您捕获运行 Ceph 软件的操作系统当前状态,以及底层硬件的状态。捕获诸如 CPU 和内存使用情况等信息将提醒您可能的资源短缺,在它变得关键之前。长期趋势分析这些数据还有助于规划 Ceph 的硬件选择。还强烈建议监控硬件以捕获硬件故障,例如磁盘、电源单元和风扇。大多数服务器硬件都是冗余的,除非进行监控,否则很难察觉到它在降级状态下运行。此外,监控网络连接,以确保两块网卡在绑定配置中都处于正常工作状态,也是一个好主意。

智能统计

使用操作系统的智能监控工具套件来探测磁盘的健康状况也是一个好主意。它们可以帮助您突出显示故障磁盘或错误率异常的磁盘。对于 SSD,您还可以测量闪存单元的磨损率,这可以很好地指示 SSD 可能会发生故障的时间。最后,能够捕获磁盘的温度将帮助您确保服务器不会过热。

网络

由于 Ceph 依赖于其运行所依赖的网络的可靠性,因此监控网络设备的错误和性能问题是非常有益的。大多数网络设备可以通过 SNMP 进行轮询,以获取这些数据。自 Mimic 版本发布以来,Ceph 会自动在其心跳中发送巨型帧,以尝试捕捉网络中未正确配置巨型帧的情况。然而,考虑部署您自己的巨型帧检查监控工具,以捕获配置错误的情况也是值得的,因为配置错误的巨型帧很容易使 Ceph 集群陷入瘫痪。

性能计数器

通过同时监控操作系统和 Ceph 的性能计数器,您可以获得大量知识,帮助更好地理解 Ceph 集群的性能。如果存储条件允许,尽可能捕获更多这些指标是值得的;你永远不知道什么时候这些指标会派上用场。在诊断问题时,经常会遇到这种情况:一个曾经被认为与问题无关的指标,突然揭示出问题的实际原因。仅仅监控关键指标的传统方法在这方面是非常有限的。

大多数运行在 Linux 上的监控代理都允许您捕获各种指标,从资源消耗到文件系统使用情况。值得花时间分析您可以收集的指标并适当配置它们。一些监控代理还将提供 Ceph 插件,可以提取 Ceph 各个组件(如 osdmon 节点)的所有性能计数器。

Ceph 仪表板

在 Mimic 版本中引入的 Ceph 现在有一个基于开源 ATTIC 项目的极为实用的仪表板。初始的 Mimic 版本中的仪表板使 Ceph 运维人员能够监控日常所需的 Ceph 集群的许多方面。随着 Ceph 之前版本的发布,仪表板进行了进一步的改进,现在可以用来管理一些常见的任务;随着时间的推移,预计仪表板将继续增加新功能。

仪表板作为 Ceph Mgr 模块提供,并与任何依赖项一起包含在标准的 Ceph 安装中。这意味着,开始使用 Ceph 仪表板所需的唯一步骤是启用mgr模块:

sudo ceph mgr module enable dashboard

SSL 需要禁用,或者需要配置 SSL 证书。幸运的是,Ceph 提供了一个简单的一行命令来帮助您启动自签名证书:

sudo ceph dashboard create-self-signed-cert

推荐在生产部署中使用合适的证书。

最后,登录仪表板需要用户名和密码。同样,Ceph 提供了一个简单的命令来执行此操作:

sudo ceph dashboard set-login-credentials <user> <password>

现在,您应该能够浏览到https://<active mgr>:8443并使用刚才创建的凭据登录。在这种情况下,<active mgr>是当前运行活动 mgr 守护进程的 Ceph 节点;可以通过ceph -s Ceph 状态屏幕查看此信息:

登录仪表板时呈现的第一个屏幕提供了 Ceph 集群的健康状况和利用率概览。

页面顶部有多个菜单,允许您查看有关 Ceph 集群的更详细信息,如有关 OSD 和 PG 的详细信息。块菜单允许您查看已创建的 RBD 镜像的详细信息,同样,文件系统菜单显示有关任何 CephFS 文件系统的信息。

对象网关将显示有关 RADOS 网关的信息,但需要使用有效的 RGW 用户进行配置;如有需要,请查阅官方 Ceph 文档获取更多信息。

随着 Ceph 仪表板的未来发展前景看好,强烈推荐为您管理的任何 Ceph 集群部署它。未来版本预计将带来更多的增强功能,能够通过仪表板管理 Ceph 集群,随着时间的推移,仪表板将变得越来越有用。然而,即使在当前状态下,较不熟悉的管理员能够轻松查看正在运行的 Ceph 集群的当前状态也是非常有用的。

PG 状态——好、坏和丑

Ceph 中的每个放置组(PG)都有一个或多个分配给它的状态;通常,您希望看到所有 PG 的状态为active+clean。理解每个状态的含义可以帮助我们识别 PG 发生了什么,是否需要采取行动。

好的状态

以下状态表示一个健康运行的集群,此时无需采取任何措施。

激活状态

active 状态意味着 PG 处于完全健康状态,能够接受客户端请求。

clean 状态

clean 状态意味着 PG 的对象已经按照正确的次数进行复制,且所有对象处于一致的状态。

Scrubbing 和深度 Scrubbing

Scrubbing 意味着 Ceph 检查数据的一致性,这是一个正常的后台过程。单纯的 Scrubbing 是指 Ceph 检查对象及其相关的元数据是否存在。当 Ceph 执行深度 Scrub 时,它会对比对象及其副本的内容来检查一致性。

不良状态

以下状态表示 Ceph 并非完全健康,但不会立即导致问题。

不一致状态

inconsistent 状态意味着在 Scrub 过程中,Ceph 发现一个或多个对象与其副本不一致。请参阅本书后面的 第十一章,故障排除,了解如何处理这些错误。

backfilling、backfill_wait、recovering 和 recovery_wait 状态

这些状态意味着 Ceph 正在将数据从一个 OSD 复制或迁移到另一个 OSD。这可能意味着该 PG 的副本数少于期望的数量。如果处于 wait 状态,则表示由于每个 OSD 上的限流,Ceph 限制了并发操作的数量,以减少对客户端操作的影响。

降级状态

degraded 状态意味着 PG 缺少或拥有过时副本的一个或多个对象。这些通常会通过恢复/回填过程得到纠正。

已重新映射

为了变为活动状态,PG 当前被映射到不同的 OSD 或一组 OSD 上。这通常发生在 OSD 宕机但尚未恢复到其余的 OSD 时。

Peering

Peering 状态是 PG 成为活动状态的正常过程的一部分,通常它只会短暂停留在此状态。如果 PG 长时间停留在 Peering 状态,将会阻塞 I/O 操作,因此它被列在不良状态中。

丑陋状态

这些状态是你不希望看到的。如果看到这些状态,很可能会影响客户端访问集群,并且除非问题得到修复,否则可能会发生数据丢失。

incomplete 状态

incomplete 状态意味着 Ceph 无法在当前集群中任何在线的 OSD 上找到 PG 内对象的有效副本。这可能是因为对象根本不存在,或者现有的对象缺少可能在现在不可用的 OSD 上发生的较新写入。

down 状态

这将伴随 incomplete 状态。PG 缺少已知可能位于不可用 OSD 上的对象,PG 无法启动。

backfill_toofull 和 recovery_toofull 状态

Ceph 尝试恢复你的数据,但 OSD 磁盘已满,无法继续。需要更多的 OSD 来解决这个问题。

使用 collectd 监控 Ceph

在本章前面,我们讨论了在整个 Ceph 基础设施中应该进行哪些监控,并查看了新内置的 Ceph 仪表盘。为了更深入了解 Ceph 集群及其相关基础设施的操作,我们需要进行更详细的监控设置。虽然警报监控不在本书范围之内,但我们现在将看看如何使用 collectd 捕获 Ceph 性能指标,存储到 Graphite 中,然后使用 Grafana 创建带有图表的仪表盘。这些捕获的指标可以在下一章中帮助调整 Ceph 集群。

我们将在测试集群的一个监控节点上构建这个监控基础设施。在生产集群中,强烈建议为其配置独立的服务器。

Graphite

Graphite 是一个时间序列数据库,擅长存储大量的度量数据,并且具有成熟的查询语言,应用程序可以使用它来操作数据。

我们首先需要安装所需的 Graphite 软件包:

sudo apt-get install graphite-api graphite-carbon graphite-web

上述命令将给出以下输出:

编辑 /etc/graphite/storage-schemas.conf 存储模式文件,并将以下内容放入其中:

[carbon]
pattern = ^carbon\.
retentions = 60:90d
[default_1min_for_1day]
pattern = .*
retentions = 60s:1d

现在,我们可以通过运行以下命令来创建 Graphite 数据库:

sudo graphite-manage syncdb

上述命令将给出以下输出:

在提示时设置 root 用户的密码:

sudo apt-get install apache2 libapache2-mod-wsgi

上述命令将给出以下输出:

为了防止默认的 Apache 站点与 Graphite Web 服务冲突,我们需要通过运行以下命令禁用它:

sudo a2dissite 000-default 

上述命令将给出以下输出:

现在我们可以将 Apache Graphite 配置复制到 Apache 环境中:

sudo cp /usr/share/graphite-web/apache2-graphite.conf
/etc/apache2/sites-available sudo a2ensite apache2-graphite

上述命令将给出以下输出:

重启 Apache 服务:

sudo service apache2 reload

Grafana

我们将编辑 apt 仓库文件并添加 Grafana 的仓库:

sudo nano /etc/apt/sources.list.d/grafana.list

将以下行放入文件并保存:

deb https://packagecloud.io/grafana/stable/debian/ jessie main

现在运行以下命令以获取 gpg 密钥并更新软件包列表:

curl https://packagecloud.io/gpg.key | sudo apt-key add –
sudo apt-get update

使用以下命令安装 Grafana:

sudo apt-get install grafana

上述命令将给出以下输出:

使用标准的 Vagrant 配置,你将无法连接到 Grafana 提供的 HTTP 端口。为了访问 Grafana,我们需要通过 ssh port 3000 将端口转发到我们的本地机器。

以下是使用 PuTTY 的示例截图:

现在,在 URL 中使用 http://localhost:3000。你应该能进入 Grafana 的主页。进入数据源设置,然后配置 Grafana 来轮询我们新安装的 Graphite 安装:

如果点击 "Save & Test" 按钮时出现绿色成功条,说明你已经成功安装和配置了 Graphite 和 Grafana。

collectd

现在我们已经安装了 Graphite 和 Grafana,可以开始向其中添加一些数据,以便生成图表。collectd 是一个备受尊敬的度量收集工具,可以将度量数据输出到 Graphite。core collectd 应用程序非常简洁,它依赖于一系列插件来收集度量数据并将其转发到如 Graphite 之类的应用程序以进行存储。

在开始从 Ceph 节点收集度量数据之前,让我们在与 Graphite 和 Grafana 相同的虚拟机上安装 collectd。这样做是为了更好地理解 collectd 及其配置过程。然后,我们将使用 Ansible 在所有 Ceph 节点上安装和配置 collectd,这是如果在生产环境中部署时推荐的方法。我们有以下代码:

sudo apt-get install collectd-core

上述命令将输出以下内容:

这将安装 collectd 和一组用于查询标准操作系统资源的基本插件。示例配置存储在以下位置:

/usr/share/doc/collectd-core/examples/collectd.conf

它列出了所有核心插件和示例配置选项。值得查看此文件以了解各种插件及其配置选项。然而,对于这个示例,我们将从一个空的配置文件开始,并配置一些基本资源:

  1. 使用以下命令创建一个新的 collectd 配置文件:
sudo nano /etc/collectd/collectd.conf
  1. 添加以下内容:
Hostname "ansible"

       LoadPlugin cpu
       LoadPlugin df
       LoadPlugin load
       LoadPlugin memory
       LoadPlugin write_graphite

       <Plugin write_graphite>
          <Node "graphing">
              Host "localhost"
              Port "2003"
              Protocol "tcp"
              LogSendErrors true
              Prefix "collectd."
              StoreRates true
              AlwaysAppendDS false
              EscapeCharacter "_"
          </Node>
       </Plugin>

       <Plugin "df">
         FSType "ext4"
      </Plugin>
  1. 使用以下命令重启 collectd 服务:
sudo service collectd restart
  1. 现在,返回 Grafana 并浏览仪表盘的菜单项。点击屏幕中间的按钮来创建一个新的仪表盘:

  1. 选择 Graph 来向仪表盘添加一个新的图表。现在会出现一个示例图表,我们将编辑它以替换为我们自己的图表。为此,点击图表标题,会弹出一个浮动菜单:

  1. 点击 "Edit" 进入图表小部件编辑界面。在这里,我们可以通过选择 dustbin 图标删除虚假图表数据,如下图所示的三个按钮菜单框:

  1. 现在,在下拉菜单中,将面板数据源更改为我们刚刚添加的 Graphite 数据源,并点击 Add query 按钮:

  1. 一个查询框将出现在编辑面板的顶部。它还将具有像以前一样的三个按钮菜单框。从这里,我们可以通过点击包含三条横线的按钮来切换查询编辑器的编辑模式:

切换编辑模式选项可以在点击选择模式和文本编辑模式之间切换。在点击选择模式下,你可以浏览可用的指标并构建基本查询;而文本编辑模式则适用于更高级的查询。如果你不熟悉指标名称并且只想创建基本查询,点击选择模式会很有用。对于更复杂的查询,需要使用文本编辑器。

我们将首先使用基本编辑器模式为我们的图表创建查询,然后切换到文本模式,接下来的章节将使用文本模式,以便更容易复制书中的查询。

首先让我们绘制安装了 collectd 的虚拟机的系统负载图:

这将生成一个图表,显示系统负载。

通过进一步点击 + 符号,你可以通过对数据应用不同的函数来扩展查询。这些可以用来将多个数据源相加或计算平均值。我们将在本章中进一步探讨,当我们开始编写查询来分析 Ceph 性能时。在继续之前,让我们将查询编辑模式切换为文本模式,看看查询的样子:

你可以看到,每个指标树的叶子通过点号分隔。这就是 Graphite 查询语言的工作方式。

使用 Ansible 部署 collectd

现在我们已经确认监控栈已安装并正常工作,让我们使用 Ansible 将 collectd 部署到所有 Ceph 节点,以便开始监控。

切换到 ansible 目录:

cd /etc/ansible/roles
git clone https://github.com/fiskn/Stouts.collectd

编辑你的 Ansible site.yml 文件,并将 collectd 角色添加到 monosd 节点的 play 中,使其如下所示:

编辑 group_vars/all 并输入以下内容:

现在,运行你的 site.yml playbook:

ansible-playbook -K site.yml

前面的命令将给出以下输出:

从最后的状态可以看出,Ansible 已经将 collectd 部署到所有的 Ceph 节点,并且配置了 collectd Ceph 插件。在 Grafana 中,你现在应该能够看到 Ceph 节点作为可用指标出现。以下是我们的一个监控节点:

例如,现在我们可以创建一个图表,展示 Ceph 集群中存储的对象数量。在 Grafana 中创建一个新图表,并输入以下查询:

collectd.mon1.ceph.mon.mon1.ceph_bytes.Cluster.numObject

这将生成如下图所示的图表:

建议你在继续下一节之前,花一些时间浏览可用的指标,以便熟悉它们。

Ceph 的示例 Graphite 查询

尽管您可以通过简单地选择单个指标生成一些非常有用的图表,但通过利用 Graphite 的功能来操作指标,可以创建提供更详细洞察的图表,帮助您更好地了解 Ceph 集群。以下的 Graphite 查询对于生成常见图表非常有用,也是您创建自定义查询的一个良好起点。

OSD 的 Up 和 In 数量

能够快速查看仪表板并查看有多少 OSD 是 UpIn 是非常方便的。以下两个查询展示了这些值:

maxSeries(collectd.mon*.ceph.mon.mon*.ceph_bytes.Cluster.numOsdIn) maxSeries(collectd.mon*.ceph.mon.mon*.ceph_bytes.Cluster.numOsdUp)

请注意 maxSeries 函数的使用,它允许从所有 mon 节点提取数据,并会取最高值。

显示最偏差的 OSD 使用情况

由于 CRUSH 方式将 PG 放置在每个 OSD 上,因此每个 OSD 上的 PG 数量永远不会完美平衡。以下查询将创建一个图表,展示十个最偏差的 OSD,您可以查看 PG 平衡是否会有益。我们有以下代码:

mostDeviant(10,collectd.osd*.df.var-lib-ceph-osd-ceph-   *.df_complex.used)

所有 OSD 的 IOP 总数

这使用 sumSeries 函数和通配符将所有 OSD 的 op 指标加在一起:

sumSeries(collectd.osd*.ceph.osd.*.ceph_rate.Osd.op)

也有分别显示读操作和写操作的计数器,分别名为 opRopW

所有 OSD 的总 MBps

同样,也有显示每个 OSD 的 MBps 的计数器,例如 op 计数器;也可以使用 sumSeries 函数。我们有以下代码:

sumSeries(collectd.osd*.ceph.osd.*.ceph_rate.Osd.{opInBytes,opOutBytes})

集群容量和使用情况

以下两个查询展示了集群中字节的总容量和已用字节数。它们可以用来在 Grafana 中生成一个饼图,显示已用空间的百分比。请注意,这些计数器显示的是在复制之前的原始容量:

maxSeries(collectd.mon*.ceph.mon.mon*.ceph_bytes.Cluster.osdBytes) maxSeries(collectd.mon*.ceph.mon.mon*.ceph_bytes.Cluster.osdBytesUsed)

平均延迟

以下两个查询可以用来绘制集群的平均延迟图。如果每次操作的 I/O 大小较大,平均延迟将增加,因为较大的 I/O 需要更长时间来处理。因此,如果平均 I/O 大小随时间变化,这些图表将无法清晰地展示集群的延迟。我们有以下代码:

averageSeries(collectd.osd*.ceph.osd.*.ceph_latency.Osd.opWLatency) averageSeries(collectd.osd*.ceph.osd.*.ceph_latency.Osd.opRLatency)

自定义 Ceph collectd 插件

尽管标准的 collectd Ceph 插件在收集 Ceph 的所有性能计数器方面表现良好,但它无法收集所有所需的数据,以便您全面了解集群的健康状况和性能。本节将展示如何使用额外的自定义 collectd 插件来收集 PG 状态、每个池的性能统计信息以及更为真实的延迟数据:

  1. 通过 SSH 登录到您的一个 mon 节点,并克隆以下 Git 仓库:
git clone https://github.com/grinapo/collectd-ceph
  1. collectd/plugins 目录下创建一个 ceph 目录:
sudo mkdir -p /usr/lib/collectd/plugins/ceph
  1. 使用以下命令将 plugins 目录复制到 /usr/lib/collectd/plugins/ceph
sudo cp -a collectd-ceph/plugins/*  
/usr/lib/collectd/plugins/ceph/
  1. 现在,创建一个新的 collectd 配置文件以启用插件:
sudo nano /etc/collectd/collectd.conf.d/ceph2.conf
  1. 将以下配置放入其中并保存新文件:
<LoadPlugin "python">
 Globals true
 </LoadPlugin>

 <Plugin "python">
 ModulePath "/usr/lib/collectd/plugins/ceph"

 Import "ceph_pool_plugin"
 Import "ceph_pg_plugin"
 Import "ceph_latency_plugin"

 <Module "ceph_pool_plugin">
 Verbose "True"
 Cluster "ceph"
 Interval "60"
 </Module>
 <Module "ceph_pg_plugin">
 Verbose "True"
 Cluster "ceph"
 Interval "60"
 </Module>
 <Module "ceph_latency_plugin">
 Verbose "True"
 Cluster "ceph"
 Interval "60"
 TestPool "rbd"
 </Module>
 </Plugin>

延迟插件使用 RADOS 基准测试来确定集群的延迟;这意味着它实际上在运行 RADOS 基准测试并会向集群写入数据。TestPool参数确定了 RADOS 基准命令的目标。因此,建议在生产集群上为此用途创建一个单独的小池。

如果你尝试在 Kraken+版本的 Ceph 上使用这些额外的插件,你需要编辑ceph_pg_plugin.py文件,并在第 71 行修改变量名,从fs_perf_stat更改为perf_stat

  1. 重启collectd服务:
service collectd restart

现在可以通过以下查询获取平均集群延迟:

collectd.mon1.ceph-ceph.cluster.gauge.avg_latency

这个图是基于进行 64KB 写入的,因此与 OSD 指标不同,它不会根据客户端 I/O 的平均大小发生变化。

总结

在本章中,你了解了监控 Ceph 集群及其支持基础设施的重要性。你还应该对需要监控的各种组件以及一些可用的示例工具有了很好的理解。我们讨论了一些 PG 状态,结合监控解决方案,可以帮助你了解 Ceph 集群的当前状态。最后,我们部署了一个高可扩展性的监控系统,包括 collectd、Graphite 和 Grafana,这将使你能够创建专业的仪表盘,展示 Ceph 集群的状态和性能。

在下一章中,我们将探讨如何调优 Ceph 集群的性能,这在很大程度上依赖于能够捕获性能统计数据,而你现在应该能够通过本章内容来做到这一点。

问题

  1. Ceph Dashboard 运行在哪个端口上?

  2. Ceph Dashboard 是由哪个 Ceph 守护进程控制的?

  3. 不一致的 PG 状态意味着什么?

  4. 回填 PG 状态意味着什么?

  5. 你应该在 Ceph 基础设施中监控哪些内容?

第九章:调优 Ceph

虽然 Linux 和 Ceph 的默认配置通常能提供合理的性能,得益于开发人员多年来的研究和调整,但 Ceph 管理员可能希望从硬件中榨取更多性能。通过调优操作系统和 Ceph 配置,可能会实现性能提升。在第一章《Ceph 规划》中,你学习了如何为 Ceph 集群选择硬件;现在,让我们学习如何最大化其性能。

在本章中,你将学习以下内容:

  • 延迟及其重要性

  • 能够观察调优结果的重要性

  • 你应该关注的关键调优选项

延迟

在运行基准测试以测试 Ceph 集群性能时,你最终是在测量延迟的结果。所有其他类型的基准测试指标,包括 IOPS、MBps,甚至是更高层次的应用程序指标,都是由该请求的延迟推导出来的。

IOPS 是每秒执行的 I/O 请求次数;每个请求的延迟直接影响可能的 IOPS,并可以通过以下公式进行计算:

如果每个请求的平均延迟为 2 毫秒,则假设每个请求是同步提交的,将得到大约 500 IOPS:

1/0.002 = 500

MBps 只是 IOPS 乘以 I/O 大小:

500 IOPS * 64 KB = 32,000 KBps

在进行基准测试时,实际上是在测量延迟的最终结果。因此,进行的任何调优都应旨在减少每个 I/O 请求的端到端延迟。

在学习如何基准测试 Ceph 集群的各个组件及可用的各种调优选项之前,我们首先需要了解典型 I/O 请求的各种延迟来源。一旦我们能够将每个延迟来源分解为独立的类别,就可以对每个类别进行基准测试,从而在每个阶段可靠地跟踪正负调优效果。

以下图示展示了一个示例 Ceph 写请求及其主要的延迟来源:

客户端到主 OSD

从客户端开始,我们可以看到,平均而言,客户端与主 OSD 通信大约需要 100 微秒的延迟。使用 1 G 网络时,这个延迟可能接近 1 毫秒。我们可以通过使用 pingiperf 来测量两个节点之间的往返延迟,从而确认这一数字。

从前面的公式中,我们可以看出,即使没有其他延迟来源,使用 1 G 网络时,最大同步写入 IOPS 也将接近 1,000。

尽管客户端本身会引入一些延迟,但与其他来源的延迟相比,这部分延迟是微不足道的,因此在图示中没有列出。

主 OSD 到副本 OSD(们)

接下来,运行 Ceph 代码的 OSD 在处理请求时会引入延迟。很难准确给出这个延迟的数字,但它受 CPU 速度的影响。更高频率的快速 CPU 会更快地运行代码路径,从而减少延迟。本书前面提到,主 OSD 会将请求发送到副本集中的另两个 OSD。这两个请求会并行处理,因此从 2x 到 3x 副本的延迟增加最小,假设后端磁盘能够承受负载。

主 OSD 和复制的 OSD 之间还存在一个额外的网络跳跃,这会导致每个请求的延迟增加。

从主 OSD 到客户端

一旦主 OSD 将请求提交到其日志并从所有复制 OSD 收到确认它们也已提交,主 OSD 就可以向客户端发送确认并提交下一个 I/O 请求。

关于日志,提交延迟可能会有所不同,取决于所使用的介质类型。NVMe SSD 通常会在 10-20 微秒范围内响应请求,而基于 SATA/SAS 的 SSD 通常在 50-100 微秒范围内响应请求。NVMe 设备还倾向于在队列深度增加时保持更一致的延迟特性,使它们非常适合多个磁盘可能共享同一个 SSD 作为日志的情况。相比之下,硬盘的延迟通常在数十毫秒范围内,尽管随着 I/O 大小的增加,它们的延迟比较一致。

很明显,对于小型高性能工作负载,硬盘延迟将主导总延迟,因此应该使用 SSD,最好是 NVMe。

总体而言,在一个设计良好且经过优化的 Ceph 集群中,所有这些部分结合起来应该能够让平均写入 4 KB 请求在大约 500-750 微秒内得到服务。

基准测试

基准测试是一个重要的工具,能够快速查看调优工作的效果,并确定集群的能力极限。然而,重要的是,您的基准测试应该反映您通常在 Ceph 集群上运行的工作负载类型。如果您的最终目的是在集群上运行对延迟非常敏感的 在线事务处理 (OLTP) 数据库,那么调优您的 Ceph 集群,使其在大块顺序读取和写入方面表现出色是没有意义的。如果可能,您应该尽量包含一些实际使用与您实际工作负载相同软件的基准测试。以 OLTP 数据库为例,查看是否有针对您数据库软件的基准测试,这将给出最准确的结果。

基准测试工具

以下是推荐的工具集,用于开始进行基准测试:

  • Fio:Fio 是一个灵活的 I/O 测试工具,它通过其广泛的配置选项,允许你模拟各种复杂的 I/O 模式。它有针对本地块设备和 RBD 的插件,这意味着你可以直接或通过 Linux RBD 内核驱动程序挂载 RBD,从 Ceph 集群中测试 RBD。

  • Sysbench:Sysbench 具有一个 MySQL OLTP 测试套件,可以模拟 OLTP 应用程序。

  • Ping:不要低估这个简单的 ping 工具;它不仅能够诊断许多网络问题,还能通过往返时间帮助确定网络链路的延迟。

  • iPerf:iPerf 允许你进行一系列网络测试,以确定两个服务器之间的带宽。

网络基准测试

在网络上有多个区域需要基准测试,以便了解任何限制并确保没有配置错误。

标准以太网帧大小为 1,500 字节,而jumbo 帧通常为 9,000 字节。增加的数据帧大小减少了发送数据的开销。如果你已经配置了 jumbo 帧,首先需要检查它们是否在所有服务器和网络设备上正确配置。如果 jumbo 帧配置错误,Ceph 将表现出奇怪的、随机的行为,且很难追踪。因此,在部署 Ceph 之前,确保 jumbo 帧已正确配置并且正常工作是非常重要的。

为了确认 jumbo 帧是否正常工作,你可以使用ping命令发送大数据包,并设置不分段标志:

 ping -M do -s 8972 <destination IP> 

该命令应在所有节点上运行,以确保它们可以使用 jumbo 帧互相 ping。如果失败,需调查问题并解决后再部署 Ceph。

接下来的测试是测量往返时间,也可以使用 ping 工具。再次使用数据包大小参数,但同时设置不分段标志,你可以测试某些数据包大小的往返时间,直到 64 KB,这是最大 IP 数据包大小。

以下是两个主机在10GBase-T网络上的一些示例读数:

  • 32 B = 85 微秒

  • 4 KB = 112 微秒

  • 16 KB = 158 微秒

  • 64 KB = 248 微秒

如你所见,较大的数据包大小会影响往返时间;这是为什么在 Ceph 中较大的 I/O 大小会导致 IOPS 下降的原因之一。

最后,让我们测试两个主机之间的带宽,以确定我们是否获得了预期的性能。

在将充当 iPerf 服务器角色的服务器上运行 iperf -s

然后,运行iperf -c <iperf 服务器地址>命令:

在此示例中,两个主机通过 10 G 网络连接,并获得接近理论最大吞吐量的性能。如果你没有看到正确的吞吐量,需要对网络进行调查,包括主机配置。

磁盘基准测试

理解你 Ceph 集群中硬盘和 SSD 的底层性能是个好主意,因为这将帮助你预测整个 Ceph 集群的性能。为了基准测试集群中的磁盘,将使用 fio 工具。

如果你在写模式下操作,请小心使用 fio。如果你指定了块设备,fio 会毫不犹豫地覆盖该磁盘上现有的任何数据。

Fio 是一个功能复杂的工具,具有许多配置选项。在本章中,我们将专注于使用它进行基本的读写基准测试:

  1. 在 Ceph OSD 节点上安装 fio 工具:
 apt-get install fio

上述命令会输出以下内容:

  1. 创建一个新文件,并将以下 fio 配置放入其中:
 [global] ioengine=libaio randrepeat=0 invalidate=0 rw=randwrite bs=4k direct=1 time_based=1 runtime=30 numjobs=1 iodepth=1 filename=/test.fio size=1G

上述 fio 配置会运行一个单线程的 4 KB 随机写入测试,持续 30 秒。它将在文件系统的根目录下创建一个 1G 的 test.fio 文件。如果你希望直接对块设备进行测试,只需将文件名设置为块设备名称。但请注意,根据前述警告,fio 会覆盖该块设备上的任何数据。

注意,作业设置为使用方向,因此页面缓存不会加速任何 I/O 操作。

要运行 fio 作业,只需调用 fio 并指定先前配置文件的名称:

 fio <filename> 

上述命令会输出以下内容:

一旦作业完成,fio 会生成类似于前述截图中的输出。你可以看到,fio 作业运行了 39 IOPS 和 162 MBps 的平均速度,且平均延迟为 25 毫秒。

还有延迟百分位数的详细信息,这对于理解请求延迟的分布非常有用。

RADOS 基准测试

下一步是基准测试 RADOS 层。这将给出一个综合数据,包括磁盘性能、网络性能——加上 Ceph 代码的开销——以及额外的复制数据副本。

RADOS 命令行工具具有内建的基准测试命令,默认启动 16 个线程,所有线程写入 4 MB 的对象。要运行 RADOS 基准测试,运行以下命令:

 rados -p rbd bench 10 write

这将运行一个持续 10 秒的写入基准测试:

在前面的示例中,可以看到集群能够维持约 480 MBps 的写入带宽。输出还给出了延迟和其他有用的数字。请注意,在测试结束时,它会自动删除作为基准测试一部分创建的对象。如果你希望使用 RADOS 工具进行读取基准测试,则需要指定 --no-cleanup 选项,以保留对象,然后将基准测试类型指定为 seq 而不是 write,然后再次运行基准测试。之后,你需要手动清理基准对象。

RBD 基准测试

最后,我们将使用我们喜爱的工具 fio 来测试 RBD 的性能。这将测试整个软件和硬件堆栈,其结果将非常接近客户预期的观察结果。通过配置 fio 来模拟某些客户应用程序,我们还可以感受到这些应用程序的预期性能。

为了测试 RBD 的性能,我们将使用 fio RBD 引擎,这使得 fio 可以直接与 RBD 镜像通信。创建一个新的 fio 配置,并将以下内容放入其中:

[global]
ioengine=rbd
randrepeat=0
clientname=admin
pool=rbd
rbdname=test
invalidate=0
rw=write
bs=1M
direct=1
time_based=1
runtime=30
numjobs=1
iodepth=1

您可以看到,与磁盘基准配置不同,此配置文件现在使用 rbd 引擎而不是 libaio 引擎。使用 rbd 引擎时,您还需要指定 RADOS 池和 cephx 用户。最后,而不是指定文件名或块设备,您只需指定已存在于您配置的 RADOS 池中的 RBD 镜像。

然后,运行 fio 作业来测试您的 RBD 的性能:

如前述输出所示,fio 工具直接使用 RBD 引擎,无需将 RBD 挂载到 Linux 操作系统即可进行测试。

推荐的调整

通过调整您的 Ceph 集群,您可以获得最佳性能,并从您的硬件中获得最大的好处。在本节中,我们将看看推荐的 Ceph 调整选项。

重要的是要理解,通过调整,您所做的只是减少瓶颈。如果您成功地减少了一个区域的足够多的瓶颈,那么瓶颈将简单地转移到另一个区域。您总会在某个地方遇到瓶颈,并最终会达到一个硬件能够提供的极限。因此,目标应该是减少软件和操作系统中的瓶颈,以释放硬件的全部潜力。

CPU

由于 Ceph 是存储的软件定义,其性能受 OSD 节点中 CPU 速度的影响较大。更快的 CPU 意味着 Ceph 代码可以更快地运行,并且在处理每个 I/O 请求时花费的时间更少。结果是每个 I/O 的更低延迟,如果底层存储可以处理,将减少 CPU 作为瓶颈,并提供更高的整体性能。第一章《规划 Ceph》指出,出于性能原因,高 GHz 处理器比高核心数更受青睐;然而,对于高核心数 CPU,当它们被过度规定用于作业时,还存在其他问题。

为了理解这些问题,我们需要简要回顾 CPU 设计的历史。在 2000 年代初期,所有的 CPU 都是单核设计,始终以相同的频率运行,并且不支持许多低功耗模式。随着它们频率的提高和核心数的增加,显而易见,并非每个核心都能始终运行在其最大频率上。CPU 包装产生的热量实在是太大了。快进到今天,这一情况仍然成立:没有所谓的 4 GHz 20 核 CPU,它会产生过多的热量,根本不可行。

然而,设计 CPU 的聪明人想出了一个解决方案,这使得每个核心可以在不同的频率下运行,并且可以将其自身关闭进入深度睡眠状态。这两种方法都降低了 CPU 的功耗和冷却需求,将其降至单个位数瓦特。

这些 CPU 的时钟速度较低,但通过一定数量的核心启动涡轮模式,较高的 GHz 变得可能。通常随着活跃核心数的增加,最高涡轮频率会逐渐降低,以保持热输出在某个阈值以下。如果启动了一个低线程的进程,CPU 会唤醒几个核心,并将它们加速到更高的频率,以提高单线程性能。在 Intel 的 CPU 中,不同的频率级别称为P 状态,而睡眠级别称为C 状态

这一切听起来像是完美的方案:当 CPU 闲置时,几乎不消耗任何功率,而当需要时,它可以通过涡轮加速几个核心来实现高时钟速度。不幸的是,世上没有免费的午餐。这种方法确实存在一些开销,这些开销对对延迟敏感的应用程序产生不利影响,其中 Ceph 就是一个例子。

这种方法有两个主要问题,它们影响着对延迟敏感的应用程序,第一个问题是核心从睡眠状态唤醒需要时间。睡眠越深,唤醒所需的时间就越长。核心必须重新初始化某些内部组件,才能准备好使用。以下是来自 Intel E3-1200v5 CPU 的列表;较旧的 CPU 可能会稍微差一些:

  • POLL = 0 微秒

  • C1-SKL = 2 微秒

  • C1E-SKL = 10 微秒

  • C3-SKL = 70 微秒

  • C6-SKL = 85 微秒

  • C7s-SKL = 124 微秒

  • C8-SKL = 200 微秒

我们可以看到,在最坏的情况下,一个核心可能需要最多 200 微秒才能从其最深的休眠状态中唤醒。考虑到单个 Ceph I/O 可能需要多个线程跨多个节点来唤醒 CPU 核心,这些退出延迟可能会累积起来。虽然 P 状态影响核心频率的程度不如 C 状态的退出延迟那样大,但核心的频率不会在被使用时立即以最大速度增加。这意味着在低利用率下,CPU 核心可能仅在较低的 GHz 频率下运行。这就引出了第二个问题,即 Linux 调度程序的问题。

Linux 知道哪个核心是活跃的,以及每个核心正在运行的 C 状态和 P 状态。它可以完全控制每个核心的行为。不幸的是,Linux 的调度程序并没有考虑到这些信息,而是倾向于尝试均衡地将线程分配到各个核心。这意味着在低利用率情况下,所有 CPU 核心大部分时间都会处于最低的 C 状态,并以较低的频率运行。在低利用率时,这会对小 I/O 的延迟产生 4-5 倍的影响,这是一个显著的影响。

直到 Linux 拥有一个能够根据哪些核心已经处于活动状态并将线程调度到这些核心上以减少延迟的电源感知调度程序为止,最佳的方法是强制 CPU 只进入某个 C 状态,并始终以最高频率运行。这确实会增加功耗,但在最新的 CPU 型号中,这一情况有所减少。因此,应该清楚为什么建议根据工作负载为 CPU 选择适当的配置。将一个 40 核的服务器以高 C 状态和高频率运行将消耗大量电力。

为了强制 Linux 仅降至 C1 C 状态,请将以下内容添加到你的 GRUB 配置中:

 intel_idle.max_cstate=1 

一些 Linux 发行版提供了一个性能模式,该模式将 CPU 运行在最大频率下。然而,手动实现此模式的方法是通过sysfs回显值。将以下内容添加到/etc/rc.local中将在启动时设置所有核心运行在其最大频率下:

 /sys/devices/system/cpu/intel_pstate/min_perf_pct

重启你的 OSD 节点后,这些更改应该会生效。通过运行以下命令来确认这一点:

 sudo cpupower monitor

如本章前文所述,在进行这些更改之前,请先运行基准测试,然后再进行一次,以便了解通过此更改所获得的收益。

BlueStore

Ceph 的最新版本包含了针对 BlueStore OSD 的自动调优功能。自动调优通过分析 OSD 的缓存利用率,并根据当前的命中率调整 OSD、RocksDB 和数据缓存的缓存阈值。它还限制这些缓存的总和,以尽量将总的 OSD 内存使用量限制在osd_memory_target变量设置的限制范围内,默认值为 4 GB。

显然,如果 Ceph 节点的内存较少,因此无法为每个 OSD 提供 4 GB 内存,则需要减少该值,以避免节点内存不足。然而,如果 Ceph 节点内存充足,建议增加 osd_memory_target 变量,以便 Ceph 尽可能多地利用已安装的内存。分配足够的 RAM 给 OSD 和 RocksDB 后,任何额外的内存将被用作数据缓存,并能更有效地服务于高百分比的读 IO。当前的自动调节算法比较缓慢,需要一些时间才能充分发挥作用,因此应该给 osd_memory_target 变量调整至少 24-48 小时,以观察其完全效果。

WAL 延迟写入

BlueStore 可以在 RocksDB WAL 中记录写入操作,并在稍后的时间进行刷新,从而实现写入合并和排序。这可以为使用旋转磁盘和基于闪存的设备的 RocksDB 的集群带来显著的性能提升。

默认情况下,如果 OSD 被识别为旋转硬盘,则小于或等于 32 KB 的写入操作会写入 OSD 的 WAL,然后进行确认并发送回客户端。这由 bluestore_prefer_deferred_size_hdd 变量控制;如果确定您的工作负载有利于通过 WAL 延迟更大的写入以实现更低的延迟和更高的 IOPS,可以调整该值。同时,还应考虑持有 WAL 的闪存设备的写入负载,既要考虑带宽,也要考虑耐久性。

BlueStore 配置还限制了在 OSD 强制刷新数据到磁盘之前,最多可以排队多少写入操作;这个值可以通过 bluestore_deferred_batch_ops 变量进行控制,默认设置为 64。增加这个值可能会提高总吞吐量,但也有可能导致硬盘花费大量时间处于饱和状态,进而增加平均延迟。

Filestore

在几乎所有情况下,BlueStore 的性能优于 filestore,并解决了多个限制,因此建议将您的集群升级到 BlueStore。不过,为了完整性,以下是可以调整的项,以提高 filestore 性能,前提是您的集群仍在运行它。

VFS 缓存压力

顾名思义,filestore 对象存储通过将 RADOS 对象作为文件存储在标准 Linux 文件系统中来工作。在大多数情况下,这将是 XFS。由于每个对象都作为文件存储,因此每个磁盘上可能会有数十万甚至百万个文件。一个 Ceph 集群由 8 TB 磁盘组成,用于 RBD 工作负载。假设 RBD 由标准的 4 MB 对象组成,那么每个磁盘上大约会有 200 万个对象。

当一个应用程序请求 Linux 读取或写入文件系统上的文件时,它需要知道该文件在磁盘上的实际位置。为了找到这个位置,它需要遵循目录项和 inode 的结构。如果这些查找项没有被缓存到内存中,每一次查找都需要磁盘访问。如果 Ceph 对象在一段时间内没有被访问,因此没有被缓存,这可能会导致在某些情况下性能不佳。由于随机读取的影响,在旋转磁盘集群中这种惩罚会比在基于 SSD 的集群中更高。

默认情况下,Linux 偏好将数据缓存到页缓存中,而不是缓存 inode 和目录项。在 Ceph 中的许多情况下,这正是你不希望发生的。幸运的是,Linux 提供了一个可调的内核参数,允许你告诉系统优先缓存目录项和 inode 而不是页缓存;这一设置可以通过以下 sysctl 设置进行控制:

 vm.vfs_cache_pressure

在设置较低数值时,系统会偏好缓存 inode 和目录项,绝对不要将此值设置为零。零值会告诉内核即使在低内存情况下,也不刷新旧的条目,这可能带来不良影响。推荐设置为 1

WBThrottle 和/或 nr_requests

Filestore 使用缓冲 I/O 进行写入;如果 Filestore 日志位于更快的存储介质上,这带来了一些优势。客户端请求在写入日志后立即得到确认,然后由 Linux 中的标准写回功能在稍后的时间刷新到数据磁盘。这使得旋转磁盘 OSD 在小规模写入时能够提供与 SSD 相似的写入延迟。延迟写回还允许内核重新排列对磁盘的 I/O 请求,以期将它们合并,或者使磁头在盘片上沿着更优路径移动。最终效果是,你可以从每个磁盘中挤出更多的 I/O,相比直接或同步 I/O,性能得到了提升。

然而,问题出现在 Ceph 集群中写入请求的数量超出了底层磁盘的处理能力。在这种情况下,待写入磁盘的挂起 I/O 数量可能会失控地增加,导致 I/O 队列饱和,进而饱和磁盘和 Ceph 队列。读取请求尤其会受到较大影响,因为它们会被数千个写入请求堵塞,这些写入请求可能需要几秒钟才能刷新到磁盘上。

为了解决这个问题,Ceph 在 Filestore 中内建了一种写回节流机制,叫做 WBThrottle。它的设计目的是限制可以排队的写回 I/O 的数量,并比内核自然触发的写回过程提前启动刷新。然而,测试显示,默认设置仍可能无法遏制这种行为,从而减轻对读取延迟的影响。

调优可以改变这种行为,以减少写队列的长度,并且允许读取不受太大影响。然而,这也有一个权衡;通过减少允许排队的最大写入数量,您可以减少内核最大化重排请求效率的机会。需要根据您给定的使用案例、工作负载进行思考,并进行调整以匹配它。

为了控制写回队列深度,您可以通过调整 Ceph 的 WBThrottle 设置来减少最大未完成 I/O 的数量,或者在内核的块层面降低最大未完成请求数。这两种方法都能有效地控制相同的行为,实际上只是在于您希望如何实现该配置。

还应该注意,Ceph 中的操作优先级在磁盘级别的队列较短时更为有效。通过缩短磁盘上的队列,主要的排队位置会移到 Ceph 中,Ceph 可以更好地控制哪些 I/O 具有优先权。考虑以下示例:

echo 8 > /sys/block/sda/queue/nr_requests

随着 Linux 4.10 内核的发布,引入了一项新特性,即降低写回 I/O 的优先级;这大大减少了 Ceph 写饥饿的影响,如果可以运行 4.10 内核,值得考虑进行调查。

节流 filestore 队列

在默认配置下,当磁盘变得饱和时,其磁盘队列将逐渐填满。然后,filestore 队列将开始填满。直到此时,I/O 会以日志能够接受的最快速度被接受。一旦 filestore 队列填满和/或 WBThrottle 生效,I/O 将突然停止,直到队列恢复到低于阈值的状态。这种行为会导致大幅波动,并且很可能会出现低性能的时段,其他客户端请求会遇到较高的延迟。

为了减少磁盘饱和时 filestore 的波动性,可以设置一些额外的配置选项,以便在 filestore 队列填满时逐渐降低操作速率,而不是在硬限制下反复波动。

filestore_queue_low_threshhold

这个值是一个介于 0.0 和 1.0 之间的百分比。低于该阈值时,不进行节流。

filestore_queue_high_threshhold

这个值是一个介于 0.0 和 1.0 之间的百分比。在低阈值和高阈值之间,通过引入每个 I/O 的延迟进行节流,延迟会线性增加,从 0 增加到 filestore_queue_high_delay_multiple/filestore_expected_throughput_ops

从高阈值到最大值,会按 filestore_queue_max_delay_multiple/filestore_expected_throughput_ops 确定的速率进行节流。

这两个节流速率使用配置的值,即磁盘的预期吞吐量来计算要引入的正确延迟。delay_multiple 变量的存在是为了允许在队列超过高阈值时增加这个延迟。

filestore_expected_throughput_ops

该值应设置为 OSD 运行所在底层磁盘的预期 IOPS 性能。

filestore_queue_high_delay_multiple

在低阈值和高阈值之间,使用此倍数来计算应引入的正确延迟量。

filestore_queue_max_delay_multiple

当超过最大队列大小时,此乘数用于计算更大的延迟,以期望防止队列填满。

拆分 PG

文件系统对于可以存储在目录中的文件数量有一个限制,当要求列出内容时,性能会开始下降:

  • 由于 Ceph 每个磁盘存储数百万个对象——它们就是文件。它通过嵌套的目录结构拆分文件,以限制每个目录中放置的文件数量。

  • 随着集群中对象数量的增加,每个目录中的文件数量也会增加。

  • 当这些目录中的文件数量超过限制时,Ceph 会将目录拆分成更多的子目录,并将对象迁移到它们中。

此操作在发生时可能会产生显著的性能惩罚。此外,XFS 尝试将文件尽可能地放在磁盘上同一目录的相近位置。当 PG 拆分发生时,XFS 文件系统可能会出现碎片化,进一步导致性能下降。

默认情况下,当 PG 包含 320 个对象时,Ceph 会拆分该 PG。配置了推荐每 OSD PG 数量的 8 TB 磁盘,可能每个 PG 会有超过 5000 个对象。这个 PG 在其生命周期中可能已经经历了多次 PG 拆分操作,导致目录结构更深、更复杂。

VFS 缓存压力 部分所述,为了避免高昂的目录项查找,内核会尝试缓存它们。PG 拆分的结果是需要缓存更多的目录,可能没有足够的内存来缓存它们,从而导致性能下降。

解决此问题的常见方法是通过设置 OSD 配置选项来增加每个目录中允许的文件数量,具体如下:

filestore_split_multiple 

此外,使用以下设置:

filestore_merge_threshold

使用以下公式,您可以设置 Ceph 何时拆分 PG 的阈值:

然而,必须小心。尽管增加阈值将减少 PG 拆分的发生次数,并减少目录结构的复杂性,但当 PG 拆分发生时,它将必须拆分更多的对象。需要拆分的对象数量越多,对性能的影响越大,甚至可能导致 OSD 超时。拆分频率与拆分时间之间存在权衡;默认值可能稍显保守,尤其是在较大的磁盘上。

将拆分阈值加倍或三倍通常可以安全地完成,无需过多担心;更大的值应在集群处于 I/O 负载时进行测试,然后再投入生产环境。

刷新

清理是 Ceph 用于验证存储在 RADOS 中的对象是否一致的方式,并防止位腐化或其他损坏。清理操作可以是正常的或深度的,取决于设定的计划。在正常的清理操作中,Ceph 会读取某个 PG 的所有对象,并比较它们的副本,以确保它们的大小和属性匹配。

深度清理操作进一步比较对象的实际数据内容。这会比简单的标准清理例程产生更多的 I/O。正常的清理操作每天执行,而深度清理由于额外的 I/O 负载,应该每周执行一次。

尽管清理操作的优先级被降低,但清理仍然会对客户端 I/O 产生影响,因此,有许多 OSD 设置可以调整,指导 Ceph 何时进行清理操作。

osd_scrub_begin_hourosd_scrub_end_hour OSD 配置选项决定了 Ceph 尝试安排清理的时间窗口。默认情况下,这些值设置为允许清理操作在 24 小时内进行。如果你的工作负载只在白天运行,你可能希望调整清理开始和结束的时间,以告诉 Ceph 你只希望它在非高峰时段进行清理。osd_scrub_sleep 配置选项控制清理操作在每个块之间等待的时间(以秒为单位),这有助于在读取每个对象之间为客户端 I/O 提供服务。块大小由两个变量 osd_scrub_chunk_minosd_scrub_chunk_max 决定。

需要注意的是,这次只有当 PG 没有超出其最大清理间隔时,窗口才会生效。如果超出了最大间隔,它将被清理,无论时间窗口设置如何。正常和深度清理的默认最大间隔都设置为一周。

操作优先级

Ceph 能够优先执行某些操作,目的是确保客户端 I/O 优先于恢复、清理和快照修剪 I/O。这些优先级由以下配置选项控制:

osd client op priority osd recovery op priority osd scrub priority osd snap trim priority

在这里,值越高,优先级越高。默认值通常效果很好,通常不需要更改它们。但是,降低清理和恢复操作的优先级可能有助于限制它们对客户端 I/O 的影响。需要理解的是,Ceph 只能优先处理其控制的 I/O 路径部分的 I/O。因此,可能需要调整上一部分中磁盘队列的长度以获得最大效果。

网络

网络是 Ceph 集群的核心组件,其性能会极大地影响集群的整体性能。10 GB 应被视为最低要求;1 GB 的网络无法为高性能的 Ceph 集群提供所需的延迟。通过减少延迟和提高吞吐量,有许多调整可以帮助提高网络性能。

如果你希望使用 Jumbo 帧,首先要考虑的是使用 9,000 的 MTU 而不是 1,500;这样每个 I/O 请求可以通过较少的以太网帧发送。由于每个以太网帧都有小的开销,增加最大以太网帧到 9,000 可以有所帮助。实际上,性能提升通常不到 5%,且需要权衡每个设备都配置正确的劣势。

以下网络选项建议在你的 sysctl.conf 文件中设置,以最大化网络性能:

#Network buffers net.core.rmem_max = 56623104 net.core.wmem_max = 56623104 net.core.rmem_default = 56623104 net.core.wmem_default = 56623104 net.core.optmem_max = 40960 net.ipv4.tcp_rmem = 4096 87380 56623104 net.ipv4.tcp_wmem = 4096 65536 56623104  #Maximum connections and backlog net.core.somaxconn = 1024 net.core.netdev_max_backlog = 50000 #TCP tuning options net.ipv4.tcp_max_syn_backlog = 30000 net.ipv4.tcp_max_tw_buckets = 2000000 net.ipv4.tcp_tw_reuse = 1 net.ipv4.tcp_tw_recycle = 1 net.ipv4.tcp_fin_timeout = 10  #Don't use slow start on idle TCP connections net.ipv4.tcp_slow_start_after_idle = 0

如果你正在为 Ceph 集群使用 IPv6,请确保使用适当的 IPv6 sysctl 选项。

一般系统调优

有许多一般的系统参数建议进行调优,以便更好地满足 Ceph 的性能要求。可以将以下设置添加到 /etc/sysctl.conf 文件中。

确保系统始终有足够的空闲内存:

vm/min_free_kbytes = 524288

增加允许的最大进程数:

kernel.pid_max=4194303

使用以下命令设置最大文件句柄数:

fs.file-max=26234859

内核 RBD

Linux 内核 RBD 驱动允许你将 Ceph RBD 直接映射为标准 Linux 块设备,并像使用其他设备一样使用它们。通常,内核映射的 RBD 需要最小的配置,但在某些特殊情况下,可能需要一些调整。

首先,建议使用尽可能新的内核,因为较新的内核将提供更好的 RBD 支持,并且在某些情况下,性能也有所提升。

队列深度

从内核 4.0 开始,RBD 驱动使用 blk-mq,该系统旨在提供比旧有排队系统更高的性能。默认情况下,当使用 blk-mq 时,RBD 的最大未完成请求数为 128。对于大多数使用场景,这已经足够;然而,如果你的工作负载需要充分利用大型 Ceph 集群的全部性能,你可能会发现仅有 128 个未完成请求不够用。映射 RBD 时有一个选项可以增加这个值,接下来可以进行设置。

预读

默认情况下,RBD 会配置为 128 KB 的 readahead。如果你的工作负载主要涉及大规模顺序读取,通过增加 readahead 值可以显著提升性能。在 4.4 版本之前的内核中,readahead 值大于 2 MB 会被忽略。在大多数存储系统中,这不是问题,因为条带大小通常小于 2 MB。只要 readahead 大于条带大小,所有磁盘都会参与进来,性能也会提升。

默认情况下,Ceph RBD 会在 4 MB 的对象上进行条带化,因此 RBD 的块大小为 4 MB,条带大小为 4 MB × 集群中的 OSD 数量。因此,如果 readahead 大于 4 MB,大多数情况下,readahead 对性能的提升作用非常有限,你可能会发现读取性能难以超过单个 OSD 的性能。

在内核 4.4 及以上版本中,你可以将 readahead 值设置得更高,从而在一秒钟内体验到数百 MB 的读取性能。

调优 CephFS

有两个主要的性能特征决定了 CephFS 的性能——元数据访问速度和数据访问速度,尽管在大多数情况下,这两者都对访问请求有所贡献。

需要理解的是,在 CephFS 中,一旦文件的元数据被检索,实际文件数据的读取就不再需要任何进一步的元数据操作,直到文件被客户端关闭。同样,当写入文件时,只有当脏数据被客户端刷新时,元数据才会被更新。因此,对于大型的顺序缓冲 IO,元数据操作可能只占总集群 IO 的一小部分。

类似地,对于那些处理大量客户端不断打开和关闭多个小文件的 CephFS 文件系统,元数据操作在确定整体性能方面将起到更大的作用。此外,元数据还用于提供与文件系统相关的客户端信息,例如提供目录列表。

处理 CephFS 数据池性能时,应像本章中涵盖的其他 Ceph 性能需求一样进行处理,因此本节的重点将放在元数据性能上。

元数据性能由两个因素决定:通过 RADOS 元数据池读取/写入元数据的速度,以及 MDS 处理客户端请求的速度。首先,确保元数据池存储在闪存上,因为这将把元数据请求的延迟至少减少一个数量级,如果不是更多的话。然而,正如本章 延迟 部分所讨论的那样,分布式网络存储平台引入的延迟也可能影响元数据性能。

为了规避一些延迟,MDS 引入了本地缓存的概念,用来处理热点元数据请求。默认情况下,MDS 会保留 1 GB 的 RAM 用作缓存,通常来说,你分配的 RAM 越多越好。该预留量由 mds_cache_memory_limit 变量控制。通过增加 MDS 可用作缓存的内存量,可以减少需要访问 RADOS 池的请求数量,同时 RAM 的本地性也会降低元数据访问延迟。

当增加额外的 RAM 带来的好处变得很小时,可能是因为缓存的大小已经足够,绝大多数请求都来自缓存,或者是实际的 MDS 已经达到了它可以处理的请求数量。

关于后者,MDS 进程是单线程的,因此会有一个时刻,元数据请求的数量使得 MDS 占用 100% 的单个 CPU 核心,任何额外的缓存或 SSD 都无法解决。当前的建议是尽可能在高频率 CPU 上运行 MDS。四核 Xeon E3 处理器非常适合这种用途,并且通常可以以接近 4 GHz 的频率以合理的价格获得。与一些低频率的 Xeon CPU(通常具有较高的核心数)相比,确保使用快速 CPU 可以带来接近双倍的性能提升。

如果你已经购买了最快的 CPU,且发现单个 MDS 进程仍然是瓶颈,最后的选择应该是开始部署多个活动 MDS,这样元数据请求就能在多个 MDS 之间分散。

RBD 和纠删码池

当使用存储在纠删码池中的 RBD 时,为了保持最佳性能,应该尽量生成完整的条带写入。纠删码池进行完整条带写入时,可以通过单次 IO 完成该操作,而不会受到读取-修改-写入周期和部分写入操作的性能惩罚。

RBD 客户端具备一些智能功能,它们会发出 RADOS 请求,从而在检测到更高级别的客户端 IO 覆盖整个对象时,写入完整的命令。确保 RBD 上的文件系统格式化时具有正确的条带对齐非常重要,以确保生成尽可能多的完整写入操作。

在 4 + 2 EC 池上格式化 RBD 上的 XFS 文件系统的示例如下:

mkfs.xfs /dev/rbd0 -d su=1m,sw=4

这将指示 XFS 对齐分配,以便最佳地适应由 4 + 2 纠删池中存储的 4 MB 对象组成的 4x1 MB 条带。

此外,如果使用案例要求直接将 RBD 挂载到 Linux 服务器,而不是通过 QEMU/KVM 虚拟机,则也值得考虑使用rbd-nbd。用户空间的 RBD 客户端使用 librbd,而内核 RBD 客户端完全依赖于运行内核中存在的 Ceph 代码。

不仅 librbd 使得你可以使用最新的功能(这些功能可能在运行中的内核中不存在),它还具有额外的写回缓存功能。写回缓存在将写入合并为完整对象写入时,比内核客户端更有效,从而减少了性能开销。请记住,librbd 中的写回缓存是非持久性的,因此任何同步写入都无法从中受益。

PG 分布

虽然这不是一个严格意义上的性能调优选项,但确保 Ceph 集群中的 PG 均匀分布是一个关键任务,应在集群部署的早期阶段进行。由于 Ceph 使用 CRUSH 算法伪随机地决定数据的位置,它不会总是将 PG 均匀分布到每个 OSD 上。一个不平衡的 Ceph 集群将无法充分利用原始容量,因为最过载的 OSD 实际上将成为容量的瓶颈。

不均衡的集群意味着更多的请求将被定向到持有最多 PG 的 OSD。这些 OSD 将给集群带来人工的性能瓶颈,尤其是在集群由旋转磁盘 OSD 组成的情况下。

要在 Ceph 集群中重新平衡 PG,只需重新加权 OSD,以便 CRUSH 调整存储在它上的 PG 数量。需要注意的是,默认情况下,每个 OSD 的权重为 1,且不能将一个未充分利用的 OSD 的权重提高到 1 以上来增加其利用率。唯一的选择是降低过度利用的 OSD 的加权值,这将把 PG 移动到利用率较低的 OSD 上。

还需要了解的是,OSD 的 CRUSH 权重和重载加权值之间是有区别的。重载加权值用于覆盖 CRUSH 算法的错误分配。重载命令只会影响 OSD 本身,不会影响它所属于的桶(例如,主机)的权重。重载值在 OSD 重启时也会被重置为 1.0。虽然这可能令人沮丧,但需要理解的是,任何未来对集群的修改,无论是增加 PG 数量还是添加额外的 OSD,都可能导致任何重载值变得不准确。因此,重新加权 OSD 不应视为一次性的操作,而应视为一个持续进行的过程,它会随着集群的变化而进行调整。

要重新加权一个 OSD,可以使用以下简单命令:

Ceph osd reweight <osd number> <weight value 0.0-1.0>

执行完命令后,Ceph 将开始回填操作,将 PG 移动到它们新分配的 OSD 上。

当然,遍历所有的 OSD 并尝试找到需要重新加权的 OSD,然后为每个 OSD 执行此命令,将是一个非常漫长的过程。幸运的是,还有一个 Ceph 工具可以自动化这个过程的大部分操作:

    ceph osd reweight-by-utilization <threshold> <max change>
    <number of OSDs>

此命令将比较集群中的所有 OSD,并更改前N个 OSD 的重载加权值,其中N由最后一个参数控制,该值超出了阈值。你还可以通过指定第二个参数来限制应用于每个 OSD 的最大更改:通常推荐的数值是 0.05 或 5%。

还有一个test-reweight-by-utilization命令,它允许你在运行命令之前查看它将会执行的操作。

虽然这个命令是安全的,但在执行之前需要考虑一些事项:

  • 它不考虑不同 OSD 上的不同池。例如,如果你有一个 SSD 层和一个 HDD 层,reweight-by-utilization 命令仍然会试图跨所有 OSD 平衡数据。如果你的 SSD 层没有像 HDD 层那样满,命令将无法按预期工作。如果你希望平衡仅限于单个桶中的 OSD,可以查看 CERN 创建的该命令的脚本版本。

  • 可以重新调整集群权重,直到 CRUSH 无法为某些 PG 确定位置。如果恢复停止,并且一个或多个 PG 留在重新映射状态下,这很可能就是发生的情况。只需增加或重置权重值即可解决问题。

一旦你确认了命令的操作,便可以通过 cron 来调度它,这样集群就能自动保持更平衡的状态。

自 Luminous 版本发布以来,新增了一个管理模块,叫做 Ceph balancer。这个新模块会在后台持续工作,优化 PG 分布,并确保 Ceph 集群的最大容量可用。

Ceph balancer 模块可以使用两种方法来平衡数据分布。第一种是 crush-compat;这种方法使用额外的权重字段来调整每个 OSD 的权重。crush-compat 的主要优点是它与旧版客户端兼容。另一种方法叫做 upmap;upmap 能比 crush-compat 实现更精细的 PG 映射,因为它利用 OSD 映射中的新功能来影响 PG 映射。缺点是,由于这些新功能,Ceph 客户端需要运行 Luminous 或更新版本。

要启用 ceph balancer,只需运行这两个命令:

ceph mgr module enable balancer ceph balancer on

你将看到 Ceph 开始回填,因为 PG 被重新映射到新的 OSD,以平衡空间利用率;这将持续进行,直到 Ceph balancer 将 OSD 利用率的偏差降低。

总结

现在你应该已经掌握了如何调优 Ceph 集群,以最大化性能并实现更低的延迟。通过使用基准测试,你应该能够进行前后对比测试,确认调优是否达到了预期效果。值得回顾官方 Ceph 文档,以更好地理解其他可能对集群有益的配置选项。

你还学习了影响 Ceph 性能的一些关键因素,以及如何调整它们,例如 CPU 时钟速度和睡眠状态。确保 Ceph 集群运行的基础设施处于最佳性能状态,将确保 Ceph 发挥其最佳性能。

在下一章,我们将讨论分层技术及其如何通过将不同的磁盘技术结合起来,提高性能。

问题

  1. 默认情况下,PG 分布是否均匀?

  2. 为什么在 EC 池上偏好进行全条带写操作?

  3. 对于低延迟,应该选择哪种类型的 CPU?

  4. 哪三个因素主要影响延迟?

  5. 可以使用什么自动化工具来平衡集群中的空间利用率?

第十章:Ceph 的分层

Ceph 中的分层功能允许你将一个 RADOS 池覆盖到另一个 RADOS 池上,并让 Ceph 智能地在它们之间提升和驱逐对象。在大多数配置中,顶层池将由快速存储设备组成,如固态硬盘SSDs),而基础池则由较慢的存储设备组成,如串行 ATASATA)或串行附加 SCSISAS)硬盘。如果你的数据工作集的比例相对较小,这允许你使用 Ceph 提供高容量存储,同时仍能保持对频繁访问数据的良好性能。

在本章中,我们将讨论以下主题:

  • Ceph 的分层功能如何工作

  • 分层的良好应用场景是什么

  • 如何将两个池配置为一个分层

  • 可用于分层的各种调整选项

如果你希望使用分层功能,建议至少运行 Ceph 的 Jewel 版本。以前的版本缺乏许多使分层可用的特性。

分层与缓存的区别

虽然通常将其描述为缓存分层,但更好的理解方式是将 Ceph 中的功能视为一种分层技术,而非缓存。在继续阅读之前,理解两者之间的差异非常重要。

缓存通常旨在加速对一组数据的访问,除非它是一个写回缓存;它不会持有数据的唯一副本,通常将数据提升到缓存的开销较小。缓存通常在较短的时间框架内操作,并且经常会将所有访问的数据提升到缓存中。

分层解决方案也旨在加速对一组数据的访问;然而,它的提升策略通常在更长的时间内工作,并且在选择提升哪些数据时更加谨慎,这主要是因为提升操作对整体存储性能的影响较小。此外,在分层技术中,通常只有一个分层可能持有数据的有效状态,因此系统中的所有分层都需要得到平等的保护,以防数据丢失。

Ceph 的分层功能如何工作

一旦你配置了一个 RADOS 池作为另一个 RADOS 池的覆盖,Ceph 的分层功能基于以下基本原理工作:如果一个对象不存在于顶层分层中,则它必须存在于基础分层中。所有来自客户端的对象请求都会发送到顶层分层;如果 OSD 没有请求的对象,则根据分层模式,它可能会将读取或写入请求代理到基础分层,或强制进行提升。然后,基础分层通过顶层分层将请求代理回客户端。需要注意的是,分层功能对客户端是透明的,不需要特定的客户端配置。

在分层中有三种主要操作将对象在各个层级之间移动。提升操作将对象从基础层复制到顶层。如果分层配置为写回模式,则使用刷新操作来更新基础层对象的内容。最后,当顶层池达到容量时,对象会通过逐出操作被逐出。

为了能够做出在两个层级之间移动对象的决策,Ceph 使用 HitSets 来追踪对象的访问。HitSet是所有对象访问请求的集合,查询它可以判断一个对象自从该 HitSet 创建以来是否有读或写请求。HitSets 使用Bloom 过滤器来统计性地追踪对象访问,而不是存储每个对象的每次访问,这样会产生巨大的开销。Bloom 过滤器只存储二进制状态,一个对象只能被标记为已访问或未访问,并且没有存储单个 HitSet 中对象访问次数的概念。如果一个对象出现在多个最近的 HitSets 中,并且位于基础池中,那么它将被提升。

同样,如果对象不再出现在最近的 HitSets 中,当顶层受到压力时,它们将成为刷新或逐出的候选对象。可以配置 HitSets 的数量以及每个新 HitSet 创建的频率,此外,还可以配置写入或读取 I/O 必须出现在多少个最近的 HitSets 中,才能触发提升操作。顶层的大小也可以配置,并且与其所在 RADOS 池的可用容量无关。

有许多配置和调优选项定义了 Ceph 如何处理生成的 HitSets,以及在什么阈值下会发生提升、刷新和逐出操作。后续章节会更详细地介绍这些内容。

什么是 Bloom 过滤器?

Bloom 过滤器在 Ceph 中用于提供一种高效的方式来追踪一个对象是否是 HitSet 的成员,而无需单独存储每个对象的访问状态。它本质上是概率性的,尽管它可能会返回假阳性,但绝不会返回假阴性。这意味着在查询 Bloom 过滤器时,它可能报告某个项存在,尽管它并不存在,但它绝不会报告某个项不存在,尽管它实际上存在。

Ceph 使用 Bloom 过滤器可以高效地追踪数百万个对象的访问,而无需存储每个访问的开销。如果发生假阳性,这可能意味着某个对象被错误地提升;然而,这种情况发生的概率以及其带来的影响微乎其微,因此不需要过多担忧。

分层模式

有多种分层模式可以决定 Ceph 对 HitSets 内容的具体反应。然而,在大多数情况下,回写模式将被使用。可用于分层的模式有 回写转发读取转发代理读取代理。以下章节简要描述了这些模式及其行为。

回写

在回写模式下,数据通过读取和写入被提升到顶层,根据对象的访问频率决定。顶层的对象可以被修改,脏数据将在稍后的时间被刷新到池中。如果对象需要在底层读取或写入,并且底层池支持该操作,Ceph 将尽量直接代理此操作,以最小化延迟影响。

转发

转发模式只是将所有请求从顶层转发到基础层,而不进行任何提升。需要注意的是,转发操作会导致 OSD 告诉客户端将请求重新发送到正确的 OSD,因此它对延迟的影响比单纯的代理操作要大。

读取转发

读取转发模式强制每次写入时都进行提升,像前面提到的转发模式一样,将所有读取请求重定向到基础池。如果你希望仅使用顶层池进行写入加速,这种模式会非常有用。使用以写入为主的 SSD 覆盖以读取为主的 SSD 就是一个典型的例子。

代理

这类似于转发模式,不同之处在于它代理所有的读取和写入,而不进行任何提升。通过代理请求,OSD 本身从基础层 OSD 获取数据,然后将其传递回客户端。与使用转发相比,这减少了开销。

读取代理

类似于读取转发模式,不同之处在于它代理读取请求,并始终提升写入请求。需要注意的是,回写模式和读取代理模式是唯一经过严格测试的模式,因此在使用其他模式时需要谨慎。同时,使用其他模式可能收效甚微,并且它们可能会在未来的版本中逐步淘汰。

使用案例

正如本章开头所提到的,分层功能应被视为分层,而非缓存。之所以这样说,是因为与大多数缓存解决方案相比,提升操作会对集群性能产生不利影响,后者通常在启用非缓存工作负载时不会降低性能。提升操作对性能的影响主要由两个原因引起。首先,提升发生在 I/O 路径中;整个待提升的对象需要从基础层读取,然后再写入顶层,最终才将 I/O 返回给客户端。

其次,这个提升动作可能还会导致刷新和驱逐,从而导致两个层之间更多的读取和写入。如果两个层都使用 3 倍复制,这甚至可能仅仅因为一次提升就造成大量写放大。在最坏的情况下,一次 4 KB 的访问导致提升,可能会在两个层之间造成 8 MB 的读取 I/O 和 24 MB 的写入 I/O。这种增加的 I/O 会导致延迟增加;因此,提升应该被视为昂贵操作,并应进行调优以尽量减少提升的发生。

考虑到这一点,Ceph 分层应仅在热数据或活动数据能够适应顶部层时使用。均匀随机的工作负载可能不会看到任何好处,并且在许多情况下可能会导致性能下降,因为没有合适的对象可供提升,或者提升过多。

大多数涉及为通用虚拟机提供存储的工作负载通常是不错的选择,因为通常只有小部分虚拟机会被访问。

在线事务处理OLTP)数据库通常在使用缓存或分层时会有所改进,因为它们的热数据集相对较小,数据模式相对一致。然而,报表或批处理数据库通常不适合,因为它们经常需要访问大范围的数据,而且通常没有预热期。

RADOS 块设备RBD)工作负载涉及无特定模式的随机访问或涉及大量读取或写入流的工作负载应避免使用,并且可能会因为增加缓存层而遭受性能下降。

在 Ceph 中创建分层

为了测试 Ceph 的分层功能,需要两个 RADOS 池。如果您在笔记本电脑或桌面硬件上运行这些示例,尽管可以使用基于旋转磁盘的 OSD 来创建池,但如果有任何读取和写入数据的需求,强烈建议使用 SSD。如果测试硬件中有多种磁盘类型可用,则可以将基础层放在旋转磁盘上,并将顶部层放置在 SSD 上。

让我们使用以下命令来创建分层,所有这些命令都使用了 Ceph 的 tier 命令:

  1. 创建两个 RADOS 池:
 ceph osd pool create base 64 64 replicated ceph osd pool create top 64 64 replicated

前面的命令会产生以下输出:

  1. 创建一个由两个池组成的层:
 ceph osd tier add base top

前面的命令会产生以下输出:

  1. 配置缓存模式:
 ceph osd tier cache-mode top writeback

前面的命令会产生以下输出:

  1. 将顶部层和基础层叠加:
 ceph osd tier set-overlay base top

前面的命令会产生以下输出:

  1. 现在分层已配置好,我们需要设置一些简单的值,以确保分层代理能够正常工作。如果没有这些设置,分层机制将无法正常工作。请注意,这些命令只是设置池上的变量:
 ceph osd pool set top hit_set_type bloom ceph osd pool set top hit_set_count 10 ceph osd pool set top hit_set_period 60 ceph osd pool set top target_max_bytes 100000000

上述命令将产生以下输出:

之前提到的命令只是告诉 Ceph,HitSets 应该使用布隆过滤器创建。它应该每 60 秒创建一个新的 HitSet,并且应该保留其中的十个,丢弃最旧的一个。最后,顶层池的大小不应超过 100 MB;如果达到此限制,I/O 操作将被阻塞。关于这些设置的更详细解释将在下一节中给出。

  1. 接下来,我们需要配置各种选项,控制 Ceph 如何从顶层到基础层刷新和驱逐对象:
 ceph osd pool set top cache_target_dirty_ratio 0.4 ceph osd pool set top cache_target_full_ratio 0.8

上述命令将产生以下输出:

之前的示例告诉 Ceph,当顶层满 40% 时,它应该开始将脏对象从顶层刷新到基础层。当顶层满 80% 时,应该从顶层驱逐对象。

  1. 最后,最后两个命令指示 Ceph,任何对象必须在顶层至少停留 60 秒,才能考虑进行刷新或驱逐:
 ceph osd pool set top cache_min_flush_age 60 ceph osd pool set top cache_min_evict_age 60

上述命令将产生以下输出:

调优分层

与 Ceph 大多数功能不同,Ceph 的分层功能需要仔细配置其各个参数,以确保良好的性能。你还应该对工作负载的 I/O 特性有一个基本的了解;只有当数据中有一小部分热数据时,分层才能发挥良好的作用。均匀随机访问或大量顺序访问模式的工作负载要么没有改进,要么在某些情况下可能会变得更慢。

刷新与驱逐

首先应该查看的主要调优选项是定义顶层大小限制、何时刷新以及何时驱逐的选项。

以下两个配置选项配置顶层池中存储数据的最大大小:

 target_max_bytes target_max_objects 

大小可以用字节数或对象数来指定,且不必与实际池的大小相同,但不能大于实际池的大小。大小也是基于 RADOS 池在复制后的可用容量,因此对于一个 3 副本池来说,这将是原始容量的三分之一。如果该池中的字节数或对象数超过此限制,I/O 将被阻塞;因此,必须考虑后续的其他配置选项,以确保不会达到此限制。设置该值也非常重要,因为如果没有此值,刷新或驱逐操作将不会发生,池将会填满 OSD 并阻塞 I/O。

这个设置存在的原因,而不是让 Ceph 直接使用 RADOS 池中磁盘的底层容量,是因为通过指定大小,你可以在同一组磁盘上拥有多个顶层池。

如你之前所学,target_max_bytes 设置了池中分层数据的最大大小,如果达到此限制,I/O 将被阻塞。为了确保 RADOS 池不会达到此限制,cache_target_full_ratio 指示 Ceph 尝试通过在超过此目标时驱逐对象来保持池的大小在 target_max_bytes 的某一百分比以内。与晋升和刷新操作不同,驱逐是相对低成本的操作:

 cache_target_full_ratio

该值指定为介于 01 之间的数值,类似于百分比。需要注意的是,虽然 target_max_bytescache_target_full_ratio 是针对池设置的,但 Ceph 在内部是使用这些值来计算每个 PG 的限制的。这可能意味着在某些情况下,某些 PG 会比其他 PG 先达到计算出的最大限制,并有时会导致意外结果。由于这个原因,建议不要将 cache_target_full_ratio 设置得太高,并保留一些余量;通常情况下,0.8 的值效果良好。我们有以下代码:

 cache_target_dirty_ratio cache_target_dirty_high_ratio

这两个配置选项控制了在分层存储已配置为写回模式时,Ceph 将脏对象从顶层刷新到基础层的时机。如果一个对象在顶层被修改,它被视为脏对象;在基础层修改的对象不会被标记为脏。刷新操作涉及将对象从顶层复制到基础层;由于这是一次完整的对象写入,基础层可以是一个擦除编码池。该行为是异步的,除了增加 RADOS 池的 I/O 负载外,并不会直接影响客户端的 I/O。对象通常以低于驱逐速度的速度被刷新。由于刷新操作相较于驱逐是一个昂贵的操作,这意味着如果需要,可以快速驱逐大量对象。

这两个比率控制 OSD 允许的刷新速度,通过限制允许同时运行的并行刷新线程数量来实现。这些可以通过分别设置 osd_agent_max_opsosd_agent_max_high_ops OSD 配置选项来控制。默认情况下,这些设置为 2 和 4 个并行线程。

理论上,脏对象的百分比应该在正常集群使用期间保持在较低的脏比率附近。这意味着对象以较低的并行刷新度被刷新,以最小化对集群延迟的影响。随着正常的写入突发流量到达集群,脏对象的数量可能会增加,但随着时间的推移,这些写入会被刷新到基础层。

然而,如果出现持续的写入超出了低速刷新能力的情况,那么脏对象的数量将开始上升。希望这种高写入 I/O 的持续时间不会长到足以让顶层被脏对象填满,因此它将逐渐减少到低阈值。然而,如果脏对象数量继续增加并达到高比率,那么刷新并行度将会增加,并且希望能够阻止脏对象数量进一步增加。一旦写入流量减少,脏对象数量将再次降回低比率。以下图表展示了这一过程:

这两个脏比率应该有足够的差异,以便正常的写入突发流量能够被吸收,而不会触发较高的脏比率。高脏比率应当视为紧急限制。一个好的起始值是将低比率设置为 0.4,将高比率设置为 0.6。

osd_agent_max_ops 配置设置应该进行调整,使得在正常操作条件下,脏对象的数量保持在低脏比率附近或略高。由于这些设置会在很大程度上依赖于顶层和基础层的大小与性能比率,因此不容易推荐一个具体值。不过,可以从将osd_agent_max_ops设置为1并根据需要增加的方式开始,并将osd_agent_max_high_ops设置为至少是双倍。

如果你在 Ceph 状态页面看到指示正在发生高速刷新状态信息,那么你需要增加osd_agent_max_ops。如果你看到顶层存储满了并且阻塞了 I/O,那么你要么考虑降低cache_target_dirty_high_ratio变量,要么增加osd_agent_max_high_ops设置,以防止顶层被脏对象填满。

提升

接下来需要调整的选项是定义 HitSets 以及触发提升所需的时效性:

 hitset_count hitset_period

hitset_count 设置控制最多可以存在多少个 HitSets 在最旧的一个开始被修剪之前。hitset_period 设置控制多久创建一个 HitSet。如果在实验室环境中测试分层存储,需要注意的是必须对 PG 进行 I/O 操作才能创建 HitSet;在空闲集群上,不会创建或修剪任何 HitSets。

保证正确的数量并控制创建 HitSets 的频率是可靠地控制对象晋升时间的关键。请记住,HitSets 只包含对象是否已访问的数据;它们不包含对象被访问的次数计数。如果 hitset_period 太长,即使访问较少的对象也会出现在大多数 HitSets 中。例如,如果 hitset_period 是两分钟,那么包含日志文件更新磁盘块的 RBD 对象每分钟更新一次,将与每秒访问 100 次的对象出现在完全相同的 HitSets 中。

相反,如果周期太低,即使热对象也可能不会出现在足够多的 HitSets 中,以使它们成为推广的候选对象,并且您的顶层可能不会被充分使用。通过找到正确的 HitSet 周期,您应该能够捕获适当比例的热对象候选对象的正确视图:

 min_read_recency_for_promote min_write_recency_for_promote

这两个设置定义了对象必须出现在最近 HitSets 中的数量,以便晋升。由于概率的影响,半热对象和最近性设置之间的关系不是线性的。一旦设置的最近性超过约 3 或 4,可以晋升的对象数量呈对数方式下降。应当注意,尽管可以根据读或写单独作出晋升决策,但它们都引用相同的 HitSet 数据,无法确定访问是读取还是写入。作为一个方便的功能,如果将最近性设置得比 hitset_count 设置更高,则永远不会晋升。例如,可以确保写入 I/O 绝不会导致对象晋升,方法是将写入最近性设置得高于 hitset_count 设置。

推广节流

正如之前所述,推广在分层中是非常昂贵的操作,必须小心确保仅在必要时进行。这在很大程度上是通过精心调整 HitSet 和最近性设置来完成的。然而,为了限制推广的影响,还有一个额外的节流器限制推广速度的数量。此限制可以通过两个 OSD 配置选项指定为每秒字节或对象的数量:

 osd_tier_promote_max_bytes_sec osd_tier_promote_max_objects_sec

默认限制是 4 MBps 或每秒五个对象。虽然与最新 SSD 的性能相比,这些数字可能听起来很低,但它们的主要目的是最小化提升对延迟的影响。应该进行仔细调优,以便找到集群的良好平衡。需要注意的是,这个值是按 OSD 配置的,因此总的提升速度将是所有 OSD 的和。

最后,以下配置选项允许调优对象刷新选择过程:

 hit_set_grade_search_last_n

这控制了查询多少个 HitSets 以确定对象的温度,其中对象的温度反映了它被访问的频率。冷对象很少被访问,而热对象则被更频繁地访问,是驱逐的候选者。建议将其设置为与最近性设置类似的值。我们有以下代码:

 hit_set_grade_decay_rate

这与 hit_set_grade_search_last_n 设置配合使用,并随着时间推移,逐渐衰减 HitSet 结果。那些比其他对象更频繁被访问的对象会有更高的温度评级,并确保那些频繁被访问的对象不会被错误地刷新。需要注意的是,在刷新或驱逐对象时,min_flushevict_age 设置可能会覆盖对象的温度:

 cache_min_flush_age cache_min_evict_age

cache_min_evict_agecache_min_flush_age 设置简单地定义了一个对象在被允许刷新或驱逐之前,必须有多长时间没有被修改。这些设置可以用来阻止那些仅仅低于阈值的对象,在不断在不同层级之间移动的循环中卡住。将它们设置为 10 到 30 分钟之间可能是一个不错的做法,尽管需要注意的是,如果没有符合条件的对象可以被刷新或驱逐,顶层可能会填满。

监控参数

为了监控 Ceph 集群中缓存层的性能和特性,有一些性能计数器可以进行监控。我们暂时假设你已经从管理员插座收集了 Ceph 性能计数器,正如下一章所讨论的那样。

在查看性能计数器时,最重要的事情是记住,一旦你在 Ceph 中配置了一个层级,所有客户端请求都会通过顶级层。因此,只有在组成顶级层的 OSD 上的读写操作计数器才会显示任何请求,前提是底层的 OSD 没有用于其他池。要了解底层处理的请求数量,有代理操作计数器,它会显示这个数字。这些代理操作计数器也会在顶层 OSD 上计算,因此要监控带有分层的 Ceph 集群的吞吐量,只需要将顶层 OSD 包括在计算中。

以下计数器可用于监控 Ceph 中的分层;所有计数器需在顶层 OSD 上进行监控:

计数器 描述
op_r OSD 处理的读取操作
op_w OSD 处理的写操作
tier_proxy_read 被代理到基础层的读取操作
tier_proxy_write 被代理到基础层的写操作
tier_promote 从基础层提升到顶层的次数
tier_try_flush 从顶层到基础层的刷新次数
tier_evict 从顶层到基础层的驱逐次数

替代缓存机制

原生的 RADOS 分层功能提供了多种灵活性优势,并且可以使用相同的 Ceph 工具集进行管理。然而,不可否认的是,在纯粹的性能方面,RADOS 分层比其他通常在块设备级别运行的缓存技术落后。

Bcache 是 Linux 内核中的一个块设备缓存,可以使用 SSD 来缓存较慢的块设备,例如旋转磁盘。

Bcache 是通过 SSD 提升 Ceph 性能的流行方法之一。与 RADOS 分层不同,RADOS 分层允许选择缓存哪个池,而 bcache 则是缓存整个 OSD。此种缓存方法在性能方面带来了一些优势。首先,由于 SSD 缓存,OSD 本身的延迟响应更加一致。Filestore 会在每个 Ceph 请求中增加大量的随机 I/O,无论 Ceph 请求是随机还是顺序的。Bcache 可以吸收这些随机 I/O,并允许旋转磁盘执行更多的顺序 I/O。这在高负载时期非常有用,因为在这些时候,正常的旋转磁盘 OSD 会开始出现较高的延迟。其次,RADOS 分层是在池中存储的对象大小上运行的,默认情况下,对于 RBD 工作负载来说是 4 MB。Bcache 缓存的数据块更小,这使得它能够更好地利用可用的 SSD 空间,并且在提升开销方面也较少。

分配给 bcache 的 SSD 容量也将作为热数据的读取缓存;这将提高读取性能以及写入性能。由于 bcache 仅将该容量用于读取缓存,它只会存储数据的一份副本,因此与在 RADOS 层级池中使用相同 SSD 相比,读取缓存容量将大三倍。

然而,使用 bcache 存在一些缺点,这使得使用 RADOS 缓存池仍然具有吸引力。如前所述,bcache 会缓存整个 OSD。在某些情况下,如果多个池可能位于相同的 OSD 上,这种行为可能是不可取的。另外,一旦 bcache 配置了 SSD 和 HDD,如果未来需要扩展缓存容量,就变得更加困难。如果你的集群目前没有任何形式的缓存,这种情况尤其如此;在这种情况下,引入 bcache 会非常具有破坏性。使用 RADOS 分层时,你可以根据需要简单地添加额外的 SSD 或专门设计的 SSD 节点,以增加或扩展顶部层。

dm-cache 是另一种 Linux 块缓存解决方案,内置于 Linux 的 逻辑卷管理器LVM)中。尽管其缓存算法不如 bcache 的先进,但由于它在 LVM 中非常容易启用,即使在卷创建后也可以启用,因此它非常适合与 BlueStore OSD 一起使用。现在,BlueStore OSD 是通过 ceph-volume 创建的,ceph-volume 在块设备上创建逻辑卷,从而仅需几步即可启用现有 OSD 的缓存。

另一种方法是将旋转磁盘 OSD 放置在带有电池支持写回缓存的 RAID 控制器后面。RAID 控制器执行类似于 bcache 的角色,吸收大量与 OSD 的额外元数据相关的随机写入 I/O。由此,延迟和顺序写入性能都会提升。然而,读取性能可能不会提升,因为 RAID 控制器缓存的大小相对较小。

通过使用带有 filestore OSD 的 RAID 控制器,OSD 的日志可以直接放置在磁盘上,而不是使用单独的 SSD。这样,日志写入会被 RAID 控制器的缓存吸收,从而提高日志的随机性能,因为大多数时候,日志内容可能会存放在控制器的缓存中。然而,需要小心的是,如果传入的写入流量超过了控制器缓存的容量,日志内容将开始被刷新到磁盘,从而导致性能下降。为了获得最佳性能,应使用单独的 SSD 或 NVMe 来存储 filestore 日志,但也要考虑使用具有足够性能和缓存的 RAID 控制器的成本,以及 SSD 的成本。

对于 BlueStore OSD,RAID 控制器上的写回缓存大大有利于写入延迟;然而,BlueStore 的元数据仍然需要存储在闪存介质上,以保证良好的性能。因此,在使用写回 RAID 控制器时,仍然强烈建议使用单独的 SSD。

这两种方法各有优缺点,在集群中实现缓存之前,应考虑它们。

总结

在本章中,我们已经介绍了 Ceph 的 RADOS 层次化功能背后的理论,并讨论了配置和调优操作,以使其最好地适应你的工作负载。必须记住,最重要的方面是了解你的工作负载,并确信其 I/O 模式和分布是适合缓存的。通过本章中的示例,你现在也应该理解实施层次化池所需的步骤,以及如何应用配置选项。

在下一章中,我们将讨论在维护一个健康的 Ceph 集群时会出现哪些问题,以及我们如何处理它们。

问题

  1. 列出使用层次化技术的两个理由。

  2. 列举一个 Ceph 之外的层次化技术。

  3. RADOS 层次化使用什么方法来跟踪命中请求?

  4. 哪个池变量控制在读取请求提升对象之前的最近命中次数?

  5. RADOS 层次化的一个良好使用场景是什么?

第三部分:故障排除与恢复

本书的这一部分涵盖了 Ceph 管理员日常工作中的阴暗时刻。读完这一部分后,读者将更加自信地恢复 Ceph 集群并解决问题,万一出现问题时。

本节中的以下章节:

  • 第十一章,故障排除

  • 第十二章,灾难恢复

第十一章:故障排除

Ceph 在大多数情况下能够自主管理和从故障情景中恢复,但在某些情况下需要人工干预。本章将讨论常见的错误和故障场景,并介绍如何通过故障排除将 Ceph 恢复到正常工作状态。

本章将涉及以下主题:

  • 如何正确修复不一致的对象

  • 如何通过对等来解决问题

  • 如何处理 near_fulltoo_full OSD

  • 如何通过 Ceph 日志调查错误

  • 如何调查性能问题

  • 如何调查处于停机状态的 PG

修复不一致的对象

使用 BlueStore 时,所有数据默认都会进行校验和检查,因此本节中用于安全确认对象副本的步骤不再适用。

现在我们将展示如何正确修复不一致的对象:

  1. 为了能够重现不一致的场景,创建一个 RBD,稍后我们将在其上创建文件系统:

  1. 通过检查已创建的对象,确定通过格式化 RBD 后文件系统上创建的对象:

  1. 随机选择一个对象,并使用 osd map 命令找出该对象存储在哪个 PG 中:

  1. 在一个 OSD 节点的磁盘上找到该对象;在此案例中,它位于 OSD1 上的 OSD.0

  1. 通过向其写入垃圾数据来破坏它:

  1. 告诉 Ceph 对我们破坏的对象所在的 PG 执行清理:

  1. 如果你检查 Ceph 状态,你将看到 Ceph 已经检测到损坏的对象并将 PG 标记为不一致。从现在开始,忘记我们手动破坏对象的事实,按照正常流程进行修复:

通过查看详细的健康报告,我们可以找到包含损坏对象的 PG。我们可以直接让 Ceph 修复 PG;但是,如果主要的 OSD 持有损坏的对象,它将覆盖其余的正常副本。这是不可取的,因此为了确保这种情况不会发生,在运行 repair 命令之前,我们将确认哪个 OSD 持有损坏的对象。

通过查看健康报告,我们可以看到持有对象副本的三台 OSD;第一台 OSD 是主 OSD。

  1. 登录到主 OSD 节点并打开主 OSD 的日志文件。你应该能够找到标明哪个对象被 PG 清理标记的日志条目。

  2. 浏览 PG 结构,找到日志文件中提到的对象,并计算每个副本的 md5sum

md5sum 计算 osd 节点一上的对象。

md5sum 计算 osd 节点二上的对象。

md5sum的对象在osd节点三。

我们可以看到OSD.0上的对象有不同的md5sum,因此我们知道它是那个损坏的对象。

OSD.0 = \0d599f0ec05c3bda8c3b8a68c32a1b47 OSD.2 = \b5cfa9d6c8febd618f91ac2843d50a1c OSD.3 = \b5cfa9d6c8febd618f91ac2843d50a1c

尽管我们已经知道哪个对象副本被破坏,因为我们手动在OSD.0上破坏了该对象,假设我们没有这么做,并且这个破坏是由某些随机的宇宙射线造成的。现在我们有了三个副本的md5sum,并且可以清楚地看到OSD.0上的副本是错误的。这就是为什么 2x 复制方案不好的一大原因;如果 PG 变得不一致,你无法分辨哪个副本是坏的。由于该 PG 的主 OSD 是 2,正如在 Ceph 健康详情和 Ceph OSD map命令中看到的,我们可以放心地运行ceph pg repair命令,而不必担心将坏对象覆盖到剩余的好副本上:

我们可以看到不一致的 PG 已经自我修复:

如果主 OSD 上的副本损坏,则应采取以下步骤:

  1. 停止主 OSD。

  2. 从 PG 目录中删除该对象。

  3. 重启 OSD。

  4. 指示 Ceph 修复 PG。

完整的 OSD

默认情况下,当 OSD 的利用率接近 85%时,Ceph 会警告我们,并且当达到 95%时,它将停止向 OSD 写入 I/O。如果由于某些原因,OSD 完全填满至 100%,OSD 很可能会崩溃,并拒绝重新上线。一个超过 85%警告级别的 OSD 也会拒绝参与回填,因此当 OSD 处于接近满载状态时,集群的恢复可能会受到影响。

在讨论有关满载 OSD 的故障排除步骤之前,强烈建议你监控 OSD 的容量利用率,正如在第八章《监控 Ceph》中所述。这将为你提供提前警告,提醒 OSD 接近near_full警告阈值。

如果你发现集群的状态接近满载警告状态,你有两个选择:

  • 增加一些更多的 OSD。

  • 删除一些数据。

然而,在现实世界中,这两种情况要么是不可能的,要么需要时间,这时情况可能会恶化。如果 OSD 仅处于near_full阈值,你可能通过检查 OSD 的利用率是否平衡来重新调整状态,然后执行 PG 平衡操作。如果没有平衡,这在第九章《调整 Ceph》中有更详细的说明。too_full的 OSD 也适用相同的方法;虽然你不太可能将它们恢复到 85%以下,但至少可以恢复写操作。

如果你的 OSD 完全满了,它们将处于离线状态,并且会拒绝启动。现在你遇到了一个额外的问题。如果 OSD 无法启动,无论你进行什么重新平衡或删除数据,都无法在已满的 OSD 上反映出来,因为它们处于离线状态。唯一能从这种情况恢复的方法是手动从磁盘文件系统中删除一些 PG,以便 OSD 可以启动。

为此应采取以下步骤:

  1. 确保 OSD 进程没有在运行。

  2. 在集群上设置nobackfill,以防止 OSD 重新上线时发生恢复。

  3. 找到一个处于活动、清洁和重新映射状态并存在于离线 OSD 上的 PG。

  4. 使用 ceph-objectstore-tool 从离线 OSD 删除该 PG。

  5. 重启 OSD。

  6. 从 Ceph 集群中删除数据或重新平衡 PG。

  7. 移除nobackfill

  8. 运行擦除操作并修复你刚刚删除的 PG。

Ceph 日志

在调查错误时,能够查看 Ceph 日志文件非常方便,这有助于更好地了解发生了什么。默认情况下,日志级别设置为只记录重要事件。在故障排除过程中,可能需要提高日志级别,以揭示错误的原因。要提高日志级别,你可以编辑ceph.conf,添加新的日志级别,然后重启组件,或者,如果你不希望重启 Ceph 守护进程,你可以将新的配置参数注入到正在运行的守护进程中。要注入参数,可以使用ceph tell命令:

ceph tell osd.0 injectargs --debug-osd 0/5

然后,将osd.0的 OSD 日志级别设置为0/5。数字0表示磁盘日志级别,数字5表示内存日志级别。

20的日志级别下,日志会非常详细且快速增长。不要长时间保持高详细度日志开启。更高的日志级别也会影响性能。

慢性能

慢性能定义为集群正在积极处理 I/O 请求,但似乎运行在低于预期的性能水平。通常,慢性能是由于 Ceph 集群的某个组件达到了饱和并成为瓶颈。这可能是由于客户端请求增加或组件故障,导致 Ceph 执行恢复操作。

原因

虽然有许多原因可能导致 Ceph 性能变慢,以下是一些最可能的原因。

客户端工作负载增加

有时候,性能慢可能不是由于底层故障引起的;可能只是客户端请求的数量和类型超出了硬件的承载能力。无论是多个独立工作负载同时运行,还是在一段时间内缓慢增加的工作负载,如果你在集群中捕获客户端请求的数量,这应该很容易进行趋势分析。如果增加的工作负载看起来像是永久性的,那么唯一的解决方案就是增加一些额外的硬件。

故障 OSD

如果在集群中有大量 OSD 被标记为down,可能是由于整个 OSD 节点下线——虽然恢复操作不会开始,直到 OSD 被标记为out——但性能会受到影响,因为现在可用于服务客户端 IO 的 IOPS 数量将减少。如果发生这种情况,你的监控解决方案应该会发出警报,并允许你采取相应的措施。

恢复与填充

当一个 OSD 被标记为out时,受影响的 PG 将与新的 OSD 重新配对,并开始在集群中恢复和填充数据的过程。这个过程可能会对 Ceph 集群中的磁盘造成压力,并导致客户端请求的延迟增加。有几个调优选项可以通过降低恢复速率和优先级来减少填充的影响。这些应该与从故障磁盘恢复较慢所带来的影响进行权衡,因为这可能会降低集群的持久性。

检查

当 Ceph 执行深度检查以检查数据是否存在不一致时,它必须读取 OSD 中的所有对象;这可能是一个非常 IO 密集的任务,在大容量磁盘上,这个过程可能需要很长时间。深度检查对于防止数据丢失至关重要,因此不应禁用。在第九章《调优 Ceph》中讨论了各种调优选项,关于设置深度检查的时间窗口和优先级。通过调整这些设置,可以避免深度检查对客户端工作负载的性能影响。

快照修剪

当你删除一个快照时,Ceph 必须删除由于快照过程中的写时复制特性所创建的所有对象。从 Ceph 10.2.8 开始,提供了一个改进的 OSD 设置osd_snap_trim_sleep,使 Ceph 在修剪每个快照对象之间等待指定的时间间隔。这确保了后端对象存储不会过载。

尽管在之前的 Jewel 版本中已有此设置,但其行为有所不同,且不应使用。

硬件或驱动程序问题

如果你最近将新硬件引入到 Ceph 集群中,并且在数据回填重新平衡后开始遇到性能缓慢,检查与硬件相关的固件或驱动程序更新,因为较新的驱动程序可能需要更新的内核。如果你仅引入了少量硬件,你可以暂时将其中的 OSD 标记为不可用,而不低于池的 min_size;这可以是排除硬件问题的好方法。

监控

这是你在第八章 Ceph 分层 中配置的监控真正派上用场的地方,它可以让你将长期趋势与当前的指标数据进行对比,并查看是否存在明显的异常。

建议你首先查看磁盘性能,因为在大多数性能不佳的情况下,底层磁盘通常是成为瓶颈的组件。

如果你没有配置监控,或者希望手动深入挖掘性能指标,有许多工具可以帮助你实现这一目标。

iostat

iostat 可以用来实时查看你所有 OSD 节点上磁盘的性能和延迟。使用以下命令运行 iostat

iostat -d 1 -x

你将看到类似这样的显示,每秒刷新一次:

一般来说,如果你的磁盘在一段时间内显示出较高的 % 利用率,那么很可能你的磁盘已经达到饱和。你还可以查看 r_await 时间,看看读取请求是否比你在 OSD 节点上预期的磁盘类型需要的时间更长。如前所述,如果你发现高磁盘利用率是导致性能缓慢的原因,并且触发因素不太可能很快消失,增加磁盘是唯一的解决办法。

htop

与标准的 top 工具类似,htop 提供了主机 CPU 和内存消耗的实时视图。然而,它也生成了一个更直观的显示,这可以使判断系统资源使用情况变得更容易,特别是对于 Ceph 资源使用快速变化的情况:

atop

atop 是另一个有用的工具。它可以捕获 CPU、RAM、磁盘、网络的性能指标,并将这些指标展示在一个视图中;这样,你可以轻松地获得系统资源使用的完整概览。

诊断

有一些内部的 Ceph 工具可以帮助诊断性能缓慢问题。调查性能问题时最有用的命令是转储当前的飞行操作,可以通过以下命令完成:

sudo ceph daemon osd.x dump_ops_in_flight

这将转储指定 OSD 当前的所有操作,并细分每个操作步骤的各种时间。以下是一个飞行中的 IO 示例:

从前面的 IO 示例中,我们可以看到每个操作的各个阶段都被记录下来;显然该操作没有任何性能问题。然而,在性能缓慢的情况下,你可能会看到两个步骤之间存在较大的延迟,集中调查这个区域可能会引导你找到根本原因。

极端缓慢的性能或没有 IO

如果你的集群运行非常缓慢,几乎无法处理 IO 请求,可能存在潜在的故障或配置问题。这些缓慢的请求通常会在 Ceph 状态显示中以被阻塞的时间计数器的形式突出显示。在这种情况下,有很多事情需要检查。

OSD 波动

检查监视器上的 ceph.log,查看是否有任何 OSD 波动的迹象。当 OSD 加入集群时,它的 PG 开始进行对等操作。在对等过程中,IO 会暂时暂停,因此如果有多个 OSD 波动,客户端的 IO 可能会受到严重影响。如果发现 OSD 波动的证据,下一步是查看那些波动的 OSD 的日志,看看是否有任何线索说明它们波动的原因。追踪 OSD 波动可能很困难,因为可能有多个不同的原因,且问题可能是广泛存在的。

Jumbo 帧

检查是否由于网络变化导致使用中的 Jumbo 帧出现问题。如果 Jumbo 帧工作不正常,小的数据包很可能能够顺利传输到其他 OSD 和 MON,但较大的数据包则会被丢弃。这将导致 OSD 显示为半正常工作状态,而找出明显的原因可能非常困难。如果发现有异常情况,始终检查网络是否允许 Jumbo 帧通过,可以使用 ping 命令进行检查。

故障磁盘

由于 Ceph 会将数据条带化分布到集群中的所有磁盘,因此一个正在失败但尚未完全失败的单一磁盘,可能会开始导致集群中的 IO 变慢或被阻塞。通常,这是由于磁盘遭遇大量读错误,但错误还不严重到导致磁盘完全失败。通常,只有当写入坏道时,磁盘才会重新分配扇区。通过监控磁盘的 SMART 状态,通常可以发现此类问题并采取相应措施。

慢速 OSD

有时候,OSD 可能会在没有明显原因的情况下表现得非常差。如果监控工具没有显示任何明显的问题,可以查看 ceph.log 和 Ceph 健康状态的详细输出。你还可以运行 Ceph osd perf,它会列出所有 OSD 的提交和应用延迟,可能有助于你识别出问题 OSD。

如果在慢请求中引用了某种常见模式的 OSD,说明所提到的 OSD 很可能是问题的根源。此时重启 OSD 可能会解决问题;如果 OSD 仍然存在问题,建议标记该 OSD 并替换它。

容量不足

如果您的 Ceph 集群使用超过 95% 的容量,OSD 将停止接收 I/O 操作。要恢复这种情况,唯一的方法是删除一些数据,以减少每个 OSD 的使用率。如果某个 OSD 无法启动或您无法删除数据,您可以通过调整 mon_osd_full_ratio 变量来修改满阈值。这将为您争取足够的时间来删除数据,并使集群恢复到可用状态。

调查处于故障状态的 PG

处于故障状态的 PG 将无法提供任何客户端操作,PG 中的任何对象都将无法访问。这会导致集群中的慢请求逐渐积累,因为客户端尝试访问这些对象。PG 处于故障状态最常见的原因是当多个 OSD 离线时,这意味着在任何活动的 OSD 上都没有有效的 PG 副本。然而,要找出 PG 故障的原因,您可以运行以下命令:

ceph pg x.y query

这将产生大量输出;我们关注的部分显示了对等状态。此处的示例取自一个池,该池的 min_size 设置为 1,且在仅有 OSD 0 启动时已写入数据。然后 OSD 0 被停止,OSD 1 和 OSD 2 被启动:

我们可以看到对等过程被阻塞,因为 Ceph 知道 PG 有更新的数据写入 OSD 0。它已尝试从 OSD 1 和 OSD 2 中获取数据,但没有找到所需的内容。它想要重新检查 OSD 0,但由于该 OSD 已停机,无法进行,因此出现了 starting or marking this osd lost may let us proceed 消息。

大型监视器数据库

Ceph 监视器使用 leveldb 来存储集群所需的所有监视器数据。这包括监视器映射、OSD 映射和 PG 映射等内容,OSD 和客户端从监视器中获取这些信息,以便在 RADOS 集群中定位对象。需要特别注意的一点是,在集群健康状况不等于 HEALTH_OK 的期间,监视器不会从其数据库中丢弃任何旧的集群映射。如果集群处于降级状态时间过长,或者集群中有大量 OSD,监视器数据库可能会变得非常大。

在正常的操作条件下,监视器对资源的消耗非常轻量;因此,使用较小磁盘来存储监视器是很常见的。在长期处于降级状态的情况下,存储监视器数据库的磁盘可能会满,如果这种情况发生在所有监视节点上,就会导致整个集群宕机。

为了防止这种行为,值得考虑使用 LVM 部署监视节点,这样,如果磁盘需要扩展,可以更轻松地进行。当你遇到这种情况时,增加磁盘空间是唯一的解决方案,直到你能够让集群的其余部分处于HEALTH_OK状态。

如果你的集群处于HEALTH_OK状态,但监视数据库仍然很大,可以通过运行以下命令来压缩它:

sudo ceph tell mon.{id} compact

然而,这只有在集群处于HEALTH_OK状态时才有效;集群不会丢弃旧的集群映射,直到它处于HEALTH_OK状态,这些映射才可以被压缩。

总结

本章中,你学习了如何处理 Ceph 无法自行解决的问题。现在你已经了解了排查各种问题的必要步骤,如果不处理,这些问题可能会变得更严重。此外,你也了解了在 Ceph 集群无法正常运行时,应该关注的关键领域。你应该有信心,能够处理任何 Ceph 相关的问题。

在下一章,我们将继续探讨故障排除过程,并探讨数据丢失已经发生的情况。

问题

  1. 修复不一致 PG 的命令是什么?

  2. 你可以使用什么命令来动态更改日志级别?

  3. 为什么监视数据库随着时间的推移可能会变大?

  4. 查询 PG 的命令是什么?

  5. 为什么 scrubbing 会对 Ceph 集群的性能产生影响?

第十二章:灾难恢复

在上一章中,你学习了如何排除常见的 Ceph 问题,尽管这些问题可能影响集群的运行,但不太可能导致完全停机或数据丢失。本章将涵盖更严重的场景,涉及 Ceph 集群完全停机或无响应的情况。还将介绍从数据丢失中恢复的各种技术。需要理解的是,这些技术本身可能会导致严重的数据丢失,因此应仅作为最后手段尝试。如果你与 Ceph 供应商有支持合同,或与 Red Hat 有合作关系,强烈建议在执行本章列出的任何恢复技术之前先咨询他们。

在本章中,我们将涵盖以下主题:

  • 避免数据丢失

  • 使用 RBD 镜像提供高可用的块存储

  • 调查断言

  • 从 OSD 重建监视器数据库

  • 从故障的 OSD 中提取 PG

  • 从离线 Bluestore OSD 中检查数据

  • 从丢失对象或非活动 PG 中恢复

  • 从故障的 CephFS 文件系统中恢复

  • 从故障的 OSD 中重建 RBD

什么是灾难?

要能够从灾难中恢复,首先你需要理解并能够识别灾难。对于本章的目的,我们假设任何导致持续停机的情况都被视为灾难。这里不会涵盖 Ceph 正在积极修复的故障场景,或者认为故障可能是短暂的灾难。另一种灾难是导致数据永久丢失,除非可以恢复 Ceph 集群。数据丢失可能是最严重的问题,因为数据可能是不可替代的,或者会对公司的未来造成严重损害。

避免数据丢失

在开始介绍一些恢复技术之前,重要的是先讨论第一章中提到的一些要点,Ceph 的规划。灾难恢复应视为最后的手段;本章中的恢复指南不应被视为代替遵循最佳实践的手段。

首先,确保你有可用且经过测试的数据备份;在发生故障时,如果你知道在最坏的情况下可以依赖备份,你会感到轻松万分。虽然故障可能会给你的用户或客户带来不便,但告诉他们曾托付给你们的数据现在丢失了,远比故障本身要糟糕得多。此外,虽然你可能已经建立了备份系统,但并不意味着你可以盲目依赖它。定期进行恢复测试,确保在需要时可以依赖它们。

确保你遵循一些在第一章中提到的设计原则,为 Ceph 规划。不要使用诸如 nobarrier 之类的配置选项,并强烈考虑在 Ceph 中使用的复制级别来保护你的数据。数据丢失的几率与 Ceph 中配置的冗余级别密切相关,因此在此需要进行仔细规划。

什么原因可能导致停机或数据丢失?

大多数停机和数据丢失情况将直接由在短时间内丧失超过复制级别的多个 OSD 导致。如果这些 OSD 无法重新上线,无论是由于软件还是硬件故障,且 Ceph 无法在 OSD 故障之间恢复对象,则这些对象将丢失。

如果 OSD 因磁盘故障而失败,除非使用昂贵的磁盘恢复服务,否则恢复的可能性不大,并且没有任何保证恢复的数据会处于一致状态。本章不会涵盖从物理磁盘故障中恢复的内容,只是建议使用默认的复制级别 3 来保护你免受多次磁盘故障的影响。

如果 OSD 因软件错误而失败,结果可能会更加积极,但过程复杂且耗时。通常,虽然物理磁盘状况良好但无法启动的 OSD,通常与软件错误或某种形式的损坏有关。软件错误可能是由未捕获的异常引发的,使得 OSD 处于无法恢复的状态。损坏可能发生在意外断电后,硬件或软件未正确配置以维持数据一致性。在这两种情况下,OSD 本身的前景可能是终结的,如果集群已经从丢失的 OSD 中恢复,最好将 OSD 清除并作为空磁盘重新引入。

如果离线的 OSD 数量导致所有对象副本都离线,应尝试恢复过程,从故障的 OSD 中提取对象,并将其重新插入到集群中。

RBD 镜像

如前所述,正常工作的备份是确保故障不会导致数据丢失的关键策略。从 Jewel 版本开始,Ceph 引入了 RBD 镜像,它允许你将一个集群的 RBD 异步镜像到另一个集群。请注意,Ceph 的本地复制是同步的,而 RBD 镜像是异步的。同步复制要求节点之间的低延迟,而异步复制允许两个 Ceph 集群地理位置遥远,因为延迟不再是一个问题。

通过在一个单独的集群上复制一份 RBD 镜像,你可以显著减少 恢复时间目标RTO)和 恢复点目标RPO)。RTO 是从启动恢复到数据可用所需的时间度量。它是每个数据点之间的最坏情况时间测量,描述了预期的数据丢失。每日备份的 RPO 为 24 小时;例如,如果你必须从备份中恢复,可能会丢失自上次备份以来的最多 24 小时内写入的任何数据。

使用 RBD 镜像时,数据会异步地复制到目标 RBD,因此,在大多数情况下,RPO 应该在一分钟以内。由于目标 RBD 也是一个副本,而不是需要先恢复的备份,因此 RTO 也可能非常低。此外,由于目标 RBD 存储在一个单独的 Ceph 集群中,它为快照提供了额外的保护,如果 Ceph 集群本身出现问题,快照也可能受到影响。乍一看,RBD 镜像似乎是防止数据丢失的完美工具,在大多数情况下,它确实是一个非常有用的工具。然而,RBD 镜像并不能替代正确的备份流程。在因 RBD 内部操作(如文件系统损坏或用户错误)导致数据丢失的情况下,这些更改将被复制到目标 RBD。拥有一个独立的隔离数据副本至关重要。

话虽如此,让我们更详细地了解一下 RBD 镜像如何工作。

日志

RBD 镜像的一个关键组件是日志。RBD 镜像日志存储所有写入到 RBD 的数据,并在写入完成后通知客户端。然后,这些写入会写入主 RBD 镜像。日志本身作为 RADOS 对象存储,并以类似于 RBD 镜像的方式加上前缀。远程的 rbd-mirror 守护进程会轮询配置的 RBD 镜像,并将新写入的日志对象拉取到目标集群并在目标 RBD 中重放。

rbd-mirror 守护进程

rbd-mirror 守护进程负责将日志内容重放到另一个 Ceph 集群中的目标 RBD。rbd-mirror 守护进程只需要在目标集群上运行,除非你希望实现双向复制,在这种情况下,它需要在两个集群上都运行。

配置 RBD 镜像

为了使用 RBD 镜像功能,我们需要两个 Ceph 集群。我们可以部署之前使用过的两个相同的集群,但涉及的虚拟机数量可能超过大多数个人计算机的承载能力。因此,我们将修改我们的 vagrant 和 ansible 配置文件,以部署两个独立的 Ceph 集群,每个集群都包含一个监视器和一个 OSD 节点。

所需的Vagrantfile与第二章《使用容器部署 Ceph》中的非常相似,用于部署初始测试集群;顶部的 hosts 部分现在应该如下所示:

nodes = [
 { :hostname => 'ansible', :ip => '192.168.0.40', :box => 'xenial64' },
  { :hostname => 'site1-mon1', :ip => '192.168.0.41', :box => 'xenial64' },
  { :hostname => 'site2-mon1', :ip => '192.168.0.42', :box => 'xenial64' },
  { :hostname => 'site1-osd1',  :ip => '192.168.0.51', :box => 'xenial64', :ram => 1024, :osd => 'yes' },
  { :hostname => 'site2-osd1',  :ip => '192.168.0.52', :box => 'xenial64', :ram => 1024, :osd => 'yes' }
]

对于 Ansible 配置,我们将维护两个独立的 Ansible 配置实例,以便每个集群可以单独部署。然后,我们将为每个实例维护独立的主机文件,并在运行 playbook 时指定它们。为了实现这一点,我们不会将ceph-ansible文件复制到/etc/ansible,而是通过以下命令将其保留在主目录中:

git clone https://github.com/ceph/ceph-ansible.git
cp -a ceph-ansible ~/ceph-ansible2

group_vars目录中创建与第二章《使用容器部署 Ceph》相同的两个文件,分别命名为allCeph。这需要在ceph-ansible的两个副本中完成:

  1. 在每个ansible目录中创建一个 hosts 文件,并将两个主机添加到其中:

上面的截图是第一个主机的,下面的截图是第二个主机的:

  1. 在每个ceph-ansible实例下运行site.yml playbook,以部署我们的两个 Ceph 集群:
ansible-playbook -K -i hosts site.yml
  1. 将默认池的复制级别调整为1,因为我们的集群只有1个 OSD。请在两个集群上运行以下命令:

  1. 在两个集群上安装 RBD 镜像守护进程:
sudo apt-get install rbd-mirror

以下截图是前一个命令的输出:

  1. ceph.confkeyring从两个集群互相复制。

  2. ceph.confsite1-mon1复制到site2-mon1,并命名为remote.conf

  3. ceph.client.admin.keyringsite1-mon1复制到site2-mon1,并命名为remote.client.admin.keyring

  4. 重复前两步,但这次将文件从site2-mon1复制到site1-mon1

  5. 确保keyring的实例由ceph:ceph所有:

sudo chown ceph:ceph /etc/ceph/remote.client.admin.keyring
  1. 告诉 Ceph,名为rbd的池应该启用镜像功能:
sudo rbd --cluster ceph mirror pool enable rbd image
  1. 对目标集群重复此操作:
sudo rbd --cluster remote mirror pool enable rbd image
  1. 将目标集群添加为池镜像配置的对等集群:
sudo rbd --cluster ceph mirror pool peer add rbd client.admin@remote
  1. 在第二个 Ceph 集群上本地运行相同的命令:
sudo rbd --cluster ceph mirror pool peer add rbd client.admin@remote
  1. 返回第一个集群,我们来创建一个测试的 RBD 以供镜像实验使用:
sudo rbd create mirror_test --size=1G
  1. 在 RBD 映像上启用日志功能:
sudo rbd feature enable rbd/mirror_test journaling
  1. 为 RBD 启用镜像功能:
sudo rbd mirror image enable rbd/mirror_test

需要注意的是,RBD 镜像是通过拉取系统工作的。rbd-mirror守护进程需要在你希望镜像 RBD 的集群上运行;它然后连接到源集群并拉取 RBD。 如果你打算实现双向复制,即每个 Ceph 集群相互复制,你需要在两个集群上都运行rbd-mirror守护进程。考虑到这一点,让我们在目标主机上启用并启动rbd-mirrorsystemd服务:

sudo systemctl enable ceph-rbd-mirror@admin sudo systemctl start ceph-rbd-mirror@admin

rbd-mirror 守护进程现在将开始处理你在主集群上配置的所有 RBD 镜像。

我们可以通过在目标集群上运行以下命令来确认一切按预期工作:

sudo rbd --cluster remote mirror pool status rbd –verbose

以下截图是之前命令的输出:

在前面的截图中,我们可以看到我们的 mirror_test RBD 处于 up+replaying 状态;这意味着镜像正在进行中,我们可以通过 entries_behind_master 看到它目前是最新的。

注意两集群上 RBD info 命令输出的不同。在源集群上,主状态为 true,这使你能够确定哪个集群的 RBD 处于主状态,并且可以被客户端使用。这也确认了,尽管我们只在主集群上创建了 RBD,但它已经被复制到了副集群。

这里展示的是源集群:

这里展示的是目标集群:

执行 RBD 故障转移

在我们将 RBD 故障转移到副集群之前,让我们映射它,创建文件系统,并在其上放置一个文件,以便确认镜像是否正常工作。从 Linux 内核 4.11 开始,内核 RBD 驱动不支持 RBD 镜像所需的 RBD 日志功能;这意味着你不能使用内核 RBD 客户端映射 RBD。因此,我们需要使用 rbd-nbd 工具,它结合了 librbd 驱动和 Linux nbd 设备,通过用户空间映射 RBD。虽然有许多因素可能导致 Ceph 性能较慢,以下是一些最可能的原因:

sudo rbd-nbd map mirror_test

sudo mkfs.ext4 /dev/nbd0

sudo mount /dev/nbd0 /mnt echo This is a test | sudo tee /mnt/test.txt sudo umount /mnt sudo rbd-nbd unmap /dev/nbd0 Now lets demote the RBD on the primary cluster and promote it on the
secondary sudo rbd --cluster ceph mirror image demote rbd/mirror_test sudo rbd --cluster remote mirror image promote rbd/mirror_test

现在,在副集群上映射并挂载 RBD,你应该能够读取在主集群上创建的测试文本文件:

我们可以清楚地看到,RBD 已经成功地被镜像到副集群,文件系统的内容与我们在主集群上留下的一样。

如果你尝试在 RBD 不处于主状态的集群上映射和挂载 RBD,操作将会挂起;这是因为 Ceph 不允许对非主状态的 RBD 镜像进行 I/O 操作。

RBD 恢复

如果有多个 OSD 失败,并且你无法通过 ceph-object-store 工具恢复它们,集群很可能会处于一种大多数(如果不是所有)RBD 镜像都无法访问的状态。然而,你仍然有可能从 Ceph 集群中的磁盘中恢复 RBD 数据。有一些工具可以搜索 OSD 数据结构,找到与 RBD 相关的对象文件,然后将这些对象组装回磁盘映像,类似于原始的 RBD 镜像。

本节将重点介绍 Lennart Bader 的一个工具,用于从我们的测试 Ceph 集群中恢复测试 RBD 镜像。该工具允许从 Ceph OSD 的内容中恢复 RBD 镜像,而不要求 OSD 处于运行或可用状态。需要注意的是,如果 OSD 由于底层文件系统损坏而遭到损坏,则 RBD 镜像的内容可能仍然损坏。RBD 恢复工具可以在以下 GitHub 仓库中找到:gitlab.lbader.de/kryptur/ceph-recovery

在开始之前,请确保在 Ceph 集群上创建了一个小的、具有有效文件系统的测试 RBD。由于我们在第二章《使用容器部署 Ceph》中创建的测试环境中磁盘的大小,建议 RBD 仅为 1GB 大小。

我们将在其中一个监视器节点上执行恢复,但实际上,这个恢复过程可以在任何可以访问 Ceph OSD 磁盘的节点上进行。为了访问磁盘,我们需要确保恢复服务器有足够的空间来恢复数据。

在本示例中,我们将通过 sshfs 挂载远程 OSD 的内容,sshfs 允许您通过 ssh 挂载远程目录。然而,在实际情况中,没有什么能阻止您将磁盘物理插入到另一台服务器中或使用所需的任何方法。该工具仅需要查看 OSD 的数据目录:

  1. 从 Git 仓库克隆 Ceph 恢复工具:
git clone https://gitlab.lbader.de/kryptur/ceph-recovery.git

以下截图是前述命令的输出:

  1. 确保已安装 sshfs
sudo apt-get install sshfs

以下截图是前述命令的输出:

  1. 切换到克隆的工具目录,并为每个 OSD 创建空目录:
cd ceph-recovery sudo mkdir osds sudo mkdir osds/ceph-0 sudo mkdir osds/ceph-1 sudo mkdir osds/ceph-2

Filestore

对于文件存储,我们可以将每个远程 OSD 挂载到我们刚创建的目录中。请注意,您需要确保您的 OSD 目录与实际测试集群匹配:

sudo sshfs vagrant@osd1:/var/lib/ceph/osd/ceph-0 osds/ceph-0 sudo sshfs vagrant@osd2:/var/lib/ceph/osd/ceph-2 osds/ceph-2 sudo sshfs vagrant@osd3:/var/lib/ceph/osd/ceph-1 osds/ceph-1

BlueStore

由于 Bluestore 不会将对象存储在本地 Linux 文件系统中,因此我们不能直接挂载文件系统。然而,ceph-object-store 工具允许您将 BlueStore OSD 的内容作为 fuse 文件系统挂载。

在每个 OSD 节点上,在 /mnt 文件夹下创建一个目录,以挂载该节点上的每个 OSD:

mkdir /mnt/osd-0

现在,将 BlueStore OSD 挂载到这个新目录:

ceph-objectstore-tool --op fuse --data-path /var/lib/ceph/osd/ceph-0 --mountpoint /mnt/osd-0

以下截图是前述命令的输出:

BlueStore OSD 现在作为fuse文件系统挂载到/mnt/osd-0目录中。然而,它只会在ceph-object-store命令运行时保持挂载状态。因此,如果你希望挂载多个 OSD 或手动浏览目录树,请打开到 Ceph 节点的额外 SSH 会话。以下是一个截图,展示了来自新的 SSH 会话的/mnt/osd-0目录的内容:

当你完成对 OSD 的操作时,只需在运行ceph-objectstore-tool命令的 SSH 会话中按下Ctrl + C即可卸载。

现在我们可以像处理 filestore 一样,将 fuse 挂载的 OSD 挂载到我们的管理服务器:

sudo sshfs vagrant@osd1:/mnt/osd-0 osds/ceph-0 sudo sshfs vagrant@osd2:/mnt/osd-1 osds/ceph-1 sudo sshfs vagrant@osd3:/mnt/osd-2 osds/ceph-2

RBD 组装 - filestore

现在我们可以使用工具扫描 OSD 目录并编译一个可用的 RBD 列表。此命令唯一需要的参数是 OSD 挂载的位置。在本例中,它位于名为osds的目录中。结果将列出在 VM 目录中:

sudo ./collect_files.sh osds

以下截图是上面命令的输出:

如果我们查看 VM 目录,就会看到工具找到了我们的测试 RBD 镜像。现在我们已经找到了镜像,下一步是组装位于 OSD 上的各种对象。此命令的三个参数是前一步中找到的 RBD 镜像名称、镜像的大小以及恢复镜像文件的目标位置。镜像的大小以字节为单位指定,重要的是它至少与原始镜像一样大;它可以更大,但如果大小小于原始镜像,RBD 将无法恢复:

sudo ./assemble.sh vms/test.id 1073741824 .

以下截图是上面命令的输出:

RBD 现在将从挂载的 OSD 内容恢复到指定的镜像文件中。根据镜像的大小,恢复可能需要一段时间,并且进度条将显示恢复的进度。

RBD 组装 - BlueStore

RBD 组装脚本不适用于 BlueStore OSD,因为 BlueStore 使用稍有不同的命名规则来存储 RBD 对象。以下步骤提供了一个更新的脚本,以帮助进行 RBD 恢复。

下载脚本以帮助从 BlueStore OSD 恢复 RBD:

wget https://raw.githubusercontent.com/fiskn/assemble_bluestore_rbd/master/assemble_bluestore.sh
chmod +x ./assemble_bluestore.sh

使用三个参数运行恢复脚本,第一个是 RBD 镜像的哈希名称,第二个是 RBD 的大小(以字节为单位),第三个是输出文件的文件名。

以下示例来自一个 10 GB 的测试 RBD:

./assemble_bluestore.sh 7d8ad6b8b4567 1073741824 text.img 

以下截图是上面命令的输出:

RBD 镜像现在应该已经恢复。

恢复确认

完成后,我们可以在镜像上运行一个名为fsck的文件系统检查工具,以确保其已经正确恢复。在这种情况下,RBD 是使用ext4格式化的,因此我们可以使用e2fsck工具检查该镜像:

sudo e2fsck test.raw

以下截图是上面命令的输出:

很好,图像文件是干净的,这意味着现在我们的所有数据恢复成功的可能性非常高。

现在我们可以最终将图像挂载为回环设备来访问我们的数据。如果命令没有返回任何输出,则表示我们已成功挂载:

sudo mount -o loop test.raw /mnt

您可以看到图像已成功挂载为回环设备:

RGW 多站点

Ceph 还支持运行两个或更多的 RADOS 网关区(RGW)以提供多个站点之间的 S3 和 swift 兼容存储的高可用性。每个区域都由一个完全独立的 Ceph 集群支持,这意味着硬件或软件故障完全使服务下线的可能性极低。使用 RGW 的多站点配置时,必须注意数据是最终一致的,因此数据在每个区域中并不保证在写入后立即处于相同的状态。

欲了解更多有关 RGW 多站点配置的信息,请参阅官方 Ceph 文档。

CephFS 恢复

与 RBD(仅是对象的串联)不同,CephFS 在数据池和元数据池中都需要一致的数据。它还要求 CephFS 日志健康;如果这些数据源中的任何一个出现问题,CephFS 将下线并可能无法恢复。本章的这一部分将讨论如何将 CephFS 恢复到活动状态,并在元数据池损坏或不完整的情况下采取进一步的恢复步骤。

CephFS 可能会在某些条件下下线,但不会导致任何永久性的数据丢失;这些通常是由 Ceph 集群中的瞬态事件引起的,但不应导致长期的数据丢失,并且在大多数情况下,CephFS 应该会自动恢复。

由于 CephFS 基于 RADOS,除非 CephFS 中存在软件缺陷,否则数据丢失或损坏应仅在 RADOS 层发生数据丢失的情况下发生,这可能是由于多个 OSD 故障导致 PG 丢失。

数据池中对象或 PG 的丢失不会导致 CephFS 文件系统下线,但会导致对受影响文件的访问请求返回零值。这很可能导致堆栈更高层的任何应用程序失败,并且由于文件或文件部分与 PG 的映射具有半随机性质,结果很可能意味着 CephFS 文件系统仍然大部分可用。在这种情况下,最好的情况是尝试恢复 RADOS 池 PG,如本章后续所示。

元数据池中的对象或 PG 丢失将使 CephFS 文件系统下线,并且在没有人工干预的情况下无法恢复。需要指出的是,实际的数据内容不会受到元数据丢失的影响,但存储这些数据的对象在没有元数据的情况下将大多毫无意义。Ceph 提供了多种工具,可以用来恢复和重建元数据,这可能会帮助你从元数据丢失中恢复。然而,正如本书多次提到的那样,预防总是胜于治疗,因此这些工具不应被视为标准恢复机制,而应仅在常规备份恢复失败时作为最后的手段使用。

创建灾难

为了创建 CephFS 文件系统丢失或损坏元数据池的场景,我们将简单地删除元数据池中的所有对象。本示例将使用第五章中部署的文件系统,RADOS 池与客户端访问,但此过程应该与任何其他部署的 CephFS 文件系统相同。

首先,让我们切换到 root 账户并挂载 CephFS 文件系统:

sudo -i
mount -t ceph 192.168.0.41:/ /mnt/cephfs -o name=admin,secret=AQC4Q85btsqTCRAAgzaNDpnLeo4q/c/q/0fEpw==

在 CephFS 文件系统上放置一些测试文件,稍后我们将尝试恢复它们:

echo "doh" > /mnt/cephfs/doh
echo "ray" > /mnt/cephfs/ray
echo "me" > /mnt/cephfs/me

现在我们已经放置了测试文件,让我们删除元数据池中的所有对象,以模拟元数据池丢失:

rados purge cephfs_metadata --yes-i-really-really-mean-it

以下截图是前面命令的输出:

让我们重启 mds 守护进程;触发故障:

systemctl restart ceph-mds@*

如果我们现在使用 ceph -s 命令检查 CephFS 状态,可以看到 mds 已经检测到元数据损坏,并将文件系统下线:

为了获取更多关于损坏的信息,我们可以运行以下命令。检查 CephFS 日志:

cephfs-journal-tool journal inspect

以下截图是前面命令的输出:

是的,正如预期的那样,它被严重损坏了。

CephFS 元数据恢复

通常建议导出日志以便安全保存,最小化数据丢失,但在这种情况下,由于我们知道可以安全地立即重置日志:

cephfs-journal-tool journal reset

以下截图是前面命令的输出:

下一条命令重置文件系统的 RADOS 状态,以便恢复过程能够从一致的状态重新构建:

ceph fs reset cephfs --yes-i-really-mean-it

接下来,MDS 表会被重置,以便重新生成。这些表作为对象存储在元数据池中。以下命令会创建新的对象:

 cephfs-table-tool all reset session
 cephfs-table-tool all reset snap
 cephfs-table-tool all reset inode

以下截图是前面命令的输出:

重置 CephFS 日志:

cephfs-journal-tool --rank=0 journal reset

最后,创建根 inode 并准备数据对象的发现:

cephfs-data-scan init

现在 CephFS 的状态已完全重置,可以开始对数据池进行扫描,以从可用的数据对象中重建元数据。这是一个三阶段的过程,使用以下三个命令。第一个命令扫描数据池,找到构成每个文件的所有扩展并将其存储为临时数据。信息,如创建时间和文件大小也会被计算并存储。第二阶段将搜索这些临时数据并将 inode 重建到元数据池中。最后,inodes 的链接操作会发生:

cephfs-data-scan scan_extents cephfs_data
cephfs-data-scan scan_inodes cephfs_data
cephfs-data-scan scan_links

扫描 inodes 和扩展命令在大型文件系统上运行时可能需要极长时间。为了加速这个过程,可以并行运行这些操作;更多信息请查看官方 Ceph 文档。

一旦过程完成,请检查 CephFS 文件系统是否现在处于健康状态:

我们现在还应该能够从挂载点浏览文件系统,那个挂载点是在本节开始时挂载的:

请注意,尽管恢复工具已经成功定位了文件并重建了一些元数据,但像文件名这样的信息已经丢失,因此被放入了 lost+found 目录中。通过检查文件内容,我们可以识别每个文件,并将其重命名为原始文件名。

在实践中,尽管我们已经恢复了 CephFS 文件系统,但由于缺少文件的原始名称和目录位置,可能意味着恢复只是部分成功。还应注意,恢复的文件系统可能不稳定,强烈建议在文件系统被销毁并重建之前,尽可能恢复任何抢救出来的文件。这是一个灾难恢复过程,应该在排除了从备份恢复的可能性后使用。

丢失的对象和非活动的 PG

本章的这一部分将介绍一个场景,其中多个 OSD 在短时间内下线,导致某些对象没有有效的副本。需要注意的是,丢失副本的对象与仍有副本但另一个副本已做过更改的对象之间是有区别的。后者通常出现在将集群的 min_size 设置为 1 时。

为了演示如何恢复一个拥有过期数据副本的对象,让我们执行一系列步骤来破坏集群:

  1. min_size 设置为 1;希望通过本示例的结束,您能明白为什么在实际操作中绝对不想这样做:
sudo ceph osd pool set rbd min_size 1

以下截图是前面命令的输出:

  1. 创建一个测试对象,稍后我们将让 Ceph 认为它丢失了:
sudo rados -p rbd put lost_object logo.png sudo ceph osd set norecover sudo ceph osd set nobackfill

这两个标志确保当 OSD 重新上线并向单个 OSD 写入时,变更不会被恢复。由于我们仅用单一选项进行测试,我们需要这些标志来模拟实际生活中的情况,在这种情况下,可能并非所有对象都能在足够的时间内恢复,尤其是当唯一副本的 OSD 由于某种原因离线时。

  1. 关闭两个 OSD 节点,剩下一个 OSD。由于我们已将min_size设置为1,我们仍然可以向集群写入数据。你可以看到,Ceph 状态显示两个 OSD 现在都已离线:

  1. 再次写入该对象,写入将进入剩余的 OSD:
sudo rados -p rbd put lost_object logo.png
  1. 关闭剩余的 OSD;当其离线后,重新启动另外两个 OSD:

你可以看到,Ceph 已经知道它在恢复过程开始之前就已经有一个未找到的对象。这是因为在对等阶段,包含修改对象的 PG 已经知道唯一有效的副本在osd.0上,而该副本现在已离线。

  1. 移除nobackfillnorecover标志,让集群尝试执行恢复。你会看到,即使恢复过程有所进展,仍然会有一个 PG 处于降级状态,且未找到对象的警告依然存在。这是一个好现象,因为 Ceph 正在保护你的数据免受损坏。试想,如果一个包含数据库的 RBD 的 4 MB 块突然回退到之前的状态,会发生什么。

如果你尝试读取或写入我们的测试对象,你会注意到请求会一直挂起;这是 Ceph 在保护你的数据。解决此问题有三种方法。第一种也是最理想的解决方案是让该对象的有效副本重新上线;这可以通过将osd.0重新上线,或使用objectstore工具导出并将此对象导入到健康的 OSD 中。为了本节的目的,假设这两种方法都不可行。在介绍剩下的两种解决方案之前,让我们进一步调查,揭示其背后的情况。

运行 Ceph 健康详细信息,找出哪个 PG 存在问题:

在这种情况下,是pg 0.31,处于降级状态,因为它有一个未找到的对象。让我们查询一下这个pg

ceph pg 0.31 query

以下截图是前面命令的输出:

查找恢复部分,我们可以看到 Ceph 已经尝试对对象进行探测,探测对象时,"osd": "0"不可用。它尝试对"osd": "1"进行探测,但由于某些原因无效,我们知道的原因是因为它是一个过时的副本。

现在,让我们更详细地查看缺失的对象:

sudo ceph pg 0.31 list_missing

以下截图是前面命令的输出:

needhave行揭示了原因。我们有epoch 383'5,但该对象的有效副本存在于398'6;这就是为什么min_size=1是错误的原因。你可能会处于只有一个有效对象副本的情况。如果这是由磁盘故障引起的,那你将面临更大的问题。

为了从中恢复,我们有两种选择:要么选择使用较旧的对象副本,要么直接删除它。需要注意的是,如果这个对象是新的,而剩余的 OSD 中没有较旧的副本,它也将删除该对象。

要删除该对象,请运行以下命令:

ceph pg 0.31 mark_unfound_lost delete

要恢复,请运行以下命令:

ceph pg 0.31 mark_unfound_lost revert

从完整的监视器故障中恢复

如果不太可能的情况下你丢失了所有监视器,仍然不算彻底失败。你可以使用ceph-objectstore-tool从 OSD 的内容中重建监视器数据库。

为了设置场景,我们假设发生了某种事件,导致所有三个监视器都遭到破坏,从而使 Ceph 集群无法访问。为了恢复集群,我们将关闭两个监视器,只保留一个失败的监视器运行。然后,我们将重建监视器数据库,覆盖损坏的副本,并重新启动监视器,使 Ceph 集群恢复上线。

objectstore工具需要能够访问集群中的每个 OSD,以便重建监视器数据库;在本示例中,我们将使用一个脚本,通过ssh连接来访问 OSD 数据。由于 OSD 数据并非所有用户都能访问,我们将使用 root 用户登录到 OSD 主机。默认情况下,大多数 Linux 发行版不允许远程通过密码登录 root 用户,因此请确保你已经将你的公钥ssh复制到某些远程 OSD 节点的 root 用户。

以下脚本将连接到hosts变量中指定的每个 OSD 节点,并提取构建监视器数据库所需的数据:

#!/bin/bash hosts="osd1 osd2 osd3" ms=/tmp/mon-store/ mkdir $ms # collect the cluster map from OSDs for host in $hosts; do
 echo $host rsync -avz $ms root@$host:$ms rm -rf $ms ssh root@$host <<EOF for osd in /var/lib/ceph/osd/ceph-*; do ceph-objectstore-tool --data-path \$osd --op update-mon-db --mon-store-path $ms done EOF
 rsync -avz root@$host:$ms $ms done

这将生成以下内容到/tmp/mon-store目录:

我们还需要通过keyring分配新的权限:

sudo ceph-authtool /etc/ceph/ceph.client.admin.keyring --create-keyring --gen-key -n client.admin --cap mon 'allow *' --cap osd 'allow *' --cap mds 'allow *'

以下截图为前述命令的输出:

sudo ceph-authtool /etc/ceph/ceph.client.admin.keyring --gen-key -n mon. --cap mon 'allow *' sudo cat /etc/ceph/ceph.client.admin.keyring

现在监视器数据库已重建,我们可以将其复制到监视器目录中,但在此之前,让我们先备份现有数据库:

sudo mv /var/lib/ceph/mon/ceph-mon1/store.db /var/lib/ceph/mon/ceph-mon1/store.bak

现在,复制重建后的版本:

sudo mv /tmp/mon-store/store.db /var/lib/ceph/mon/ceph-mon1/store.db sudo chown -R ceph:ceph /var/lib/ceph/mon/ceph-mon1

如果你现在尝试启动监视器,它会陷入探测状态,因为它试图探测其他监视器。这是 Ceph 试图避免脑裂情况;然而,在这种情况下,我们希望强制它形成法定人数并完全上线。为此,我们需要编辑monmap,删除其他监视器,然后将其重新注入监视器数据库:

sudo ceph-mon -i mon1 --extract-monmap /tmp/monmap

检查monmap的内容:

sudo monmaptool /tmp/monmap –print

以下截图为前述命令的输出:

你会看到有三个 mons 存在,所以让我们移除其中两个:

sudo monmaptool /tmp/monmap --rm noname-b sudo monmaptool /tmp/monmap --rm noname-c

现在,再次检查以确保它们完全消失:

sudo monmaptool /tmp/monmap –print

以下截图是前述命令的输出:

sudo ceph-mon -i mon1 --inject-monmap /tmp/monmap

重启所有 OSD,使它们重新加入集群;然后你将能够成功查询集群状态,看到你的数据仍然存在:

使用 Ceph 对象存储工具

希望如果你遵循了最佳实践,你的集群应该以三个副本运行,并且没有配置任何危险的配置选项。在大多数情况下,Ceph 应该能够从任何故障中恢复。

然而,在一些 OSD 离线的情况下,可能会有一些 PG 或对象变得不可用。如果你无法将这些 OSD 重新引入集群以便让 Ceph 优雅地恢复它们,那么这些 PG 中的数据实际上是丢失的。不过,有可能该 OSD 仍然是可读取的,可以使用 objectstore 工具来恢复 PG 的内容。这个过程包括从故障 OSD 中导出 PG,然后将其重新导入集群。objectstore 工具要求 OSD 的内部元数据仍然处于一致状态,因此不能保证完全恢复。

为了演示如何使用 objectstore 工具,我们将关闭三台测试集群中的两台 OSD,然后将丢失的 PG 恢复到集群中。在现实中,你不太可能遇到所有故障 OSD 中的每一个 PG 都丢失的情况,但为了演示目的,所需步骤是相同的:

  1. 将池大小设置为 2,这样当我们停止 OSD 服务时,可以确保丢失所有某些 PG 的副本:

  1. 关闭两台 OSD 服务,你会从 Ceph 状态页面看到 PG 的数量会离线:

  1. 运行 Ceph 健康状态详细信息也会显示哪些 PG 处于降级状态:

过期的 PG 是那些不再有存活副本的 PG,并且可以看到,正在处理的 OSD 是已经关闭的那台。

如果我们使用 grep 过滤出仅过期的 PG,我们可以利用结果列表来确定需要恢复的 PG。如果 OSD 已经从集群中移除,PG 会显示为不完整而非过期。

  1. 检查 OSD 以确保 PG 存在于其中:
ceph-objectstore-tool --op list-pgs --data-path /var/lib/ceph/osd/ceph-0

以下截图是前述命令的输出:

  1. 使用 objectstore 工具将 pg 导出到文件。由于我们测试集群中的数据量较小,我们可以将数据直接导出到操作系统磁盘。在实际情况下,你可能需要考虑为服务器连接额外的存储。USB 磁盘是理想选择,因为它们可以轻松在服务器之间移动,作为恢复过程的一部分:
sudo ceph-objectstore-tool --op export --pgid 0.2a --data-path /var/lib/ceph/osd/ceph-2 --file 0.2a_export

以下截图是前述命令的输出:

如果在运行工具时遇到断言错误,你可以尝试使用 --skip-journal-replay 参数运行它,这将跳过将日志重播到 OSD。如果日志中有任何未处理的数据,它将丢失。但这可能会让你恢复大部分本来无法恢复的丢失 PG。并且重复此操作,直到你导出所有丢失的 PG。

  1. 将丢失的 PG 导入到一个运行中的 OSD。虽然我们可以将 PG 导入现有的 OSD,但在新的 OSD 上进行导入要更安全,这样我们就不必冒着进一步数据丢失的风险。为此,在故障 OSD 使用的磁盘上创建一个基于目录的 OSD。在实际灾难恢复情况下,强烈建议将数据插入运行在单独磁盘上的 OSD,而不是使用现有的 OSD。这样做是为了避免进一步对 Ceph 集群中的任何数据造成风险。

此外,导入的 PG 都插入到同一个临时 OSD 中也没有关系。一旦 Ceph 发现这些对象,它会将它们恢复到集群中的正确位置。

  1. 创建一个新的空文件夹用于 OSD:
sudo mkdir /var/lib/ceph/osd/ceph-2/tmposd/
  1. 使用 ceph-diskceph-volume 为 Ceph 准备目录:
sudo ceph-disk prepare  /var/lib/ceph/osd/ceph-2/tmposd/
  1. 将文件夹的所有权更改为 ceph 用户和组:
sudo chown -R ceph:ceph /var/lib/ceph/osd/ceph-2/tmposd/
  1. 激活 OSD 使其上线:
sudo ceph-disk activate  /var/lib/ceph/osd/ceph-2/tmposd/
  1. 设置 OSD 的权重,以防止任何对象被回填到其中:
sudo ceph osd crush reweight osd.3 0
  1. 继续进行 PG 导入,指定临时 OSD 位置和我们之前导出的 PG 文件:
sudo ceph-objectstore-tool --op import --data-path /var/lib/ceph/osd/ceph-3 --file 0.2a_export

以下截图是前述命令的输出:

  1. 对你之前导出的每个 PG 重复此操作。完成后,重置文件所有权并重新启动新的临时 OSD:
sudo chown -R ceph:ceph /var/lib/ceph/osd/ceph-2/tmposd/ sudo systemctl start ceph-osd@3
  1. 检查 Ceph 状态输出,你将看到你的 PG 现在处于活动状态,但处于降级状态。在我们的测试集群中,OSD 数量不足,无法使对象恢复到正确的副本数量。如果集群中有更多的 OSD,对象将被回填到集群中,并且会恢复到健康状态,副本数量正确:

调查断言

断言用于 Ceph 中,确保在代码执行过程中,任何关于操作环境的假设都保持为真。这些断言分布在 Ceph 代码的各个部分,旨在捕捉任何可能导致问题的情况,如果代码没有被停止的话。

如果你在 Ceph 中触发了断言,很可能是某些数据的值超出了预期。这可能是由于某种形式的损坏或未处理的 Bug 造成的。

如果一个 OSD 触发了断言并且拒绝重启,通常推荐的做法是销毁该 OSD,重新创建它,然后让 Ceph 将对象回填到它上面。如果你有可复现的失败场景,可能也值得在 Ceph 的缺陷跟踪系统中提交一个 Bug。

如前所述,OSD 失败可能是由于硬件或软件故障,故障可能发生在存储的数据或 OSD 代码中。软件故障更有可能影响多个 OSD;如果你的 OSD 因停电而遭到损坏,很可能会影响多个 OSD。在多个 OSD 发生断言并导致集群中一个或多个 PG 处于离线状态的情况下,单纯重建 OSD 并不是一个选择。离线的 OSD 包含了 PG 的三份副本,因此重建这些 OSD 会使任何形式的恢复变得不可能,并导致数据永久丢失。

在尝试本章中的恢复技术之前,如导出和导入 PG,应该先调查断言。根据你的技术能力和在开始关注其他恢复步骤之前能容忍的停机时间,调查断言可能并不会取得任何成功。通过调查断言并查看断言中引用的 Ceph 源代码,可能可以识别出断言的原因。如果可能的话,可以在 Ceph 代码中实施修复,以避免 OSD 断言。不要害怕在这些问题上向社区寻求帮助。

在某些情况下,OSD 损坏可能非常严重,以至于即使是objectstore工具本身在尝试从 OSD 读取时也可能会触发断言。这将限制本章中概述的恢复步骤,而尝试修复断言背后的原因可能是唯一的选择。尽管到这时,OSD 可能已经遭受了严重的损坏,恢复可能已不再可能。

示例断言

以下断言来自 Ceph 用户邮件列表:

2017-03-02 22:41:32.338290 7f8bfd6d7700 -1 osd/ReplicatedPG.cc: In function 'void ReplicatedPG::hit_set_trim(ReplicatedPG::RepGather*, unsigned int)' thread 7f8bfd6d7700 time 2017-03-02 22:41:32.335020 osd/ReplicatedPG.cc: 10514: FAILED assert(obc) ceph version 0.94.7 (d56bdf93ced6b80b07397d57e3fa68fe68304432)
 1: (ceph::__ceph_assert_fail(char const*, char const*, int, char const*)+0x85) [0xbddac5] 2: (ReplicatedPG::hit_set_trim(ReplicatedPG::RepGather*, unsigned int)+0x75f) [0x87e48f] 3: (ReplicatedPG::hit_set_persist()+0xedb) [0x87f4ab] 4: (ReplicatedPG::do_op(std::tr1::shared_ptr<OpRequest>&)+0xe3a) [0x8a0d1a] 5: (ReplicatedPG::do_request(std::tr1::shared_ptr<OpRequest>&, ThreadPool::TPHandle&)+0x68a) [0x83be4a] 6: (OSD::dequeue_op(boost::intrusive_ptr<PG>, std::tr1::shared_ptr<OpRequest>, ThreadPool::TPHandle&)+0x405) [0x69a5c5] 7: (OSD::ShardedOpWQ::_process(unsigned int, ceph::heartbeat_handle_d*)+0x333) [0x69ab33] 8: (ShardedThreadPool::shardedthreadpool_worker(unsigned int)+0x86f) [0xbcd1cf] 9: (ShardedThreadPool::WorkThreadSharded::entry()+0x10) [0xbcf300] 10: (()+0x7dc5) [0x7f8c1c209dc5] 11: (clone()+0x6d) [0x7f8c1aceaced]

断言的顶部部分显示了触发断言的函数,以及可以找到该断言的行号和文件。在这个例子中,hit_set_trim函数显然是断言的原因。我们可以查看ReplicatedPG.cc文件中的第 10,514 行左右,尝试理解可能发生了什么。请注意 Ceph 版本(0.94.7),因为 GitHub 上的行号只有在查看相同版本时才会匹配。

从代码中看,get_object_context函数调用返回的值直接传递给了assert函数。如果该值为零——表示包含待修剪的命中集的对象未找到——OSD 会触发断言。根据这些信息,可能可以进行调查,找出对象丢失的原因并恢复它。或者,可以注释掉assert命令,看看是否允许 OSD 继续工作。在这个例子中,允许 OSD 继续处理可能不会导致问题,但在其他情况下,断言可能是防止更严重的损坏发生的唯一措施。如果你不完全理解为什么某些事情会导致断言,或者不清楚你可能做的任何更改会产生什么影响,请在继续之前寻求帮助。

总结

在本章中,你学会了如何在 Ceph 看似无法恢复时进行故障排除。如果 Ceph 无法自行恢复 PG,你现在知道如何手动从故障的 OSD 重建 PG。如果丢失了所有监视器节点,但仍然可以访问 OSD,你还可以重建监视器的数据库。你还探讨了如何从 OSD 上剩余的原始数据中重新创建 RBD。最后,你配置了两个独立的 Ceph 集群,并使用 RBD 镜像配置了它们之间的复制,以提供一个故障转移选项,以防遇到整个 Ceph 集群的故障。

问题

  1. 哪个 Ceph 守护进程允许将 RBD 复制到另一个 Ceph 集群?

  2. 对错判断:默认情况下,RBD 只是一个拼接的对象字符串。

  3. 哪种工具可以用来将 PG 从 OSD 导入或导出?

  4. 对错判断:未找到的对象状态意味着数据已经永远丢失了吗?

  5. 重建 CephFS 元数据后,你所面临的主要缺点是什么?

第十三章:评估

第一章,Ceph 规划

  1. 可靠的自主管理分布式对象存储

  2. 可扩展哈希下的受控复制

  3. 电源丧失保护

  4. 一致性

  5. BlueStore

  6. Sage Weil

  7. RADOS 块设备(RBD)

  8. 对象存储守护进程(OSD)

第二章,使用容器部署 Ceph

  1. Vagrant

  2. Rook

  3. 容器化技术

  4. Playbook

第三章,BlueStore

  1. BlueStore

  2. RocksDB

  3. 压缩

  4. 延迟写入

  5. 使用ceph-objectstore-tool

  6. Snappy

  7. x10

第四章,Ceph 与非原生协议

  1. iSCSI、NFS 和 SMB

  2. iSCSI

  3. SMB

  4. Ganesha

  5. Pacemaker corosync

  6. 安全性

第五章,RADOS 池和客户端访问

  1. Reed Solomon,Cauchy

  2. 读取、修改、写入

  3. 两个奇偶校验碎片的擦除编码可以加速

  4. 扁平化

  5. MDS

  6. 跨多个 MDS 的分片性能

  7. RadosGW

  8. S3 和 Swift

第六章,使用 Librados 进行开发

  1. 移除第三方协议的性能开销或访问 Ceph 的完整功能集

  2. 在对象被修改时通知监视器

  3. C、C++、Python、PHP 和 Java

第七章,使用 Ceph RADOS 类进行分布式计算

  1. OSD

  2. Lua 和 C++

  3. 性能,利用所有 Ceph 节点 CPU 的联合处理能力

  4. 不稳定和不需要的数据损坏

第八章,监控 Ceph

  1. 8443

  2. ceph-mgr — Ceph 管理器

  3. 在读取或比较对象内容时,scrub 检测到错误

  4. 包含一个或多个对象的 PG 正在恢复到新的 OSD

  5. 尽可能多

第九章,调优 Ceph

  1. 错误—PG 分布在新集群中不会均匀,并且可能根据工作负载随时间变化

  2. 完整条带写入只需要一次写入操作,而部分写入则需要更高的写入惩罚

  3. 高 GHz

  4. CPU 速度、磁盘延迟和网络速度

  5. Ceph 平衡器

第十章,Ceph 分层

  1. 提高性能或允许使用较低成本的容量层

  2. bcache, dmcache

  3. Bloom 过滤器中存储的命中集

  4. min_read_recency_for_promote

  5. 对少量热点对象有强烈偏向的工作负载

第十一章,故障排除

  1. ceph pg repair <pg id>

  2. ceph tell osd.0 injectargs --debug-osd 0/5

  3. Ceph 集群长时间不处于HEALTH_OK状态

  4. ceph pg query

  5. Scrubbing 是读取密集型操作,可能会使磁盘的 I/O 超负荷

第十二章,灾难恢复

  1. rbd-mirror

  2. 正确

  3. ceph-objectstore-tool

  4. 错误,它可能存在于当前未参与集群的 OSD 中

  5. 缺失的文件名

posted @ 2025-06-27 17:08  绝不原创的飞龙  阅读(130)  评论(0)    收藏  举报