制作开箱即用的 Ubuntu qcow2 镜像

最近负责的一个组件需要针对 Ubuntu 系统做兼容性适配,由于组件运行时会修改系统配置并写入大量文件,有必要使用虚拟机来开发测试。但通常开发使用的 Linux 发行版都是 RedHat 系的,找遍公司也没有能直接用的 Ubuntu 虚拟机镜像,最后自己手搓了一个,过程记录如下。

下载所需版本的 cloud image

首先需要确定所使用的 Ubuntu 版本,然后从官方镜像列表中下载相应的云主机镜像,云主机镜像预装了 cloud-init,可以更方便地运行在 openstack 等云平台中。

在镜像列表页面,需要根据发布频率(如 Release、Daily builds)、镜像大小(完整包和 Minimal)以及 Ubuntu 版本选择合适的镜像,在镜像下载页面有许多不同类型的文件,如校验和文件、manifest 以及不同架构的镜像,我们只需要文件后缀为 .img 的 QEMU qcow2 镜像。``

本示例将选择 X86 架构的 20.04-lts 版本,最终所下载的镜像为 ubuntu-20.04-server-cloudimg-amd64.img

使用 wget 命令将其下载到本地:

1
wget https://cloud-images.ubuntu.com/releases/focal/release-20211129/ubuntu-20.04-server-cloudimg-amd64.img

cloud-init

cloud-init 是用于跨平台初始化云主机实例的行业标准方法。它支持所有的主要公有云供应商、私有云配置系统和裸机安装。

有了 cloud-init,只需要提供系统镜像和规定的元数据(以及可选的用户数据和供应商数据)即可初始化一个云主机实例,在启动过程中,cloud-init 将读取所提供的元数据,识别它所运行的平台并完成相应的系统初始化步骤。初始化过程涉及设置网络和存储设备,配置 SSH 访问密钥及其他系统配置,还可将可选的用户或供应商数据传递给实例。

cloud-init 大幅简化了云主机的复杂配置过程,只需要编写一个统一的配置文件,就可以在不同的云平台创建出相同规格的主机实例。

配置模板镜像

下载的初始镜像并不能直接上传到云主机平台使用,否则创建的虚拟机无法通过 SSH 登录,会提示如下类似错误(SSH 用户只能使用镜像默认的 ubuntu ):

1
ubuntu@10.10.40.125: Permission denied (publickey).

下面需要使用一些虚拟机管理工具,基于模板镜像运行一个虚拟机,在虚拟机中完成所需配置,最终将模板镜像修改至可直接使用的镜像。

安装虚拟机管理工具

以下安装命令基于 CentOS,其他发行版本也可通过对应的包管理系统下载所需工具:

1
$ yum install -y libvirt-client cloud-utils virt-install libguestfs-tools

创建模板镜像并配置磁盘大小

基于初始镜像创建模板镜像,并命名为 root-disk.qcow2

1
$ qemu-img convert -f qcow2 -O qcow2 ubuntu-20.04-server-cloudimg-amd64.img root-disk.qcow2

根据需要,设置基于该模板镜像所创建的虚拟机磁盘大小,示例设置为 50G:

1
$ qemu-img resize root-disk.qcow2 50G

准备 cloud-init 配置

假设将使用的默认主机名和密码如下:

1
2
$ VM_NAME="ubuntu-vm"
$ PASSWORD="thisIsMyPassword"

用于测试开发的虚拟机,可配置为直接使用 root 用户并以密码登录,但切勿用于生产用途。生产环境镜像需使用普通用户加 sudo 权限以及 public key 等更安全的登录途径。

接下来创建一个 cloud-init 的配置文件,并在其中定义主机名和密码等:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
$ echo "#cloud-config
system_info:
  default_user:
    name: ubuntu
    home: /home/ubuntu

password: $PASSWORD
chpasswd: { expire: False }
hostname: $VM_NAME

# 配置 sshd 允许使用密码登录
ssh_pwauth: True
" | tee cloud-init.cfg

如果还需要对主机做更多配置,可参考 cloud-init 的文档示例:

Cloud config examples - cloud-init 21.4 documentation

