你可能会熟悉Dockerfile,这是让Docker为你构建映像的说明。这里有一个简单的例子。
FROM ubuntu:15.04 COPY app.py /app CMD python /app/app.py |
每一行都是Docker关于如何构建镜像的说明。它将ubuntu:15.04用作基础,然后复制python脚本。CMD指令是让容器执行操作指令(将映像转换为正在运行的进程)。
让我们运行docker build .并检查输出。
$ docker build -t my_test_image . Sending build context to Docker daemon 364.2MB Step 1/3 : FROM ubuntu:15.04 ---> d1b55fd07600 Step 2/3 : COPY app.py /app/ ---> 44ab3f1d4cd6 Step 3/3 : CMD python /app/app.py ---> Running in c037c981012e Removing intermediate container c037c981012e ---> 174b1e992617 Successfully built 174b1e992617 Successfully tagged my_test_image:latest |
看看最后两行,我们已经成功构建了一个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 $ tar -xvf my_test_image -C unpacked_image x 174b1e9926177b5dfd22981ddfab78629a9ce2f05412ccb1a4fa72f0db21197b.json x 28441336175b9374d04ee75fdb974539e9b8cad8fec5bf0ff8cea6f8571d0114/ x 28441336175b9374d04ee75fdb974539e9b8cad8fec5bf0ff8cea6f8571d0114/VERSION x 28441336175b9374d04ee75fdb974539e9b8cad8fec5bf0ff8cea6f8571d0114/json x 28441336175b9374d04ee75fdb974539e9b8cad8fec5bf0ff8cea6f8571d0114/layer.tar x 4631663ba627c9724cd701eff98381cb500d2c09ec78a8c58213f3225877198e/ x 4631663ba627c9724cd701eff98381cb500d2c09ec78a8c58213f3225877198e/VERSION x 4631663ba627c9724cd701eff98381cb500d2c09ec78a8c58213f3225877198e/json x 4631663ba627c9724cd701eff98381cb500d2c09ec78a8c58213f3225877198e/layer.tar x 6c91b695f2ed98362f511f2490c16dae0dcf8119bcfe2fe9af50305e2173f373/ x 6c91b695f2ed98362f511f2490c16dae0dcf8119bcfe2fe9af50305e2173f373/VERSION x 6c91b695f2ed98362f511f2490c16dae0dcf8119bcfe2fe9af50305e2173f373/json x 6c91b695f2ed98362f511f2490c16dae0dcf8119bcfe2fe9af50305e2173f373/layer.tar x c4f8838502da6456ebfcb3f755f8600d79552d1e30beea0ccc62c13a2556da9c/ x c4f8838502da6456ebfcb3f755f8600d79552d1e30beea0ccc62c13a2556da9c/VERSION x c4f8838502da6456ebfcb3f755f8600d79552d1e30beea0ccc62c13a2556da9c/json x c4f8838502da6456ebfcb3f755f8600d79552d1e30beea0ccc62c13a2556da9c/layer.tar x cac0b96b79417d5163fbd402369f74e3fe4ff8223b655e0b603a8b570bcc76eb/ x cac0b96b79417d5163fbd402369f74e3fe4ff8223b655e0b603a8b570bcc76eb/VERSION x cac0b96b79417d5163fbd402369f74e3fe4ff8223b655e0b603a8b570bcc76eb/json x cac0b96b79417d5163fbd402369f74e3fe4ff8223b655e0b603a8b570bcc76eb/layer.tar x manifest.json x repositories |
我们将开始研究 manifest.json:
[ { "Config": "174b1e9926177b5dfd22981ddfab78629a9ce2f05412ccb1a4fa72f0db21197b.json", "RepoTags": [ "my_test_image:latest" ], "Layers": [ "cac0b96b79417d5163fbd402369f74e3fe4ff8223b655e0b603a8b570bcc76eb/layer.tar", "28441336175b9374d04ee75fdb974539e9b8cad8fec5bf0ff8cea6f8571d0114/layer.tar", "4631663ba627c9724cd701eff98381cb500d2c09ec78a8c58213f3225877198e/layer.tar", "c4f8838502da6456ebfcb3f755f8600d79552d1e30beea0ccc62c13a2556da9c/layer.tar", "6c91b695f2ed98362f511f2490c16dae0dcf8119bcfe2fe9af50305e2173f373/layer.tar" ] } ] |
清单文件是一段元数据,它准确描述了这个镜像中的内容。我们可以看到镜像有一个标签my_test_image,它有一个叫做Layers的东西,另一个叫做Config。
配置JSON文件的前12个字符与我们从docker build中看到的镜像ID相同,巧合 - 我想不是!
$ cat 174b1e9926177b5dfd22981ddfab78629a9ce2f05412ccb1a4fa72f0db21197b.json
{ "architecture": "amd64", "config": { "Hostname": "d2d404286fc4", "Domainname": "", "User": "", "AttachStdin": false, "AttachStdout": false, "AttachStderr": false, "Tty": false, "OpenStdin": false, "StdinOnce": false, "Env": [ "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" ], "Cmd": [ "/bin/sh", "-c", "python /app/app.py" ], "ArgsEscaped": true, "Image": "sha256:44ab3f1d4cd69d84c9c67187b378b1d1322b5fddf4068c11e8b11856ced7efc0", "Volumes": null, "WorkingDir": "", "Entrypoint": null, "OnBuild": null, "Labels": null }, "container": "c037c981012e8f03ac5466fcdda8f78a14fb9bb5ee517028c66915624a5616fa", "container_config": { "Hostname": "d2d404286fc4", "Domainname": "", "User": "", "AttachStdin": false, "AttachStdout": false, "AttachStderr": false, "Tty": false, "OpenStdin": false, "StdinOnce": false, "Env": [ "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" ], "Cmd": [ "/bin/sh", "-c", "#(nop) ", "CMD [\"/bin/sh\" \"-c\" \"python /app/app.py\"]" ], "ArgsEscaped": true, "Image": "sha256:44ab3f1d4cd69d84c9c67187b378b1d1322b5fddf4068c11e8b11856ced7efc0", "Volumes": null, "WorkingDir": "", "Entrypoint": null, "OnBuild": null, "Labels": {} }, "created": "2018-11-01T03:19:16.8517953Z", "docker_version": "18.09.0-ce-beta1", "history": [ { "created": "2016-01-26T17:48:17.324409116Z", "created_by": "/bin/sh -c #(nop) ADD file:3f4708cf445dc1b537b8e9f400cb02bef84660811ecdb7c98930f68fee876ec4 in /" }, { "created": "2016-01-26T17:48:31.377192721Z", "created_by": "/bin/sh -c echo '#!/bin/sh' > /usr/sbin/policy-rc.d \t&& echo 'exit 101' >> /usr/sbin/policy-rc.d \t&& chmod +x /usr/sbin/policy-rc.d \t\t&& dpkg-divert --local --rename --add /sbin/initctl \t&& cp -a /usr/sbin/policy-rc.d /sbin/initctl \t&& sed -i 's/^exit.*/exit 0/' /sbin/initctl \t\t&& echo 'force-unsafe-io' > /etc/dpkg/dpkg.cfg.d/docker-apt-speedup \t\t&& echo 'DPkg::Post-Invoke { \"rm -f /var/cache/apt/archives/*.deb /var/cache/apt/archives/partial/*.deb /var/cache/apt/*.bin || true\" };' > /etc/apt/apt.conf.d/docker-clean \t&& echo 'APT::Update::Post-Invoke { \"rm -f /var/cache/apt/archives/*.deb /var/cache/apt/archives/partial/*.deb /var/cache/apt/*.bin || true\" };' >> /etc/apt/apt.conf.d/docker-clean \t&& echo 'Dir::Cache::pkgcache \"\" Dir::Cache::srcpkgcache \"\"' >> /etc/apt/apt.conf.d/docker-clean \t\t&& echo 'Acquire::Languages \"none\"' > /etc/apt/apt.conf.d/docker-no-languages \t\t&& echo 'Acquire::GzipIndexes \"true\" Acquire::CompressionTypes::Order:: \"gz\"' > /etc/apt/apt.conf.d/docker-gzip-indexes" }, { "created": "2016-01-26T17:48:33.59869621Z", "created_by": "/bin/sh -c sed -i 's/^#\\s*\\(deb.*universe\\)$/\\1/g' /etc/apt/sources.list" }, { "created": "2016-01-26T17:48:34.465253028Z", "created_by": "/bin/sh -c #(nop) CMD [\"/bin/bash\"]" }, { "created": "2018-11-01T03:19:16.4562755Z", "created_by": "/bin/sh -c #(nop) COPY file:8069dbb6bfc301562a8581e7bbe2b7675c2f96108903c0889d258cd1e11a12f6 in /app/ " }, { "created": "2018-11-01T03:19:16.8517953Z", "created_by": "/bin/sh -c #(nop) CMD [\"/bin/sh\" \"-c\" \"python /app/app.py\"]", "empty_layer": true } ], "os": "linux", "rootfs": { "type": "layers", "diff_ids": [ "sha256:3cbe18655eb617bf6a146dbd75a63f33c191bf8c7761bd6a8d68d53549af334b", "sha256:84cc3d400b0d610447fbdea63436bad60fb8361493a32db380bd5c5a79f92ef4", "sha256:ed58a6b8d8d6a4e2ecb4da7d1bf17ae8006dac65917c6a050109ef0a5d7199e6", "sha256:5f70bf18a086007016e948b04aed3b82103a36bea41755b6cddfaf10ace3c6ef", "sha256:9720cebfd814895bf5dc4c1c55d54146719e2aaa06a458fece786bf590cea9d4" ] } } |
这是一个非常大的JSON文件,但通过它你可以看到有很多不同的元数据。特别是,有关于如何将此镜像转换为正在运行的容器的元数据 - 要运行的命令和要添加的环境变量。
镜像就像洋葱
它们都有层次。但是什么代表一层?
$ ls cac0b96b79417d5163fbd402369f74e3fe4ff8223b655e0b603a8b570bcc76eb VERSION json layer.tar |
这是另一个tarfile,让我们打开压缩包看一看。
$ tree -L 1 . ├── bin ├── boot ├── dev ├── etc ├── home ├── lib ├── lib64 ├── media ├── mnt ├── opt ├── proc ├── root ├── run ├── sbin ├── srv ├── sys ├── tmp ├── usr └── var |
这是Docker镜像的大秘密,它由文件系统组成不同的视图!几乎所有你在标准的Ubuntu文件系统中看到的这里面都有。
那么每个图层究竟包含什么?那么它将有助于知道哪些层来自基本镜像,以及哪些层是由我们添加的。
使用我们之前做过的相同过程,但在ubuntu:15.04我可以看到这些层:
cac0b96b79417d5163fbd402369f74e3fe4ff8223b655e0b603a8b570bcc76eb 28441336175b9374d04ee75fdb974539e9b8cad8fec5bf0ff8cea6f8571d0114 4631663ba627c9724cd701eff98381cb500d2c09ec78a8c58213f3225877198e c4f8838502da6456ebfcb3f755f8600d79552d1e30beea0ccc62c13a2556da9c |
都属于ubuntu基础镜像,来自FROM ubuntu:15.04命令,知道这一点,我预测我们my_test_image镜像的最顶层6c91b695f2ed98362f511f2490c16dae0dcf8119bcfe2fe9af50305e2173f373应该来自命令COPY app.py /app/。
$ tree . └── app └── app.py |
确实,而且内部的所有内容都是我们对文件系统所做的更改,它只是添加了app.py文件。
工具
如果您希望将来分析镜像,可以使用开源工具Dive!
这怎么变成一个正在运行的容器?
现在我们了解了Docker镜像是什么,Docker如何将其转换为正在运行的容器?
每个容器都有自己的文件系统视图,Docker将获取图像中的所有层,并将它们放在彼此的顶部,以呈现文件系统的一个视图。这种技术称为Union Mounting,Docker支持Linux上的几个Union Mount Filesystems,主要是OverlayFS和AUFS。
但这并非全部,容器意味着短暂,容器运行时对文件系统的更改不应在容器停止后保存。一种方法是将整个镜像复制到其他位置,这样更改不会影响原始文件。这不是非常有效,替代方案(和Docker所做的)是在容器中的文件系统的最顶部添加一个瘦读/写层,进行更改。如果您需要对下面某个图层中的文件进行更改,则需要将该文件复制到进行更改的顶层。这称为Copy-On-Write。当容器停止运行时,将丢弃最顶层的文件系统层。
在文件系统之后,除了配置一些后续步骤的元数据之外,镜像不会用于其他许多其他操作。为了完整起见,要创建一个正在运行的容器,我们需要使用命名空间来控制进程可以看到的内容(文件系统,进程,网络,用户等); 使用cgroups来控制进程可以使用的资源(内存,CPU,网络等); 和安全功能来控制进程可以执行的操作(功能,AppArmor,SELinux,Seccomp)。
猜你喜欢