ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

从零制作openEuler 22.03 LTS OpenStack云镜像:原理、工具与最佳实践

2026/8/13 10:07:15 拓冰建站 浏览量
从零制作openEuler 22.03 LTS OpenStack云镜像:原理、工具与最佳实践

1. 项目缘起:为什么要在openEuler上折腾OpenStack镜像?

最近在给一个内部私有云平台做技术栈升级,底层虚拟化平台从老旧的KVM管理工具切换到了OpenStack。平台选型定了,虚拟机的操作系统镜像就成了第一个要啃的硬骨头。客户那边对安全性和可控性要求极高,指定了要使用国产的openEuler 22.03 LTS作为基础操作系统。市面上现成的、针对OpenStack优化好的openEuler镜像几乎没有,就算有,也不敢直接用——谁知道里面有没有夹带私货,或者配置不符合我们的安全基线?所以,自己动手,从零开始制作一个干净、合规、高性能的OpenStack镜像,就成了必须完成的任务。

这件事听起来就是qemu-img转个格式,但真做起来,坑是一个接一个。镜像做出来能启动只是第一步,更重要的是要让它能在OpenStack环境下“活得舒服”:云初始化(cloud-init)能不能正确注入主机名、密钥?磁盘驱动和网卡驱动是不是最优性能?镜像尺寸是不是够精简,上传到Glance会不会太慢?这些细节,直接关系到后续成百上千台虚拟机的创建效率和运行稳定性。今天,我就把这次从零制作openEuler 22.03 OpenStack镜像的完整过程、踩过的坑以及最终打磨出的最佳实践,毫无保留地分享出来。无论你是刚开始接触OpenStack的运维,还是需要为特定发行版定制镜像的开发者,这篇指南都能让你少走弯路。

2. 核心原理与工具链选型:镜像不只是“一个文件”

在动手之前,我们必须搞清楚OpenStack镜像到底是什么,以及为什么不能直接用从官网下载的ISO。OpenStack期望的镜像,通常是一个包含可启动操作系统、并预装了特定“云环境”代理程序的磁盘文件。它的核心目标是实现虚拟机的自动化初始化。

2.1 OpenStack镜像的核心要求

一个合格的OpenStack镜像,至少需要满足以下几个条件:

  1. 文件格式: 必须是QCOW2(QEMU Copy-On-Write)格式。这是OpenStack Glance镜像服务最推荐、也是性能最好的格式。它支持稀疏文件(节省存储空间)、快照链以及后端存储(如Ceph)的高级特性。
  2. 云初始化支持: 这是灵魂所在。虚拟机首次启动时,OpenStack Nova(计算服务)会通过配置驱动(ConfigDrive)或者元数据服务(Metadata Service)将实例的元数据(如主机名、IP地址、SSH公钥、用户数据脚本等)传递给虚拟机。镜像内部必须有一个服务(通常是cloud-init)来接收并处理这些数据,完成系统的个性化配置。没有它,创建出来的所有虚拟机都一个样,无法自动化。
  3. 虚拟化驱动优化: 为了在KVM虚拟化环境下获得最佳I/O性能,镜像内应该使用半虚拟化驱动(VirtIO)。这包括:
    • 磁盘驱动: 使用virtio_blkvirtio_scsi,而不是模拟的IDE或SATA控制器。
    • 网卡驱动: 使用virtio_net,而不是模拟的e1000或rtl8139。
    • 气球驱动(可选)virtio_balloon用于动态调整内存。
    • 串口控制台: 配置好ttyS0(即VirtIO控制台),这样我们才能通过OpenStack的“控制台”功能看到虚拟机的启动日志,这在排错时至关重要。
  4. SSH服务器与安全配置: 默认启用SSH服务(sshd),并且通常建议禁用密码登录,仅允许密钥认证,这符合云环境的安全最佳实践。
  5. 精简与通用性: 镜像应该尽可能小,删除不必要的软件包、缓存和日志,以加快上传和下载速度。同时,它应该是一个“通用”镜像,不包含特定于某台机器的配置(如固定的IP地址、主机名、网络管理器持久化配置等)。

