本节书摘来自华章社区《Docker进阶与实战》一书中的第3章,第3.3节Docker image的组织结构,作者华为Docker实践小组,更多章节内容可以访问云栖社区“华章社区”公众号查看
3.3 Docker image的组织结构
上节讲到Docker image是用来启动容器的只读模板,提供容器启动所需要的rootfs,那么Docker是怎么组织这些数据的呢?
3.3.1 数据的内容
Docker image包含着数据及必要的元数据。数据由一层层的image layer组成,元数据则是一些JSON文件,用来描述数据(image layer)之间的关系以及容器的一些配置信息。下面使用overlay存储驱动对Docker image的组织结构进行分析,首先需要启动Docker daemon,命令如下:
# docker daemon -D –s overlay –g /var/lib/docker
这里从官方镜像库下载busybox镜像用作分析。由于前面已经下载过该镜像,所以这里并没有重新下载,而只是做了简单的校验。可以看到Docker对镜像进行了完整性校验,这种完整性的凭证是由镜像仓库提供的。相关内容会在后面的章节提到,这里不再展开
介绍。
$ docker pull busybox
Using default tag: latest
latest: Pulling from library/busybox
cf2616975b4a: Already exists
8c2e06607696: Already exists
Digest: sha256:38a203e1986cf79639fb9b2e1d6e773de84002feea2d4eb006b52004ee8502d
Status: Image is up to date for busybox:latest
$ docker history busybox #为了排版对结果做了一些整理
IMAGE CREATED CREATED BY SIZE
8c2e06607696 4 months ago 0 B
6ce2e90b0bc7 4 months ago /bin/sh -c #(nop) ADD file 2.43 MB
cf2616975b4a 4 months ago /bin/sh -c #(nop) MAINTAINER 0 B
该镜像包含cf2616975b4a、6ce2e90b0bc7、 8c2e06607696三个layer。让我们先到本地存储路径一探究竟吧。
# ls -l /var/lib/docker
total 44
drwx------ 2 root root 4096 Jul 24 18:41 containers #存放容器运行相关信息
drwx------ 3 root root 4096 Apr 13 14:32 execdriver
drwx------ 6 root root 4096 Jul 24 18:43 graph #Image各层的元数据
drwx------ 2 root root 4096 Jul 24 18:41 init
-rw-r--r-- 1 root root 5120 Jul 24 18:41 linkgraph.db
drwxr-xr-x 5 root root 4096 Jul 24 18:43 overlay #Image各层数据
-rw------- 1 root root 106 Jul 24 18:43 repositories-overlay #Image总体信息
drwx------ 2 root root 4096 Jul 24 18:43 tmp
drwx------ 2 root root 4096 Jul 24 19:09 trust #验证相关信息
drwx------ 2 root root 4096 Jul 24 18:41 volumes #数据卷相关信息
1.总体信息
从repositories-overlay文件可以看到该存储目录下的所有image以及其对应的layer ID。为了减少干扰,实验环境之中只包含一个镜像,其ID为8c2e06607696bd4af,如下。
# cat repositories-overlay |python -m json.tool
{
"Repositories": {
"busybox": {
"latest": "8c2e06607696bd4afb3d03b687e361cc43cf8ec1a4a725bc96e39f05ba97dd55"
}
}
}
2.数据和元数据
graph目录和overlay目录包含本地镜像库中的所有元数据和数据信息。对于不同的存储驱动,数据的存储位置和存储结构是不同的,本章不做深入的讨论。可以通过下面的命令观察数据和元数据中的具体内容。元数据包含json和layersize两个文件,其中json文件包含了必要的层次和配置信息,layersize文件则包含了该层的大小。
# ls -l graph/8c2e06607696bd4afb3d03b687e361cc43cf8ec1a4a725bc96e39f05ba97dd55/
total 8
-rw------- 1 root root 1446 Jul 24 18:43 json
-rw------- 1 root root 1 Jul 24 18:43 layersize
# ls -l overlay/8c2e06607696bd4afb3d03b687e361cc43cf8ec1a4a725bc96e39f05ba97dd55/
total 4
drwxr-xr-x 17 root root 4096 Jul 24 18:43 root
可以看到Docker镜像存储路径下已经存储了足够的信息,Docker daemon可以通过这些信息还原出Docker image:先通过repositories-overlay获得image对应的layer ID;再根据layer对应的元数据梳理出image包含的所有层,以及层与层之间的关系;然后使用联合挂载技术还原出容器启动所需要的 rootfs和一些基本的配置信息。
3.3.2 数据的组织
从上节看到,通过repositories-overlay可以找到某个镜像的最上层layer ID,进而找到对应的元数据,那么元数据都存了哪些信息呢?可以通过docker inspect得到该层的元数据。为了简单起见,下面的命令输出中删除了一些与讨论无关的层次信息。
注
意 docker inspect并不是直接输出磁盘中的元数据文件,而是对元数据文件进行了整理,使其更易读,比如标记镜像创建时间的条目由created改成了Created;标记容器配置的条目由container_config改成了ContainerConfig,但是两者的数据是完全一致的。
$ docker inspect busybox:latest
[
{
"Id": "8c2e06607696bd4afb3d03b687e361cc43cf8ec1a4a725bc96e39f05ba97dd55",
"Parent": "6ce2e90b0bc7224de3db1f0d646fe8e2c4dd37f1793928287f6074bc451a57ea",
"Comment": "",
"Created": "2015-04-17T22:01:13.062208605Z",
"Container": "811003e0012ef6e6db039bcef852098d45cf9f84e995efb93a176a11e9aca6b9",
"ContainerConfig": {
"Hostname": "19bbb9ebab4d",
"Domainname": "",
"User": "",
"AttachStdin": false,
"AttachStdout": false,
"AttachStderr": false,
"ExposedPorts": null,
"PublishService": "",
"Tty": false,
"OpenStdin": false,
"StdinOnce": false,
"Env": null,
"Cmd": [
"/bin/sh",
"-c",
"#(nop) CMD [\"/bin/sh\"]"
],
"DockerVersion": "1.6.0",
"Author": "Jevome Petazzoni \u003cjerome@docker.com\u003e",
"Config": {
"Hostname": "19bbb9ebab4d",
"Domainname": "",
"User": "",
"AttachStdin": false,
"AttachStdout": false,
"AttachStderr": false,
"ExposedPorts": null,
"PublishService": "",
"Tty": false,
"OpenStdin": false,
"StdinOnce": false,
"Env": null,
"Cmd": [
"/bin/sh"
],
"Architecture": "amd64",
"Os": "linux",
"Size": 0,
"VirtualSize": 2433303,
"GraphDriver": {
"Name": "aufs",
"Data": null
}
}
]
对于上面的输出,有几项需要重点说明一下:
Id:Image的ID。通过上面的讨论,可以看到image ID实际上只是最上层的layer ID,所以docker inspect也适用于任意一层layer。
Parent:该layer的父层,可以递归地获得某个image的所有layer信息。
Comment:非常类似于Git的commit message,可以为该层做一些历史记录,方便其他人理解。
Container:这个条目比较有意思,其中包含哲学的味道。比如前面提到容器的启动需要以image为模板。但又可以把该容器保存为镜像,所以一般来说image的每个layer都保存自一个容器,所以该容器可以说是image layer的“模板”。
Config:包含了该image的一些配置信息,其中比较重要的是:“env”容器启动时会作为容器的环境变量;“Cmd”作为容器启动时的默认命令;“Labels”参数可以用于docker images命令过滤。
Architecture:该image对应的CPU体系结构。现在Docker官方支持amd64,对其他体系架构的支持也在进行中。
通过这些元数据信息,可以得到某个image包含的所有layer,进而组合出容器的rootfs,再加上元数据中的配置信息(环境变量、启动参数、体系架构等)作为容器启动时的参数。至此已经具备启动容器必需的所有信息。