mkosi 是一个轻量级工具，用于从发行版软件包构建镜像。本文介绍如何使用 mkosi 从 RHEL 和 RHEL 通用基础镜像 Universal Base Image （UBI）的软件包构建镜像。RHEL UBI 是 RHEL 的一个子集，可以在没有订阅的情况下免费使用。mkosi 特性mkosi 支持一些输出格式，但最重要的是 可发现磁盘镜像 Discoverable Disk Images （DDI）。同一个 DDI 可用于引导容器、或运行在虚拟机、抑或是复制到 U 盘以引导真实物理机，然后从 U 盘复制到磁盘以引导系统。该镜像具有标准化的布局和描述其用途的元数据。mkosi 依赖其他工具来完成大部分工作：使用 systemd-repart 在磁盘镜像上创建分区，使用 mkfs.btrfs / mkfs.ext4 / mkfs.xfs 等创建文件系统，并使用 dnf / apt / pacman / zypper 下载和解压包。mkosi 支持一系列发行版：Debian 和 Ubuntu、Arch Linux、openSUSE，当然还包括 Fedora、CentOS Stream 及其衍生版本，以及最近的 RHEL UBI 和 RHEL 。由于实际的“重活”是由其他工具完成的，mkosi 可以进行交叉构建。这意味着可以使用一个发行版构建各种其他发行版的镜像。唯一的要求是主机上安装了相应的工具。Fedora 有原生的 apt、pacman 和 zypper，因此它为使用 mkosi 构建任何其他发行版提供了良好的基础。它还有一些有趣的功能：镜像可以由非特权用户创建，或者在没有设备文件的容器中创建，特别是没有对回环设备的访问权限。它还可以在没有特权的情况下将这些镜像启动为虚拟机（使用 qemu）。配置是声明性的，非常容易创建。使用 systemd-repart 创建磁盘分区，并使用 repart.d 配置文件定义应该如何完成此操作。有关更多详细信息，请参见 Daan DeMeyer 在 All Systems Go 大会上的两个演讲：《systemd-repart: Building Discoverable Disk Images》 和 《mkosi: Building Bespoke Operating System Images》。项目目标mkosi 的一个目标是允许对软件项目进行针对不同发行版的测试。它将为一个发行版创建一个镜像（使用该发行版的软件包），然后将软件项目编译并安装到该镜像中，插入不属于软件包的额外文件。但是，首个阶段，即从软件包创建镜像的过程，本身就是有用的。这是我们将首先展示的内容。我们 [1] 最近添加了对 RHEL 和 RHEL UBI 的支持。让我们从 RHEL UBI 开始，利用发行版软件包创建镜像。请注意，下面的示例要求 mkosi 19，而且不适用于更早的版本。带有 Shell 的基本 RHEL UBI 镜像$ mkdir -p mkosi.cache $ mkosi \ -d rhel-ubi \ -t directory \ -p bash,coreutils,util-linux,systemd,rpm \ --autologin 上面的命令指定了发行版 rhel-ubi，输出格式 directory，并请求安装软件包 bash、coreutils、…、rpm。rpm 通常不需要放到镜像内部，但在这里用于内省会很有用。我们还启用了以 root 用户自动登录。在启动构建之前，我们创建了缓存目录 mkosi.cache。当存在缓存目录时，mkosi 会自动使用它来持久化下载的 RPM 包。这将使相同软件包集合的后续调用速度更快。然后，我们可以使用 systemd-nspawn 将此镜像作为容器启动：$ sudo mkosi \ -d rhel-ubi \ -t directory \ boot systemd 252-14.el9_2.3 running in system mode (+PAM +AUDIT +SELINUX -APPARMOR +IMA +SMACK +SECCOMP +GCRYPT +GNUTLS +OPENSSL +ACL +BLKID +CURL +ELFUTILS -FIDO2 +IDN2 -IDN -IPTC +KMOD +LIBCRYPTSETUP +LIBFDISK +PCRE2 -PWQUALITY +P11KIT -QRENCODE +TPM2 +BZIP2 +LZ4 +XZ +ZLIB +ZSTD -BPF_FRAMEWORK +XKBCOMMON +UTMP +SYSVINIT default-hierarchy=unified) Detected virtualization systemd-nspawn. Detected architecture x86-64. Detected first boot. Red Hat Enterprise Linux 9.2 (Plow) ... [ OK ] Created slice Slice /system/getty. [ OK ] Created slice Slice /system/modprobe. [ OK ] Created slice User and Session Slice. ... [ OK ] Started User Login Management. [ OK ] Reached target Multi-User System. Red Hat Enterprise Linux 9.2 (Plow) Kernel 6.5.6-300.fc39.x86_64 on an x86_64 image login: root (automatic login) [root@image ~]# rpm -q rpm systemd rpm-4.16.1.3-22.el9.x86_64 systemd-252-14.el9_2.3.x86_64 正如前面提到的，此镜像可以用于启动虚拟机。但在此设置下，这是不可能的 —— 我们的镜像没有内核。事实上，RHEL UBI 根本不提供内核，因此我们无法使用它进行引导（无论是在虚拟机上还是在裸机上）。创建镜像我一开始说是要创建镜像，但到目前为止我们只有一个目录。让我们开始实际创建一个镜像：$ mkosi \ -d rhel-ubi \ -t disk \ -p bash,coreutils,util-linux,systemd,rpm \ --autologin 这将生成 image.raw，一个带有 GPT 分区表和单个根分区（用于本机架构）的磁盘镜像。$ sudo systemd-dissect image.raw Name: image.raw Size: 301.0M Sec. Size: 512 Arch.: x86-64 Image UUID: dcbd6499-409e-4b62-b251-e0dd15e446d5 OS Release: NAME=Red Hat Enterprise Linux VERSION=9.2 (Plow) ID=rhel ID_LIKE=fedora VERSION_ID=9.2 PLATFORM_ID=platform:el9 PRETTY_NAME=Red Hat Enterprise Linux 9.2 (Plow) ANSI_COLOR=0;31 LOGO=fedora-logo-icon CPE_NAME=cpe:/o:redhat:enterprise_linux:9::baseos HOME_URL=https://www.redhat.com/ DOCUMENTATION_URL=https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/9 BUG_REPORT_URL=https://bugzilla.redhat.com/ REDHAT_BUGZILLA_PRODUCT=Red Hat Enterprise Linux 9 REDHAT_BUGZILLA_PRODUCT_VERSION=9.2 REDHAT_SUPPORT_PRODUCT=Red Hat Enterprise Linux REDHAT_SUPPORT_PRODUCT_VERSION=9.2 Use As: ✗ bootable system for UEFI ✓ bootable system for container ✗ portable service ✗ initrd ✗ sysext extension for system ✗ sysext extension for initrd ✗ sysext extension for portable service RW DESIGNATOR PARTITION UUID PARTITION LABEL FSTYPE ARCHITECTURE VERITY GROWFS NODE PARTNO rw root 1236e211-4729-4561-a6fc-9ef8f18b828f root-x86-64 xfs x86-64 no yes /dev/loop0p1 1 好的，我们现在有一个镜像，镜像中包含了一些来自 RHEL UBI 软件包的内容。我们如何在其上加点我们自己的东西呢？使用自己的文件扩展镜像有几种方法可以扩展镜像，包括从头开始编译某些东西。但在那之前，让我们做一些更简单的事情，将一个现成的文件系统注入到镜像中：$ mkdir -p mkosi.extra/srv/www/content $ cat >mkosi.extra/srv/www/content/index.html <<'EOF' Hello, World! EOF 现在，该镜像将包含 /srv/www/content/index.html。这种方法用于注入额外的配置或简单的程序。从源代码构建现在让我们过一遍完整流程，从源代码构建一些东西。例如，一个简单的 Meson 项目，有一个单独的 C 文件：$ cat >hello.c <<'EOF' #include <stdio.h> int main(int argc, char **argv) { char buf[1024]; FILE *f = fopen("/srv/www/content/index.html", "re"); size_t n = fread(buf, 1, sizeof buf, f); fwrite(buf, 1, n, stdout); fclose(f); return 0; } EOF $ cat >meson.build <<'EOF' project('hello', 'c') executable('hello', 'hello.c', install: true) EOF $ cat >mkosi.build <<'EOF' set -ex mkosi-as-caller rm -rf "$BUILDDIR/build" mkosi-as-caller meson setup "$BUILDDIR/build" "$SRCDIR" mkosi-as-caller meson compile -C "$BUILDDIR/build" meson install -C "$BUILDDIR/build" --no-rebuild EOF $ chmod +x mkosi.build 总结一下：我们有一些源代码（hello.c），一个构建系统配置文件（meson.build），以及一个由 mkosi 调用的胶水脚本（mkosi.build）。对于实际的项目，也会有相同的元素，只是更加复杂。这个脚本需要一些解释。mkosi 在创建镜像时使用用户命名空间。这允许包管理器（例如 dnf）安装由不同用户拥有的文件，即使它是由一个普通非特权用户调用的。我们使用 mkosi-as-caller 切换回调用者以进行编译。这样，在 $BUILDDIR 下编译期间创建的文件将由调用者拥有。现在让我们使用我们的程序构建镜像。与之前的调用相比，我们需要额外的软件包：meson、gcc。由于我们现在有了构建脚本，mkosi 将执行两个构建阶段：首先创建一个构建镜像，并在其中调用构建脚本，将安装产物存储在一个临时目录中，然后构建最终镜像，并将安装产物注入其中。（mkosi 设置 $DESTDIR，meson install 自动使用 $DESTDIR，因此并不需要我们明确指定。）$ mkosi \ -d rhel-ubi \ -t disk \ -p bash,coreutils,util-linux,systemd,rpm \ --autologin \ --build-package=meson,gcc \ --build-dir=mkosi.builddir \ --build-script=mkosi.build \ -f 此时，我们有了带有自定义载荷的镜像 image.raw。我们可以启动我们新创建的可执行文件作为 shell 命令：$ sudo mkosi -d rhel-ubi -t directory shell hello Hello, World! 获取 RHEL 的开发者订阅RHEL UBI 主要用作容器构建的基础层。它提供了有限的软件包（约 1500 个）。现在让我们切换到完整的 RHEL 安装。获取 RHEL 的最简单方法是使用 开发者许可证。它提供了权限注册 16 个运行 RHEL 的物理或虚拟节点，并提供自助式支持。首先，创建一个账户。然后，转到 管理页面 并确保启用了“用于 Red Hat 订阅管理的简化内容访问”。接下来，创建一个新的激活密钥，选择 “Red Hat 个人开发者订阅”。记下显示的组织 ID。在下面，我们将使用密钥名称和组织 ID 分别表示为 $KEY_NAME 和 $ORGANIZATION_ID。现在，我们准备使用 RHEL 内容：$ sudo dnf install subscription-manager $ sudo subscription-manager register \ --org $ORGANIZATION_ID --activationkey $KEY_NAME 使用 RHEL 构建镜像在之前的示例中，我们通过参数开关指定了所有配置。这对于快速开发很友好，但可能在情况复杂时变得难以处理。RHEL 是一个严肃的发行版，所以让我们改为使用配置文件：$ cat >mkosi.conf <<'EOF' [Output] Format=directory Output=rhel-directory [Distribution] Distribution=rhel [Content] Packages= bash coreutils util-linux systemd systemd-boot systemd-udev kernel-core Bootable=yes Bootloader=uki Autologin=yes WithDocs=no EOF 首先，让我们检查一下一切是否正常：$ mkosi summary 现在让我们构建镜像（呃，或者说，目录）：$ mkosi build $ mkosi qemu Welcome to Red Hat Enterprise Linux 9.2 (Plow)! [ OK ] Created slice Slice /system/modprobe. [ OK ] Reached target Initrd Root Device. [ OK ] Reached target Initrd /usr File System. [ OK ] Reached target Local Integrity Protected Volumes. [ OK ] Reached target Local File Systems. [ OK ] Reached target Path Units. [ OK ] Reached target Remote Encrypted Volumes. [ OK ] Reached target Remote Verity Protected Volumes. [ OK ] Reached target Slice Units. [ OK ] Reached target Swaps. ... [ OK ] Listening on Journal Socket. [ OK ] Listening on udev Control Socket. [ OK ] Listening on udev Kernel Socket. ... Red Hat Enterprise Linux 9.2 (Plow) Kernel 5.14.0-284.30.1.el9_2.x86_64 on an x86_64 localhost login: root (automatic login) [root@localhost ~]# 很好，我们将“镜像”构建为一个带有文件系统树的目录，并在虚拟机中引导了它。在引导的虚拟机中，findmnt / 显示根文件系统是 virtiofs。这是一个虚拟文件系统，将主机的目录暴露给客户机。我们其实也可以构建一个更传统的镜像，其中包含文件系统和文件内的分区表，但“目录 + virtiofs” 对于开发来说更快且更友好。我们刚刚引导的镜像未注册。要允许从镜像“内部”下载更新，我们必须将 yum 、subscription-manager 和 NetworkManager 添加到软件包列表，并在下载任何更新之前以与上述相同的方式调用 subscription-manager。在这之后，我们在基本仓库中就有大约 4500 个软件包可用，并且还有一些包含更多专业软件包的额外仓库。最后今天就是这些了。如果您有问题，可以在 Matrix 上找到我们，地址为 #mkosi:matrix.org，或者在 systemd 邮件列表 上找到我们。（题图：MJ/a6e60316-ed03-4e23-b8b4-22332a0f5bfa）Daan DeMeyer、Lukáš Nykrýn、Michal Sekletár、Zbigiew Jędrzejewski-Szmek ↩︎via: https://fedoramagazine.org/create-images-directly-from-rhel-and-rhel-ubi-package-using-mkosi/作者：Zbigniew Jędrzejewski-Szmek 选题：lujun9972 译者：GlassFoxowo 校对：wxy本文由 LCTT 原创编译，Linux中国 荣誉推出