2.2 我们的工具链选择与理由

基于以上要求,我们选择了一套经典且稳定的工具链:

  • 虚拟化平台: QEMU/KVM。这是Linux下事实标准的虚拟化方案,与OpenStack底层完全一致,能保证最大的兼容性。
  • 系统安装: 使用官方openEuler 22.03 LTS的ISO文件,通过virt-install命令进行自动化、无交互的安装。这比手动在图形界面点击要可靠和可重复得多。
  • 镜像操作: 核心工具是qemu-img,用于创建、转换和调整镜像文件。virt-customize(来自libguestfs-tools套件)是一个神器,它可以在不启动虚拟机的情况下,直接挂载镜像文件并向其中注入文件、安装软件包、运行脚本,极大地提高了定制效率。
  • 云初始化: openEuler 22.03 官方仓库已经提供了cloud-init包,我们直接安装并配置即可。

为什么不使用Docker或chroot?因为它们无法处理内核安装、引导加载程序(GRUB2)配置等需要完整虚拟硬件环境才能完成的操作。QEMU虚拟机是最接近生产环境的沙箱。

3. 实战第一步:准备构建环境与自动化安装脚本

构建环境最好是一台干净的、安装了KVM的Linux主机(可以是物理机,也可以是一台性能足够的虚拟机)。我这里使用的是另一台openEuler 22.03的宿主机。

3.1 环境准备与依赖安装

首先,确保构建主机满足基本条件,并安装所有必要的工具。

# 1. 检查CPU是否支持虚拟化(对于物理机) egrep -c '(vmx|svm)' /proc/cpuinfo # 输出大于0即可 # 2. 安装KVM及相关管理工具 sudo dnf install -y qemu-kvm libvirt virt-install libvirt-client libvirt-daemon virt-manager virt-viewer # 3. 启动libvirtd服务并设置开机自启 sudo systemctl enable --now libvirtd # 4. 安装镜像操作神器 libguestfs-tools # 它提供了 virt-customize, virt-sysprep, virt-resize 等命令 sudo dnf install -y libguestfs-tools # 5. 下载 openEuler 22.03 LTS 官方ISO # 建议从国内镜像站下载,速度更快 wget https://repo.openeuler.org/openEuler-22.03-LTS/ISO/x86_64/openEuler-22.03-LTS-x86_64-dvd.iso # 下载完成后,验证一下SHA256校验和,确保文件完整

3.2 编写自动化安装的Kickstart文件

手动安装无法保证一致性,我们必须使用Kickstart实现无人值守安装。创建一个名为openeuler-ks.cfg的文件:

# 文件名:openeuler-ks.cfg # 语言和键盘 lang en_US.UTF-8 keyboard us # 时区 timezone Asia/Shanghai --utc # 根密码,这里仅为示例,生产环境应使用加密密码或后续通过cloud-init修改 rootpw --plaintext a_strong_password_should_be_changed # 禁用首次启动的配置向导(非常重要!) firstboot --disable # 使用文本模式安装(无图形界面) text # 跳过安装介质检查,加快速度 cdrom # 清除所有分区并初始化磁盘 zerombr clearpart --all --initlabel # 使用自动分区方案,创建一个LVM卷组 autopart --type=lvm # 引导加载程序安装位置 bootloader --location=mbr --boot-drive=vda # 网络配置:安装时启用网卡,但使用DHCP。最终网络由cloud-init管理。 network --onboot=on --device=link --bootproto=dhcp --noipv6 # 防火墙和SELinux配置(根据需求调整,云镜像通常先放宽松) firewall --disabled selinux --disabled # 软件包选择:安装最小化服务器环境,并包含“标准系统工具”组,确保有ifconfig等命令 %packages --ignoremissing @^minimal-environment @standard %end # 安装后脚本:这里可以执行一些最基础的设置 %post --log=/root/ks-post.log # 禁用不必要的服务,加快启动 systemctl disable firewalld systemctl disable tuned # 确保网络服务启用(使用NetworkManager) systemctl enable NetworkManager # 清理安装缓存,减小镜像体积 dnf clean all rm -rf /var/cache/dnf/* # 创建一个标志文件,便于后续识别 touch /.build-by-ks %end

