1
2
3
4
5
6
7
作者:李晓辉

联系方式:

1. 微信:Lxh_Chat

2. 邮箱:939958092@qq.com

在云计算的世界里,虚拟机镜像(VM Image)是构建和部署虚拟化环境的基础。它就像是虚拟机的“模板”,包含了操作系统、预装软件以及配置信息,使得用户能够快速部署和启动新的虚拟机实例。OpenStack作为领先的开源云平台,提供了强大的工具和灵活的机制来创建、管理和分发虚拟机镜像。

本文将深入探讨如何在OpenStack环境中构建虚拟机镜像。我们将从基础概念入手,逐步介绍如何创建自定义镜像,包括选择合适的操作系统、安装必要的软件、进行系统优化以及将镜像上传到OpenStack的镜像服务(Glance)中。此外,我们还会分享一些最佳实践和技巧,帮助你高效地管理和使用虚拟机镜像,以满足不同业务场景的需求。

无论你是OpenStack的初学者,还是希望深入了解镜像管理的资深用户,本文都将为你提供实用的指导和参考。让我们一起走进OpenStack虚拟机镜像的世界,探索如何通过镜像技术提升云环境的部署效率和灵活性。

常见镜像格式

格式描述
raw这是一种最简单的镜像格式,就像把硬盘的内容直接拷贝下来,没有经过任何加工。它的文件扩展名通常有 .img.raw.bin。这种格式的好处是简单直接,但文件可能会比较大。
QCOW2这是一种比较智能的镜像格式,由 QEMU 仿真器支持。它可以随着数据的增加而动态扩展空间,就像一个可以自动扩容的气球。这是 Linux KVM 最常用的格式,因为它既灵活又节省空间。
ISO大家可能都见过这种格式,它通常用于光盘(CD 或 DVD)的内容。如果你曾经刻录过光盘,或者下载过操作系统的安装光盘镜像,那很可能就是 ISO 格式。
AKI这是 Amazon 的一种内核映像格式,主要用于 Amazon 的云服务。在 Linux KVM 或 Red Hat OpenStack Platform 中,我们通常用不到它。
AMI同样是 Amazon 的一种系统映像格式,主要用于 Amazon 的云服务。在 Linux KVM 或 Red Hat OpenStack Platform 中,我们也不常用它。
ARI这是 Amazon 的一种 ramdisk 映像格式,也是为 Amazon 的云服务设计的。在 Linux KVM 或 Red Hat OpenStack Platform 中,我们一般不会用到它。
VDI这是 VirtualBox 的一种磁盘镜像格式,最初是为了 VirtualBox 虚拟机管理程序设计的。不过,QEMU 仿真器也支持它。
VHD这是微软 Hyper-V 使用的虚拟硬盘格式,最早由 Windows Virtual PC 使用。虽然 VMware、Xen、VirtualBox 等也支持这种格式,但它并不是特别流行。
VMDK这是 VMware 最初创建的一种虚拟机磁盘格式,但现在已经被广泛接受,成为了一种通用的开放格式。

RAW or QCOW2?

RAW和QCOW2是最常见的格式,以下是一些主要区别:

属性RAWQCOW2
Image 大小RAW Image 是源磁盘的精确副本,包括空白空间。如果文件系统或存储后端支持稀疏存储,RAW Image 实际占用的空间通常会比它显示的大小小很多。QCOW2 是一种经过优化的虚拟磁盘格式,可以节省空间。不要把它和压缩或者稀疏存储混淆,它的优化是通过文件内部结构实现的。通常,QCOW2 Image 比来自同一源的 RAW Image 小。此外,QCOW2 还支持 zlib 压缩。QCOW2 Image 已经经过优化,所以它可能不需要依赖稀疏文件系统或存储功能。
性能RAW 格式性能更高,因为它在虚拟机启动时就分配好了磁盘空间,避免了动态分配空间带来的延迟。使用 RAW Image 时,不需要额外的处理步骤。QCOW2 格式性能稍低,因为它的特性需要在运行时动态处理。当需要的空间超过当前分配的大小时,扩展空间会带来一定的延迟。
加密不支持支持可选加密。可以使用 256 位 AES 加密或 LUKS 加密。
快照不支持QCOW2 支持快照功能,每个快照都是 Image 在某个时间点的只读副本。快照是存储在 Image 内部的。
写入时复制不支持当 QCOW2 Image 发生更改时,更改会写入一个单独的叠加文件,而原始 Image 文件保持不变。在修改之前,需要将要更改的块复制到叠加层。多个叠加文件可以共享同一个基础 Image,这样就可以从同一个基础 Image 启动多个实例。

