自Linux诞生以来,一直以其简洁灵活的特性受到开发者的热烈追捧。Linux坚守Unix的KISS(Keep It Simple, Stupid)设计原则,但随着新的计算场景不断涌现,一些问题也逐渐浮现,其中之一便是操作系统生命周期的管理。

回顾历史,开发者们为解决这个问题尝试了各种各样的方法,其中包括包管理工具和漏洞自动修复工具等等。但是,是否还存在更出色的解决方案呢?当然存在,其中之一就是建立一个与Git类似的操作系统。想象一下,将操作系统的生命周期管理类比为Git仓库的管理,我们可以随意切换到不同的版本,创建新的分支,然后将它们合并到主分支,或者回滚到任何特定的版本。这样一来,我们就可以随心所欲地管理操作系统的生命周期,就像在创建多重宇宙一样。

为什么要重新造轮子

我们常说“less is more”。如果你的智能手机已经提供了GPS地图应用程序,你可能就不会再购买物理GPS导航器了。同样地,如果你的Linux系统能够提供高效且易于使用的即插即用功能,那么为什么还需要配置和维护额外的工具呢?

让我们深思熟虑一下那些声称支持操作系统回滚、增量更新、一致性或变更历史跟踪的解决方案所涉及的复杂性(以及成本)。如果我们能够仅仅通过操作系统本身的功能就能够实现这些目标,那岂不是更理想?当然,前提是即插即用的功能至少要与额外工具提供的功能一样出色。接下来,我们将详细了解这种全新的操作系统生命周期管理方法是如何实现这一目标的。

这个创新的轮子被称为libostree,当然,许多人也习惯称其为OSTree。OSTree的模型类似于Git,因为它对文件进行了校验和,并采用内容寻址的对象存储方式。但与Git不同的是,OSTree通过硬链接来“checkout”文件,因此需要将它们保持不变,以防止数据损坏。

在我们深入研究OSTree之前,让我们先了解一些相关的基础知识。

chroot 监狱

多年前,一些Linux开发者认识到在操作系统核心功能的开发中需要极高的谨慎,以防意外破坏环境。为了确保他们可以在一个安全的沙盒中进行开发,就像拥有时光机一样,可以随时回到原始环境,他们开发了一种在同一系统中快速创建新的操作系统“实例”的方式。这种方法允许他们在一个相对独立的环境中进行开发,从而安全地进行实验,而无需担心对主系统的影响。此外,这种方法还具有其他优点,如共享数据、使用系统安装的应用程序和特定硬件等。

或许你会提到虚拟化和容器技术也可以实现类似的隔离和环境复制功能。但问题是,虚拟机和容器的创建过程相对较慢,且在多个“版本”之间共享数据、应用程序和硬件有时会变得复杂。更重要的是,回滚操作通常不容易。因此,开发者们决定采用chroot技术来实现这种特殊的超能力。

“chroot”是早期Unix引入的一项操作,它改变了进程的“根目录”,从而可以创建一个隔离的“监狱”环境,以限制进程只能访问该环境内的资源。这种技术可用于创建进程的沙盒,防止它们对chroot目录之外的数据进行恶意更改,或者作为轻量级虚拟机的替代方案。尽管容器技术也使用了“namespace”等更复杂的机制来实现隔离,但chroot技术同样可以提供类似的隔离效果,而且更加轻量。

回到操作系统的生命周期管理问题,核心思想是允许从不同的chroot目录引导,这样开发者可以在一个chroot监狱环境中开发新功能,可以访问一些共享数据和其他应用程序。如果新环境出现问题,只需回到“原始”的chroot监狱,而不会影响其他环境。然而,仅仅使用chroot还不足够,因为要实现这些功能,需要与不同的引导加载程序组件(如GRUB、内核初始化文件、文件系统挂载等)协同工作。此外,还需要考虑如何执行诸如平台更新等操作,这就是libostree的核心功能。

rhel-for-edge-chroot-directories.png

类Git的可引导文件系统

首先,让我们来看libostree的官方定义:

Libostree is both a shared library and suite of command line tools that combines a “git-like” model for committing and downloading bootable filesystem trees, along with a layer for deploying them and managing the bootloader configuration.

简而言之,libostree是一个共享库和一组命令行工具,它结合了一种“类似于Git”的模型,用于提交和下载可引导的文件系统树,同时还提供了用于部署它们和管理引导加载程序配置的层次。

换句话说,libostree用于管理可引导的操作系统生命周期,它采用了类似于Git版本控制系统的工作方式。与Git一样,libostree使用校验和来管理文件,将操作系统的不同版本存储为一组不可变的对象。这些对象可以像源代码一样进行版本控制和回滚。这使得libostree能够轻松地管理不同版本的操作系统,并实现类似Git的分支、合并和回滚操作,为操作系统的生命周期管理提供了强大的工具。

然而,正如你所指出的,使用chroot创建不同“版本”的根文件系统可能会导致存储空间的浪费,因为相同的文件会重复出现。为了解决这个问题,需要一种方式来在不复制或共享文件的情况下,在不同的根文件夹中存储相同的文件,并能够跟踪版本更改。

这就是类似Git的思想发挥作用的地方。Git是一个由哈希标识的对象组成的数据库,它使用键值数据存储概念来存储数据。Git具有四种不同类型的对象,用于表示文件的内容(blob)、目录结构(tree)、版本信息(commit)和标签(tag)。

虽然“blobs”和“trees”足以表示完整的文件系统,但“commits”包含对描述存储库根目录的“tree”对象的引用,从而提供了完整的版本控制系统。如果两个不同版本之间的文件内容没有更改,它们可以指向相同的“blob”,而不需要复制文件内容。

libostree不仅仅是Git的翻版,它借鉴了Git的概念,并以非常相似的方式应用这些概念,但在具体实现上有所不同。Git主要设计用于源代码仓库版本控制,因此它的功能更侧重于“文本文件”。相反,libostree需要处理混合版本控制,包括对“文本文件”和“二进制文件”的优化,因此它的功能更广泛,不仅局限于文本文件。

“有效地”复制文件系统

我们已经理解了使用chroot技术创建可引导的根文件系统环境,并希望实现类似Git的版本控制系统,以便有效管理不同版本的文件系统。现在让我们来谈谈如何实现“有效地”复制文件系统,这涉及到Linux中的硬链接。

在Linux中,有两种类型的文件链接:软链接(符号链接)和硬链接。软链接是一种特殊的文件,它指向另一个常规文件,而硬链接则是不同文件名直接指向相同数据和属性(inode)的文件。这两种链接类型之间有一个关键区别,使得硬链接更适合类似Git的版本控制用例。使用硬链接,即使你删除了“目标”文件,数据仍然可以访问,而软链接在目标文件被删除后将不再有效。在我们的情况下,我们需要在同一磁盘分区上拥有多个“文件副本”,并且这些副本必须是独立的,以防止删除一个文件影响到其他版本。

libostree使用硬链接来“checkout”文件,这意味着它可以在不复制文件内容的情况下创建多个文件系统版本,这是非常高效的。但是,硬链接也带来了一个问题。假设你有两个操作系统的“快照”(我们将它们称为“部署A”和“部署B”),它们是相同的。然后,你在“部署B”上进行了一些二进制文件的更改,但后来发现这些更改导致问题。你决定回到“部署A”,但问题是,“部署B”中的更改已经影响了“部署A”,因为它们实际上共享了相同的硬链接文件。这使得文件必须是不可变的,否则一个版本的更改将影响到其他版本。

为了解决这个问题,操作系统基于只读文件系统构建,并在启动时使用符号链接来选择可用的操作系统根文件系统“快照/镜像/部署”。每当你需要进行更改时,会创建一个全新的根文件系统的副本,但不需要复制所有文件内容,因此速度非常快。只有发生更改的文件将成为新的“常规”文件,而其他文件将保持为硬链接或在新版本中被删除。

rhel-for-edge-deployment-changes.png