这个Kickstart文件做了几件关键事:1) 最小化安装;2) 使用LVM自动分区(更灵活);3) 禁用首次启动向导和防火墙(由cloud-init接管);4) 清理缓存。注意:这里的根密码是明文的,仅用于构建阶段。最终我们会通过cloud-init禁用密码登录,所以问题不大,但最好在构建完成后立即修改或使用加密密码。

3.3 使用virt-install启动自动化安装

现在,我们使用virt-install命令,基于Kickstart文件启动一个临时的虚拟机来完成安装。

# 创建一个20GB的QCow2格式的原始磁盘镜像 qemu-img create -f qcow2 /var/lib/libvirt/images/openeuler-raw.qcow2 20G # 使用virt-install启动安装过程 sudo virt-install \ --name=openeuler-builder \ --vcpus=2 \ --memory=4096 \ --disk path=/var/lib/libvirt/images/openeuler-raw.qcow2,format=qcow2,size=20,bus=virtio \ --network network=default,model=virtio \ --graphics none \ --console pty,target_type=serial \ --location=/path/to/your/openEuler-22.03-LTS-x86_64-dvd.iso \ --extra-args="inst.ks=file:///run/install/openeuler-ks.cfg console=ttyS0,115200n8" \ --initrd-inject=./openeuler-ks.cfg \ --noautoconsole

参数拆解与避坑点:

  • --graphics none--console pty: 我们使用串口控制台,这是无头(headless)服务器安装的标准做法,也方便我们通过virsh console命令查看安装日志。
  • --location: 指定ISO文件路径。这里使用--location而非--cdrom,是为了配合--extra-args向安装内核传递参数。
  • --extra-args: 这是核心。inst.ks指定了Kickstart文件的位置(我们通过--initrd-inject将其注入到了虚拟机的初始内存盘initrd中)。console=ttyS0,115200n8设置了内核的串口控制台参数,必须与--console选项匹配。
  • --initrd-inject: 将Kickstart文件放入虚拟机的initrd,使其在安装早期即可被访问。
  • --noautoconsole: 不自动连接控制台,因为安装是自动的。

命令执行后,虚拟机开始启动并自动安装。你可以通过以下命令观察安装进度:

sudo virsh console openeuler-builder

安装完成后,虚拟机会自动关闭。此时,原始的openeuler-raw.qcow2镜像已经包含了一个最小化的、可启动的openEuler系统。

4. 镜像定制与“云化”改造

得到一个原始系统镜像只是开始,接下来才是让它蜕变为“云镜像”的关键步骤。我们将使用virt-customize在不启动虚拟机的情况下,直接对镜像文件进行手术刀式的修改。

4.1 安装cloud-init并完成基础配置

首先,我们需要在镜像里安装cloud-init,并对其进行正确配置,使其能够从OpenStack获取元数据。

# 使用 virt-customize 安装 cloud-init 并进行初步配置 sudo virt-customize -a /var/lib/libvirt/images/openeuler-raw.qcow2 \ --run-command 'dnf install -y cloud-init cloud-utils-growpart' \ --run-command 'systemctl enable cloud-init-local cloud-init cloud-config cloud-final' \ --mkdir /var/lib/cloud/seed/nocloud \ --copy-in ./99-disable-network-config.cfg:/etc/cloud/cloud.cfg.d/ \ --run-command 'ln -sf /dev/null /etc/systemd/network/99-default.link'

