RPM packages, ostree commits
RPM 软件包,ostree 提交

  1. RPMs + config -> single OSTree commit
    RPMs + 配置 -> 单个 OSTree 提交
  2. Philosophy: Every change is “from scratch”
    哲学:每次更改都是“从头开始”的
  3. Base vs extensions split
    基础 vs 扩展分离
  4. Overall architecture: 总体架构:
    1. Generating the filesystem tree
      生成文件系统树
    2. Sandboxing scripts 沙盒脚本
    3. Content in /var /var 中的内容
    4. Kernel handling 内核处理
    5. SELinux
      1. SELinux policy storage location
        SELinux 策略存储位置

RPMs + config -> single OSTree commit
RPM 包 + 配置 -> 单个 OSTree 提交

On the compose side, to generate the “base image” the core idea is that we take a set of packages as input, along with other configuration and data and generate a single OSTree commit - a versioned filesystem tree.
在组合方面,生成“基础镜像”的核心思想是,我们将一组软件包作为输入,以及其他配置和数据,并生成一个单个的 OSTree 提交 - 一个带版本的文件系统树。

The same is also true on the client side, but it starts from a “base commit”.
客户端也是如此,但它是从一个“基本提交”开始的。

This document will describe the “core” phases and steps in that process that apply both build/compose side and client side.
本文档将描述该过程中适用于构建/组合端和客户端的“核心”阶段和步骤。

Philosophy: Every change is “from scratch”
哲学:每一次变更都是“从头开始”。

For every change today, rpm-ostree generally rebuilds the target filesystem “from scratch” - adding accurate caching where needed. The goal is to avoid hysteresis (related blog).
对于今天的每一次更改,rpm-ostree 通常会“从头开始”重新构建目标文件系统 - 在需要时添加准确的缓存。目标是避免滞后效应(相关博客)。

In other words if e.g. you rpm-ostree install foo and then rpm-ostree install bar, the new target filesystem tree will be regenerated “from scratch” and all RPM %post scripts etc. will rerun.
换句话说,例如,如果您 rpm-ostree install foo 然后 rpm-ostree install bar ,新的目标文件系统树将被“从头开始”重新生成,所有 RPM %post 脚本等将重新运行。

Base vs extensions split
基础与扩展分离

rpm-ostree today goes to some lengths to create a distinction between the “base image” and optional “layered/extension” packages. The idea is that the base image has been tested on a build server, and any overrides or replacements are more likely to break things, and should be explicit actions.
rpm-ostree 今天在创建“基础镜像”和可选的“分层/扩展”软件包之间进行了一些区分。其想法是基础镜像已在构建服务器上经过测试,任何覆盖或替换更有可能导致故障,应该是明确的操作。

This is the reason why on the client side e.g. rpm-ostree upgrade kernel does not work today. Instead, this requires an invocation of rpm-ostree override replace.
这就是为什么在客户端上例如 rpm-ostree upgrade kernel 今天无法工作的原因。相反,这需要调用 rpm-ostree override replace

Another related rationale for this is that rpm-ostree does not have a per-package “user installed” type database as exists in e.g. dnf. Everything in the base image is always replaced by the contents of the new base image, without considering any per-package semantics. For more on this topic, see this blog entry.
这种做法的另一个相关理由是,rpm-ostree 没有像 dnf 中存在的每个软件包“用户安装”类型数据库。基础镜像中的所有内容总是被新基础镜像的内容替换,而不考虑任何软件包的语义。有关此主题的更多信息,请参阅此博客条目。

In some cases, a dependency may “span” the base image and a layered package. For discussion related to this, see e.g. https://github.com/coreos/rpm-ostree/issues/415 A solution chosen for in Fedora is to ensure that such packages have versions corresponding to the base image available in the rpm-md repository as well; this is implemented via the updates-archive repository.
在某些情况下,依赖关系可能“跨越”基础镜像和分层包。有关此问题的讨论,请参见例如 https://github.com/coreos/rpm-ostree/issues/415 Fedora 中选择的解决方案是确保这些包的版本与 rpm-md 存储库中基础镜像的版本对应;这是通过 updates-archive 存储库实现的。

