目录
作者:木子(才云)
编辑:Sarah(K8sMeetup)
docker pull –> docker tag –> docker push
。其镜像同步的流程如下图所示:docker pull –> docker tag –> docker push
的方式。我在《深入浅出容器镜像的一生》 中分析过:在 docker pull 和 docker push 的过程中 Docker 守护进程都会对镜像的 layer 做解压缩的操作,这是极其耗时和浪费 CPU 资源的。docker pull –> docker tag –> docker push
的性能问题,当时第一个方案想到的就是使用 Skopeo 来替代它。使用 skopeo copy
直接将镜像从一个 registry 复制到另一个 registry 中。这样可以避免 Docker 守护进程对镜像的 layer 进行解压缩而带来的性能损耗。关于 Skopeo 的使用和其背后的原理可以参考我之前的博客 《镜像搬运工 Skopeo 初体验》。使用 Skopeo 之后镜像同步比之前快了很多,平均快了 5 倍左右。写时复制
。就好比如 docker run 启动一个容器,在容器内进行修改和删除文件的操作,这些操作并不会影响到镜像本身。因为 Docker 使用 overlay2 联合挂载的方式将镜像的每一层挂载为一个 merged 的层。在容器内看到的就是这个 merged 的层,在容器内对 merged 层文件的修改和删除操作是通过 overlay2 的 upper 层完成的,并不会影响到处在 lower 层的镜像本身。贴一张 Docker 官方文档 Use the OverlayFS storage driver 的一张图片:LowerDir: these are the read-only layers of an overlay filesystem. For docker, these are the image layers assembled in order.
UpperDir: this is the read-write layer of an overlay filesystem. For docker, that is the equivalent of the container specific layer that contains changes made by that container.
WorkDir: this is a required directory for overlay, it needs an empty directory for internal use.
MergedDir: this is the result of the overlay filesystem. Docker effectively chroot’s into this directory when running the container.
-
如何清理旧数据? -
如何复用历史的镜像? -
如何区分出历史的镜像和本次的镜像? -
如何保障本次镜像同步的结果只包含本次需要的镜像?
-
镜像的 manifests -
镜像的 image config 文件 -
镜像的 layer 层文件
debian:buster
,那么对于整个 registry 镜像仓库而言,只需要存一份 debian:buster
镜像的 layer 即可。k8s.gcr.io
复制到本地的一个镜像仓库时,复制完第一个镜像后,在 copy 后面的镜像时都会提示 Copying blob 83b4483280e5 skipped: already exists
的日志信息。这是因为这些镜像使用的是同一个 base 镜像,这个 base 镜像只包含了一个 layer,也就是 83b4483280e5
这一个 blob 文件。虽然本地的镜像仓库中没有这些镜像的 base 镜像,但是有 base 镜像的 layer,Skopeo 也就不会再 copy 这个相同的 blob。_layers
这个目录,而这个目录下的内容正是指向镜像 layer 和 image config 的 link 文件。也就是说:只要某个镜像的 _layers 下有指向 blob 的 link 文件,并且该 link 文件指向的 blobs 下的 data 文件确实存在,那么在 push 镜像的时候 registry 就会向客户端返回该 blob 已经存在,而 Skopeo 就会略过处理已经存在的 blob。以此,我们就可以达到复用历史数据的目的。_layers
目录即可;_manifests
目录下是镜像的 tag 我们并不需要他们;_uploads
目录则是 push 镜像时的临时目录可有可无。那么我们最终需要的历史镜像仓库中的文件就如下图所示:Dockerfile
如下:-
然后使用这个 Dockerfile
构建一个镜像,并命名为registry:v0.1.0-base
,使用这个镜像来 docker run 一个容器。
-
接着同步镜像。
-
同步完成镜像之后,需要删除掉 repositories 下没有生成 _manifests 目录的镜像。因为如果本次同步镜像有该镜像的话,会在 repositories 目录下重新生成 _manifests 目录,如果没有生成的话就说明本次同步的列表中不包含该镜像。通过这种方式可以解决如何区分出历史镜像和本次镜像的问题,又能保障本次镜像同步的结果只包含本次需要的镜像。
-
最后还需要使用 registry GC 来删除掉 blobs 目录下没有被引用的文件。
-
再使用 docker cp 的方式将镜像从容器里复制出来并打包成一个 tar 包。
docker run -v
参数将这个 merged 目录以 bind 的方式挂载到 registry 容器内呢?下面我们就做一个简单的验证和测试:-
首先创建 overlay2 需要的目录。
-
将历史镜像仓库数据放到 lower 目录内。
-
删除所有镜像的 _manifests 目录,让 registry 认为里面没有镜像只有 blobs 数据。
-
模拟容器的启动,使用 overlay2 联合挂载为一层 merged 层。
-
docker run 启动一个 registry ,并将 merged 目录挂载到容器内的 /var/lib/registry/docker
目录。
-
同步镜像,将本次发布需要的镜像同步到 registry 中。
-
同步完成镜像后,进行 registry GC,删除无用的 blob 数据。
-
最后打包 merged 目录,就是本次最终的结果。

-
第一次同步镜像的时候将镜像同步到 overlay2 镜像仓库,这个镜像仓库中的镜像将作为第二次镜像同步的 lower 层。
-
第一次镜像同步完成之后,先清理掉 overlay2 的 merged、upper、work 这三层,只保留 lower 层。因为 lower 层里保留着第一次镜像同步的结果。
-
接下来就是使用 mount 挂载 overlay2,挂载完成之后进入到 merged 层删除掉所有的 _manifests 目录。
-
接着进行第二次的镜像同步,这一次的同步目的是重新建立 _manifests 目录。
-
第二次同步完成之后再使用自制的 registry GC 脚本来删除不必要的 blob 文件和 link 文件。 -
最后将镜像仓库存储目录打包就得到了本次需要的镜像啦。
参考链接:https://mp.weixin.qq.com/s/Q9cyspwKXKzTMqifwlL-LQ