这里有几个关键操作:

  1. 安装软件包: 除了cloud-init,还安装了cloud-utils-growpart,这个工具允许系统在启动时自动扩展根分区以填满虚拟磁盘,对于按需分配磁盘的云环境非常有用。
  2. 启用服务: 确保cloud-init的各个阶段服务都开机自启。
  3. 创建种子目录: 虽然OpenStack通常用ConfigDrive或元数据服务,但创建这个目录是良好实践。
  4. 注入自定义配置: 这是第一个大坑。OpenStack的网络配置能力很强(通过Neutron),我们不应该让cloud-init再去尝试配置网络,否则容易冲突。我们创建一个配置文件99-disable-network-config.cfg,内容如下:
    # 文件名:99-disable-network-config.cfg network: config: disabled
    这个配置告诉cloud-init:“不要动网络配置”,网络交给OpenStack的Neutron和实例内部的NetworkManager或systemd-networkd来处理。
  5. 处理默认链路规则: 删除或屏蔽99-default.link,防止systemd根据网卡MAC地址重命名网卡(例如将eth0重命名为ens3),确保网卡名称在OpenStack中保持稳定(通常是eth0)。

4.2 配置SSH与安全加固

云环境安全至关重要,默认的密码SSH登录是巨大的风险。

sudo virt-customize -a /var/lib/libvirt/images/openeuler-raw.qcow2 \ --run-command "sed -i 's/^#*PasswordAuthentication.*/PasswordAuthentication no/' /etc/ssh/sshd_config" \ --run-command "sed -i 's/^#*PermitRootLogin.*/PermitRootLogin prohibit-password/' /etc/ssh/sshd_config" \ --run-command 'echo \"AuthorizedKeysFile .ssh/authorized_keys\" >> /etc/ssh/sshd_config' \ --run-command 'systemctl enable sshd'
  • PasswordAuthentication no: 彻底禁用密码认证,强制使用密钥。
  • PermitRootLogin prohibit-password: 允许root用户通过密钥登录(根据你的安全策略,也可以设置为no完全禁止root登录,通过普通用户sudo)。
  • 确保AuthorizedKeysFile配置正确,cloud-init会将OpenStack中注入的SSH公钥写入这个文件。

4.3 优化内核与驱动,适配虚拟化环境

为了让虚拟机在KVM下跑得更快,我们需要确保使用VirtIO驱动,并优化内核参数。

sudo virt-customize -a /var/lib/libvirt/images/openeuler-raw.qcow2 \ --run-command 'dnf install -y kernel kernel-devel acpid' \ --run-command 'grub2-mkconfig -o /boot/grub2/grub.cfg' \ --run-command 'dracut -f /boot/initramfs-$(uname -r).img $(uname -r)' \ --run-command 'systemctl enable acpid'
  • 确保安装了与当前内核版本对应的kernel-devel,有些驱动编译可能需要。
  • 重新生成GRUB配置和initramfs镜像,确保新的内核和驱动被正确包含。
  • 安装并启用acpid(高级电源管理接口守护进程),这样在OpenStack界面上执行“关机”操作时,虚拟机可以正常响应ACPI信号,而不是被强制断电。

关于驱动的一个深坑: openEuler 22.03 的内核默认已经包含了virtio_net,virtio_blk,virtio_pci,virtio_console等驱动。但为了万无一失,特别是确保控制台(ttyS0)可用,我们需要检查并修改引导参数。

# 检查当前的GRUB命令行参数 sudo virt-customize -a /var/lib/libvirt/images/openeuler-raw.qcow2 \ --run-command 'cat /proc/cmdline' # 输出可能类似:BOOT_IMAGE=/vmlinuz-5.10.0-xxx root=/dev/mapper/openeuler-root ro rhgb quiet # 我们需要修改GRUB配置,添加串口控制台参数 sudo virt-customize -a /var/lib/libvirt/images/openeuler-raw.qcow2 \ --run-command "grubby --update-kernel=ALL --args='console=ttyS0,115200n8'" \ --run-command "grubby --update-kernel=ALL --remove-args='rhgb quiet'"
  • grubby是一个很好的修改GRUB参数的工具。我们为所有内核添加console=ttyS0,115200n8,这样内核日志就会输出到串口,OpenStack的控制台才能看到。
  • 移除了rhgb(图形化启动)和quiet(安静模式),让启动日志更详细,便于调试。

