FreeBSD:Isilon 经验
作者:Benno Rice
从前有一家叫 Isilon 的公司,由一些人创办,他们想要找到一种方法构建一种存储阵列,不仅在 GB 容量上易于扩展,在 IOPS 上也易于扩展。
传统存储阵列往往由某种头设备构建,可选配热备、冷备或温备,外加一堆装满硬盘的托架。这是传统的块存储模型。当 NetApp 等较新的面向文件的产品出现时,它们复制了这个模型。问题在于如何随时间推移扩展它。
扩展存储容量(GB)在某种程度上很容易。你在托架中添加更多硬盘,当它们装满时,你添加更多托架,直到头设备的控制器端口用尽。到那时你必须升级头设备并承受停机。另一个扩展维度是你从硬盘读取数据并输出到客户端的能力(IOPS)。同样,头设备模型往往会遇到瓶颈,需要停机来更换为更强大的头设备。
Isilon 模型的关键区别在于从“头设备加托架”模型转向一个由多数冗余节点组成的集群模型。每个节点都是一台功能完备的、基本标配的服务器级系统,包含 CPU、RAM、NIC 和硬盘。每个节点与集群中的其他节点协作,共享单个文件系统,并且每添加一个节点,不仅存储容量增加,服务能力也增加。这个模型的另一个优势是弹性。集群可以以某种方式放置文件,以防范硬盘甚至节点故障。这带来了更好的资源利用率,因为在“头设备加托架”模型中,你要么必须有冗余容量闲置,要么在某个头设备宕机时损失容量。该产品足够出色,EMC 在 2010 年收购了 Isilon,Isilon 继续为 EMC 的投资带来丰厚回报。
这和 FreeBSD 有什么关系?
嗯,Isilon 销售的每个节点——他们已经销售了大约 10 年——都运行 FreeBSD。它远非原版 FreeBSD,但被称为 OneFS 的操作系统构建在 FreeBSD 之上,为我们提供了一个坚实的基础和骨架来构建产品。显然,我们不是唯一利用这一点的公司,似乎存储行业很大一部分——至少是面向文件的部分——都在使用 FreeBSD。我们想在这篇文章中展示的是我们如何使用 FreeBSD、在哪里遇到了问题、我们现在做得更好的地方、为什么我们认为我们找到了一个好模型,可供其他在 FreeBSD 上构建企业级产品的公司参考。
过去
OneFS 的第一个版本构建在 FreeBSD 5 之上,跟踪 Subversion 中现在称为 stable/5 的分支。当然,那时全都是 CVS。有一些项目将我们底层的 FreeBSD 代码升级到 stable/6,然后是 stable/7,两种情况下版本控制系统都切换到了 Subversion,但在合并到 stable/7 的过程中事情并未完全按计划进行。在将 OneFS 底层的 FreeBSD 版本更新到 stable/7 分支的项目中出现了一堆问题,最终结果是我们变得畏首畏尾。FreeBSD 升级项目开始看起来有风险。最终结果是我们停留在 stable/7 的时间远远超过了应有的时长。
我们在 stable/7 合并期间遇到问题的主要原因是,我们对 FreeBSD 的修改非常多,而且在某些情况下,标记得不好。这导致每次切换到新的 stable 分支时都有大片的变更需要合并。每一次变更都有可能丢失、错误合并或以其他方式破坏某些东西。stable/7 合并期间发生的一次重大问题是一个重要的内部变更被遗漏,直到最后一刻才发现。这类事情,加上合并项目本身的艰巨性,导致了一定程度的自满。
不升级看起来可能很诱人:你不必担心丢失变更,没有风险,你可以把宝贵的工时花在其他项目上。问题是,随着时间的推移,你最终会缺少上游代码库所做的改进。更糟糕的是,你缺少正在流入的修复。当 stable 分支退出主动维护时,你可能最终处于这样一个位置:你本质上是你本可以与他人共享的代码的唯一“所有者”。对我来说,这是 FreeBSD 发布模式的隐患。最终我们意识到不能永远停留在 stable/7 上。
希望拥有“安全”FreeBSD 的组织,受分支命名(分支名里就写着 stable!)影响,倾向于使用 stable 分支。问题在于,一旦到了那里,你就面临一个合并/升级项目才能到达下一个。然后再下一个。再下一个。这抑制了你进行升级的意愿,诱使你在这个分支上多停留一段时间。然后你所在的 stable 分支不再被主动维护。突然之间,你不得不把节省下来的工时花在诊断和修复上游不再感兴趣的代码的 bug 上。这是典型的技术债务处境。
幸运的是,停留在 stable 分支并非唯一选择。
我 2013 年加入 Isilon 部门时,我们已经决定,如果想从节点中获得更多性能,就需要改造 FreeBSD 内核中的一堆东西,主要是 VM、数据在硬盘间传输的各个方面。其中一些工作已经在 FreeBSD 上游完成,这让我们得出了一个显而易见的结论:终于到了离开 stable/7 的时候。显而易见的路径是迁移到 stable/9,它是当时最高的 stable 分支。然而,我们回顾了之前这种模式的问题,提出了一个新想法。
我们要迁移到 head。
起初这感觉非常可怕。head 本应是一片未知领域,充满了会跳出来毁掉你生活的 bug。然而现实是,FreeBSD 的 head 长期以来一直相对稳定,除了一些波动。它确实比 stable 分支变化更快,但过去你必须选择安装哪个修订版以免最终得到无法启动的系统的日子,随着时间推移,已经被这样一种情况取代:只要你避开一些已知的不良修订,通常都是安全的。
事实是,FreeBSD 的 stable 分支是其发布模式的产物,而 FreeBSD 的发布模式对我们来说基本无关紧要。对 Isilon 来说,FreeBSD 是一组我们用来构成产品运行软件基础的代码。我们不需要它处于其发布计划的某个特定时间点,只要我们获得的代码是稳定的。关键的认知是,我们想从 stable 分支获得的是稳定性。我们想要的是没有 bug。问题是,每个 stable 分支合并项目都引入了 bug。那我们到底获得了什么?最终我们认定,通向舒适感的正确路径不是通过停留在 stable 分支来避免 bug,而是专注于 QA 和测试,以确保我们能发现 bug 并修复它们。这将使我们受益——我们可以更直接地与 FreeBSD 合作,并让它在合理的时间框架内出现在我们的发布中——也使 FreeBSD 受益——我们会发现并修复 bug,开发新东西。这一切听起来很棒;主要问题是如何实现。
现在
现在我们处于这样一个位置:我们知道我们想到达哪里;只需要弄清楚如何到达那里。理论上这是与之前做过的同类项目,但不是从一个 stable 分支跨越到下一个,而是从 stable/7 迁移到 head,在此过程中跳过 stable/8、stable/9,最终还有 stable/10。这不是一个小项目。
项目最初的负责人采取了相当雄心勃勃的方法。他们的计划是取一份干净的 FreeBSD head 检出,然后根据需要将我们的整套修改重建到上面。这有一个优点:最终结果将去除我们不再需要的任何修改。缺点是这是一个漫长的过程,需要大量的前期猜测工作,以确定在哪个时间点实际需要什么、不需要什么。这是我加入时正在执行的流程。最终,一位曾运行过先前 stable 合并项目的工程师加入并提出了新方法。
这种方法几乎是前一种的逆向。新计划是找出我们合理确定已经合并到哪个 FreeBSD head 分支 Subversion 修订版本。然后我们开始按顺序将 FreeBSD Subversion 修订版本应用到 head,一路上解决冲突。我们必须克服的第一个问题是 Subversion 不会在仓库之间跟踪合并元数据。我们用一个 Python 脚本解决了这个问题,它可以管理一个包含我们自己合并元数据的 Subversion 属性。其他问题更大。
如前所述,我们在 stable/7 上。我们应用来自 head 的修订。这些并不总是完美对齐,因此这个项目的头几周花在了解决大量冲突上。此外,这个过程非常单线程。一次只能有一个人在合并。我们利用了这一点:我当时在澳大利亚墨尔本,另一位做这项工作的工程师在加州圣克拉拉。这意味着我们工作日只有一两个小时的重叠时间可以用来同步和交接。
这种方法的一个有趣之处在于,它不是从一个干净的 FreeBSD 开始在其上构建 OneFS,而是从 OneFS 开始,尝试将 FreeBSD 的变更合并回它。这意味着我们的测试基础设施可以继续工作。或者说,在 FreeBSD 8 中网络栈的一些变更打散了我们的 Infiniband 栈之前,是可以的。由于缺乏人手而非其他原因,这花了数月才解决,并大大拖慢了我们的进度。
最终这种方法也被证明太慢。此时我们已经错过了我们瞄准的发布版本。我们再次回到起点。
我们在第一种方法中面临的问题之一是,我们的 stable/7 基础代码与 FreeBSD head 之间有大量的变更需要查看。很难查看一个给定的 diff 并判断它是我们的修改还是 FreeBSD 内部的变更。我们的一些修改标记得很好,但很多没有。然而,我们在第二种方法中合并 FreeBSD 修订版本所做的工作并非白费。它让我们达到了大约 FreeBSD 8 的水平,在这个过程中也消除了我们与 head 变更线之间的许多差异(相对于 stable/7 变更线)。这意味着我们可以对我们第二次尝试所用的树运行与大约相同修订版本的 FreeBSD head 的 diff,得到的 diff 主要是我们自己的修改。最终方法由此诞生。
成功的方法涉及取我们生成的 diff,花几周时间弄清其中固有的逻辑组件。我们的大多数修改往往是为了特定目的,无论是为 buf 处理添加内容、文件系统 I/O 路径,还是调优某些参数,或是添加额外的调试支持。一旦我们大致弄清楚 diff 中的哪些部分对应哪些逻辑变更组,我们就可以将每个逻辑变更组分配给某人,从而许多人可以并行工作。
我们从 FreeBSD head 的干净检出开始,类似于最初的方法,但然后每个逻辑变更组被合并进来,并使用 FreeBSD 中存在的 Kyua/ATF 测试、我们在内部开发的一些额外测试,加上一套我们可以在那时已是高度修改版 FreeBSD 上运行的其他基本测试来测试生成的代码。一旦足够多的部分连接起来,我们开始将我们的 OneFS 文件系统代码上线,然后是支持性的用户空间部分。一旦一切就位,我们可以运行内部自动测试列表,我们开始着手修复所有 bug。
上述让它听起来很简单,但事实并非如此。项目始于 2014 年初。我们直到 2014 年 8 月下旬才能挂载 OneFS 文件系统。我们在 9 月中旬进行了第一次自动测试运行。我们在 2015 年 4 月 1 日合并到 OneFS head 分支。这是一群非常敬业的人大量工作的结晶,我为我们所取得的成就感到非常自豪。
未来
嗯,显然我们想确保永远不必再做这样规模的项目。我们已经有了一个大致流程:从我们停止合并的点到 head 中的一个适当点的所有变更将随着每个版本的发布而合并进来。我们投入了努力走到这一步,不打算再落后。我们也在寻找更多为 FreeBSD 做贡献的方式。
如果之前的讨论还不够清楚,Isilon 对 FreeBSD 高度投入。我们迁移到 head 的意图很大一部分是让我们能够直接为 FreeBSD 做贡献,而不必等待我们的贡献进入 stable 分支,或同时为 stable 和 head 开发。我们也计划将任何对主线 FreeBSD 有意义的内部修改推送到上游。
我们想关注几个领域。一是构建过程。现有的 FreeBSD 构建既出色又有缺陷。在某个层面上,我们能进入一个目录、输入 make world,然后从另一端得到一个完整的操作系统,这是一件了不起的事。但另一方面,构建的结构方式使 Isilon 难以将其适配到我们做的事情上:用一些额外部分构建 world,然后构建一堆 Ports,然后构建更多依赖 Ports 和已构建 world 的代码,然后将整个东西打包。我们很愿意开发一个新的构建系统,让我们能将 Ports 和依赖 Port 的代码作为一级依赖添加进来。我们正在关注 Simon Gerraty 在 Juniper 做的 bmake 项目,它可能成为我们某些东西的基础,但我们很乐意看到官方 FreeBSD 构建以这种方式变得更灵活。能在 Linux 或 Mac OS X 上构建 FreeBSD 镜像也会非常非常好。Isilon 对为树莓派或 BeagleBone Black 等构建镜像并没有太大兴趣,但许多人有,让嵌入式开发者能从他们选择的桌面操作系统轻松构建 FreeBSD 镜像将大大增加 FreeBSD 在这些平台上的潜在受众。
我们想关注的另一个领域是启动过程。我们目前有自己的内部服务管理守护进程。它能工作,但可以更好。我们无法单独使用 FreeBSD 原版 init/rc 代码,因为它做不了我们需要的事。不过有替代方案,我们很乐意看到类似 launchd 的东西——可能连同 configd 和 notifyd——进入 FreeBSD 或至少通过 Ports 可用。我认为这是 FreeBSD 可以大胆现代化的另一个领域。旧式 UNIX 系统启动方式在今天已极其过时,通过迁移到这些现代替代品,我们能受益于 Apple 的大量思考,获得一套服务管理守护进程,给我们带来类似 systemd 的所有好处,而不会陷入 systemd 本身的问题区域。
我们也在努力与其他使用 FreeBSD 的公司建立联系,以便在共同关心的问题上协调资源。这涵盖驱动程序、子系统性能、VM 改进等。我们知道 FreeBSD 贡献者是有限的资源,如果我们的工程师能做一些既使我们受益也使其他 FreeBSD 用户受益的项目,那就是一个明确的 win-win 局面。
总体而言,FreeBSD 对我们非常好。我们希望 FreeBSD 不仅对我们好,也对它能触及的每个人都好。我热切希望能为 FreeBSD 做一些出色的贡献,并继续使用它很长时间。•
BENNO RICE 自 2000 年起成为 FreeBSD 提交者,目前是 EMC Isilon 部门 OS 团队的经理。他的团队负责 Isilon OneFS 中使用的 FreeBSD 代码。他的兴趣包括烹饪、电影和向上游推送代码。
最后更新于