然后使用 cloud-localds 基于配置文件创建 ISO 镜像:

1
$ cloud-localds cloud-init.iso cloud-init.cfg

基于模板镜像以及配置镜像安装虚拟机:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
$ virt-install \
  --name $VM_NAME \
  --memory 1024 \
  --disk root-disk.qcow2,device=disk,bus=virtio \
  --disk cloud-init.iso,device=cdrom \
  --os-type linux \
  --os-variant ubuntu20.04 \
  --virt-type kvm \
  --graphics none \
  --network network=default,model=virtio \
  --import

命令运行成功后会进入一个新的终端会话,在自动执行一系列的初始化操作之后,将可以用上文设置的用户名和密码登录所创建的虚拟机。

初始化虚拟机

进入运行中的虚拟机后,我们希望进行一些与登录相关的初始化配置。以下操作以 Ubuntu 20.04 为例,其他版本可能有所区别。

允许以 root 用户登录

Ubuntu 20.04 默认的 SSH 配置不允许以 root 用户登录,我们需要修改 /etc/ssh/sshd_config 文件将 PermitRootLogin prohibit-password 配置更改为 PermitRootLogin yes

1
$ sed -i 's/#PermitRootLogin prohibit-password/PermitRootLogin yes/' /etc/ssh/sshd_config

重启 SSH 服务:

1
$ systemctl restart ssh

设置 root 用户密码

默认情况下 Ubuntu 20.04 未设置 root 用户密码,因此无法使用 root 用户登录系统,需要通过 passwd 命令初始化 root 用户的密码:

1
$ passwd root

密码设置完毕后,我们可以输入 ctrl + ] 退出虚拟机会话,并通过 virsh 命令获取运行中虚拟机的 IP 地址,测试是否能通过 root 用户 SSH 登录虚拟机:

1
2
3
4
5
6
7
8
$ virsh domifaddr ubuntu-vm
 Name       MAC address          Protocol     Address
----------------------------------------------------------------
 vnet0      52:54:00:1b:3b:4f    ipv4         192.168.122.201/24

$ ssh root@192.168.122.201

root@ubuntu-vm:~$

登陆成功后,可以在虚拟机中执行其他初始化操作,如安装基础依赖、配置镜像源等。

最后在虚拟机中执行关闭命令退出会话:

1
$ shutdown -h now

清理与压缩镜像

virt-install 执行过程中,虚拟机会将虚拟网卡的 Mac 地址记录到文件系统中,但每一次基于镜像启动虚拟机都会生成一个新的 Mac 地址,因此需要将镜像中已经写入配置文件的 Mac 地址清除掉。

除了进入虚拟机手动清除,还可以使用上文已安装的专门工具来清理:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
$ virt-sysprep -d $VM_NAME

[   0.0] Examining the guest ...
[  25.7] Performing "abrt-data" ...
[  25.7] Performing "backup-files" ...
[  26.4] Performing "bash-history" ...
[  26.4] Performing "blkid-tab" ...
[  26.5] Performing "crash-data" ...
[  26.5] Performing "cron-spool" ...
[  26.5] Performing "dhcp-client-state" ...
[  26.5] Performing "dhcp-server-state" ...
[  26.5] Performing "dovecot-data" ...
[  26.5] Performing "logfiles" ...
[  26.7] Performing "machine-id" ...
......

该命令还将执行清除操作历史等一系列操作。

接着在 libvirt 中注销已创建的虚拟机实例:

1
2
3
$ virsh undefine $VM_NAME

域 qfusion-vm 已经被取消定义

此时对虚拟机所做的更改都已写入到 root-disk.qcow2 模板镜像中,将其上传到云主机平台的镜像服务器,就可以基于已写入的配置重复创建新的虚拟机实例。

如果此时镜像体积较大,可执行以下命令可对镜像体积进行压缩,并使用压缩得到的 ubuntu-20.04.qcow2 镜像:

1
$ qemu-img convert -f qcow2 -O qcow2 -c root-disk.qcow2 ubuntu-20.04.qcow2

在本示例中,镜像大小从 7 个多 G 压缩到了 500M 左右,效果显著。

参考链接

updatedupdated2022-08-032022-08-03