4.4 系统清理与通用化(Sysprep)

这是制作镜像的最后一步,也是最容易出问题的一步。目的是移除所有实例特有的信息,让镜像变成一个“模板”。

# 使用 virt-sysprep 进行通用化处理 sudo virt-sysprep -a /var/lib/libvirt/images/openeuler-raw.qcow2 \ --operations customize,hostname,logfiles,machine-id,net-hostname,net-hwaddr,ssh-hostkeys,udev-persistent-net,tmp-files,utmp \ --hostname localhost.localdomain \ --run-command 'truncate -s 0 /etc/machine-id' \ --run-command 'rm -f /var/lib/dbus/machine-id && ln -s /etc/machine-id /var/lib/dbus/machine-id' \ --run-command 'rm -f /etc/ssh/ssh_host_*' \ --run-command 'rm -rf /tmp/* /var/tmp/*' \ --run-command 'rm -f /var/log/*.log /var/log/*.log-*' \ --run-command 'dnf clean all && rm -rf /var/cache/dnf/*'

每一步的操作意图和风险:

  • --operations: 指定要执行的操作集合。customize是自定义脚本,hostname重置主机名,logfiles清空日志,machine-idnet-hwaddr重中之重
  • machine-id陷阱: Linux系统的/etc/machine-id必须是唯一的。如果镜像里保留了一个固定的ID,那么所有从这个镜像启动的虚拟机都会有相同的ID,会导致很多依赖此ID的服务(如Docker、Journal日志)出现冲突。我们必须清空它(truncate -s 0),系统在首次启动时会生成一个新的。
  • 网络硬件地址(MAC)net-hwaddr操作会删除/etc/udev/rules.d/70-persistent-net.rules等文件中记录的旧MAC地址与网卡名的绑定规则,防止新虚拟机继承旧的网卡名绑定。
  • SSH主机密钥: 删除ssh_host_*密钥,每个实例在首次启动sshd服务时会自动生成独一无二的密钥,这是安全必需。
  • 清理工作: 清空临时文件、日志和DNF缓存,进一步缩小镜像体积。

警告virt-sysprep--operations参数顺序很重要,且有些操作有依赖关系。务必先阅读man virt-sysprep。我强烈建议在操作前先对镜像做一个备份,因为sysprep是不可逆的。

5. 最终打磨:压缩、转换与验证

经过sysprep的镜像已经是一个“通用”镜像了,但我们可以做得更好。

5.1 压缩与转换格式

原始的QCOW2文件可能因为稀疏文件特性,在宿主机上看起来不大,但直接上传可能效率不高。我们进行压缩和优化。

# 1. 使用 virt-sparsify 进行“稀疏化”处理,回收镜像内部的未使用空间 # 这步需要额外磁盘空间,因为它会创建一个新的镜像文件 sudo virt-sparsify --compress /var/lib/libvirt/images/openeuler-raw.qcow2 /var/lib/libvirt/images/openeuler-sparse.qcow2 # 2. 检查压缩后的镜像信息 qemu-img info /var/lib/libvirt/images/openeuler-sparse.qcow2 # 关注 `virtual size` (虚拟大小,如20G) 和 `disk size` (实际占用物理空间,可能只有1-2G) # 3. (可选但推荐)将镜像转换为“流优化”格式 # 这种格式在上传到Glance时,支持直接流式传输,无需先下载整个镜像到Glance节点,速度更快。 qemu-img convert -f qcow2 -O qcow2 -c -o compat=0.10 /var/lib/libvirt/images/openeuler-sparse.qcow2 /var/lib/libvirt/images/openeuler-22.03-openstack.qcow2
  • virt-sparsify: 它会读取镜像文件系统,将全零块或已删除文件占用的块标记为“稀疏”,从而减小物理文件大小。
  • qemu-img convert
    • -c: 进行压缩,进一步减小文件体积。
    • -o compat=0.10: 指定QCOW2版本为较老的0.10,以获得更好的兼容性(一些老版本的OpenStack或QEMU可能不支持更高的版本)。
    • 最终得到的openeuler-22.03-openstack.qcow2就是我们准备上传的镜像。

