技巧11 将虚拟机转换为容器
Docker Hub上并没有囊括所有的基础镜像,因此,针对一些小众的Linux发行版和用例,人们可能需要创建自己的镜像。例如,用户已经在一台虚拟机里运行了一个应用,想把它放到Docker里迭代,或者通过使用现有的一些工具和相关技术从Docker生态系统中获益。
在理想情况下,用户会想使用标准的Docker技术,如一些结合了标准配置管理工具(见第7章)的Dockerfile,从头开始构建一个等同于虚拟机的容器。然而,现实情况是,很多虚拟机都没有被仔细地实施过配置管理。这一点的确可能发生,因为一台虚拟机从人们开始用它的时候起会不断地演进,而以一个更加结构化的方式重新创建它的话,从成本上来说是不值得的。
问题
有一台虚拟机,想要将其转换成一个Docker镜像。
解决方案
归档和复制虚拟机文件系统,随后将其打包到一个Docker镜像。
首先,我们可以将虚拟机划分为两大类。
- 本地虚拟机——虚拟机磁盘镜像放在本地,虚拟机的执行操作发生在用户的计算机上。
- 远程虚拟机——虚拟机磁盘镜像存储在远程,虚拟机的执行操作发生在其他地方。
这两类虚拟机(以及其他任何用户想为之创建Docker镜像的虚拟机)在原则上是一致的——需要拿到整个文件系统的TAR,然后用 ADD
命令将TAR文件加到scratch镜像的 /
。
提示
在镜像中包含 ADD
命令时,Dockerfile的 ADD
命令(不像它的兄弟命令 COPY
)会自动将TAR文件(gzip压缩过的文件以及其他一些类似的文件类型也是如此)解压出来。
提示
scratch镜像是一个零字节虚拟镜像,用户可以基于此构建其他镜像。一般来说,它适用于想用一个Dockerfile复制(或添加)一个完整文件系统的情况。
现在,让我们先来看看用户有一台本地Virtualbox虚拟机的情况。
在开始之前,首先需要完成下列任务:
- 安装
qemu-nbd
工具(在Ubuntu上是作为qemu-utils
包的一部分提供的); - 定义虚拟机的磁盘镜像的路径;
- 关闭虚拟机。
如果用户的虚拟机磁盘镜像是.vdi或.vmdk格式,这一技巧应该会很奏效。其他格式则可能成败参半。代码清单3-1展示了该如何将用户的虚拟机文件转换成一个虚拟磁盘,这样一来就可以从这里复制出所有文件。
代码清单3-1 提取一个虚拟机镜像的文件系统
$ VMDISK="$HOME/VirtualBox VMs/myvm/myvm.vdi" ⇽--- 设置一个环境变量指向用户的虚拟机磁盘镜像
$ sudo modprobe nbd ⇽--- 初始化一个qemu-nbd需要的内核模块
$ sudo qemu-nbd -c /dev/nbd0 -r $VMDISK3 ⇽--- 将虚拟机的磁盘连接到一个虚拟设备节点
$ ls /dev/nbd0p* ⇽--- 列出这块磁盘上可以挂载的分区号
/dev/nbd0p1 /dev/nbd0p2
$ sudo mount /dev/nbd0p2 /mnt ⇽--- 通过qemu-nbd把选择的分区挂载到/mnt
$ sudo tar cf img.tar -C /mnt . ⇽--- 从/mnt创建一个名为img.tar的TAR文件
$ sudo umount /mnt && sudo qemu-nbd -d /dev/nbd0 ⇽--- 卸载分区然后用qemu-nbd清理
注意
要选择挂载的分区,可以执行 sudo cfdisk /dev/nbd0
来查看可选项。注意,如果看到了LVM选项,也就意味着磁盘采用的不是普通的分区方案,关于如何挂载LVM分区,用户将需要做一些额外的调研。
如果是远程虚拟机,用户需要作出一个选择:要么关掉虚拟机并让运维团队介入,转储分区文件,要么在虚拟机运行的状态下为它创建一个TAR。
如果用户拿到的是分区转储文件,就可以很轻松地进行挂载,然后按照代码清单3-2中给出的命令把它转换成一个TAR文件。
代码清单3-2 提取一个分区
$ sudo mount -o loop partition.dump /mnt
$ sudo tar cf $(pwd)/img.tar -C /mnt .
$ sudo umount /mnt
另外,也可以选择从一个正在运行的系统创建TAR文件。在登录到系统后可以轻松实现这一点,如代码清单3-3所示。
代码清单3-3 提取一个正在运行的虚拟机的文件系统
$ cd /
$ sudo tar cf /img.tar --exclude=/img.tar --one-file-system /
至此,用户拿到了文件系统镜像的TAR,紧接着可以通过 scp
把它发送到其他机器。
警告
从一个正在运行的系统创建TAR看上去可能是最简单的方案(没有关机,不需要安装软件或请求别的团队),但是它也存在一些弊端——用户复制出来的文件可能存在状态不一致的情况,并且可能会在试用制作出来的新的Docker镜像时遇到一些莫名其妙的问题。如果只能这样做,那就尽可能多地停掉一些应用和服务。
一旦拿到了文件系统的TAR,用户便可以将其添加到镜像里。这一过程再简单不过了,它是由代码清单3-4所示的两行代码的Dockerfile组成的。
代码清单3-4 将归档文件添加到Docker镜像里
FROM scratch
ADD img.tar /
现在可以执行 docker build .
,然后便能得到自己的镜像了!
注意
除了 ADD
,Docker还提供了一个替代的 docker import
形式的命令,可以使用 cat img.tar | docker import - new_image_name
来导入文件。然而,即便选择在构建镜像时附带一些额外的指令,用户仍然始终需要创建一个Dockerfile。因此,使用 ADD
命令可能会更简单一些,也可以轻松地看到镜像的历史。
现在,因为在Docker里已经有了一个镜像,所以我们可以开始用它来做一些实验了。在本例中,用户可能会从基于新镜像创建一个新的Dockerfile开始,通过剥离文件和软件包来实验。
一旦完成了这一点并且得到了满意的结果,紧接着便可以在运行中的容器上用 docker export
导出一个新的、更小巧的TAR,用户可以把它用作新一层镜像的基础,然后重复这一过程直到用户对得到的镜像满意为止。
图3-1中的流程图展示了这一过程。
讨论
本技巧演示了一些基本原理和技术,这些原理和技术在将VM转换为Docker镜像之外的上下文中非常有用。
更宽泛地讲,它表明Docker镜像本质上是一组文件和一些元数据:scratch镜像是一个空文件系统,可以在上面叠放一个TAR文件。在我们讨论如何精简镜像时,我们将回到这个主题。
更具体地说,用户已经了解了如何将TAR文件添加到Docker镜像,以及如何使用 qemu-nbd
工具。
获得镜像后,用户可能需要知道如何像更传统的主机一样运行它。由于Docker容器通常只运行一个应用程序进程,这样做可能有点影响产出,我们将在技巧12中介绍。