在上一文《Docker实战03|Cgroups》中主要介绍了Cgroups基本原理以及实现。相信读完以后可以更加深入的了解Linux Cgroups的底层实现原理了。
本文继续针对Docker 在构建时都干了哪些事情做一些详细的讲解。
本系列所有代码均已经开源。关公众号回复「Go语言实现Docker」即可获得。
以构建镜像方式演示以下 docker 是如何使用 overlayfs 的。
先拉一下 Ubuntu:20.04 的镜像:
~# docker pull ubuntu:20.04
20.04: Pulling from library/ubuntu
527f5363b98e: Pull complete
Digest: sha256:f2034e7195f61334e6caff6ecf2e965f92d11e888309065da85ff50c617732b8
Status: Downloaded newer image for ubuntu:20.04
docker.io/library/ubuntu:20.04
然后写个简单的 Dockerfile :
FROM ubuntu:20.04
RUN echo "Hello world" > /tmp/newfile
开始构建:
docker build -t hello-ubuntu .
使用docker history
命令,查看镜像使用的 image layer 情况:
带 missing 标记的 layer 是自 Docker 1.10 之后,一个镜像的 image layer image history 数据都存储在 个文件中导致的,这是 Docker 官方认为的正常行为。
可以看到,c9afac9d4fa7 这一层在最上面,只用了 12Bytes,而下面的两层都是共享的,这也证明了 AUFS 是如何高效使用磁盘空间的。
然后去找一下具体的文件:
docker 默认的存储目录是/var/lib/docker,具体如下:
在这里,我们只关心image和overlay2就足够了。
先看 image目录:
docker 会在/var/lib/docker/image目录下按每个存储驱动的名字创建一个目录,如这里的overlay2。
这里的关键地方是imagedb和layerdb目录,看这个目录名字,很明显就是专门用来存储元数据的地方。
因为 docker image 是由 layer 组成的,而 layer 也已复用,所以分成了 layerdb 和 imagedb。
先去 imagedb 看下刚才构建的镜像:
可以看到,都是 64 位的 ID,这些就是具体镜像信息,刚才构建的镜像 ID 为c9afac9d4fa7,所以就找c9afac9d4fa7开头的文件:
这就是 image 的 metadata,这里主要关注 rootfs:
"rootfs":{"type":"layers","diff_ids":["sha256:3a03f09d212915b240e9d216069aba5652ed4765c7e4b098c65e71860d47b8e1","sha256:f37c6b555bc81f96fc6352f3d6be66f6c24f043feab35b15eb2bdad09a8c6a0f"]}}
可以看到 rootfs 的 diff_ids 是一个包含了两个元素的数组,这两个元素就是组成 hello-ubuntu 镜像的两个 Layer 的diffID。
从上往下看,就是底层到顶层,即3a03f09d212915b…是 image 的最底层。
然后根据 layerID 去layerdb目录寻找对应的 layer:
# tree -L 2 layerdb/
layerdb/
├── mounts
├── sha256
└── tmp
在这里我们只管mounts和sha256两个目录,先打印以下 sha256 目录
可以看到,layer 里也是 64 位随机 ID 构成的目录,找到刚才 hello-ubuntu 镜像的最底层 layer:
文件含义如下:
docker 使用了 chainID 的方式来保存 layer,layer.ChainID 只用本地,根据 layer.DiffID 计算,并用于 layerdb 的目录名称。
chainID 唯一标识了一组(像糖葫芦一样的串的底层)diffID 的 hash 值,包含了这一层和它的父层(底层),
# cat diff
sha256:3a03f09d212915b240e9d216069aba5652ed4765c7e4b098c65e71860d47b8e1