5.2 本地验证镜像

在投入生产环境前,强烈建议在本地启动一次这个最终镜像,验证cloud-init和基本功能。

# 创建一个验证用的临时镜像,避免污染原镜像 cp /var/lib/libvirt/images/openeuler-22.03-openstack.qcow2 /var/lib/libvirt/images/openeuler-test.qcow2 # 创建一个简单的cloud-init元数据文件 cat > /tmp/meta-data << EOF instance-id: test-instance-001 local-hostname: test-vm EOF cat > /tmp/user-data << EOF #cloud-config password: changeme chpasswd: { expire: False } ssh_pwauth: True EOF # 创建一个包含元数据和用户数据的ISO文件,模拟OpenStack的ConfigDrive genisoimage -output /tmp/config-drive.iso -volid cidata -joliet -rock /tmp/meta-data /tmp/user-data # 启动测试虚拟机 sudo virt-install \ --name=openeuler-test \ --vcpus=1 \ --memory=1024 \ --disk path=/var/lib/libvirt/images/openeuler-test.qcow2,format=qcow2,bus=virtio \ --disk path=/tmp/config-drive.iso,device=cdrom \ --network network=default,model=virtio \ --graphics none \ --console pty,target_type=serial \ --import \ --noautoconsole # 连接控制台,观察启动过程 sudo virsh console openeuler-test

在控制台日志中,你应该能看到cloud-init在运行,并应用你的user-data(例如设置密码)。登录后,检查:

  • 主机名是否变成了test-vm
  • df -h查看根分区是否自动扩展到了整个虚拟磁盘?
  • ip addr查看网卡是否正常获取了IP(来自libvirt的default网络)?
  • /var/log/cloud-init.log是否有错误?

如果一切正常,恭喜你,一个高质量的openEuler 22.03 OpenStack镜像就制作完成了。

6. 上传至OpenStack Glance及后续优化建议

最后一步就是将镜像上传到你的OpenStack环境中。

# 使用OpenStack CLI命令上传 openstack image create \ --file /var/lib/libvirt/images/openeuler-22.03-openstack.qcow2 \ --disk-format qcow2 \ --container-format bare \ --property hw_disk_bus=virtio \ --property hw_vif_model=virtio \ --min-disk 20 \ --min-ram 1024 \ --public \ "openEuler 22.03 LTS Cloud"

参数解释:

  • --min-disk--min-ram: 设置实例启动所需的最小资源,引导用户合理选择规格。
  • --property: 设置镜像属性,强制使用VirtIO驱动,确保性能。
  • --public: 根据你的权限,可以设置为--shared--private

上传完成后,在Horizon界面或通过openstack image list查看镜像状态,确认状态为active后,就可以用它来启动实例了。

一些进阶优化建议:

  • 使用Packer自动化: 上述步骤可以完全用HashiCorp Packer编写成模板,实现一键构建、版本化管理,这是团队协作和CI/CD的最佳实践。
  • 集成安全基线: 在%post阶段或virt-customize时,运行安全合规脚本(如根据等保要求配置密码策略、审计规则等)。
  • 预装监控代理: 如果公司统一使用Prometheus Node Exporter、Zabbix Agent等,可以预装并配置好,但注意不要写入具体的服务器地址,可以通过cloud-inituser-data在启动时注入配置。
  • 制作不同规格镜像: 可以制作“最小化”、“开发版”、“桌面版”等不同软件包集合的镜像,满足不同场景需求。

制作一个“能用”的镜像不难,但制作一个“好用”、“安全”、“高效”的云镜像,需要对这些细节有深刻的理解和把控。整个过程就像打磨一件工艺品,每一步的精心处理,最终都会在成百上千台虚拟机的稳定运行中得到回报。希望这份超详细的指南能帮你避开我踩过的所有坑,顺利打造出属于你自己的完美openEuler云镜像。