创建qcow2镜像文件

可以看到,虚拟大小为1G,实际大小只有193k

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
(undercloud) [stack@director ~]$ qemu-img create -f qcow2 test.qcow2 1G
Formatting 'test.qcow2', fmt=qcow2 size=1073741824 cluster_size=65536 lazy_refcounts=off refcount_bits=16

(undercloud) [stack@director ~]$ ls -lh test.qcow2
-rw-r--r--. 1 stack stack 193K Jan 1 11:43 test.qcow2

(undercloud) [stack@director ~]$ qemu-img info test.qcow2
image: test.qcow2
file format: qcow2
virtual size: 1 GiB (1073741824 bytes)
disk size: 196 KiB
cluster_size: 65536
Format specific information:
compat: 1.1
lazy refcounts: false
refcount bits: 16
corrupt: false

创建raw镜像文件

可以看到虚拟大小和实际大小都是1G

1
2
3
4
5
6
7
8
9
10
(undercloud) [stack@director ~]$ qemu-img create -f raw test.raw 1G
Formatting 'test.raw', fmt=raw size=1073741824
(undercloud) [stack@director ~]$
(undercloud) [stack@director ~]$ ls -lh test.raw
-rw-r--r--. 1 stack stack 1.0G Jan 1 11:45 test.raw
(undercloud) [stack@director ~]$ qemu-img info test.raw
image: test.raw
file format: raw
virtual size: 1 GiB (1073741824 bytes)
disk size: 4 KiB

镜像文件的格式转换

1
2
3
4
5
6
7
8
(undercloud) [stack@director ~]$ qemu-img convert -f qcow2 test.qcow2 -O raw test-convert.raw
(undercloud) [stack@director ~]$ qemu-img info test-convert.raw
image: test-convert.raw
file format: raw
virtual size: 2 GiB (2147483648 bytes)
disk size: 4 KiB
(undercloud) [stack@director ~]$ ls -lh test-convert.raw
-rw-r--r--. 1 stack stack 2.0G Jan 1 11:48 test-convert.raw

构建⾃定义镜像

你可以用以下三种方法中的任意一种来制作自定义镜像:

  1. diskimage-builder
    这是一个强大的工具,可以帮你快速构建各种操作系统镜像,支持多种自定义配置。

  2. Guestfish 或 Virt-customize
    这两个工具可以让你对现有的虚拟机镜像进行修改,比如安装软件、配置网络等,非常适合对镜像进行精细调整。

  3. Cloud-init
    这是一个在虚拟机启动时自动配置系统的工具。你可以通过它来实现自定义的初始化设置,比如设置用户名、安装软件包等。

Diskimage-builder

Diskimage-builder概述

Diskimage-builder在构建镜像的时候,会在 chroot 环境里把 /proc、/sys 和 /dev 给绑定挂载上。它生成的镜像可精简了,只包含实现 OpenStack 功能所需的那些组件。镜像可以很简单,比如文件系统镜像,也可以很复杂,做成完整的磁盘镜像,全看怎么定制。

还有个好玩的,它用元素来决定往镜像里放啥内容,还有要做的修改。镜像至少得有一个基础分发元素,比如 rhel,然后还能用其他元素来修改这个 rhel 基础镜像。它会根据这些元素去调用脚本,把各种东西应用到镜像里。

每个元素都有两个文件,element-depselement-provides,它们是干啥用的呢?

  1. 定义依赖element-deps 文件就是一个普通的文本文件,里面写的是这个元素需要依赖的其他元素。就好比你做饭需要食材一样,这个元素也需要其他元素先准备好。Diskimage-builder 在构建镜像的时候,会先看看这个文件,把里面提到的依赖元素先搞定,然后再来构建这个元素。比如说,如果一个元素依赖于 ubuntu 元素,那 Diskimage-builder 就会先搞定 ubuntu,再继续往下做。

  2. 定义提供的功能element-provides 文件也是一个文本文件,不过它写的是这个元素能提供啥功能或者服务。这就相当于你已经准备好了某些东西,别人就不需要再重复准备了。在构建镜像的时候,Diskimage-builder 会检查这个文件,如果某个功能已经有人提供过了,那它就不会再重复构建,这样就能省事儿,避免重复劳动。

