节省镜像库空间的一个思路

最近遇到一个有趣的状况,某镜像仓库占用了大量的磁盘空间。通常要解决这种问题,给 Registry 发删除指令,并进行 GC 就可以了。然而很多时候,所有镜像都正常,在删除多个 Tag 甚至是 Repository 之后,问题仍然没能缓解,原理也很容易理解——删除的镜像虽然大,可能只是复用了一些比较大的层,删除镜像并不会真正的发出,所以还是需要对镜像库的存储进行更多的了解,进行进一步的统计,在层一级对镜像仓库进行分析,才能获取更有效的途径。

Docker Registry Exporter

首先发现了一个有意思的项目:DockerRegistryExporter,这个项目是一个 Python 编写的 Prometheus Exporter,其中包含四个 Gauge:

-repository_tags_total:按镜像计算的 Tag 数量。 -repository_revisions_total:按镜像计算的版本数量。 -repository_tag_layers_total:以镜像和 Tag 计算的 Layer 数量。 -repository_tag_size_bytes:以镜像和 Tag 计算的文件尺寸。

该镜像使用挂卷的方式,直接对镜像库文件系统进行扫描,例如:

containers:
- image: registry:2
  name: registry
  ports:
  - containerPort: 5000
    name: http
    protocol: TCP
  readinessProbe:
    httpGet:
      path: /
      port: 5000
    initialDelaySeconds: 1
    timeoutSeconds: 1
  livenessProbe:
    httpGet:
      path: /
      port: 5000
    initialDelaySeconds: 1
    timeoutSeconds: 1
  volumeMounts:
  - name: storage
    mountPath: /var/lib/registry

- image: skyuk/docker-registry-exporter:v1.0.0
  name: registry-exporter
  args:
    - /var/lib/registry/docker/registry/v2
  ports:
  - containerPort: 8080
    name: http
    protocol: TCP
  volumeMounts:
  - name: storage
    mountPath: /var/lib/registry

volumes:
- name: storage
  persistentVolumeClaim:
    claimName: registry

通过Sidecar的部署方式和Registry容器共享文件系统,可以定时输出监控指标,例如:

$ curl http://registry:8080
# HELP repository_tag_size_bytes Size of eachtag
# TYPE repository_tag_size_bytes gauge
repository_tag_size_bytes{repository="org/image1", tag="0.3.0"} 162749959.0
repository_tag_size_bytes{repository="org/image2", tag="1009140546"} 226608092.0
...

然而这并不能满足我的要求,关于引用的数据并没有体现,另外前面也提到,我们需要比较精确地获得镜像版本、Tag 和 Layer 之间的引用关系以及各自的尺寸,用 PromQL 有点别扭。

我做了个奇怪的事情

这并不是一个很常见的需求,只能是一个清理之前的准备动作,目前看来我需要找到的就是引用数量少、但是体量比较大的 Layer,但是谁知道以后会需要什么新的标准呢?干脆把这些东西写入到数据库里算了,把这些东西写入数据库之后,还掌握 SQL 这样传统才艺的程序员就可以随便搞一搞其它条件了。

关于镜像仓库的一点基础

镜像库根目录中有两个子目录:blobs 中保存了所有的 Layer,而 repositories 中则是以镜像为单位保存的元数据。

首先看看镜像的数据

$ tree/org/repo/gameserver
.
├── revisions
│   └── sha256
│       └── ecfb0206e8b...
│           └── link
└── tags
    └── latest
        ├── current
        │   └── link
        └── index
            └── sha256
                └── ecfb020...
                    └── link

每个镜像的 Manifests 有两个目录,分别承载的是版本和 Tag,正常来说 Tag 和版本是一致的,但实际上在一些特别情况下,这两个数量可能是不一致的,就会导致只用 Tag 已经无法拉取该镜像,属于一种半孤立状态,应该说是需要清除的。

两个目录中的link文件中包含的是一个哈希码,可以使用这个哈希码在_layers中查找到该镜像的版本/tag 对应的清单层,使用这个字符串可以在根_layer中查到对应的目录,目录下面的data文件中就是每个层的具体数据,对于清单层,其中会是一个json字符串:

{
    "schemaVersion": 2,
    "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
    "config": {
        "mediaType": "application/vnd.docker.container.image.v1+json",
        "size": 2694,
        "digest": "sha256:7929bcd70e47d3726d55a870b2ca11c25792758f3ba8b4ff136811f0809af636"
    },
    "layers": [
        {
            "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
            "size": 2546278,
            "digest": "sha256:3db1cceb1cccb362634e914bfe76d329c64d148262a9e139a046337d82e1aeec"
        },
        {
            "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
            "size": 32,
            "digest": "sha256:4f4fb700ef54461cfa02571ae0db9a0dc1e0cdb5577484a6d75e68dc38e8acc1"
        }
    ]
}

这里看到清单中包含两个主节点,configlayer,至此,一个镜像是由三种不同的层构成的:清单、Config 和 Layer。我们关注的主要是 Layer,其中的 data 文件包含的就是各层的具体内容,清单和 Config 中都是文本,Layer 通常都是二进制的,也是我们要关注的主要内容。

接下来的问题就顺理成章了,把 Repository、Tag、Revision 以及 Layer 的关系建立起来,随便用个 SQL 语句,就能够按照具体需求对“引用少、尺寸大”的 Layer 进行过滤了。

相关链接

  • Docker Registry Exporterhttps://github.com/sky-uk/docker-registry-exporter
Avatar
崔秀龙

简单,是大师的责任;我们凡夫俗子,能做到清楚就很不容易了。

comments powered by Disqus
下一页
上一页

相关