虽然文件是不可变的,但仍然可以更新文件系统。假设你需要更新多个应用程序的二进制文件,你只需创建一个新的chroot文件系统副本,其中包含新版本的二进制文件。但是,如何在运行中的操作系统上应用这些更改呢?在启动时,通过修改符号链接中的路径来选择不同的操作系统根文件系统“快照/镜像”。这是通过修改内核参数中的软链接来实现的。

rhel-for-edge-boot-pointer.png

在启动时选择哪个操作系统版本(部署)是一项决策,如果要切换到新的部署,必须重新启动系统以使更改生效。这确保了文件系统镜像的一致性,因为每个部署都是一个相对独立的镜像,更改在重新启动后生效。

这种方法的好处在于它提供了一种有效管理多个文件系统版本的方法,并允许在需要时轻松切换版本。此外,这种一致性也允许系统回滚,即在需要时返回到先前的部署,因为它们都共享相同的文件系统镜像。这种方式也适用于大规模管理,可以在中央位置生成更新并将它们应用于多个系统,以确保一致性。这种方法大大简化了管理,减少了网络带宽和计算资源的浪费。

rhel-for-edge-image-generating.png

那我们还需要包管理系统吗?

虽然libostree可以管理整个操作系统的更新和版本控制,但它并不完全替代了传统的软件包管理系统,因为它们在应用程序和库的管理方面有着不同的关注点和用途。

软件包管理系统(如APT、DNF等)主要用于管理单个软件包的安装、更新、卸载和依赖性解决。它们提供了更灵活的方式来安装和维护特定应用程序和库,允许用户选择性地安装和更新软件包,以满足其特定需求。软件包管理器还提供了易于使用的命令行工具和图形界面,用于搜索、安装和管理软件包。

与此不同,libostree专注于管理整个操作系统文件系统的版本和更新。它将整个文件系统作为一个不可变的对象来管理,以确保文件系统的一致性和可回滚性。这对于保持操作系统的稳定性和可靠性非常重要,特别是在大规模部署中。

因此,答案是,虽然libostree可以管理操作系统的更新和版本控制,但仍然需要传统的软件包管理系统来处理应用程序和库的安装和管理。这两者可以协同工作,为用户提供全面的软件管理解决方案,既可以管理整个操作系统,又可以管理个别软件包。在某些Linux发行版中,如RHEL for Edge和Fedora,已经有了特定于libostree的混合软件包管理器(如rpm-ostree),以便更好地集成libostree和传统软件包管理系统的功能。

那么,libostree和rpm之间的这种协同工作是如何实现的呢?首先,DNF会将软件包安装到由libostree创建的文件系统中,这个文件系统是从原始部署复制而来的。然后,libostree将这个更新后的文件系统副本检入为一个新对象,并将其检出,以创建一个全新的操作系统版本。最后,通过重新启动操作系统,新的系统文件将得以生效,成为实际的“libostree部署的新版本”。简而言之,这个过程可以总结为以下步骤:

  1. libostree检出原始文件系统的一个副本
  2. DNF将软件包安装到此新文件系统副本中
  3. libostree将此副本保存为新的对象
  4. libostree将新对象检出,生成新的文件系统
  5. 重新启动操作系统以应用新的系统文件

这种方式有效地结合了libostree的版本控制和不可变性特性以及传统软件包管理器(DNF)的能力,使得操作系统的更新和应用程序管理变得更加灵活和可管理。

系统配置与用户数据

我们一直在讨论将根操作系统文件系统挂载为只读,以防止在libostree控制之外更改文件硬链接。然而,任何操作系统都需要对配置文件或用户数据进行写访问,因此不能将所有操作系统目录都设置为只读。

实际上,默认情况下,libostree只将/usr目录设置为只读,其中包含了不应该修改的所有目录树,例如库和二进制文件等。此外,/usr/etc目录包含了所有已更改的/etc文件,以便提供像“恢复系统到出厂配置”等功能。

