乐趣区

关于docker:Docker-registry-GC-原理分析

作者:木子(才云)

编辑:Bach(才云)

校对:bot(才云)

此前,《Docker 容器镜像是怎么炼成的》一文简略提到了容器镜像的一些常识,并介绍了镜像在 registry 中存储的目录构造。本文从文件系统层面剖析了 registry GC 的原理,相比源码剖析更加直观。

部署 registry 容器

首先咱们在本地部署一个 registry 容器,再应用 skopeo 工具代替 Docker 命令行客户端进行 copy 镜像和 delete 镜像。

自签 SSL 证书

这样咱们在应用 skopeo 时不必加额定参数。

信赖证书,依据不同的发行版抉择相应的门路和命令行即可。

创立明码 auth 认证 auth.htpasswd 文件

因为 push 镜像和 delete 镜像是通过 HTTP 申请 registry 的 API 实现的,每个申请都须要一个 token 能力实现操作,这个 token 则须要应用 AUTH 文件进行鉴权。咱们应用 htpasswd 来生成一个明文的用户、明码即可。

启动 registry 容器,docker run!

  • -v /var/lib/registry:/var/lib/registry,将本地的存储目录挂载到容器内的 registry 存储目录下。
  • -v pwd/certs:/certs,将生成的 SSL 证书挂载到容器内。
  • -e REGISTRY_STORAGE_DELETE_ENABLED=true,增加该参数能力进行 DELETE 镜像操作,不然的话会提醒 Error in deleting repository in a private registry V2 #1573 这种谬误。

docker login

这一步是为了在 ~/.docker/.config.json 中增加 auth 认证,不便前面应用 skopeo。

copy 镜像到 registry

registry 存储目录

这是 registry 容器内的 /var/lib/registry/docker/registry/v2 存储目录。联合上图,通过 tree 目录咱们能够清晰地看到:registry 存储目录下只有两种文件名的文件,一个是 data 文件,一个是 link 文件。其中 link 文件是一般的文本文件,寄存在 repositories 目录下,其内容是指向 data 文件的 sha256 digest 值。

data 文件寄存在 blobs 目录下, 文件又分为了三种文件,一个是镜像每一层的 layer 文件和镜像的 config 文件,以及镜像的 manifest 文件。

repositories 目录下每个镜像的 _layers/sha256 目录下文件夹名是镜像的 layer 和 config 文件的 digest,该目录下的 link 文件就是指向对应 blobs 目录下的 data 文件。当咱们 pull 一个镜像的 layer 时,是通过 link 文件找到 layer 在 registry 中理论的存储地位的。

_manifests 文件夹下的 tags 和 revisions 目录下的 link 文件则指向该镜像的 manifest 文件,保留在所有历史镜像 tag 的 manifest 文件 的 link。当删除一个镜像时,只会删除该镜像最新的 tag 的 link 文件。

tags 目录下的文件夹名例如 3.10,就是该镜像的 tag,在其子目录下的 current/link 文件则记录了以后 tag 指向的 manifest 文件地位。例如 alpine:latest 镜像,每次 push 新的 latest 镜像时,current/link 都会更新并指向最新镜像的 manifest 文件。

咱们察看删除镜像时文件的变动,就能够得悉通过 registry API 进行 delete 操作能够转换成文件系统层面上对 link 文件的删除操作。

blobs 存储目录,寄存了镜像的三个必须文件,layermanifestconfig。通过文件大小咱们能够大抵推算出最大的 2.7M 是镜像的 layer。

image layer 文件,gzip 格局的 tar 包,是镜像层实在内容的 tar.gzip 格局存储模式。

image manifest 文件,json 文件格式,用于寄存该镜像 layerimage config 文件的索引。

image config 文件,json 格局,是构建时生成的。依据 Dockerfile 和宿主机的一些信息,以及一些构建过程中的容器,能够生成 digest 惟一的 image config 文件。仔细观察 image config 文件,咱们能够发现无论是 manifest 还是 config 文件外面的内容都没有镜像的名称和 tag。其实,镜像就好比一个文件,文件的内容和文件名毫无关系。在 registry 中,是通过路径名的形式来对一个镜像进行命名的。当咱们往 registry 中 push 一个镜像时,以 localhost/library/alpine:3.10 为例,localhost 就是该 registry 的域名或者 URL;library 就是 project;alpine:3.10 就是镜像名和镜像的 tag。registry 会依据 localhost/library/alpine:3.10repositories 目录下顺次创立相应的目录。

咱们再往 registry 中 copy 一个镜像,不便前面的剖析过程。

这个 registry 中只有 alpine:3.10debian:buster-slim 这两个根底镜像,此时的 registry 存储目录的构造如下:

DELETE 镜像

这里通过 skopeo delete 删除镜像,留神,通过 registry 的 API 删除镜像每次只能删除一个 tag 镜像。

察看删除后的 registry 存储目录下,alpine 目录里都少了哪些货色?

