摘要:内容为一个,指向本地的真正存储位置。但是,因为该镜像只有一层,很多关联关系并没有很好的体现,接下来用一个稍微复杂的镜像再过一遍上述过程。
写在前面
在使用Docker时候,针对镜像的操作一般就是docker pull,docker build,docker commit(刚开始接触Docker的时候,还不会Dockerfile,经常使用这个命令,但是经历了一次血的教训,我已经放弃这个命令很久)这些操作,大概都知道Images在Docker中是由无数个Layer组成,但是,Image在本地是如何存储的?上述操作又会对本地存储带来怎样的变化?抱着学习的态度,我从刚刚安装完docker开始,一步一步研究docker image的目录结构和含义。
本人也只是docker初学者,写文章的目的也是希望自己不仅仅停留在会使用docker的阶段,还能够边用边学边总结,一方面加深自己的理解,另一方面希望通过这种方式与一起学习Docker的童鞋们交流。如有错误,欢迎批评指正,谢谢。
以前基本都在本地服务器上使用Dockerfile构建镜像,一般来说磁盘的空间都是足够的,而且基本不需要docker save,应用场景也不存在频发启动容器的情况,所以不管是空间还是效率的角度,都没有刻意去压缩构建出来的镜像大小。但是,最近因为需要在VPS上构建,可用的空间严重受限,因此,觉得重写Dockerfile来压缩镜像大小。本以为应该是一件很简单的事情,果然太年轻。直接从dockerfile说起:
FROM alpine ........ RUN apk -U upgrade && apk -v add --no-cache bash curl && apk -v add --no-cache --virtual .build-deps gcc make && apk -v add --no-cache mysql-client libc-dev mariadb-dev && rm -rf /var/cache/apk/* COPY ./startService.sh / ........ RUN make clean && make && make install && apk del .build-deps CMD ["/bin/bash", "/startService.sh"]
实验发现上面的写法,apk del .build-deps这一句加不加,大小都是一样的,也就是说完全没有像预期的一样,卸载环境就可以压缩大小。一通googole,问题得到了解决,大家给出来的原因基本可以总结为:“Image是由多个Layer组成的,后面的Layer没办法修改前面的Layer”,修改一下dockerfile就能解决:
FROM alpine ........ RUN apk -U upgrade && apk -v add --no-cache bash curl && apk -v add --no-cache mysql-client libc-dev mariadb-dev && rm -rf /var/cache/apk/* COPY ./startService.sh / ........ RUN apk -v add --no-cache --virtual .build-deps gcc make && make clean && make && make install && apk del .build-deps CMD ["/bin/bash", "/startService.sh"]
问题确实解决了,也大概能体会到在dockerfile中,最好把中间过程写在一起,减少Layer,但是,为什么会这样?很明显,从最开始的错误理解和现在的不理解,都是因为对Image的实现原理不清楚,所以,决定从Image本地目录结构的角度来分析和理解。如果只想看结论,直接跳到最后吧。
环境Centos 7.4
Docker 18.09.0
因为不同的Docker版本,目录结构有一些差异,下面的操作都是针对V18.09.0,而不同的操作系统会影响默认的存储方式等,这里使用的是Centos 7.4。
接下来的内容,首先根据最初始的Docker环境,拉去一个alpine镜像分析本地目录结构,以及每一个目录或文件的含义;然后基于alpine镜像,从dockerfile中构建一个简单的test-image镜像,完成构建之后进一步分析和验证目录或文件的含义,并分析Image和Layer的关联关系在本地文件系统是如何实现关联的。
一般默认安装启动Docker,所有相关的文件都会存储在/var/lib/docker下面,可以使用tree /var/lib/docker 查看目录结构,而与Image相关的目录主要包括两个:image和overlay2,需要注意overlay2,是存储驱动,不同的操作系统和docker版本可能不太一致,所以在查看目录的时候要结合自己的环境:
/var/lib/docker ├── builder │ └── fscache.db ├── buildkit │ ├── cache.db │ ├── content │ │ └── ingest │ ├── executor │ ├── metadata.db │ └── snapshots.db ├── containerd │ └── daemon │ ├── ........ │ └── tmpmounts ├── containers ├── image │ └── overlay2 │ ├── distribution │ ├── imagedb │ │ ├── content │ │ │ └── sha256 │ │ └── metadata │ │ └── sha256 │ ├── layerdb │ └── repositories.json ├── network │ └── files │ └── local-kv.db ├── overlay2 │ ├── backingFsBlockDev │ └── l ├── plugins │ ├── storage │ │ └── blobs │ │ └── tmp │ └── tmp ├── runtimes ├── swarm ├── tmp ├── trust └── volumes └── metadata.db
因为上面是刚安装完的状态,并没有pull或者build任何镜像,所以目前image目录下只有一些默认的文件或者目录,而且文件和目录也没有存什么有用的信息。现在我们使用docker pull alpine获取一个最简单的镜像。
[root@docker-learn docker]# docker pull alpine Using default tag: latest latest: Pulling from library/alpine cd784148e348: Pull complete Digest: sha256:46e71df1e5191ab8b8034c5189e325258ec44ea739bba1e5645cff83c9048ff1 Status: Downloaded newer image for alpine:latest
上面拉去过程只会产生一个Layer,我们可以通过docker images --digests命令查看拉取的镜像,注意Image ID和digest的区别。
[root@docker-learn docker]# docker images --digests REPOSITORY TAG DIGEST IMAGE ID CREATED SIZE alpine latest sha256:46e71df1e5191ab8b8034c5189e325258ec44ea739bba1e5645cff83c9048ff1 3f53bb00af94 8 days ago 4.41MB
此时,我们可以再看文件系统的变化,为了方便,只展示image目录:
image/ └── overlay2 ├── distribution │ ├── diffid-by-digest │ │ └── sha256 │ │ └── cd784148e3483c2c86c50a48e535302ab0288bebd587accf40b714fffd0646b3 │ └── v2metadata-by-diffid │ └── sha256 │ └── 7bff100f35cb359a368537bb07829b055fe8e0b1cb01085a3a628ae9c187c7b8 ├── imagedb │ ├── content │ │ └── sha256 │ │ └── 3f53bb00af943dfdf815650be70c0fa7b426e56a66f5e3362b47a129d57d5991 │ └── metadata │ └── sha256 ├── layerdb │ ├── sha256 │ │ └── 7bff100f35cb359a368537bb07829b055fe8e0b1cb01085a3a628ae9c187c7b8 │ │ ├── cache-id │ │ ├── diff │ │ ├── size │ │ └── tar-split.json.gz │ └── tmp └── repositories.jsonrepositories.json
这个文件存储了本地的所有images列表,里面目前包含了两个,"alpine:latest"和"alpine@sha256:46e71df1e5191ab8b8034c5189e325258ec44ea739bba1e5645cff83c9048ff1",其实这两个是同一个镜像,你可以在刚刚docker images --digests看到,前者是tag,后者是digest(docker inspect 3f53bb00af94也可以看到相同的效果)。
{ "Repositories": { "alpine": { "alpine:latest": "sha256:3f53bb00af943dfdf815650be70c0fa7b426e56a66f5e3362b47a129d57d5991", "alpine@sha256:46e71df1e5191ab8b8034c5189e325258ec44ea739bba1e5645cff83c9048ff1": "sha256:3f53bb00af943dfdf815650be70c0fa7b426e56a66f5e3362b47a129d57d5991" } } }imagedb目录
imagedb/ ├── content │ └── sha256 │ └── 3f53bb00af9...... └── metadata └── sha256
该目录存储了镜像的相关信息,每个镜像的内容都包含在自己的目录下,目录名即为该镜像的Image ID。
首先是metadata目录,该目录保存每个镜像的parent镜像ID,因为这里的alpine:lasted镜像没有更上层的镜像,所以目录为空,后续我们使用docker build构建一个镜像,再进一步分析。
其次是content目录,该目录下存储了镜像的JSON格式描述信息:
{ "architecture": "amd64", "config": { "ArgsEscaped": true, "AttachStderr": false, "AttachStdin": false, "AttachStdout": false, "Cmd": [ "/bin/sh" ], "Domainname": "", "Entrypoint": null, "Env": [ "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" ], "Hostname": "", "Image": "sha256:49573004c44f9413c7db63cbab336356e7a8843139fca5e68f92d84a56f0e6df", "Labels": null, "OnBuild": null, "OpenStdin": false, "StdinOnce": false, "Tty": false, "User": "", "Volumes": null, "WorkingDir": "" }, "container": "c44d11fa67899a984d66f5542092b474f11ca95cc9b03b1470546f16ec8ce74f", "container_config": { "ArgsEscaped": true, "AttachStderr": false, "AttachStdin": false, "AttachStdout": false, "Cmd": [ "/bin/sh", "-c", "#(nop) ", "CMD ["/bin/sh"]" ], "Domainname": "", "Entrypoint": null, "Env": [ "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" ], "Hostname": "c44d11fa6789", "Image": "sha256:49573004c44f9413c7db63cbab336356e7a8843139fca5e68f92d84a56f0e6df", "Labels": {}, "OnBuild": null, "OpenStdin": false, "StdinOnce": false, "Tty": false, "User": "", "Volumes": null, "WorkingDir": "" }, "created": "2018-12-21T00:21:30.122610396Z", "docker_version": "18.06.1-ce", "history": [ { "created": "2018-12-21T00:21:29.97055571Z", "created_by": "/bin/sh -c #(nop) ADD file:2ff00caea4e83dfade726ca47e3c795a1e9acb8ac24e392785c474ecf9a621f2 in / " }, { "created": "2018-12-21T00:21:30.122610396Z", "created_by": "/bin/sh -c #(nop) CMD ["/bin/sh"]", "empty_layer": true } ], "os": "linux", "rootfs": { "diff_ids": [ "sha256:7bff100f35cb359a368537bb07829b055fe8e0b1cb01085a3a628ae9c187c7b8" ], "type": "layers" } }
解释以下主要的几个部分:
config: 未来根据这个image启动container时,config里面的配置就是运行container时的默认参数。
container: 此处为一个容器ID,一般我们执行docker build构建镜像时,可以看见是不断地生成新的container,然后提交为新的image,此处的容器ID即生成该镜像时临时容器的ID,后面通过docker build构建镜像会进一步验证。
container_config:上述临时容器的配置,可以对比containner_config与config的内容,字段完全一致,验证了config的作用。
history:构建该镜像的所有历史命令
rootfs:该镜像包含的layer层的diff id。
layerdb目录和imagedb目录一样,根据命名即可理解该目录主要用来存储Docker的Layer信息,在只有一个alpine:lasted镜像的情况下,目录结构如下:
layerdb/ ├── sha256 │ └── 7bff100f35cb359a368537bb07829b055fe8e0b1cb01085a3a628ae9c187c7b8 │ ├── cache-id │ ├── diff │ ├── size │ └── tar-split.json.gz └── tmp
在我们docker pull alpine:lasted的时候,可以发现只pull了一层,而在上面imagedb/content中的镜像信息中,rootfs中也只有一个diff,因此,与此处的一个Layer层吻合。但是,需要注意此处的 7bff100f35... 与rootfs中的diff_id 7bff100f35...虽然值一样,但是含义并不相同,此处标识Layer的Chain ID,之所以此处一致,是因为在只有一层Layer,没有parent时,diff id与chain id相等,后面我们构建test-image后再来分析即可看出区别。
在改Layer的目录下,包含四个文件:
diff:该Layer层的diff id
[root@docker-learn overlay2]# cat layerdb/sha256/7bff100f35cb359a368537bb07829b055fe8e0b1cb01085a3a628ae9c187c7b8/diff sha256:7bff100f35cb359a368537bb07829b055fe8e0b1cb01085a3a628ae9c187c7b8
如上面所述,最底层的Layer具有相同的chain id 和 diff id
size:该Layer的大小,单位字节
[root@docker-learn overlay2]# cat layerdb/sha256/7bff100f35cb359a368537bb07829b055fe8e0b1cb01085a3a628ae9c187c7b8/size 4413428
在docker images中,我们可以看到alpine镜像的大小为4.41MB,将此处的大小进行换算 4413428/(1024*1024),发现大小不一致,第一反应是Image相对于Layer还增加了其他信息,但是理论上似乎无法解释,于是使用docker inspect alpine查看了镜像的具体信息,发现其中Size: 4413428,与该处数值一直,那么4.41M应该是4413428/(1000000)计算得来,后面我们会使用test-image镜像进一步验证。
tar-split.json.gz:layer层数据tar压缩包的split文件
该文件生成需要依赖tar-split,通过这个文件可以还原layer的tar包。
cache_id:内容为一个uuid,指向Layer本地的真正存储位置。
[root@docker-learn layerdb]# cat sha256/7bff100f35cb359a368537bb07829b055fe8e0b1cb01085a3a628ae9c187c7b8/cache-id 281c53a74496be2bfcf921ceee5ec2d95139c43fec165ab667a77899b3691d56
那么Layer本地真正的存储位置又在何处呢?便是上面提到的/var/lib/docker/overlay2目录下:
[root@docker-learn overlay2]# ls 281c53a74496be2bfcf921ceee5ec2d95139c43fec165ab667a77899b3691d56 backingFsBlockDev l
需要注意,layerdb目录下除了diff、size、cache_id和tar-split.json.gz文件,还应该包括一个parent文件,文件存储了当前Layer层的父层chain_id,因为当前alpine镜像只有一层,所以也就没有parent。
distribution目录该目录包含了Layer层diif id和digest之间的对应关系。
[root@docker-learn overlay2]# tree distribution/ distribution/ ├── diffid-by-digest │ └── sha256 │ └── cd784148e3483c2c86c50a48e535302ab0288bebd587accf40b714fffd0646b3 └── v2metadata-by-diffid └── sha256 └── 7bff100f35cb359a368537bb07829b055fe8e0b1cb01085a3a628ae9c187c7b8 4 directories, 2 files
v2metadata-by-diffid目录下,我们可以通过Layer的diff id找到对应的digest,并且包含了生成该digest的源仓库。
[ { "Digest": "sha256:cd784148e3483c2c86c50a48e535302ab0288bebd587accf40b714fffd0646b3", "HMAC": "", "SourceRepository": "docker.io/library/alpine" } ]
diffid-by-digest目录则与v2metadata-by-diffid相反
[root@docker-learn overlay2]# cat distribution/diffid-by-digest/sha256/cd784148e3483c2c86c50a48e535302ab0288bebd587accf40b714fffd0646b3 sha256:7bff100f35cb359a368537bb07829b055fe8e0b1cb01085a3a628ae9c187c7b8
到这里为止,基于最简单的alpine镜像,我们看到了Image的本地目录结构,以及每一个目录或文件大概的作用。但是,因为该镜像只有一层,很多关联关系并没有很好的体现,接下来用一个稍微复杂的镜像再过一遍上述过程。
基于docker build test-image进一步理解目录结构一个简单的dockerfile构建test-image
FROM alpine LABEL name="test-image" RUN apk -v add --no-cache bash RUN apk -v add --no-cache curl COPY ./startService.sh / CMD ["/bin/bash", "/startService.sh"]
构建过程输出如下:
[root@docker-learn docker]# docker build -t test-image . Sending build context to Docker daemon 3.072kB Step 1/6 : FROM alpine ---> 3f53bb00af94 Step 2/6 : LABEL name="test-image" ---> Running in 3bd6320fc291 Removing intermediate container 3bd6320fc291 ---> bb97dd1fb1a1 Step 3/6 : RUN apk -v add --no-cache bash ---> Running in f9987ff57ad7 fetch http://dl-cdn.alpinelinux.org/alpine/v3.8/main/x86_64/APKINDEX.tar.gz fetch http://dl-cdn.alpinelinux.org/alpine/v3.8/community/x86_64/APKINDEX.tar.gz (1/5) Installing ncurses-terminfo-base (6.1_p20180818-r1) (2/5) Installing ncurses-terminfo (6.1_p20180818-r1) (3/5) Installing ncurses-libs (6.1_p20180818-r1) (4/5) Installing readline (7.0.003-r0) (5/5) Installing bash (4.4.19-r1) Executing bash-4.4.19-r1.post-install Executing busybox-1.28.4-r2.trigger OK: 18 packages, 136 dirs, 2877 files, 13 MiB Removing intermediate container f9987ff57ad7 ---> a5635f1b1d00 Step 4/6 : RUN apk -v add --no-cache curl ---> Running in c49fb2e4b311 fetch http://dl-cdn.alpinelinux.org/alpine/v3.8/main/x86_64/APKINDEX.tar.gz fetch http://dl-cdn.alpinelinux.org/alpine/v3.8/community/x86_64/APKINDEX.tar.gz (1/5) Installing ca-certificates (20171114-r3) (2/5) Installing nghttp2-libs (1.32.0-r0) (3/5) Installing libssh2 (1.8.0-r3) (4/5) Installing libcurl (7.61.1-r1) (5/5) Installing curl (7.61.1-r1) Executing busybox-1.28.4-r2.trigger Executing ca-certificates-20171114-r3.trigger OK: 23 packages, 141 dirs, 3040 files, 15 MiB Removing intermediate container c49fb2e4b311 ---> 9156d1521a2f Step 5/6 : COPY ./startService.sh / ---> 704626646baf Step 6/6 : CMD ["/bin/bash", "/startService.sh"] ---> Running in 1c5e6e861264 Removing intermediate container 1c5e6e861264 ---> 6cd0a66e83f1 Successfully built 6cd0a66e83f1 Successfully tagged test-image:latest
镜像build过程可以理解为基于一个镜像启动一个容器,在容器内执行Dockerfile里的一条命令,生成一个新的镜像。根据上述的输入,test-image的构建过程可以表示为:
最终生成的test-image镜像ID为 6cd0a66e83f1,我们从该镜像开始,再一次分析本地目录。首先查看镜像的基本信息:
[root@docker-learn docker]# docker images --digests test-image REPOSITORY TAG DIGEST IMAGE ID CREATED SIZE test-image latest6cd0a66e83f1 About an hour ago 9.88MB
如前面所述,digest是有docker repository生成,因为本地构建完之后并没有推送至远程仓库,所以为None。此时,image目录发生了如下变化:
image/ └── overlay2 ├── distribution │ ├── diffid-by-digest │ │ └── sha256 │ │ └── cd784148e3483c2c86c50a48e535302ab0288bebd587accf40b714fffd0646b3 │ └── v2metadata-by-diffid │ └── sha256 │ └── 7bff100f35cb359a368537bb07829b055fe8e0b1cb01085a3a628ae9c187c7b8 ├── imagedb │ ├── content │ │ └── sha256 │ │ ├── 3f53bb00af943dfdf815650be70c0fa7b426e56a66f5e3362b47a129d57d5991 │ │ ├── 6cd0a66e83f133a2bad37103ed03f6480330fa3c469368eb5871320996d3b924 │ │ ├── 704626646baf8bdea82da237819cded076a0852eb97dba2fc731569dd85ae836 │ │ ├── 9156d1521a2fd50d972e1e1abc30d37df7c8e8f7825ca5955170f3b5441b3341 │ │ ├── a5635f1b1d0078cd926f21ef3ed77b357aa899ac0c8bf80cae51c37129167e3a │ │ └── bb97dd1fb1a10b717655594950efb4605ff0d3f2f631feafc4558836c2b34c3c │ └── metadata │ └── sha256 │ ├── 6cd0a66e83f133a2bad37103ed03f6480330fa3c469368eb5871320996d3b924 │ │ ├── lastUpdated │ │ └── parent │ ├── 704626646baf8bdea82da237819cded076a0852eb97dba2fc731569dd85ae836 │ │ └── parent │ ├── 9156d1521a2fd50d972e1e1abc30d37df7c8e8f7825ca5955170f3b5441b3341 │ │ └── parent │ ├── a5635f1b1d0078cd926f21ef3ed77b357aa899ac0c8bf80cae51c37129167e3a │ │ └── parent │ └── bb97dd1fb1a10b717655594950efb4605ff0d3f2f631feafc4558836c2b34c3c │ └── parent ├── layerdb │ ├── mounts │ ├── sha256 │ │ ├── 0e88764cdf90e8a5d6597b2d8e65b8f70e7b62982b0aee934195b54600320d47 │ │ │ ├── cache-id │ │ │ ├── diff │ │ │ ├── parent │ │ │ ├── size │ │ │ └── tar-split.json.gz │ │ ├── 7bff100f35cb359a368537bb07829b055fe8e0b1cb01085a3a628ae9c187c7b8 │ │ │ ├── cache-id │ │ │ ├── diff │ │ │ ├── size │ │ │ └── tar-split.json.gz │ │ ├── 80fe1abae43103e3be54ac2813114d1dea6fc91454a3369104b8dd6e2b1363f5 │ │ │ ├── cache-id │ │ │ ├── diff │ │ │ ├── parent │ │ │ ├── size │ │ │ └── tar-split.json.gz │ │ └── db7c15c2f03f63a658285a55edc0a0012ccd0033f4695d4b428b1b464637e655 │ │ ├── cache-id │ │ ├── diff │ │ ├── parent │ │ ├── size │ │ └── tar-split.json.gz │ └── tmp └── repositories.json
可以看到,相比于只有alpine镜像的时候,在imagedb的content和metadata下,增加了build过程中产生的镜像(镜像ID能够一一对应),在layerdb下增加了三个Layer,目前还看不出来关联关系,后续分析。
repositories.json[root@docker-learn docker]# cat image/overlay2/repositories.json | python -m json.tool { "Repositories": { "alpine": { "alpine:latest": "sha256:3f53bb00af943dfdf815650be70c0fa7b426e56a66f5e3362b47a129d57d5991", "alpine@sha256:46e71df1e5191ab8b8034c5189e325258ec44ea739bba1e5645cff83c9048ff1": "sha256:3f53bb00af943dfdf815650be70c0fa7b426e56a66f5e3362b47a129d57d5991" }, "test-image": { "test-image:latest": "sha256:6cd0a66e83f133a2bad37103ed03f6480330fa3c469368eb5871320996d3b924" } } }
可以看见,增加了test-image这一项,包含其tag和id。
imagedb目录[root@docker-learn overlay2]# tree imagedb/ imagedb/ ├── content │ └── sha256 │ ├── 3f53bb00af943dfdf815650be70c0fa7b426e56a66f5e3362b47a129d57d5991 │ ├── 6cd0a66e83f133a2bad37103ed03f6480330fa3c469368eb5871320996d3b924 │ ├── 704626646baf8bdea82da237819cded076a0852eb97dba2fc731569dd85ae836 │ ├── 9156d1521a2fd50d972e1e1abc30d37df7c8e8f7825ca5955170f3b5441b3341 │ ├── a5635f1b1d0078cd926f21ef3ed77b357aa899ac0c8bf80cae51c37129167e3a │ └── bb97dd1fb1a10b717655594950efb4605ff0d3f2f631feafc4558836c2b34c3c └── metadata └── sha256 ├── 6cd0a66e83f133a2bad37103ed03f6480330fa3c469368eb5871320996d3b924 │ ├── lastUpdated │ └── parent ├── 704626646baf8bdea82da237819cded076a0852eb97dba2fc731569dd85ae836 │ └── parent ├── 9156d1521a2fd50d972e1e1abc30d37df7c8e8f7825ca5955170f3b5441b3341 │ └── parent ├── a5635f1b1d0078cd926f21ef3ed77b357aa899ac0c8bf80cae51c37129167e3a │ └── parent └── bb97dd1fb1a10b717655594950efb4605ff0d3f2f631feafc4558836c2b34c3c └── parent 9 directories, 12 files
在构建test-image之前,metadata目录为空,因为alpine没有parent,在build之后,新增了5个目录,分别对应docker build test-image所产生的5个镜像,每一层以上一层为parent,即parent文件中存储了上一层image id。
content目录中,相比于构建test-image之前,也多了上述5个镜像的内容,以test-image为例(目前的最底层镜像),查看其描述信息:
{ "architecture": "amd64", "config": { "ArgsEscaped": true, "AttachStderr": false, "AttachStdin": false, "AttachStdout": false, "Cmd": [ "/bin/bash", "/startService.sh" ], "Domainname": "", "Entrypoint": null, "Env": [ "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" ], "Hostname": "", "Image": "sha256:704626646baf8bdea82da237819cded076a0852eb97dba2fc731569dd85ae836", "Labels": { "name": "test-image" }, "OnBuild": null, "OpenStdin": false, "StdinOnce": false, "Tty": false, "User": "", "Volumes": null, "WorkingDir": "" }, "container": "1c5e6e861264654f79a190eba5157dd4dedce59ab3de098a3625fb4e5b6f1d98", "container_config": { "ArgsEscaped": true, "AttachStderr": false, "AttachStdin": false, "AttachStdout": false, "Cmd": [ "/bin/sh", "-c", "#(nop) ", "CMD ["/bin/bash" "/startService.sh"]" ], "Domainname": "", "Entrypoint": null, "Env": [ "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" ], "Hostname": "1c5e6e861264", "Image": "sha256:704626646baf8bdea82da237819cded076a0852eb97dba2fc731569dd85ae836", "Labels": { "name": "test-image" }, "OnBuild": null, "OpenStdin": false, "StdinOnce": false, "Tty": false, "User": "", "Volumes": null, "WorkingDir": "" }, "created": "2019-01-01T02:29:19.701494089Z", "docker_version": "18.09.0", "history": [ { "created": "2018-12-21T00:21:29.97055571Z", "created_by": "/bin/sh -c #(nop) ADD file:2ff00caea4e83dfade726ca47e3c795a1e9acb8ac24e392785c474ecf9a621f2 in / " }, { "created": "2018-12-21T00:21:30.122610396Z", "created_by": "/bin/sh -c #(nop) CMD ["/bin/sh"]", "empty_layer": true }, { "created": "2019-01-01T02:29:06.530296297Z", "created_by": "/bin/sh -c #(nop) LABEL name=test-image", "empty_layer": true }, { "created": "2019-01-01T02:29:14.182236016Z", "created_by": "/bin/sh -c apk -v add --no-cache bash" }, { "created": "2019-01-01T02:29:19.327280058Z", "created_by": "/bin/sh -c apk -v add --no-cache curl" }, { "created": "2019-01-01T02:29:19.549474383Z", "created_by": "/bin/sh -c #(nop) COPY file:fff66db7f2d773b25215edcc9d5697d84813835e3b731e5a6afe9a9b9647ecec in / " }, { "created": "2019-01-01T02:29:19.701494089Z", "created_by": "/bin/sh -c #(nop) CMD ["/bin/bash" "/startService.sh"]", "empty_layer": true } ], "os": "linux", "rootfs": { "diff_ids": [ "sha256:7bff100f35cb359a368537bb07829b055fe8e0b1cb01085a3a628ae9c187c7b8", "sha256:b1ddbff022577cd249a074285a1a7eb76d7c9139132ba5aa4272fc115dfa9e36", "sha256:9edc93f4dcf640f272ed73f933863dbefae6719745093d09c6c6908f402b1c34", "sha256:a6c8828ba4b58628284f783d3c918ac379ae2aba0830f4c926a330842361ffb6" ], "type": "layers" } }
主要注意一下几个参数:
container: 这里的容器ID可以与图XX中生成该image的容器ID对应上。
container_config:容器配置状态,可以看出是执行完dockerfile中命令之后的容器状态。
rootfs:镜像包含的Layer的diff id,可以看出test-image镜像包含了四个Layer。当我第一次分析这里的时候,有一点点困惑,在我想象中Dockerfile中的每一条命令对应一个Layer,也就是一个diff id,但是在dockerfile中包含6条命令,这里却只有四层,进一步整理并分析每一个image的rootfs,如下图2所示。可以看到,在 LABEL name="test-image"和CMD ["/bin/bash", "/startService.sh"]这两行并未产生新的Layer。事实上,如果我们认为镜像是一个打包的静态OS,那么Layer可以认为是描述该OS的fs变化,即文件系统中文件或者目录发生的改变,很明显上述两行命令并不会引起fs的变化,只是会写入该镜像的config中,在生成容器时读取即可,自然也就不存在diff id。
至此,解释完了Image相关的目录,总结一下,单个Image的配置信息在content目录中,以image id为文件名存储,Image之间关联信息在metadata中,以parent文件存储。然后,我们根据image生成容器的时候,可是生成了一个文件系统,但是上述这些信息并不包含fs的数据。因为真正的fs数据是存储在Layer中的。如前面所述,Layer的信息存储在layerdb目录下,所以我们转战layerdb目录。
layerdb目录[root@docker-learn overlay2]# tree layerdb/ layerdb/ ├── mounts ├── sha256 │ ├── 0e88764cdf90e8a5d6597b2d8e65b8f70e7b62982b0aee934195b54600320d47 │ │ ├── cache-id │ │ ├── diff │ │ ├── parent │ │ ├── size │ │ └── tar-split.json.gz │ ├── 7bff100f35cb359a368537bb07829b055fe8e0b1cb01085a3a628ae9c187c7b8 │ │ ├── cache-id │ │ ├── diff │ │ ├── size │ │ └── tar-split.json.gz │ ├── 80fe1abae43103e3be54ac2813114d1dea6fc91454a3369104b8dd6e2b1363f5 │ │ ├── cache-id │ │ ├── diff │ │ ├── parent │ │ ├── size │ │ └── tar-split.json.gz │ └── db7c15c2f03f63a658285a55edc0a0012ccd0033f4695d4b428b1b464637e655 │ ├── cache-id │ ├── diff │ ├── parent │ ├── size │ └── tar-split.json.gz └── tmp
相比于只有alpine镜像时,首先,layerdb目录多了一个mounts目录,简单来说,当由镜像生成容器时,该目录下会生成容器的可读可写两个layer,可读即为由镜像生成,而可写就是未来对容器的修改都会放在着了,因为本文只讨论镜像,这个目录就不再深入分析。
其次,目前Layer已经增加至4个,这与我们上一节中看到的test-image的rootfs配置中有4个layer diif id能够对应上,然后,很显然除了第一层的"7bff100f35cb"能对应上,其他三个完全不同。进一步研究才知道这里目录名其实是layer的chain id,而非diff id,关于这两这个的区别,我们可以理解为diff id用来描述单个变化,而chain id用来便于一些列的变化,diff id和chain id之间的计算公式可以在image-spec中看到。
ChainID(A) = DiffID(A) ChainID(A|B) = Digest(ChainID(A) + " " + DiffID(B)) ChainID(A|B|C) = Digest(ChainID(A|B) + " " + DiffID(C))
在这里以test-image的rootfs验证分析他们是如何关联的。
"rootfs": { "diff_ids": [ "sha256:7bff100f35cb359a368537bb07829b055fe8e0b1cb01085a3a628ae9c187c7b8", "sha256:b1ddbff022577cd249a074285a1a7eb76d7c9139132ba5aa4272fc115dfa9e36", "sha256:9edc93f4dcf640f272ed73f933863dbefae6719745093d09c6c6908f402b1c34", "sha256:a6c8828ba4b58628284f783d3c918ac379ae2aba0830f4c926a330842361ffb6" ], "type": "layers" }
ChainID(A) = DiffID(A) = sha256:7bff100f35cb359a368537bb07829b055fe8e0b1cb01085a3a628ae9c187c7b8 ChainID(A|B) = Digest(ChainID(A) + " " + DiffID(B)) ChainID(A) = sha256:7bff100f35cb359a368537bb07829b055fe8e0b1cb01085a3a628ae9c187c7b8 DiffID(B) = sha256:b1ddbff022577cd249a074285a1a7eb76d7c9139132ba5aa4272fc115dfa9e36 计算: [root@docker-learn overlay2]# echo -n "sha256:7bff100f35cb359a368537bb07829b055fe8e0b1cb01085a3a628ae9c187c7b8 sha256:b1ddbff022577cd249a074285a1a7eb76d7c9139132ba5aa4272fc115dfa9e36" | sha256sum - db7c15c2f03f63a658285a55edc0a0012ccd0033f4695d4b428b1b464637e655 - 结果: ChainID(A|B) = sha256:db7c15c2f03f63a658285a55edc0a0012ccd0033f4695d4b428b1b464637e655 chainID(A|B|C) = sha256:0e88764cdf90e8a5d6597b2d8e65b8f70e7b62982b0aee934195b54600320d47 chainID(A|B|C|D) = sha256:80fe1abae43103e3be54ac2813114d1dea6fc91454a3369104b8dd6e2b1363f5
][3]
因此,test-image的第二层Layer对应的目录为:layerdb/sha256/db7c15c2f03f63a658285a55edc0a0012ccd0033f4695d4b428b1b464637e655,查看该Layer的信息:
[root@docker-learn sha256]# ls db7c15c2f03f63a658285a55edc0a0012ccd0033f4695d4b428b1b464637e655/ cache-id diff parent size tar-split.json.gz
与上一节中相比多了parent,包含了上一层Layer的chain id。
destribution目录[root@docker-learn overlay2]# tree distribution/ distribution/ ├── diffid-by-digest │ └── sha256 │ └── cd784148e3483c2c86c50a48e535302ab0288bebd587accf40b714fffd0646b3 └── v2metadata-by-diffid └── sha256 └── 7bff100f35cb359a368537bb07829b055fe8e0b1cb01085a3a628ae9c187c7b8 4 directories, 2 files
目前来看,destribution目录和只有alpine镜像时并没有什么区别,这是因为digest是由镜像仓库生成,本地构建的镜像在没有push到仓库之前,自然也就没有digest。使用docker push命令将test-image推送至dockerhub。
[root@docker-learn distribution]# docker push backbp/test-image:lasted The push refers to repository [docker.io/backbp/test-image] a6c8828ba4b5: Pushed 9edc93f4dcf6: Pushed b1ddbff02257: Pushed 7bff100f35cb: Mounted from library/alpine lasted: digest: sha256:3dc66a43c28ea3e994e4abf6a2d04c7027a9330e8eeab5c609e4971a8c58f0b0 size: 1156
根据过程输出,我们可以看到虽然test-image镜像包括四层Layer,但是因为最底层的7bff100f35cb本来就是在docker pull alpine时,拉取得来,自然也就不需要再push,因此真正push的只有三层。而现在destribution目录也已经增加了对应Layer的digest,Image的digest可以在上面的过程输出中看到。
distribution/ ├── diffid-by-digest │ └── sha256 │ ├── 2826782ee82560ec5f90a8a9da80880d48dd4036763f5250024fab5b3ef8e8cf │ ├── 8e905c02e6908fbb0e591cea285470208920d32408735bd6a8fcaf85ffba9089 │ ├── a5bec9983f6902f4901b38735db9c427190ffcb3734c84ee233ea391da81081b │ └── cd784148e3483c2c86c50a48e535302ab0288bebd587accf40b714fffd0646b3 └── v2metadata-by-diffid └── sha256 ├── 7bff100f35cb359a368537bb07829b055fe8e0b1cb01085a3a628ae9c187c7b8 ├── 9edc93f4dcf640f272ed73f933863dbefae6719745093d09c6c6908f402b1c34 ├── a6c8828ba4b58628284f783d3c918ac379ae2aba0830f4c926a330842361ffb6 └── b1ddbff022577cd249a074285a1a7eb76d7c9139132ba5aa4272fc115dfa9e36拓展思考 docker tag命令发生了什么?
我们通过基于test-image生成一个新的tag,test-image-tag
[root@docker-learn overlay2]# docker tag test-image test-image-tag [root@docker-learn overlay2]# docker images REPOSITORY TAG IMAGE ID CREATED SIZE test-image-tag latest 6cd0a66e83f1 4 hours ago 9.88MB test-image latest 6cd0a66e83f1 4 hours ago 9.88MB alpine latest 3f53bb00af94 11 days ago 4.41MB
查看repositories.json文件会发现两个tag的镜像都指向同一个image id,所以这个命令相当于只有修改了repositories.json。
[root@docker-learn overlay2]# cat repositories.json | python -m json.tool { "Repositories": { "alpine": { "alpine:latest": "sha256:3f53bb00af943dfdf815650be70c0fa7b426e56a66f5e3362b47a129d57d5991", "alpine@sha256:46e71df1e5191ab8b8034c5189e325258ec44ea739bba1e5645cff83c9048ff1": "sha256:3f53bb00af943dfdf815650be70c0fa7b426e56a66f5e3362b47a129d57d5991" }, "test-image": { "test-image:latest": "sha256:6cd0a66e83f133a2bad37103ed03f6480330fa3c469368eb5871320996d3b924" }, "test-image-tag": { "test-image-tag:latest": "sha256:6cd0a66e83f133a2bad37103ed03f6480330fa3c469368eb5871320996d3b924" } } }Image Size是如何计算的?
上述的alpine镜像和test-image镜像大小分别是:4.41M和9.88M。为了更准确的分析,可以使用docker inspect 镜像查看详细大小,分别为4413428和9876099。
再次查看每层Layer的大小(layerdb/layer chain id/size),分别为
Layer1:4413428
Layer2:3815516
Layer3:1647117
Layer4:38
alpine只有一层,所以image size与Layer size相等;test-image有四层,所以image size = sum(Layer1+Layer2+Layer3+Layer4)=9876099
回到最初的问题完成上面的分析,现在再回头看最初的问题,在apk del .build-deps这一层中,执行完之后生成的Layer只是记录相对于上一层删除了安装包,在计算Image size时(或者说在根据Layer合并Image时),因为安装.build-deps而带来的变换依然存在于app add这一层Layer中,所以大小并不会减小。如果将add和del放在同一个命令内,那么生成的该层Layer记录的是相对于上一层的变化,.build-deps的安装和卸载相对于上一层而言,根本就不存在,所以Layer中也就完全不存在。
说到底,还是OCI Image的原理所致,最需要记住的是每一个Layer记录的是与上一层Layer相比的变化。
image-spec
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/27645.html
摘要:你甚至可以运行自己的私有注册表。当你使用或命令时,所需的镜像将从配置的注册表中拉取。本节简要概述其中的一些对象。这允许运行的容器在其本地文件系统中创建或修改文件和目录。启动容器并执行开启容器内的终端。输入以终止命令,容器停止,但未被删除。 Docker是什么 Docker是一个开源的应用容器引擎,让开发者可以打包他们的应用以及依赖包到一个可移植的容器中,然后发布到任何流行的Linux机...
摘要:一个镜像可以放到另一个京广线的顶部,位于下面的镜像称为父镜像,最底部的称为基础镜像。镜像是基于联合文件系统的一种层式的结构,由一系列指令一步步构建处理。拉取镜像使用命令启动一个镜像时,会检查本地是否存在该镜像。 什么是镜像 Docker镜像时由文件系统叠加而成,最底端是一个引导文件系统,即bootfs,这很像典型的Linux/Unix的引导文件系统。Docker用户几乎永远不会和引导...
摘要:联调测试,无需依赖他人。针对以上问题,有两种解决方法,一个是自己搭建私有服务,另一个是用云服务的镜像管理平台如阿里云的容器镜像服务。利用,先对阿里云的服务进行登录。推送后,就能在阿里云的仓库上看到这个镜像。 Docker简述 Docker是一种OS虚拟化技术,是一个开源的应用容器引擎。它可以让开发者将应用打包到一个可移植的容器中,并且该容器可以运行在几乎所有linux系统中(Windo...
摘要:反过来别的上的镜像,也不能在树莓派上运行。如果需要找树莓派专用的镜像,那就在上搜索或相关就能找到了。有一个叫的仓库制作了非常多树莓派专用,可以参考下。树莓派安装,最难的在于正确的选择源和添加,才能找到版本适合的并下载。 最近学习Machine Learning发现好多人都用docker,之前一直听说但是感觉和自己无关。但是现在发现原来docker是个这么方便的东西,可以跨平台(不分什么...
阅读 1335·2021-10-09 09:44
阅读 1351·2021-09-28 09:36
阅读 15774·2021-09-22 15:55
阅读 1205·2021-09-22 15:45
阅读 2179·2021-09-02 09:48
阅读 2705·2019-08-29 17:19
阅读 2281·2019-08-29 10:54
阅读 856·2019-08-23 18:40