原理分析_Docker registry GC 原理分析

98f5232f328b15a94f8b5d1d8ff209aa.png

GC(Garbage Collection)即垃圾回收,本文从文件系统层面分析了 registry GC 原理,相比源码分析更加直观,并提示了一些可能遇到的错误,帮助大家避免踩坑。

作者:木子(才云)

编辑:Bach(才云)

校对:bot(才云)

此前,《Docker 容器镜像是怎么炼成的》一文简单提到了容器镜像的一些知识,并介绍了镜像在 registry 中存储的目录结构。本文从文件系统层面分析了 registry GC 的原理,相比源码分析更加直观。 部署 registry 容器 首先我们在本地部署一个 registry 容器,再使用 skopeo 工具替代 Docker 命令行客户端进行 copy 镜像和 delete 镜像。 自签 SSL 证书 这样我们在使用 skopeo 时不用加额外参数。

d0d4c457cda6c1dae486d53d8da3f7bf.png

信任证书,根据不同的发行版选择相应的路径和命令行即可。

54730fc7fd0a0bc54afee1fba705c2ca.png

创建密码 auth 认证 auth.htpasswd 文件 由于 push 镜像和 delete 镜像是通过 HTTP 请求 registry 的 API 完成的,每个请求都需要一个 token 才能完成操作,这个 token 则需要使用 AUTH 文件进行鉴权。我们使用  htpasswd  来生成一个明文的用户、密码即可。

bdd89869adca1b77e2374b2e4844ebb0.png

启动 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 这种错误。

7ff999699c048d32eccacbe360ed34fd.png

docker login 这一步是为了在  ~/.docker/.config.json  中添加 auth 认证,方便后面使用 skopeo。

10bd523c73106adfef214b5add3631e7.png

copy 镜像到 registry

7fa8f9939ce10eae3d6388f707c744de.png

registry 存储目录

43bc5f44333755f1623b050de1522c7d.png

这是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 文件的删除操作。

48d6fb273f0b08d3b677a571a5c1268f.png

blobs  存储目录,存放了镜像的三个必须文件, layermanifestconfig 。通过文件大小我们可以大致推算出最大的 2.7M 是镜像的 layer 。

d08ae216437e1adba8205a09b6983b15.png

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

ba17acb7bf237316edd630b1848115b1.png

image manifest  文件,json 文件格式,用于存放该镜像  layer  和  image config  文件的索引。

31235b07c3df85a40a85c478f5b357fe.png

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.10  在  repositories  目录下依次创建相应的目录。

e1732d30ab248c9a4a5ccc4481596ab2.png

我们再往 registry 中 copy 一个镜像,方便后面的分析过程。

d1820767beae328898110142e05505cd.png

这个 registry 中只有  alpine:3.10  和  debian:buster-slim  这两个基础镜像,此时的 registry 存储目录的结构如下:

b4a423a91f897289a610301ee5198a74.png

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

d2764807e8b6af8363d1a69714027a2c.png

观察删除后的 registry 存储目录下,alpine 目录里都少了哪些东西?

4708414b6b49090da4ce228922a96399.png

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

530255f03071484b7d4c10249a693b25.png

从上面文件的变化可以得出,通过 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。

134614f9544ae44fbd32f7c87c1e2a41.png

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

304cf7d81d3446f761f13a6325720cc7.png

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

8ffac2e308e1ce49dd5542b36645d7ed.png

GC 的过程 通过 registry GC 的源码 garbagecollect.go,我们可以看到 GC 主要分两个阶段,marking 和 sweep。 marking marking 阶段是扫描所有的 manifest 文件。根据上文提到的 link 文件,扫描所有镜像 tags 目录下的 link 文件就可以得到这些镜像的 manifest,在 manifest 中保存在该镜像所有的 layer 和 config 文件的 digest 值,把这些值标记为不能清除。

2066b29a89d5d15c0264922fb472edfb.png

这一阶段可以用 shell 脚本来实现:使用 shell 遍历 manifest,然后再 grep 出所有的 sha256 值就能得到这个镜像所有的 blobs 目录下的 data 文件。

f544da31d4b07e0e0a3c70166be8d397.png

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

ae8b812d723fe833e856f553f658a76e.png

80c0e65089ee8762a2c2ac1ca21ef54a.png

GC 做了什么? 接下来我们进行实际的 GC 操作,进入到 registry 容器中,使用 registry garbage-collect 这个子命令进行操作。 marking

43f61a29d749e3513eab527dfaeffb58.png

sweep

47c4a53e7a57b3c9893f9067721c6e8b.png

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

1b2e6309b5f177ee9faa757995acd970.png

根据 GC 后的 registry 存储目录我们可以看到,原本 blobs 目录下有 6 个 data 文件,现在已经变成了 3 个,alpine:3.10 这个镜像相关的 layer、config、manifest 这三个文件都已经被 GC 掉,但是在 repositories 目录下,该镜像的 _layers 下的 link 文件依旧存在。 总结 总结以 上,用下面这三张图片就能直观地展示这些过程。 delete 镜像之前的 registry 存储目录结构

3d8a72132c8d353c4f3e4818bf94df1f.png

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

c861c7f954b10de4644d2d11d2c3a2fb.png

GC 之后的 registry 存储目录结构

ba1c8b81fecd5226c38d15daecc6b33f.png

shell 实现 根据上面的 GC 原理和过程,实际上我们可以使用不到 25 行的 shell 脚本来实现一个简单的 GC。

112fee32b97de97d526a8ce560a5d66f.png

  • 遍历所有镜像的 tag 下最新的 link 文件指向的 manifest。

  • 根据 manifest 文件 grep 出 sha256 值的 image config 和 layer 文件,保存到 all_blobs.list 文件中。

  • 使用 find 和 for 循环遍历所有 blobs 下的的 data 文件,判断它是否在 all_blobs.list 中,不在的话直接 rm -rf 删除。

  • 最后重启 registry 容器。

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

K8sMeetup

避免踩坑

The operation is unsupported.(405 Method Not Allowed)

6208c78d10ef39765a1e3db46514d086.png

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

8c892a3cf29feb19461cd762a0e12c0d.png

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 删除,存在的话就留着。这样就能清理干净。

ef6a8e26844465f9d51690dd6fcd4025.png

GC 后要重启! GC 之后一定要重启,因为 registry 容器缓存了镜像 layer 的信息,在删除掉一个镜像 A ,后边 GC 掉该镜像的 layer 之后,如果不重启 registry 容器,当重新 push 镜像 A 的时候就会提示镜像 layer 已经存在,不会重新上传 layer ,但实际上已经被 GC 掉了,最终会导致镜像 A 不完整,无法 pull 到该镜像。 GC 不是事务性操作 GC 的时候最好暂停 push 镜像,以免把正在上传的镜像 layer 给 GC 掉。 原文地址: https://blog.k8s.li/registry-gc.html

545c7ab90dc607bc57b73d4a9250affb.png

推荐阅读:

8bba10e1a09c066f173e4126cc367c58.png

aa99271df8e0a5741b7d9e63c4a729a8.png

925b1c494d1f915b6e873306c71b5f9e.png

49f7e9817c51a537dcb7e0792d95948a.png b67f4135440894127b1cdfe682e6ace0.png 在看点一下
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值