咱们能够看到,通过 skopeo delete 一个镜像的时候,只对 _manifests 下的 link 文件进行了操作,删除的都是该 tag 镜像 manifest 文件夹下的 link 文件,实际上 manifest 文件并没有从 blobs 目录下删除,只是删除了该镜像的 manifest 文件的援用。删除一个镜像后,tags 目录下的 tag 名目录就被删除了,_manifests/revisions 目录下的 link 文件也被删除了。实际上两者删除的是同一个内容,即对该镜像 manifest 文件的 link 文件。

从下面文件的变动能够得出,通过 registry API 来 delete 一个镜像本质上是删除 repositories 元数据文件夹下的 tag 名文件夹和该 tag 的 revisions 下的 link 文件。

K8sMeetup

registry GC 原理

下面讲了文件系统层面后,咱们再回到本文主题 registry GC 原理。

GC 是什么?

GC(Garbage collection)指垃圾回收。此前,《Kubernetess 中的垃圾回收》一文对 GC 的概念、策略以及实现办法有过简略的介绍。当初,咱们通过 Docker 官网文档 Garbage collection 的例子对其进一步理解。

如果有镜像 A 和镜像 B,别离援用了 layer a、b 和 a、c。

通过 registry API 删除镜像 B 之后,layer c 并没有删掉,只是删掉了对它的援用,所以 c 是多余的。

GC 之后,layer c 就被删掉了,这样就没有无用的 layer 了。

GC 的过程

通过 registry GC 的源码 garbagecollect.go,咱们能够看到 GC 次要分两个阶段,marking 和 sweep。

marking

marking 阶段是扫描所有的 manifest 文件。依据上文提到的 link 文件,扫描所有镜像 tags 目录下的 link 文件就能够失去这些镜像的 manifest,在 manifest 中保留在该镜像所有的 layer 和 config 文件的 digest 值,把这些值标记为不能革除。

这一阶段能够用 shell 脚本来实现:应用 shell 遍历 manifest,而后再 grep 出所有的 sha256 值就能失去这个镜像所有的 blobs 目录下的 data 文件。

sweep

第二阶段是删除操作,marking 后,没有标记 blob(layer 和 config 文件)就会被革除掉。

GC 做了什么?

接下来咱们进行理论的 GC 操作,进入到 registry 容器中,应用 registry garbage-collect 这个子命令进行操作。

marking

sweep

GC 之后的 registry 存储目录是什么样子?

依据 GC 后的 registry 存储目录咱们能够看到,本来 blobs 目录下有 6 个 data 文件,当初曾经变成了 3 个,alpine:3.10 这个镜像相干的 layer、config、manifest 这三个文件都曾经被 GC 掉,然而在 repositories 目录下,该镜像的 _layers 下的 link 文件仍旧存在。

总结

总结以上,用上面这三张图片就能直观地展现这些过程。

delete 镜像之前的 registry 存储目录构造

delete 镜像之后的 registry 存储目录构造

GC 之后的 registry 存储目录构造

shell 实现

依据下面的 GC 原理和过程,实际上咱们能够应用不到 25 行的 shell 脚本来实现一个简略的 GC。

  • 遍历所有镜像的 tag 下最新的 link 文件指向的 manifest。
  • 依据 manifest 文件 grep 出 sha256 值的 image config 和 layer 文件,保留到 all_blobs.list 文件中。
  • 应用 findfor 循环遍历所有 blobs 下的的 data 文件,判断它是否在 all_blobs.list 中,不在的话间接 rm -rf 删除。
  • 最初重启 registry 容器。

这个脚本能够持续优化,将所有的 blob 的 sha256 值截取前 12 位保留在一个变量中,再通过 =~ 来判断蕴含关系来代替 grep。

K8sMeetup

防止踩坑

The operation is unsupported.(405 Method Not Allowed)

在 registry 容器启动的时候增加变量开启 REGISTRY_STORAGE_DELETE_ENABLED=true 即可,或者批改容器内的配置文件 /etc/docker/registry/config.yml,在 storage: 下增加上面的参数。

GC 不彻底,残留 link 文件

从下面咱们能够得悉,registry 无论是删除一个镜像还是进行 GC 操作,都不会删除 repositories 目录下的 _layers/sha256/digest/link 文件,在进行 GC 之后,一些镜像 layer 和 config 文件曾经在 blobs 存储目录下删除了,但指向它的 layers/link 文件仍旧保留在 repositories 目录下。GitHub 上有 PR Remove the layer’s link by garbage-collect #2288 能够清理这些无用的 layer link 文件的。

曾经被 GC 的 blob layer link 文件能够应用上面这个脚本删除。依据 layer link 的值 blobs 目录下查看该文件是否存在,不存在的话就 rm -rf 删除,存在的话就留着。这样就能清理洁净。

GC 后要重启!

GC 之后肯定要重启,因为 registry 容器缓存了镜像 layer 的信息,在删除掉一个镜像 A,后边 GC 掉该镜像的 layer 之后,如果不重启 registry 容器,当从新 push 镜像 A 的时候就会提醒镜像 layer 曾经存在,不会从新上传 layer,但实际上曾经被 GC 掉了,最终会导致镜像 A 不残缺,无奈 pull 到该镜像。

GC 不是事务性操作

GC 的时候最好暂停 push 镜像,免得把正在上传的镜像 layer 给 GC 掉。

原文地址:https://mp.weixin.qq.com/s/D8…

退出移动版