移动云私有镜像制作 ISO镜像转qcow2
0x00 前言
软件商要求用 openEuler 22.03 LTS x86_64,最小化安装,只勾选 Standard,系统盘用 LVM+XFS。手上只有 ISO,就在 Mac 上用 QEMU 装了一台,再封装成移动云能导入的 QCOW2。
所谓 ISO 转 QCOW2,其实就是先创建虚拟硬盘,用 ISO 把系统装进去,配置好驱动再导出。直接 qemu-img convert install.iso 得到的还是安装光盘内容。
我这里的环境:
制作环境:ARM Mac+OrbStack / Docker+QEMU TCG
系统版本:openEuler 22.03 LTS 原始版 x86_64
安装环境:Minimal Install+Standard
启动方式:BIOS+MBR
系统盘:20GiB,/boot 1GiB,其余给 LVM 根分区,均为 XFS
语言时区:en_US.UTF-8,Asia/Shanghai
ARM Mac 模拟 x86 安装比较慢,尤其碰到编译。经常做的话,还是找台 x86 Linux 用 KVM 省事。
0x01 准备工具
QEMU 放在 Docker 里跑。新建目录,把 ISO 放进去,统一命名为 install.iso。
mkdir -p ~/Documents/ecloud-image
cd ~/Documents/ecloud-image
cp ~/Downloads/openEuler-22.03-LTS-x86_64-dvd.iso ./install.iso
Dockerfile:
FROM ubuntu:24.04
ENV DEBIAN_FRONTEND=noninteractive
RUN sed -i \
's|http://ports.ubuntu.com/ubuntu-ports|http://mirrors.aliyun.com/ubuntu-ports|g' \
/etc/apt/sources.list.d/ubuntu.sources \
&& apt-get update \
&& apt-get install -y --no-install-recommends \
qemu-system-x86 qemu-utils genisoimage python3 \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /work
构建并进入容器,后面的 QEMU 命令在容器里执行,/work 就是 Mac 上的这个目录。
docker build -t ecloud-image-builder:local .
docker run --rm -it \
--mount "type=bind,source=$PWD,target=/work" \
ecloud-image-builder:local bash
0x02 安装系统
创建硬盘
qemu-img create -f qcow2 /work/system.qcow2 20G
配置 Kickstart
我这里用 Kickstart 自动安装,ks.cfg 如下。ROOT_PASSWORD_HASH 换成自己生成的 SHA-512 crypt 哈希。
text
cdrom
lang en_US.UTF-8
keyboard us
timezone Asia/Shanghai --isUtc
network --bootproto=dhcp --device=link --activate --onboot=yes
rootpw --iscrypted ROOT_PASSWORD_HASH
firstboot --disable
selinux --enforcing
firewall --enabled --service=ssh
services --enabled=sshd,NetworkManager,chronyd,auditd
ignoredisk --only-use=sda
zerombr
clearpart --all --initlabel --disklabel=msdos --drives=sda
part /boot --fstype=xfs --size=1024 --ondisk=sda
part pv.01 --fstype=lvmpv --size=8192 --grow --ondisk=sda
volgroup openeuler --pesize=4096 pv.01
logvol / --fstype=xfs --name=root --vgname=openeuler --size=8192 --grow
bootloader --location=mbr --boot-drive=sda --append="console=tty0 console=ttyS0,115200n8 net.ifnames=0 biosdevname=0 rd.multipath=0"
poweroff
%packages --excludedocs
@core
@standard
cloud-init
qemu-guest-agent
openssh-server
NetworkManager
chrony
acpid
irqbalance
gdisk
parted
python3
lvm2
%end
@core 对应这份 ISO 的最小化安装。--grow 是安装时用满系统盘,不是云主机开机自动扩容。清盘配置只针对这台制作虚拟机的新空盘,不要挂数据盘。
生成配置光盘,提取安装器内核:
mkdir -p /work/payload
cp /work/ks.cfg /work/payload/ks.cfg
genisoimage -quiet -R -J -V ECLOUDKS \
-o /work/kickstart.iso /work/payload
isoinfo -R -i /work/install.iso \
-x /images/pxeboot/vmlinuz > /work/vmlinuz
isoinfo -R -i /work/install.iso \
-x /images/pxeboot/initrd.img > /work/initrd.img
启动安装
把安装、启动共用的参数写成一个函数,4 核、6GiB 内存,磁盘和网卡都走 VirtIO:
run_vm() {
qemu-system-x86_64 \
-machine q35 -accel tcg,thread=multi \
-cpu max -smp 4 -m 6144 \
-display none -serial mon:stdio -no-reboot \
-device virtio-scsi-pci,id=scsi0 \
-drive file=/work/system.qcow2,format=qcow2,if=none,id=os,discard=unmap \
-device scsi-hd,drive=os,bus=scsi0.0 \
-netdev user,id=net0 -device virtio-net-pci,netdev=net0 \
-device virtio-rng-pci \
-device virtio-serial-pci \
-chardev socket,path=/tmp/qga.sock,server=on,wait=off,id=qga0 \
-device virtserialport,chardev=qga0,name=org.qemu.guest_agent.0 \
"$@"
}
run_vm \
-drive file=/work/install.iso,media=cdrom,readonly=on \
-drive file=/work/kickstart.iso,media=cdrom,readonly=on \
-kernel /work/vmlinuz -initrd /work/initrd.img \
-append 'inst.stage2=hd:LABEL=openEuler-22.03-LTS-x86_64 inst.ks=hd:LABEL=ECLOUDKS:/ks.cfg inst.text console=ttyS0,115200n8 net.ifnames=0 biosdevname=0'
inst.stage2 的卷标对应这份 ISO,其他版本用 isoinfo -d -i install.iso 核对。安装完成会关机,再从系统盘启动:
run_vm -boot c
0x03 移动云适配
这一节的命令在安装好的 openEuler 里执行,也可以放进 Kickstart 的 %post。
VirtIO
Linux 用内核自带的 VirtIO 驱动,确认驱动进了 initramfs 就行。
/etc/dracut.conf.d/90-ecloud-virtio.conf:
hostonly="no"
add_drivers+=" virtio virtio_ring virtio_pci virtio_blk virtio_scsi virtio_net virtio_console "
add_dracutmodules+=" lvm "
首次从装好的系统启动后执行:
dracut --force "/boot/initramfs-$(uname -r).img" "$(uname -r)"
lsinitrd "/boot/initramfs-$(uname -r).img" | grep virtio
如果驱动编译进内核了,/boot/config-$(uname -r) 中对应配置为 =y,不一定能在模块列表里看到。
网络和 cloud-init
启动参数用了 net.ifnames=0 biosdevname=0,网卡统一为 eth0,配置 DHCP,不绑定旧 MAC。
/etc/sysconfig/network-scripts/ifcfg-eth0:
TYPE=Ethernet
NAME=eth0
DEVICE=eth0
BOOTPROTO=dhcp
ONBOOT=yes
DEFROUTE=yes
IPV4_FAILURE_FATAL=no
IPV6INIT=yes
IPV6_AUTOCONF=yes
/etc/cloud/cloud.cfg.d/90-ecloud.cfg:
datasource_list: [ConfigDrive, Ec2]
disable_root: false
ssh_pwauth: true
preserve_hostname: false
manage_etc_hosts: false
network:
config: disabled
growpart:
mode: off
resize_rootfs: false
system_info:
default_user:
name: root
gecos: root
lock_passwd: false
shell: /bin/bash
网卡交给 NetworkManager,cloud-init 保留,用来接收平台初始化信息。原来的临时网卡配置需要清理掉,loopback 保留。
软件商要求 root 登录,/etc/ssh/sshd_config 里相应配置:
PermitRootLogin yes
PasswordAuthentication yes
PermitEmptyPasswords no
QGA 和改密
保留 ISO 自带的 qemu-guest-agent 包,再从移动云官方文档下载对应系统、架构的 QGA。核对校验值后,替换二进制。
文件先放进目标系统的 /root/ecloud-files/:
install -m 0755 /root/ecloud-files/qemu-ga /usr/bin/qemu-ga
mkdir -p /usr/local/var/run /var/log/qemu-ga
restorecon /usr/bin/qemu-ga
systemctl enable qemu-guest-agent
mkdir -p /var/lib/cloud/scripts/per-boot
install -m 0755 /root/ecloud-files/cloudRun.sh \
/var/lib/cloud/scripts/per-boot/cloudRun.sh
cloudRun.sh 用官方的离线改密脚本。QGA 需要虚拟机提供 org.qemu.guest_agent.0 端口,上面的 QEMU 命令已经加了。
growpart 也保留。我这里单独装了 openEuler 官方签名的 cloud-utils-growpart RPM,没有升级整个系统。cloud-init 的自动扩容已关闭,也不再保留自定义的 ecloud-grow-root.service 和扩容脚本,后面自己手动扩。
0x04 踩坑
multipath 接管系统盘
通用 initramfs 把普通磁盘映射成了 /dev/mapper/0QEMU_...。我这里是单路径云硬盘,直接关掉 multipath:
systemctl disable multipathd.service multipathd.socket
systemctl mask multipathd.service multipathd.socket
grubby --update-kernel=ALL --args='rd.multipath=0'
同时在 /etc/default/grub 的 GRUB_CMDLINE_LINUX 里保留 rd.multipath=0,再生成配置:
grub2-mkconfig -o /boot/grub2/grub.cfg
cloud-init 报 hosts 模板缺失
No hosts template could be found for distro redhat
这份原始版包缺少对应模板,我这里不需要它改 /etc/hosts,设置 manage_etc_hosts: false 后正常。
audit 刷屏
第一版已经在移动云启动了,控制台一直刷:
audit: audit_backlog=65 > audit_backlog_limit=64
audit: backlog limit exceeded
审计队列满了,audit_lost 也在增加。查了 ISO,auditd 默认就是启用的,原镜像本地启动也正常,不是后来额外装的服务。云端具体什么事件把队列打满,还没查到。
保留审计,先把队列调大:
systemctl enable --now auditd
auditctl -b 8192
auditctl -s
持久化:把 /etc/audit/rules.d/ 中已有的 -b 改成 -b 8192,没有就补一行,再加载:
augenrules --load
grubby --update-kernel=ALL --args='audit_backlog_limit=8192 loglevel=3'
audit_backlog_limit=8192 loglevel=3 也加入 /etc/default/grub 的 GRUB_CMDLINE_LINUX,保留其他参数,然后执行 grub2-mkconfig。
/etc/sysctl.d/90-ecloud-console.conf:
kernel.printk = 3 4 1 7
sysctl -p /etc/sysctl.d/90-ecloud-console.conf
这样控制台只输出 critical 及更严重的内核消息,普通错误也会被隐藏,排错时用 dmesg 或 journalctl -k 看。只抑制输出不会解决积压,还要看 lost 有没有继续增加。
0x05 清理和导出
在准备封装的虚拟机里清理机器标识、主机密钥和安装记录,删掉自己放进去的安装包、调试脚本,完成后直接关机。
cloud-init clean --logs --seed
rm -f /etc/ssh/ssh_host_* /var/lib/systemd/random-seed
rm -f /var/lib/dbus/machine-id
ln -s /etc/machine-id /var/lib/dbus/machine-id
: > /etc/machine-id
rm -f /root/anaconda-ks.cfg /root/original-ks.cfg /root/.bash_history
fstrim /
fstrim /boot
sync
systemctl poweroff
等 QEMU 退出,在工具容器里压缩导出:
qemu-img convert -p -f qcow2 -O qcow2 -c \
/work/system.qcow2 /work/openEuler-ecloud-20G.qcow2
qemu-img check /work/openEuler-ecloud-20G.qcow2
qemu-img info --output=json /work/openEuler-ecloud-20G.qcow2
检查通过应该提示 No errors were found on the image.,容量是 20GiB,没有 backing-filename 依赖。Mac 上再生成校验文件:
shasum -a 256 openEuler-ecloud-20G.qcow2 > openEuler-ecloud-20G.sha256
shasum -a 256 -c openEuler-ecloud-20G.sha256
我这里用临时副本做启动测试,检查 BIOS 引导、DHCP、cloud-init、QGA 实际改密和审计写入。又把副本磁盘改成 40GiB 试了一次,根分区仍然约 19GiB,没有自动扩容;审计队列为 8192,lost 0。
最后文件约 946MiB,虚拟盘 20GiB。ISO 的 3.4GiB 包含离线安装包,和最终系统盘文件大小没有直接关系。
上传目标地域的 EOS 桶,在 IMS 导入系统盘镜像,选择 QCOW2、x86_64、BIOS,系统盘至少 20GiB,具体按资源池下限选。新版 20GiB 通过了本地验证,云端还要再试启动、联网和控制台改密。
参考
macos cloud-init 移动云 openEuler qcow2 QEMU