我们内部开发用的Harbor做Docker registry,已经跑了7个多月了,一百多个镜像仓库,1T的硬盘空间逐渐被消耗,今天一看还剩下12GB了,赶紧做Garbage Collect。

GC背景知识

docker镜像是分层的,registry在存储镜像的时候,将docker镜像分成了2部分:

  1. 镜像元数据(manifests),存储在docker/registry/v2/repositories目录中,在这里会看到registry上的项目、项目中的镜像、镜像到Layer的索引信息。
  2. blobs,存储在docker/registry/v2/blobs目录中,在这里按00-ff分目录存储了所有镜像的layer。

如果有2个镜像使用了同一个基础镜像,那么在registry上存储的时候,blobs只有一份数据,而镜像元数据中两个镜像各自的索引都有一部分layer指向相同的layer。

举个例子。

初始状态,A、B两个镜像,都是基于layer a所做的镜像;A引用a,b,B引用a,c。

A -----> a <----- B
    \--> b     |
         c <--/

之后删掉B镜像(通过Harbor的web,或者通过api)

A -----> a     B
    \--> b
         c

此时layer c实际已经没人用了,但是registry在删除B镜像时,只是会删除B的元数据,并不会主动删除layer c。

layer c就是无人照看的孤儿待回收的垃圾,需要GC。

GC过程

registry的GC使用“标记-清理”法。

  • 第一步,标记。registry扫描元数据,元数据能够索引到的blob标记为不能删除
  • 第二步,清理。registry扫描所有blobs,如果改blob没有被标记,则删除它。

跟JVM老年代的GC是不是很像?

registry GC也是stop-the-world。将来registry会在起后台任务自动回收,不再需要手工去启动GC。

GC实战

registry GC的命令如下。它提供了一个--dry-run的参数,可以先test一下,看到report再考虑是不是真的要GC。

registry garbage-collect [--dry-run] /path/to/config.yml

进入Harbor的registry容器,先看一下当前的硬盘情况。

df -h
Filesystem     Size  Used Avail Use% Mounted on
/dev/mapper/b   10G  197M  9.8G   2% /
tmpfs           48G     0   48G   0% /dev
tmpfs           48G     0   48G   0% /sys/fs/cgroup
/dev/sda5      997G  935G   12G  99% /storage
shm             64M     0   64M   0% /dev/shm

存储目录是/storage,所以需要改一下config.yml。将/etc/registry/config.yml新拷贝一份,将rootdirectory改为/storage

version: 0.1
log:
  fields:
    service: registry
storage:
  cache:
    blobdescriptor: inmemory
  filesystem:
    rootdirectory: /storage

先dry-run一下,看看报告。

36770 blobs marked, 21213 blobs eligible for deletion

可以清理的blobs还是挺多的。去掉dry-run,实际跑一下,GC效果还可以,清理出来150GB左右的空间。

/dev/sda5           997G  783G  163G  83% /storage

由于registry上还有不少荒废的tag,如果把这些也清理掉,应该能GC出来更多空间。

Ref: