大家好!我是Brad,Linode的系统工程师。今天是Linux世界的一个多事之秋,Ubuntu 20.04的发布,这是最受欢迎的发行版之一的下一个LTS(长期支持)版本。与两年前的Ubuntu 18.04一样,我们预计这将成为我们最广泛部署的镜像,所有Linode客户都已经可以使用。
由于我们在构建、测试和部署新镜像方面的自动化,当天发行版的可用性已经成为Linode的常规。但是,什么是镜像(或有时被称为模板),以及如何制作它们?为了回答这个问题,我们先来看看操作系统的安装通常是如何进行的。
安装一个操作系统
你可能熟悉在家用电脑或服务器上手动安装操作系统的过程。它通常包括下载一个安装ISO,将其刻录到CD或USB驱动器上,然后从那里启动系统。在那里,你通常要通过一系列的菜单(有时是手动命令),让你控制安装的各个方面,如安装到哪个硬盘,你想包括哪些软件/包,也许还有一些定制,如设置一个用户名和密码。当一切都完成后,你就有了一个(希望是)可以完全工作的操作系统,你可以启动并开始使用。
这个过程对于进行一次性安装的个人用户来说足够好,但是如果你需要安装超过一小部分系统,那么手动安装过程就根本不可行了。这就是图像出现的地方。
什么是图像?
镜像本质上是一个预装的操作系统,你可以根据需要部署到许多系统上。它的工作原理是进行一次正常的安装,然后制作一个安装的副本,以后可以 "粘贴 "到其他系统上。有各种各样的方法和格式,你可以存储镜像供以后使用,但在大多数情况下,它们是原始安装的字节对字节的精确拷贝。
现在我们只需要执行一次手动安装,而且我们可以在其他地方重新使用。但我们仍然可以做得更好。Linode支持各种各样的发行版,从通常的嫌疑犯(Debian,Ubuntu,CentOS,OpenSUSE )到一些你可能在其他供应商上找不到的发行版(Alpine, Arch,甚至Gentoo)。每一个都有他们自己的发布时间表和支持期限。对我们支持的所有发行版进行手动安装(哪怕只是一次),并花时间确保生成的镜像能够正常工作且不包含任何错误,这将花费大量的时间,而且非常容易发生事故。因此,我们选择在HashiCorp公司生产的名为Packer的奇妙工具的帮助下,使镜像构建过程本身自动化。
自动构建
尽管每个Linux发行版都有一个共同的基础(即Linux内核),但它们彼此之间都非常不同,包括它们的安装方式。有些发行版使用命令行指令,有些使用菜单界面,有些包括自动导航和提供这些菜单的答案的方式。幸运的是,Packer是一个非常通用的工具,可以处理所有这些使用情况。
我们做的第一件事是指示Packer创建一个尽可能接近Linode的虚拟机(或VM)。这意味着模拟我们用于实际Linode的相同 "硬件 "和功能。这样,安装将在一个与最终运行环境非常相似的环境中进行。想象一下,把操作系统安装到一个USB驱动器上,然后用这个驱动器来启动一个完全不同的计算机。如果你很幸运,那么事情可能就会顺利进行,但更多的时候,一些硬件设备将无法被检测到,或者根本无法启动。通过拥有一个与实际运行环境相匹配的安装环境,我们可以消除这些问题。
一旦虚拟机创建完毕,Packer 将从发行版的安装 ISO 中启动它,它从指定的 URL 中获取安装 ISO。不同发行版之间的过程有很大不同。对于命令驱动的发行版,如 Arch,我们给它提供一个bash 脚本,执行基本的安装。对于菜单驱动的发行版,如Ubuntu ,我们使用该发行版的首选方法向安装程序提供答案(通常是在Debian-like 发行版上的Preseed或在 RHEL-like 发行版上的Kickstart)。除了我们的虚拟机之外,Packer还创建了一个微小的HTTP服务器,允许任何需要的文件被传送到虚拟机中。
所有这些都是通过一个JSON文件控制的,该文件定义了Packer将使用的设置和构建选项。要启动一个构建,我们只需要运行(例如): packer build ubuntu-20.04.json
.
定制化
在大多数情况下,我们进行的安装是尽可能虚无的。这意味着安装指定发行版认为是 "默认"(有时被称为 "基础 "或 "标准")的任何软件包。除了这些默认软件包,我们还安装了一小部分我们称之为 "支持 "的软件包:基本的实用程序,如iotop、mtr和sysstat,它们可以帮助调试任何可能出现的问题。因此,Linode支持团队在协助客户时也可以合理地假设这些工具已经安装。
安装完成后,在关闭虚拟机之前,我们进行一些最后的定制,以确保Linode平台所有功能的正常运行。例如,我们确保启动器配置了LISH(我们的带外控制台访问工具)的正确设置。不过一般来说,我们尽量使事情接近其默认值。这样,喜欢特定发行版的用户就会得到他们熟悉的东西,而不会有开别人的车的感觉。
包装
安装和配置完成后,Packer 关闭虚拟机并将其磁盘镜像导出到一个文件。这听起来好像我们已经完成了,但仍有几个步骤。一般计算机的硬盘会以分区表开始(MBR,或较新系统的GPT)。Linode有点特殊,因为GRUB引导程序本身实际上是在我们的主机上,而不是在每个Linode上(不过配置仍然是从Linode上读取的)。这意味着,我们实际上可以完全剥离分区表,只留下一个分区。
为了实现这一目标,我们运行 fdisk -l disk.img
来确定分区的开始和结束位置,以及块的大小。然后我们使用 dd if=disk.img of=part.img bs=### skip=### count=###
来 "铲除 "分区,使用我们之前命令的起始偏移。更确切地说,该命令中的每一个 "##"都会被我们先前的输出所取代 fdisk
指挥。
就像在新电脑上,驱动器上的大部分空间将是空的,这将反映在我们的磁盘镜像中。将所有这些 "空 "字节复制到周围是很愚蠢的,所以我们接下来要做的是 平放 镜像(稍后,当您将镜像部署到您的Linode时,我们将 再充气 它可以填满你的实例上的可用空间)。由于我们所有的镜像都使用ext4分区,我们可以运行 resize2fs -M part.img
,它将通过删除空的空间自动将我们的图像缩小到尽可能小的尺寸。最后,为了确保生成的图像的完整性,我们进行最后的 fsck
对其进行压缩,然后用 gzip
.
测试
构建和准备映像后,该过程的下一步是确保它确实有效。我们新生成的映像被部署到一个测试环境中,在测试环境中,一堆 Linode 实例以多种不同的配置从映像中预置。我们开发了一个自动化测试套件,可以检查各种不同的东西,例如网络连接和工作包管理器,以及 Linode 平台的各种功能,例如 Backups 和磁盘大小调整;我们把书扔在这些例子上。如果任何检查失败,则该过程将立即中止,生成将失败,以及有关检查失败及其原因的一些详细信息。
以这样一种自动化的方式进行构建和测试,可以实现快速的开发周期,从而使我们能够更快地发布更好的图像。我们已经构建了我们的测试过程,以便增加新的检查是微不足道的。如果有客户报告说我们的图像有问题,我们可以迅速发布一个修复程序,并在我们不断增长的检查列表中添加另一个项目--几乎就像一个免疫系统!"!
相同,但不同
从一个普通的镜像中大规模部署系统是很好的,但是对于一个特定的实例来说,例如根密码或网络配置,这些都是独一无二的。我们的镜像开箱即被配置为使用DHCP,这将导致你的系统自动从我们的DHCP服务器接收其分配的IP地址和一个独特的主机名。然而,我们也提供了各种 "助手",如网络助手(它将自动在你的Linode上配置静态网络),以及我们的根密码重置工具(它设置你的初始根密码,如果你需要重置它,你也可以在紧急情况下使用)。这些工具允许在基本镜像的基础上将特定的实例信息应用到你的Linode上。
当然,不是每个发行版都以同样的方式处理这些任务,所以我们的工具需要知道如何在我们支持的所有发行版上做这些事情。一个发行版的新的主要版本通常需要对这些系统进行一些更新,以便使事情完全正常。例如,Debian 传统的网络配置是在 /etc/network/interfaces
,而CentOS ,将网络配置放在 /etc/sysconfig/network-scripts
.幸运的是,大多数发行版都会提前提供测试版,这让我们有足够的时间来进行这些修改,并确保一切准备就绪,迎接发布日。
总结
正如你所看到的,支持一个新的发行版有很多事情要做,那么将这个过程自动化的真正好处是什么?几年前,在我们拥有今天的流程之前,一个典型的发行版(从构建到测试到可用性)最多需要一整天的时间,但通常是几天,而且会有很多人参与。相比之下,今天发布的Ubuntu 20.04只需要修改5行代码,从开始到结束只花了不到一个小时。这种构建图像的方法为我们节省了大量的时间和麻烦,并带来了经过全面测试和不断改进的稳定构建。如果你有任何建议或你想看到的东西,请告诉我们我在IRC上以Blaboon的身份参加OFTC,以lblaboon的身份参加Freenode。如果你对自己尝试使用Packer感兴趣,他们有很好的文档,可以在这里找到。我们也有自己的Packer的Linode构建器,你可以用它来在我们的平台上创建你自己的自定义图像。关于使用Packer的Linode构建器的更多文档,可以在我们的指南和教程库中找到。
不是Linode的客户? 在这里注册,可获得20美元的积分。
评论 (4)
This is brilliant and very timely (for me) as I happened to spend some of last weekend to “reverse engineer” the build process (and discovered the GRUB config quirks) while tried to test migrating a Linode VM over to a on-premises (lab) VM Host and vice versa. Maybe this would be a good write-up, if you are searching for tutorial/blog topics. Many thanks for publishing this!
Hey A. –
That sounds like a cool topic! If you’re interested, we actually have a paid freelance contributor program for our documentation library called Write For Linode. You can learn more about the program and apply to it here: https://www.linode.com/lp/write-for-linode/
Hi Nathan
Thanks for this, it looks very tempting. I started to work on the process for the first migration but I was stopped by Grub2. As described in the Packaging section above, the stripped out boot partition stops me to boot up the the image in my VM Host and I haven’t been able boot up the image dd’d out of Linode. If I create a vanilla VM image with the same Ubuntu version as in my Linode VM, the (virtual) disk is partitioned with two partitions, sda1 hosting grub (I assume this is what you strip out in the build process) and sda2, which is “/”. The image “exported” out of Linode, on the other hand has only a single partition as described above. Is there any documentation describing how to undu the stripping out of sda1, or how insert a new boot (sda1) partition back into the image? Many thanks, A
Ok, I managed to get through booting my local clone in the end and it turns out that the startup process didn’t crash (as I thought), it was just slow*. It is because (unsurprisingly) it has a hard-coded static IP address which now lives on the wrong network, so I had to wait until the network config time-out* during the boot-up process.
That’s easy enough I’ll just change the config in /etc/network/interfaces (the default/standard place in Ubuntu, and as also mentioned in this article). Looking at the “interfaces” file it, is blank with a note that things have moved on (since I last had to deal with linux networking) and it is now handled by something called “netplan”. [grrrrr….]
No matter, it can’t be that hard, let’s see what /etc/netplan says. Well, the yaml file says my ethernet interface should be called `enp0s3` and should be getting the address from dhcp. But my actual interface name is `eth0` and has a static address. Where is this coming from?! [&$#^%#]
Time to “brute force” search the entire config.
`find /etc/ -exec grep ‘12.34.56.78’ {} \; 2>/dev/null` results in 3 hits, one is by an application so there are only two potential places to change, it might be easy enough to check**:
`grep -Rn ‘12.34.56.78’ * 2>/dev/null`
systemd/network/05-eth0.network
systemd/network/.05-eth0.network
It was auto-generated by Linode’s Network Helper (as described in the article) which is not available in my lab, unsurprisingly, so let’s just copy back the original config:
`cp 05-eth0.network 05-eth0.network.bak`
`cp .05-eth0.network 05-eth0.network`
`shutdown -r now`
Bingo!!! The VM came up with a new dynamic IP address and I can ping and nslookup to my heart content!!! I haven’t checked if the actual web application survived the extraction, and what else may have broken in the process.
*and probably a lot more linode specific configs that are inaccessible to my local clone.
**The actual IP address is not this
Lessons learned: It is a lot more complicated to migrate out of Linode to a local VM or different cloud and require a lot of effort to fix/adapt the extracted application and the OS, it would be a lot faster just to build the lab up from the ground up. It might be a simpler process to move the other way around (i.e. develop in my own lab and pull the result into Linode) but wouldn’t hold my breath trying.
Not sure if my experience warrants an article beyond this (chain of) comment(s). But it was a great weekend project and an inspirational learning exercise, many thanks, Linode!