你可能会熟悉Dockerfile,这是让Docker为你构建映像的说明。这里有一个简单的例子。
FROM ubuntu:15.04 |
每一行都是Docker关于如何构建镜像的说明。它将ubuntu:15.04用作基础,然后复制python脚本。CMD指令是让容器执行操作指令(将映像转换为正在运行的进程)。
让我们运行docker build .并检查输出。
$ docker build -t my_test_image . |
看看最后两行,我们已经成功构建了一个Docker镜像,我们可以通过标识符174b1e992617来引用它(这个值是图像内容的SHA256哈希摘要)。
我们有最了终的图像,但各个步骤的ID如d1b55fd07600和44ab3f1d4cd6是什么意思?他们也是镜像吗?,是的。
想象一下,如果我们从Dockerfile中删除第二步:COPY app.py /app Docker仍然会成功地将其构建为一个镜像,因此,在镜像构建过程的每一步,我们都有一个镜像。
这告诉我们镜像可以建立在彼此之上!当您认为Dockerfile中的FROM 指令的意思是:指定要在其上构建哪个镜像,这是有道理的。
导出镜像并解压缩
为了便于使用,可以将图像导出到单个文件中,使我们可以轻松查看内部。
docker save my_test_image > my_test_image
导出的文件是..:
$ file my_test_image
my_test_image: POSIX tar archive
一个tarball!压缩文件或目录。我们打开它。
$ mkdir unpacked_image |
我们将开始研究 manifest.json:
[ |
清单文件是一段元数据,它准确描述了这个镜像中的内容。我们可以看到镜像有一个标签my_test_image,它有一个叫做Layers的东西,另一个叫做Config。
配置JSON文件的前12个字符与我们从docker build中看到的镜像ID相同,巧合 - 我想不是!
$ cat 174b1e9926177b5dfd22981ddfab78629a9ce2f05412ccb1a4fa72f0db21197b.json
{ |
这是一个非常大的JSON文件,但通过它你可以看到有很多不同的元数据。特别是,有关于如何将此镜像转换为正在运行的容器的元数据 - 要运行的命令和要添加的环境变量。
镜像就像洋葱
它们都有层次。但是什么代表一层?
$ ls cac0b96b79417d5163fbd402369f74e3fe4ff8223b655e0b603a8b570bcc76eb |
这是另一个tarfile,让我们打开压缩包看一看。
$ tree -L 1 |
这是Docker镜像的大秘密,它由文件系统组成不同的视图!几乎所有你在标准的Ubuntu文件系统中看到的这里面都有。
那么每个图层究竟包含什么?那么它将有助于知道哪些层来自基本镜像,以及哪些层是由我们添加的。
使用我们之前做过的相同过程,但在ubuntu:15.04我可以看到这些层:
cac0b96b79417d5163fbd402369f74e3fe4ff8223b655e0b603a8b570bcc76eb |
都属于ubuntu基础镜像,来自FROM ubuntu:15.04命令,知道这一点,我预测我们my_test_image镜像的最顶层6c91b695f2ed98362f511f2490c16dae0dcf8119bcfe2fe9af50305e2173f373应该来自命令COPY app.py /app/。
$ tree |
确实,而且内部的所有内容都是我们对文件系统所做的更改,它只是添加了app.py文件。
工具
如果您希望将来分析镜像,可以使用开源工具Dive!
这怎么变成一个正在运行的容器?
现在我们了解了Docker镜像是什么,Docker如何将其转换为正在运行的容器?
每个容器都有自己的文件系统视图,Docker将获取图像中的所有层,并将它们放在彼此的顶部,以呈现文件系统的一个视图。这种技术称为Union Mounting,Docker支持Linux上的几个Union Mount Filesystems,主要是OverlayFS和AUFS。
但这并非全部,容器意味着短暂,容器运行时对文件系统的更改不应在容器停止后保存。一种方法是将整个镜像复制到其他位置,这样更改不会影响原始文件。这不是非常有效,替代方案(和Docker所做的)是在容器中的文件系统的最顶部添加一个瘦读/写层,进行更改。如果您需要对下面某个图层中的文件进行更改,则需要将该文件复制到进行更改的顶层。这称为Copy-On-Write。当容器停止运行时,将丢弃最顶层的文件系统层。
在文件系统之后,除了配置一些后续步骤的元数据之外,镜像不会用于其他许多其他操作。为了完整起见,要创建一个正在运行的容器,我们需要使用命名空间来控制进程可以看到的内容(文件系统,进程,网络,用户等); 使用cgroups来控制进程可以使用的资源(内存,CPU,网络等); 和安全功能来控制进程可以执行的操作(功能,AppArmor,SELinux,Seccomp)。