安装diskimage-builder

1
[root@director ~]# yum install diskimage-builder

列出包含的元素

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
[root@director ~]# ls /usr/share/diskimage-builder/elements
apt-conf debian-minimal dracut-ramdisk install-static pip-and-virtualenv simple-init
apt-preferences debian-systemd dracut-regenerate install-types pip-cache source-repositories
apt-sources debian-upstart dynamic-login ironic-agent pkg-map stable-interface-names
baremetal debootstrap element-manifest iscsi-boot posix svc-map
base deploy-baremetal enable-serial-console iso proliant-tools sysctl
block-device-efi deploy-kexec ensure-venv journal-to-console pypi sysprep
block-device-gpt deploy-targetcli epel local-config python-brickclient uboot
block-device-mbr deploy-tgtadm fedora lvm python-stow-versions ubuntu
bootloader devuser fedora-minimal manifests ramdisk ubuntu-common
cache-url dhcp-all-interfaces gentoo mellanox ramdisk-base ubuntu-minimal
centos dib-init-system growroot modprobe rax-nova-agent ubuntu-signed
centos7 dib-python grub2 modprobe-blacklist redhat-common ubuntu-systemd-container
centos-minimal dib-run-parts hpdsa no-final-image rhel vm
cleanup-kernel-initrd disable-nouveau hwburnin oat-client rhel7 yum
cloud-init disable-selinux hwdiscovery openssh-server rhel-common yum-minimal
cloud-init-datasources dkms ibft-interfaces openstack-ci-mirrors rpm-distro zipl
cloud-init-disable-resizefs docker ilo opensuse runtime-ssh-host-keys zypper
cloud-init-nocloud dpkg __init__.py opensuse-minimal select-boot-kernel-initrd zypper-minimal
debian dracut-network install-bin package-installs selinux-permissive

基础元素

/usr/share/diskimage-builder/elements/base是 Diskimage-builder 里的一个超重要的地方!它里面装的是基础元素(base element)的定义和脚本。

基础元素就是构建镜像的“地基”,里面有一些最基本的配置和软件包,啥镜像都得从这儿开始搭起来。就好比盖房子,先得有个地基,这个基础元素就是镜像的地基。有了它,你才能往上加各种自定义的东西,做出自己想要的镜像。

Diskimage-builder 阶段⼦⽬录说明

说到 Diskimage-builder 构建镜像的过程,其实是有好几个阶段的,挺有意思的!每个阶段的脚本都放在 element 目录下的子目录里。这些子目录可能一开始没有,所以如果需要的话,就自己动手创建一下。

这些脚本的名字前面都有两位数字,运行的时候会按照数字的顺序来执行。有个小规矩是这样的:数据文件一般都放在 element 目录里,但只有那些可执行的脚本才会被放在阶段子目录里。要是某个脚本没有执行权限,那它就不会被运行。

这些阶段子目录的处理顺序是固定的,就像做饭的步骤一样,得按照顺序来,不然可能会乱套。

  • root.d

    • 作用:包含在 chroot 环境中执行的脚本。这些脚本在构建过程中会在目标文件系统的根目录中运行,通常用于安装和配置基本软件包和设置,所以你有什么自定义的项目需要添加,可以放这里。

    • 执行顺序:相对较早执行,以便确保基础环境的配置。

  • extra-data.d

    • 作用:包含下载和处理额外数据的脚本。这些数据可以是镜像中需要包含的特定文件或配置。

    • 执行顺序:通常在安装和配置的中期执行,以确保额外的数据在需要时可用。

  • pre-install.d

    • 作用:在安装任何软件包之前运行的脚本。这些脚本通常用于准备系统环境,例如更新包列表或设置环境变量。

    • 执行顺序:在 install.d 之前。

  • install.d

    • 作用:执行安装操作的脚本,用于安装必要的软件包和工具。

    • 执行顺序:在 pre-install.d 之后,post-install.d 之前。

  • post-install.d

    • 作用:在软件包安装完成后运行的脚本,用于配置已安装的软件或执行其他需要在安装后进行的操作。

    • 执行顺序:在 install.d 之后

  • block-device.d

    • 作用:包含用于配置块设备(如磁盘分区、文件系统等)的脚本。通常用于准备磁盘分区和文件系统结构。

    • 执行顺序:根据需要,通常在配置阶段或安装阶段之后。

  • finalize.d

    • 作用:执行最终定制操作的脚本,用于做出最后的调整和优化,例如设置引导加载程序。

    • 执行顺序:在所有配置和安装操作完成后执行。

  • cleanup.d

    • 作用:在所有操作完成后运行的脚本,用于清理构建过程中产生的临时文件和数据,以减小镜像大小并优化性能。

    • 执行顺序:最后执行,以确保所有的临时文件和数据被清除。