还有一些目录需要具有读/写权限,这些目录与不同的部署有关。有些文件附加/绑定到特定的操作系统部署,而其他文件则需要独立存在。例如,假设在部署“A”中,我们有一个版本为“1”的应用程序,它需要一个可写的配置文件,以便可以调整配置而无需创建新的镜像。现在,我们将应用程序更新为版本“2”,因此创建了一个新的部署“B”,但在应用程序版本过渡期间,开发人员更改了配置文件选项,因此配置文件必须“专用”于各自的部署,以便应用程序可以为每个发布找到预期的配置文件。

综上所述,有些情况下,在创建新的部署时,需要将可写文件与只读文件系统一起复制,而在其他情况下,这些文件只是在它们之间共享。对于需要与特定部署绑定的可写文件,默认情况下,libostree使用/etc,而对于独立的文件,它使用/var

现在我们知道/etc用于托管绑定到特定部署的文件,而/var中的文件是独立的,我们可以轻松理解libostree在创建新部署时这些目录所做的工作。/etc位置会被复制,以允许执行dnf install等操作,这可能会更改与新部署关联的新副本,同时保持旧的副本不变。与此同时,/var中的文件只是在不同部署之间共享,因此可以在两个部署中访问相同的文件。

directories-transition-between-different-deployments.png

每个libostree操作系统可以决定将什么放在/var中,但将用户的主目录(通常是/home)包括其中是一个好主意,以便用户可以保留自己的数据。在RHEL for Edge中的目录分布示例中,我们可以看到这些目录是如何配置的,与非libostree(“常规”)RHEL目录树进行了比较。这个设置确保了目录的一致性和有效的操作系统管理。

rhel-for-edge-directory-tree.png

在这个设置中,/usr被挂载为只读,/lib/sbin等目录的多个链接被创建在新位置/usr中。/etc/var具有写访问权限,而用户的主目录(通常是/home)以及其他与操作系统部署无关的文件存储在/var中。

值得注意的是,根目录树实际上是一个chroot监狱,不同的chroot目录位于/sysroot/ostree中,这是为了在不同部署之间保持一致性。这种设置使得libostree能够有效地管理操作系统的配置和用户数据。

TL/DR

总结一下,我们介绍了一种通过“fork”操作系统来管理操作系统生命周期的新方法,这个想法受到Git概念的启发,以一种类似于管理源代码的方式来管理操作系统的分叉、回滚、跟踪更改等操作。

关键技术和功能包括使用chroot来隔离不同的操作系统根文件系统,以及使用文件硬链接来避免不同操作系统根文件系统之间的文件重复,从而限制跨更改的影响。

基于这些组件,我们引入了一种新的操作系统更新生命周期管理方法,称为libostree或OSTree。Libostree提供了许多好处,包括事务升级、回滚、镜像生成、多个操作系统部署和版本控制系统等功能,使操作系统管理更加灵活和高效。这就是Linux和Git联手创造多重宇宙的魔法之处。

应用前景

既然你读到这里,说明你你喜欢学习新知识来拓展你的思维和智慧,同时也看到了这项技术的潜力与发展前景。总结一下,libostree技术具有广泛的应用前景,以下是两个主要的应用领域:

面向容器的操作系统: 基于libostree的操作系统更新和回滚方式与容器非常相似。这意味着您可以轻松地选择不同版本的容器镜像,根据标签进行选择,并执行更新,类似于重新启动容器。对于仅运行容器的系统,您可以将应用程序(容器)的生命周期与操作系统的生命周期分开管理,同时采用相同的方法。

边缘计算: libostree的特性非常适合边缘计算用例。它能够在小型硬件环境和大规模环境中运行,容忍网络中断,具有自动化的中央管理和可观测性,保护数据的安全性,并能够与外部IT和OT系统集成。此外,libostree的更新是原子性的且增量进行,减少了资源消耗,而操作系统镜像可以在中央站点生成并分发,提供了一致性和可复制性。这些特性使其成为边缘计算设备的理想基础操作系统。

总之,libostree/OSTree技术在容器化和边缘计算领域具有广泛的应用潜力,可以提供更好的管理、安全性和可维护性。