使用 mkosi 构建 RHEL 和 RHEL UBI 镜像
mkosi 是一个轻量级工具，用于从发行版软件包构建镜像。
镜像 mkosi
mkosi 是一个轻量级工具，用于从发行版软件包构建镜像。
![](/data/attachment/album/202311/27/232213c9y5jt85t7vj8men.png)


mkosi 是一个轻量级工具，用于从发行版软件包构建镜像。本文介绍如何使用 mkosi 从 RHEL 和 RHEL <ruby> <a href="https://uapi-group.org/specifications/specs/discoverable_disk_image/">  通用基础镜像 </a> <rt>  Universal Base Image </rt></ruby>（UBI）的软件包构建镜像。RHEL UBI 是 RHEL 的一个子集，可以在没有订阅的情况下免费使用。


### mkosi 特性


mkosi 支持一些输出格式，但最重要的是 <ruby> <a href="https://uapi-group.org/specifications/specs/discoverable_disk_image/">  可发现磁盘镜像 </a> <rt>  Discoverable Disk Images </rt></ruby>（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](https://media.ccc.de/v/all-systems-go-2023-191-systemd-repart-building-discoverable-disk-images)》 和 《[mkosi: Building Bespoke Operating System Images](https://media.ccc.de/v/all-systems-go-2023-190-mkosi-building-bespoke-operating-system-images)》。


### 项目目标


mkosi 的一个目标是允许对软件项目进行针对不同发行版的测试。它将为一个发行版创建一个镜像（使用该发行版的软件包），然后将软件项目编译并安装到该镜像中，插入不属于软件包的额外文件。但是，首个阶段，即从软件包创建镜像的过程，本身就是有用的。这是我们将首先展示的内容。


我们 <sup class="footnote-ref"> <a href="#fn1" id="fnref1">  [1] </a></sup> 最近添加了对 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'
<h1>Hello, World!</h1>
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
<h1>Hello, World!</h1>

```

### 获取 RHEL 的开发者订阅


RHEL UBI 主要用作容器构建的基础层。它提供了有限的软件包（约 1500 个）。现在让我们切换到完整的 RHEL 安装。


获取 RHEL 的最简单方法是使用 [开发者许可证](https://developers.redhat.com/articles/faqs-no-cost-red-hat-enterprise-linux)。它提供了权限注册 16 个运行 RHEL 的物理或虚拟节点，并提供自助式支持。


首先，[创建一个账户](https://developers.redhat.com/register)。然后，转到 [管理页面](https://access.redhat.com/management) 并确保启用了“用于 Red Hat 订阅管理的简化内容访问”。接下来，[创建一个新的激活密钥](https://access.redhat.com/management/activation_keys/new)，选择 “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](https://matrix.to/#/#mkosi:matrix.org)，或者在 [systemd 邮件列表](https://lists.freedesktop.org/mailman/listinfo/systemd-devel) 上找到我们。


*（题图：MJ/a6e60316-ed03-4e23-b8b4-22332a0f5bfa）*




---



1. Daan DeMeyer、Lukáš Nykrýn、Michal Sekletár、Zbigiew Jędrzejewski-Szmek [↩︎](#fnref1)





---


via: <https://fedoramagazine.org/create-images-directly-from-rhel-and-rhel-ubi-package-using-mkosi/>


作者：[Zbigniew Jędrzejewski-Szmek](https://fedoramagazine.org/author/zbyszek/) 选题：[lujun9972](https://github.com/lujun9972) 译者：[GlassFoxowo](https://github.com/GlassFoxowo-Dev) 校对：[wxy](https://github.com/wxy)


本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译，[Linux中国](https://linux.cn/) 荣誉推出