基本的Diskimage-builder 变量

变量描述
DIB_LOCAL_IMAGE要从中构建的基础映像
DIB_YUM_REPO_CONF在映像构建期间要复制到chroot环境中的客户端 Yum 存储库配置文件
ELEMENTS_PATH元素的工作副本的路径

Diskimage-builder 选项

1
[root@director ~]# disk-image-create vm rhel -n -p python-django-compressor -a amd64 -o web.img 2>&1 | tee diskimage-build.log
  • vm:这个参数是告诉 Diskimage-builder,我们要构建的是一个虚拟机的镜像。它会用一些默认的设置,让镜像更适合虚拟机运行。

  • rhel:这个参数是说,我们要构建的是基于 Red Hat Enterprise Linux 的镜像,也就是用 RHEL 的系统。

  • -n:这个参数有点意思,它告诉 Diskimage-builder 不要自动包含基础元素。有时候我们不想安装 cloud-init 或者更新软件包,这个参数就很有用了。

  • -p:这个参数是用来指定要安装的软件包。在这个例子中,我们要安装 python-django-compressor 包。

  • -a:这个参数是设置镜像的架构,这里我们用的是 amd64,也就是 64 位的架构。

  • -o:这个参数是设置输出的镜像文件名,这里我们把它叫做 web.img

最后,2>&1 | tee diskimage-build.log 是把构建过程中的输出信息保存到一个日志文件里,方便我们回头查看。

构建案例

我们基于osp-small.qcow2来构建新的镜像
先下载一下这个镜像

1
2
3
su -
cd
wget http://materials/osp-small.qcow2

准备基础元素目录和定制目录

1
2
cp -a /usr/share/diskimage-builder/elements/ /root/
mkdir -p elements/rhel/post-install.d

定制在rhel中安装并配置httpd服务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
cd elements/rhel/post-install.d

cat > 00-install-httpd <<-EOF
#!/bin/bash
yum makecache
yum install httpd -y
EOF

cat > 01-create-index <<-EOF
#!/bin/bash
echo hello lixiaohui > /var/www/html/index.html
EOF

cat > 02-enable-httpd <<-EOF
#!/bin/bash
systemctl enable httpd
EOF

授予执行权限

