(翻译)docker 镜像中有什么?

栏目: 编程工具 · 发布时间: 7年前

内容简介:翻译自这是一个很好的问题,在你知道答案之前,我不仅想告诉你答案,还想告诉你我是如何找到这个答案的。

翻译自 whats-in-a-docker-image

这是一个很好的问题,在你知道答案之前, 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 镜像中有什么?

推荐工具

手工的对镜像进行分析,是比较的困难,但是这还是比较值得我们去做一次的。

如果你日后还是需要快捷对镜像进行分析,可以使用 dive 这款开源工具。

(翻译)docker 镜像中有什么?

四、镜像是如何转换为运行的容器呢?

我们现在已经知道一个 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 组成。

(翻译)docker 镜像中有什么?

五、总结

容器的运行全过程超出了本文的范围。在创建了文件系统之后,除了一些配置一些后续步骤的元数据之外不会再使用到镜像。

为了完整的运行容器,我们需要使用 name spaces 查看进程的内容(文件系统,进程,网络,用户,配置)。

使用 Cgroups 查看进程可以使用哪些资源(内存,CPU,网络,配置)。

和安全功能(Security Features)控制进程的安全限制(Capabilities, AppArmor, SELinux, Seccomp)。


以上所述就是小编给大家介绍的《(翻译)docker 镜像中有什么?》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。

离心力:互联网历史与数字化未来

离心力:互联网历史与数字化未来

[英] 乔尼·赖安(Johnny Ryan) / 段铁铮 / 译言·东西文库/电子工业出版社 / 2018-2-1 / 68.00元

★一部详实、严谨的互联网史著作; ★哈佛、斯坦福等高校学生必读书目; ★《互联网的未来》作者乔纳森·L. 齐特雷恩,《独立报》《爱尔兰时报》等知名作者和国外媒体联合推荐。 【内容简介】 虽然互联网从诞生至今,不过是五六十年,但我们已然有必要整理其丰富的历史。未来的数字世界不仅取决于我 们的设想,也取决于它的发展历程,以及互联网伟大先驱们的理想和信念。 本书作者乔尼· ......一起来看看 《离心力:互联网历史与数字化未来》 这本书的介绍吧!

HTML 压缩/解压工具
HTML 压缩/解压工具

在线压缩/解压 HTML 代码

图片转BASE64编码
图片转BASE64编码

在线图片转Base64编码工具

随机密码生成器
随机密码生成器

多种字符组合密码