在上一文《Docker实战04|Union File System》中主要介绍了Linux Union File System的基本原理以及实现。相信读完以后可以更加深入的了解Docker关于Union File System的底层实现原理了。
本文继续针对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
由于这是 layer0,所以 chainID 就是 diffID,然后开始计算 layer1 的 chainID:
ChainID(layer1) = SHA256hex(ChainID(layer0) + " " + DiffID(layer1))
layer0 的 chainID 是3a03f…,而 layer1 的 diffID 根据 rootfs 中的数组可知,为f37c6…
计算 ChainID:
#echo -n "sha256:3a03f09d212915b240e9d216069aba5652ed4765c7e4b098c65e71860d47b8e1 sha256:f37c6b555bc81f96fc6352f3d6be66f6c24f043feab35b15eb2bdad09a8c6a0f" | sha256sum| awk '{print $1}'
2089b2e0fb03697ce2198bd03207fa229e27fb8d6d1620b15db60dbc98931a9d
一定注意要加上 “sha256:”和中间的空格“ ” 这两部分
因此 layer1 的 chainID 就是2089b2e…
找到 layerdb 里面以sha256+2089b2e 开头的目录
#cd /var/lib/docker/image/overlay2/layerdb/sha256/2089b2e0fb03697ce2198bd03207fa229e27fb8d6d1620b15db60dbc98931a9d
#cat size
12
# 查看 cache-id 找到 文件系统中的具体位置
#cat cache-id
kejewb3wshnuy5rmxotue6moi
根据 cache-id 进入具体数据存储目录:
可以看到,我们新增的 newfile 就在这里。
在前几篇文章中,依次详细讲解了Namespace、Cgroups以及AUFS。相信看完这三部分,面试官再问你多深的Docker问题,你都可以对答如流了。
在后续的文章中,将会继续讲解如何使用Go语言构造容器、构造镜像。
所有Docker实战内容合集:《Docker就应该这么学》
关注我学习更多的云原生知识。
本文所有内容都是基于「动手写Docker」此书。关注公众号,后台回复“动手写Docker”即可领取。
同时,准备了一份云原生实战大礼包送给大家,关注公众号,后台回复“云原生资料”即可领取。