1
2
cd
chmod +x elements/rhel/post-install.d/*

根据以上信息,定制构建变量

1
2
3
4
export DIB_LOCAL_IMAGE=/root/osp-small.qcow2
export DIB_YUM_REPO_CONF=/etc/yum.repos.d/rhel-dvd.repo
export ELEMENTS_PATH=/root/elements
export DIB_NO_TMPFS=1

开始构建镜像

这个构建可能需要较长时间,请耐心等待

1
disk-image-create vm rhel -t qcow2 -a amd64 -p httpd -o httpd.qcow2

输出

1
2
3
2024-11-11 06:36:12.495 | Converting image using qemu-img convert
2024-11-11 06:37:48.987 | Image file httpd.qcow2 created...
2024-11-11 06:37:49.169 | Build completed successfully

Guestfish和 Virt-customize

Guestfish 和 Virt-customize 都使⽤ libguestfs API 来执⾏其功能。Libguestfs 需要可以处理各种不同格式的后端,默认情况下使⽤ libvirt

Guestfish

下例使⽤ -i 选项来⾃动挂载分区,使⽤ -a 选项来添加磁盘镜像,并且使⽤ –network 选项来启⽤⽹络访问

1
2
3
4
5
6
[root@workstation ~]# yum install libguestfs-tools -y
[root@workstation ~]# guestfish -i --network -a osp-small.qcow2
libguestfs: error: could not create appliance through libvirt.

Try running qemu directly without libvirt using this environment variable:
export LIBGUESTFS_BACKEND=direct

我们发现,报告没有libvirt,因为我们是虚拟机,没有是正常的,导出这个direct的变量重新运行即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[root@workstation ~]# export LIBGUESTFS_BACKEND=direct
[root@workstation ~]# guestfish -i --network -a osp-small.qcow2

Welcome to guestfish, the guest filesystem shell for
editing virtual machine filesystems and disk images.

Type: ‘helpfor help on commands
‘man’ to read the manual
‘quit’ to quit the shell

Operating system: Red Hat Enterprise Linux 8.1 (Ootpa)
/dev/sda1 mounted on /

><fs>

不要忘了最后更新selinux标签

1
2
3
4
5
6
7
><fs> command 'yum install httpd -y'
><fs> write '/var/www/html/index.html' 'hello lixiaohui'
><fs> command 'systemctl enable httpd'
><fs> command 'useradd lixiaohui'
><fs> command 'systemctl enable httpd'
><fs> selinux-relabel /etc/selinux/targeted/contexts/files/file_contexts /
><fs> exit

尝试创建一个实例看看是否成功

Virt-customize

下例中使⽤ virt-customize 命令及 -a 选项来添加磁盘,安装⼀个软件包,设置 root 密码,再还原 SELinux 上下⽂

1
2
3
4
5
6
7
8
[root@workstation ~]# wget -O virt-test.qcow2 http://materials/osp-small.qcow2
[root@workstation ~]# virt-customize -a virt-test.qcow2 --install httpd --root-password password:lxhpassword --selinux-relabel
[ 0.0] Examining the guest ...
[ 20.9] Setting a random seed
[ 21.0] Installing packages: httpd
[ 77.1] Setting passwords
[ 88.5] SELinux relabelling
[ 246.6] Finishing off

Cloud-init

Cloud-init概述

说到镜像管理,确实是个让人头疼的问题,镜像太多太杂很容易让人晕头转向。为了避免这种情况,其实可以只维护几个通用的基础镜像,然后在真正部署的时候再根据具体需求进行定制。这样一来,既节省了存储空间,又方便管理。

说到定制,就不得不提到 Cloud-init 了。这家伙可厉害了,它是一组 Python 脚本和工具,专门用来在虚拟机或云实例启动的时候做早期初始化工作。它能干的事情可不少,比如把 SSH 密钥塞进去,这样你就能安全地登录;还能配置网络设置,让机器能正常上网;甚至还能设置主机名,让每台机器都有自己的名字。总之,它能帮你把实例的初始配置搞定得妥妥的。

Cloud-init 要完成这些任务,需要一些“指令”,这些指令就是所谓的“用户数据”。你可以把它想象成给 Cloud-init 的“任务清单”,告诉它要做什么,怎么做。有了用户数据,Cloud-init 就能按照你的要求去配置实例了。这样一来,不管是什么基础镜像,都能通过 Cloud-init 变成你需要的样子,是不是很方便呀!

Cloud-init 服务

Cloud-init 有四个主要的服务,每个服务都有自己的任务和执行时机,它们一起把实例的初始化工作安排得明明白白。我给你一个个说说:

  1. cloud-init-local.service

    • 任务:这个服务是第一个出场的,它会在网络还没准备好,但根分区已经可以读写的时候运行。它的主要工作是把网络配置信息传递给实例,相当于给实例准备好“上网指南”。

    • 执行命令cloud-init init --local

  2. cloud-init.service

    • 任务:这是第二个服务,它得等网络配置好了之后才能开始工作。这个阶段主要是处理用户数据,按照配置文件里的 cloud_init_modules 模块列表,一步步把用户要求的配置都搞定。

    • 执行命令cloud-init init

  3. cloud-config.service

    • 任务:这个服务在第二个服务之后运行,主要任务是执行配置文件里 cloud_config_modules 下列出的模块。简单来说,就是把用户指定的配置细节都落实到位。

    • 执行命令cloud-init modules --mode=config

  4. cloud-final.service

    • 任务:这是最后一个服务,它要等前面的服务都完成,甚至等 rc-local.service 也执行完了才会启动。它的任务是执行配置文件里 cloud_final_modules 下的模块,做最后的收尾工作,比如清理日志、设置开机启动项之类的。

    • 执行命令cloud-init modules --mode=final

这四个服务分工明确,一个接一个地把实例初始化的活儿干完,感觉就像接力赛一样,特别有条理!

如果有兴趣,可以看看模块的内容:

1
/usr/lib/python3.6/site-packages/cloudinit/config/