背景

由于项目组选择采用 Electron 构建跨平台客户端, 因此存在一个问题就是如何利用 CI/CD 进行自动构建与发布. 当前内部使用 Jenkins 进行自动构建, 但 Jenkins 仍需要 macOS 平台以便构建 DMG 包进行分发. 因此在不考虑实体机的情况下, 最便捷的方案便是可以能创建一台 macOS 的虚拟机了, 通过虚拟机可以方便地复制构建环境从而不和任意硬件货环境绑定.

但当前的问题一是默认情况下 CentOS 8 下提供的 4.2.0 版本的 QEMU 比较老了, 另一个问题则是 RHEL 在分发的时候移除了相当多的 QEMU 特性, 无法直接创建 macOS 虚拟机, 因此我们需要手动修改 QEMU 的代码并自行构建 RPM 包修复这两个问题.

步骤

首先执行以下命令安装 yum-utilsrpm-build 两个包以便下载对应的 SRC 包同时进行编译

sudo yum install -y yum-utils rpm-build

yum-utils 包会提供一个 yumdownloader 命令, 此时再通过以下命令下载 qemu-kvm 的源代码即可:

yumdownloader --source qemu-kvm

yumdownloader 会下载对应版本的 SRC 包到当前目录, 当前 CentOS 8 系统提供的 qemu 版本为 6.0.0, 因此下载得到的包为 qemu-kvm-6.0.0-33.el8.src.rpm. 此时再通过以下命令安装 SRC 包即可:

rpm -ivh qemu-kvm-6.0.0-33.el8.src.rpm

同样, 安装以上命令执行完成后会新建一个与执行命令用户绑定的 $HOME/rpmbuild 目录, 所有相关文件都在该目录下所示:

[root@vm-centos-8 ~]# tree -L 1 $HOME/rpmbuild
/root/rpmbuild
├── SOURCES
└── SPECS

2 directories, 0 files
[root@vm-centos-8 ~]#

在编译前还需要安装相关的依赖库, 执行以下命令安装即可:

sudo yum install -y zlib-devel wget usbredir-devel texinfo systemtap-sdt-devel systemtap systemd-devel \
  spice-server-devel spice-protocol snappy-devel rsync rdma-core-devel python3-sphinx python3-devel pixman-devel \
  perl-podlators perl-Test-Harness pciutils-devel numactl-devel bluez-libs-devel brlapi-devel check-devel cpp \
  cyrus-sasl-devel device-mapper-multipath-devel glib2-devel glusterfs-api-devel glusterfs-devel gnutls-devel \
  iasl libaio-devel libattr-devel libcacard-devel libcap-ng-devel libcurl-devel libgcrypt-devel libiscsi-devel \
  libpmem-devel libpng-devel librados-devel librbd-devel libseccomp-devel libssh-devel libtool libusbx-devel \
  libuuid-devel lzo-devel ncurses-devel nss-devel meson ninja-build libepoxy libepoxy-devel libdrm libdrm-devel \
  mesa-libgbm-devel libxkbcommon-devel

此时编译前的环境已经准备就绪了, 但因为 RedHat 在构建 qemu 时移除了一些虚拟设备, 因此我们还需要进一步修改刚刚得到的 $HOME/rpmbuild 目录下的问题. 具体修改的文件为 $HOME/rpmbuild/SOURCES/0006-Enable-disable-devices-for-RHEL.patch, 主要是添加以下内容:

CONFIG_APPLESMC=y
CONFIG_VMXNET3_PCI=y

这里在增加 APPLESMC 支持以外还同时增加了 vmxnet3 虚拟网卡设备主要原因在于相较于 E1000/E1000E 而言 vmxnet3 可以提供更好的性能, 不使用 virtio 的原因则是 macOS 当前对 virtio 的支持还不太确定, 而 vmxnet3 则是官方支持的. 另外由于我们是直接修改 patch 文件, 因此需要遵守 patch 文件格式规范, 以上只是主要修改的内容. 如果修改没有问题此时我们执行以下命令进行编译:

cd $HOME/rpmbuild
rpmbuild --nocheck --define "debug_package %{nil}" -bb SPECS/qemu-kvm.spec

以上命令通过选项禁用了编译后的 test 以及 debuginfo 包的编译以便节约时间. 如果没有意外的话以上命令正常结束后会在 $HOME/rpmbuild/RPMS/$(uname -m) 目录下生成所有编译好的 rpm 包. 对于该目录下的 RPM 包我们可以选择性的安装也可以全部安装:

cd  $HOME/rpmbuild/RPMS/$(uname -m)
yum localinstall -y ./*.rpm

也可将以上 RPM 包分发到其他 CentOS 8 的机器上安装.

补充

在完成 RPM 编译后最好先确认下是否已将 applevmxnet3 设备的支持编译进去:

export QEMU_VERSION="6.0.0"
cd $HOME/rpmbuild
BUILD/qemu-${QEMU_VERSION}/qemu_kvm_build/qemu-system-x86_64 -device help | egrep -i "apple|vmxnet3"

如果没有问题的话会看到包含 isa-applesmcvmxnet3 的两行输出.

另外一个问题是当前编译后的 RPM 包内没有包含 vmxnet3rom 文件, 因此需要收到拷贝一下:

export QEMU_VERSION="6.0.0"
cd $HOME/rpmbuild
cp BUILD/qemu-${QEMU_VERSION}/pc-bios/efi-vmxnet3.rom /usr/share/qemu-kvm

此时在创建虚拟机时便可指定网卡类型为 vmxnet3 了.

再补充

对于以上需要修改 patch 文件的步骤可以直接使用以下命令:

export PATCH_FILE="/tmp/devices-$(uuidgen).patch"
cd $HOME/rpmbuild
cat > "${PATCH_FILE}" << EOF
--- SOURCES/0006-Enable-disable-devices-for-RHEL.patch	2021-09-17 21:43:45.000000000 +0800
+++ 0006-Enable-disable-devices-for-RHEL.patch	2023-04-07 18:05:42.535508928 +0800
@@ -319,7 +319,7 @@
 index 0000000000..9f41400530
 --- /dev/null
 +++ b/default-configs/devices/x86_64-rh-devices.mak
-@@ -0,0 +1,104 @@
+@@ -0,0 +1,106 @@
 +include rh-virtio.mak
 +
 +CONFIG_AC97=y
@@ -424,6 +424,8 @@
 +CONFIG_TPM_TIS_ISA=y
 +CONFIG_TPM_EMULATOR=y
 +CONFIG_TPM_PASSTHROUGH=y
++CONFIG_APPLESMC=y
++CONFIG_VMXNET3_PCI=y
 diff --git a/default-configs/devices/x86_64-softmmu.mak b/default-configs/devices/x86_64-softmmu.mak
 index 64b2ee2960..e57bcff7d9 100644
 --- a/default-configs/devices/x86_64-softmmu.mak
EOF

patch SOURCES/0006-Enable-disable-devices-for-RHEL.patch "${PATCH_FILE}"