内容简介:翻译自这是一个很好的问题,在你知道答案之前,我不仅想告诉你答案,还想告诉你我是如何找到这个答案的。
这是一个很好的问题,在你知道答案之前, docker 镜像看起来很神秘。
我不仅想告诉你答案,还想告诉你我是如何找到这个答案的。
一、从 Dockerfile 到镜像
让我们从头开始,希望你熟系 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 镜像,我们可以通过标识符来引用它(这个是镜像的 sha256 值)。
我们有一个最终镜像,但是各个步骤的 id 是多少? d1b55fd07600 和 44ab3f1d4cd6 ?它们是镜像?事实上它们也是一个镜像。
试想一下我们把 Step 2( COPY app.py /app/ )从 Dockerfile 中删除掉,依旧能够构建出一个镜像(忽略 CMD 缺失 app.py 文件而运行失败)。因此在构建镜像的每一步骤中都会有一个镜像生成。
这告诉我们镜像可以构建在上一个镜像上,这从 Dockerfile 的 FROM 指令中也可以看出来。
镜像的结构必须以这样的方式来组织,但是为什么?我们需要把镜像文件进行解包进行解析。
二、导出镜像并解包
为了方便解析,我们可以导出一个镜像到文件,使得我们能够查看镜像文件中的内容。
docker save my_test_image > my_test_image
而导出的文件是……
$ file my_test_image my_test_image: POSIX tar archive
是一个 tar 文件,内部可能包含文件或文件夹。让我们解压看看。
$ 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"
]
}
]
manifest.json 是描述该镜像的元数据。我们可以看到镜像有一个标签 my_test_image ,它有一个叫做 Layers 的东西和一个叫做 Config 。
Config 的值前 12 位与我们在 docker 构建时看到的 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 文件,详细查看了你可以看到,有许多不同的元数据在其中。
特别是,有关于镜像装换为可运行的容器的元数据 - 要运行的命令( Cmd ),要添加的环境变量( Env )。
三、镜像就像洋葱
它们都有 layers 。什么是 layer ?我选择了 cac0b96b79417d5163fbd402369f74e3fe4ff8223b655e0b603a8b570bcc76eb ,因为这是镜像 layers 的第一个。
$ ls cac0b96b79417d5163fbd402369f74e3fe4ff8223b655e0b603a8b570bcc76eb VERSION json layer.tar
里面有一个 layer.tar 文件,通过文件后缀可以得知是一个 tar 文件,我们解包查看一下结构。
$ tree -L 1 . ├── bin ├── boot ├── dev ├── etc ├── home ├── lib ├── lib64 ├── media ├── mnt ├── opt ├── proc ├── root ├── run ├── sbin ├── srv ├── sys ├── tmp ├── usr └── var
这是 docker 镜像的重要秘密,它由不同的文件系统视图组成。
这个 layer 中有着不少的东西,二进制可文件 /bin ,用户共享库 /usr/lib ,你几乎可以看到一个标准 Ubuntu 的文件系统。
那么每一个 layer 又包含着什么呢?那么它将有帮助我们知道哪些 layer 来自基本镜像,以及哪些 layer 是由我们添加的。
重复我们之前查看镜像的过程,我们可以看到 ubuntu:15.04 的所有 layers 。
cac0b96b79417d5163fbd402369f74e3fe4ff8223b655e0b603a8b570bcc76eb 28441336175b9374d04ee75fdb974539e9b8cad8fec5bf0ff8cea6f8571d0114 4631663ba627c9724cd701eff98381cb500d2c09ec78a8c58213f3225877198e c4f8838502da6456ebfcb3f755f8600d79552d1e30beea0ccc62c13a2556da9c
全部属于 ubuntu 的基础镜像, FROM ubuntu:15.04 指令。
知道了这一点我预测属于我们添加的最顶部的 layer 的镜像 6c91b695f2ed98362f511f2490c16dae0dcf8119bcfe2fe9af50305e2173f373 应该是从 COPY app.py /app/ 的指令生成的。
$ tree
.
└── app
└── app.py
这是 layer 中的内容,而且内部的所有内容只是对文件系统添加了 app.py 。
图片辅助
把以上的 layers 和每个镜像包含的 layers 表现为一张图
推荐工具
四、镜像是如何转换为运行的容器呢?
我们现在已经知道一个 docker 镜像内部到达包含了一些什么,那么 docker 又是如何把它转换为正在运行的容器呢?
文件系统
每个容器都有着自己的文件系统, docker 将所有镜像的 layers 获取并合并在一起,以呈现为一个文件系统视图。
这种技术称为 Union Mounting , Docker 支持 Linux 上的几个 Union Mount File 系统,主要是 OverlayFS 和 AUFS 。
但这并非全部,容器运行时的文件系统更改不应该在容器停止后保存到镜像。
要做到这一点的一种方法是将镜像复制为一个副本,这样容器的文件系统就能够和镜像的分离。
但这么做并不是很有效,作为代替( docker 中的做法)是在容器的文件系统的最顶部添加一个支持 Read/Write 的 layer 来代替容器中的文件系统变化。如果你需要修改下面某个镜像的 layer 的文件,则需要将该文件复制到顶部 layer 进行修改。
这被称之为 Copy-on-write 。当容器停止时最顶部的 layer 被丢弃。
可以从 docker文档 的图片中看出容器最顶部的 layer 组成。
五、总结
容器的运行全过程超出了本文的范围。在创建了文件系统之后,除了一些配置一些后续步骤的元数据之外不会再使用到镜像。
为了完整的运行容器,我们需要使用 name spaces 查看进程的内容(文件系统,进程,网络,用户,配置)。
使用 Cgroups 查看进程可以使用哪些资源(内存,CPU,网络,配置)。
和安全功能(Security Features)控制进程的安全限制(Capabilities, AppArmor, SELinux, Seccomp)。
以上所述就是小编给大家介绍的《(翻译)docker 镜像中有什么?》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- 基于 Laravel、Lumen 框架集成百度翻译、有道翻译、Google 翻译扩展包
- KVM镜像制作及挂载镜像文件
- 腾讯发布人工智能辅助翻译 致敬人工翻译
- golang调用baidu翻译api实现自动翻译
- 查看Docker镜像仓库中镜像的所有标签
- Vagrant 使用国内镜像安装插件和 box 镜像
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
jQuery in Action
Bear Bibeault、Yehuda Katz / Manning Publications / 2008-2-17 / USD 39.99
A good web development framework anticipates what you need to do and makes those tasks easier and more efficient; jQuery practically reads your mind. Developers of every stripe-hobbyists and professio......一起来看看 《jQuery in Action》 这本书的介绍吧!