Overall architecture: 整体架构:

  • For each package, download and import into OSTree commit if necessary
    对于每个软件包,如果需要,下载并导入到 OSTree 提交。
  • Unpack the “base” filesystem tree if any via hardlinks
    通过硬链接解压“base”文件系统树(如果有的话)
  • Determine an installation order, and unpack each package-ostree commit again via hardlinks
    确定安装顺序,并通过硬链接再次解压每个软件包-ostree 提交
  • Run all the %post scripts (in install order)
    运行所有 %post 脚本(按安装顺序)
  • Run all the %posttrans scripts (in install order)
    运行所有 %posttrans 脚本(按安装顺序)
  • Write RPM database (if we had a “base commit”, starting from that)
    写入 RPM 数据库(如果有“基本提交”,则从那里开始)
  • If initramfs regeneration is enabled or the kernel was replaced, remove the base initramfs and run dracut to generate a new one.
    如果启用了 initramfs 重新生成或者内核已被替换,则删除基本 initramfs 并运行 dracut 生成新的。
  • Ask libostree to commit the resulting filesystem tree, optimized by a (device, inode) -> checksum cache, so that files that weren’t changed aren’t re-checksummed.
    请求 libostree 提交生成的文件系统树,通过 (设备,索引节点) -> 校验和缓存进行优化,以便不重新对未更改的文件进行校验。

Generating the filesystem tree
生成文件系统树

In contrast to the above, traditional package managers like RPM are usually implemented in a flow that does:
与上述相反,像 RPM 这样的传统软件包管理器通常是按照以下流程实现的:

  • Unpack package A 解压包 A
  • Run %post script for A
    运行 A 的 %post 脚本
  • Update the package database with metadata for A
    使用 A 的元数据更新包数据库
  • Unpack package B 解压包 B
  • Run %post script for B
    运行 B 的 %post 脚本
  • Update the package database with metadata for B
    使用 B 的元数据更新包数据库
  • Run %posttrans scripts 运行 %posttrans 脚本

etc. 等等。

In contrast, rpm-ostree maintains an OSTree commit corresponding to each RPM package provided as input. On a client system, you can see this in e.g. ostree refs | grep rpmostree/pkg (assuming you have layered packages). On a build system, these ostree commits will be stored in a repo at pkgcache-repo/ within the cache directory.
相比之下,rpm-ostree 维护与提供的每个 RPM 软件包对应的一个 OSTree 提交。在客户端系统上,您可以在例如 ostree refs | grep rpmostree/pkg 中看到这一点(假设您有分层软件包)。在构建系统上,这些 ostree 提交将存储在缓存目录中的 pkgcache-repo/ 存储库中。

This acts as an optimized cache for regenerating the target root filesystem. So for rpm-ostree, the phase is more like this:
这充当了重新生成目标根文件系统的优化缓存。因此对于 rpm-ostree,该阶段更像是这样:

  • Unpack the filesystem tree for all packages
    为所有软件包解压文件系统树
  • Run all the %post scripts
    运行所有 %post 脚本
  • Run all the %posttrans scripts …
    运行所有 %posttrans 脚本...

rpm-ostree is effectively reimplementing large chunks of the librpm userspace in order to make it use OSTree natively.
rpm-ostree 实际上是重新实现了大部分 librpm 用户空间,以便使其原生地使用 OSTree。

Sandboxing scripts 脚本沙盒化

On the build server side, it’s obviously desirable to have the “build” of an ostree commit for a target system not affect the running host.
在构建服务器端,显然希望目标系统的 ostree 提交的“构建”不会影响运行主机。

Similarly, on the client side, the default is to provide “offline” updates that don’t affect the running system.
同样,在客户端,提供“离线”更新是默认设置,不会影响运行系统。

As part of this, rpm-ostree currently uses the bubblewrap tool to run each script in its own isolated container.
作为其中的一部分,rpm-ostree 目前使用 bubblewrap 工具在独立的容器中运行每个脚本。

Today, scripts are run with real uid 0 (not in a user namespace), but we drop most capabilities. Additionally, scripts can’t see the real host root filesystem, most notably they do not see the real /var with all of the system data. A good example of the benefit of this is “tests: Add a test case for a %post that does rm -rf /”.
今天,脚本以真实的 uid 0 运行(不在用户命名空间中),但我们会放弃大部分权限。此外,脚本无法看到真实的主机根文件系统,特别是它们看不到带有所有系统数据的真实 /var 。这种做法的好处的一个很好的例子是“测试:为执行 rm -rf / 的 %post 添加一个测试用例”。

In addition to bubblewrap, rpm-ostree uses rofiles-fuse from the ostree project which originally enforced the model that a file that has multiple hardlinks is read-only, but more recently gained --copyup support which acts in a similar fashion to the in-kernel overlayfs. (See also https://github.com/ostreedev/ostree/issues/2281)
除了 bubblewrap 外,rpm-ostree 还使用 ostree 项目中的 rofiles-fuse ,该项目最初强制执行一个文件具有多个硬链接是只读的模型,但最近增加了 --copyup 支持,其功能类似于内核中的 overlayfs 。(另请参阅 https://github.com/ostreedev/ostree/issues/2281)

Scripts can mutate content in /etc and /usr, but this should generally be restricted to updating “cache files”. Example of this are ldconfig and gtk-update-icon-cache.
脚本可以更改 /etc/usr 中的内容,但这通常应该限制在更新“缓存文件”上。这方面的例子包括 ldconfiggtk-update-icon-cache

Scripts see a minimal, read-only view of /var. It is explicitly not writable. This helps ensure offline updates are possible; a package script running client side cannot affect persistent system state.
脚本查看 /var 的最小只读视图。明确不可写。这有助于确保离线更新的可能性;在客户端运行的软件包脚本不能影响持久系统状态。

Content in /var /var 中的内容

rpm-ostree automatically synthesizes systemd tmpfiles.d snippets from directories in /var. This helps ensure a separation between OS updates and machine local state. The directories will be created when the system boots - and it aids in a “factory reset” scenario of wiping machine-local state in /var.
rpm-ostree 会自动从 /var 中的目录合成 systemd tmpfiles.d 片段。这有助于确保 OS 更新和机器本地状态之间的分离。这些目录将在系统启动时创建 - 并且在“恢复出厂设置”场景中有助于擦除机器本地状态。

At the current time, any non-directories (i.e. regular files or symbolic links) are discarded. This will likely change to be an opt-in error in the future.
在当前时间,任何非目录(即常规文件或符号链接)都将被丢弃。这很可能会在将来更改为选择性错误。

Example bugs: 示例错误:

  • https://bugzilla.redhat.com/show_bug.cgi?id=2132103
  • https://bugzilla.redhat.com/show_bug.cgi?id=1473402

Kernel handling 内核处理

ostree is entirely oriented around bootable filesystem trees; its “source of truth” is the bootloader entries. It has opinions about where the Linux kernel binaries are stored (the current standard is in /usr/lib/modules/$kver.)
ostree 完全围绕可引导的文件系统树进行设计;它的“真相来源”是引导加载程序条目。它对 Linux 内核二进制文件存储的位置有自己的看法(当前标准是在 /usr/lib/modules/$kver 中。)

In contrast, traditional RPM is unaware of what a kernel is; it’s just another package. Most higher level package managers such as yum gained some special casing around the kernel - because it’s not possible to restart the running kernel, traditional RPM systems need to keep the kernel modules for the running kernel around. For example yum/dnf have a concept of “installonlyn” which defaults to 2 for the kernel package.
相比之下,传统的 RPM 不知道什么是内核;它只是另一个软件包。大多数高级软件包管理器(如 yum)在内核周围增加了一些特殊处理 - 因为无法重新启动正在运行的内核,传统的 RPM 系统需要保留正在运行的内核模块。例如 yum/dnf 有一个“installonlyn”的概念,默认为 2 用于内核软件包。

Additionally, for at least traditional Fedora derivatives with yum/dnf, the initramfs is generated client side as part of a kernel update.
另外,至少对于使用 yum/dnf 的传统 Fedora 派生版,initramfs 是作为内核更新的一部分在客户端生成的。

But for rpm-ostree, the decision was made to default to a pre-generated initramfs by default. Further, in order to implement transactional upgrades, rpm-ostree needs to be in control of the initramfs regeneration - it can’t just be a script forked off without its knowledge.
但是对于 rpm-ostree,决定默认使用预生成的 initramfs。此外,为了实现事务性升级,rpm-ostree 需要控制 initramfs 的重新生成 - 不能只是一个没有知识的脚本分支。

Further for rpm-ostree, easily replacing the kernel (as well as userspace) is intended to be a first-class operation; you need to be able to do that in order to debug production issues for example.
对于 rpm-ostree,轻松替换内核(以及用户空间)被设计为一种一流操作;例如,您需要能够这样做以便调试生产问题。

In contrast to the yum/dnf “installonly” for ostree there can be exactly one kernel per userspace filesystem tree. To ostree, a “bootable ostree commit” is the pair of (kernel, userspace).
与 yum/dnf 的“installonly”相比,ostree 中每个用户空间文件系统树只能有一个内核。对于 ostree,一个“可引导的 ostree 提交”是(内核,用户空间)的一对。

rpm-ostree combines these two worlds, and goes to some lengths to bend the libdnf stack to work this way. We reset the “installonly” limit back to 1 to ensure we have exactly one kernel. PR: https://github.com/coreos/rpm-ostree/pull/1228
rpm-ostree 结合了这两个世界,并且竭尽全力弯曲 libdnf 堆栈以使其按照这种方式工作。我们将“installonly”限制重置为 1,以确保我们只有一个内核。PR: https://github.com/coreos/rpm-ostree/pull/1228

Further, as noted above rpm-ostree takes over the handling of invoking dracut - just like other scripts, it is run inside a container with just read-only access to the system. dracut generates the initramfs CPIO archive, which we then place inside the /usr/lib/modules/$kver location.
此外,如上所述,rpm-ostree 接管了调用 dracut 的处理方式 - 就像其他脚本一样,它在一个容器内运行,只能以只读方式访问系统。 dracut 生成 initramfs CPIO 归档文件,然后我们将其放置在 /usr/lib/modules/$kver 位置。

If client-side initramfs regeneration is enabled, we may selectively provide desired configuration files into this process. PR: https://github.com/coreos/rpm-ostree/pull/2170
如果启用了客户端端 initramfs 重新生成,我们可以有选择性地将所需的配置文件提供给此过程。PR: https://github.com/coreos/rpm-ostree/pull/2170

SELinux

Handling SELinux is very tricky, because it is a package that can affect every other package. Specifically, the SELinux policy package contains a vast set of regular expressions in file_contexts to determine labeling.
处理 SELinux 非常棘手,因为它是一个可以影响每个其他软件包的软件包。具体来说,SELinux 策略软件包包含大量的正则表达式,用于确定标签。

For traditional librpm, this is a plugin.
对于传统的 librpm,这是一个插件。

A major goal of OSTree from the start has been to ensure fully correct handling of SELinux for the base operating system. The way rpm-ostree handles this is by:
从一开始,OSTree 的一个主要目标就是确保基本操作系统对 SELinux 的完全正确处理。rpm-ostree 处理这个的方式是:

  • Recompiling the policy as a %posttrans equivalent
    重新编译策略作为一个 %posttrans 等价物
  • Loading the policy from the target root, and pass that loaded policy to libostree, which consults it to use for the label of each committed file.
    从目标根目录加载策略,并将加载的策略传递给 libostree,libostree 会查询它以用于每个提交文件的标签。

This means that on an OSTree based system, the labels for the files in the booted deployment (e.g. in /usr) are always correct and set atomically - there’s no need to relabel.
这意味着在基于 OSTree 的系统上,引导部署中文件的标签(例如在 /usr 中)始终是正确的并原子设置的 - 无需重新标记。

SELinux policy storage location
SELinux 策略存储位置

Another major difference between traditional yum/dnf and rpm-ostree based systems is the location of the SELinux policy store database itself. rpm-ostree overrides it to be back in /etc, when it was moved to /var in the RPM package around the Fedora 24 timeframe. For more information see https://bugzilla.redhat.com/show_bug.cgi?id=1290659 and the comments in rpmostree-postprocess.cxx.
传统的 yum/dnf 和基于 rpm-ostree 的系统之间的另一个主要区别是 SELinux 策略存储数据库本身的位置。在 Fedora 24 时间段左右,rpm-ostree 将其覆盖为回到 /etc ,而在 RPM 软件包中将其移动到 /var 。有关更多信息,请参阅 https://bugzilla.redhat.com/show_bug.cgi?id=1290659 和 rpmostree-postprocess.cxx 中的评论。