乐趣区

关于腾讯云:Registry-容器镜像服务端细节

引言

通常咱们在应用集群或者容器的时候,都会接触到存储在本地的镜像,也或多或少对本地镜像存储有肯定的理解。然而服务端的镜像存储细节呢?本文次要介绍容器镜像的服务端存储构造,对于自建镜像服务或是对容器镜像底层原理或优化有趣味的同学能够理解一下。

相干开源我的项目

目前容器镜像服务相干的开源我的项目次要有以下两个。

  • Registry (https://github.com/docker/dis…
  • Harbor (https://github.com/goharbor/h…

Registry 具备根本的镜像上传、下载以及对接第三方鉴权的能力。Harbor 则基于 Registry 做了相应的企业级扩大的我的项目。提供了更多权限、审计、镜像等性能,目前是 CNCF 孵化我的项目之一。其余详情参考相干文章。这篇文章次要解说 Registry 我的项目的存储细节。

镜像细节

在理解服务端之前,咱们来理解一下客户端的镜像容器的存储环境。

联结文件系统 UnionFS(Union File System)

Docker 的存储驱动的实现是基于 UnionFS。简略列举一下 UnionFS 下存储镜像的一些特点。

首先,UnionFS 是一个分层的文件系统。一个 Docker 镜像可能有多个层组成(留神他们是有程序的)。

其次,只有顶层是可写的,其它层都是只读的。这样的机制带来的益处是镜像层能够被多个镜像共享。对于 Docker 镜像来说,所有层都是只读的。当一个镜像运行时,会在该镜像上减少一个容器层。十个雷同的镜像启动,仅仅是减少十个容器层。销毁容器时也仅仅是销毁一个容器层而已。

  • UnionFS 是一个分层的文件系统。一个 Docker 镜像可能有多个层组成(留神他们是有程序的)。
  • 只有顶层是可写的,其它层都是只读的。这样的机制带来的益处是镜像层能够被多个镜像共享。对于 Docker 镜像来说,所有层都是只读的。当一个镜像运行时,会在该镜像上减少一个容器层。十个雷同的镜像启动,仅仅是减少十个容器层。销毁容器时也仅仅是销毁一个容器层而已。
    • 当容器须要读取文件的时候:从最上层镜像开始查找,往下找,找到文件后读取并放入内存,若曾经在内存中了,间接应用。(即,同一台机器上运行的 docker 容器共享运行时雷同的文件)。
    • 当容器须要增加文件的时候:间接在最下面的容器层可写层增加文件,不会影响镜像层。
    • 当容器须要批改文件的时候:从上往上层寻找文件,找到后,复制到容器可写层,而后,对容器来说,能够看到的是容器层的这个文件,看不到镜像层里的文件。容器在容器层批改这个文件。
    • 当容器须要删除文件的时候:从上往上层寻找文件,找到后在容器中记录删除。即,并不会真正的删除文件,而是软删除。这将导致镜像体积只会减少,不会缩小。

由此能够思考很多平安和镜像优化上的问题。

  • 在镜像构建中记录敏感信息而后再下一个构建指令中删除平安吗?(不平安)
  • 在镜像构建中装置软件包而后再下一个构建指令中清理软件包能减小镜像体积吗?(并不能)

UnionFS 个别有两种实现计划:1. 基于文件实现。文件整体的笼罩重写。2. 基于块实现,对文件的批改只批改大量块。

镜像的服务端存储细节

提供一个镜像元信息 (manifest) 用于参考:

➜  ~ docker pull ccr.ccs.tencentyun.com/paas/service-controller:7b1c981c7b1c981c: Pulling from paas/service-controllerDigest: sha256:e8b84ce6c245f04e6e453532d676f7c7f0a94b3122f93a89a58f9ae49939e419Status: Image is up to date for ccr.ccs.tencentyun.com/paas/service-controller:7b1c981cccr.ccs.tencentyun.com/paas/service-controller:7b1c981c
{"schemaVersion": 2,   "mediaType": "application/vnd.docker.distribution.manifest.v2+json",   "config": {      "mediaType": "application/vnd.docker.container.image.v1+json",      "size": 4671,      "digest": "sha256:785f4150a5d9f62562f462fa2d8b8764df4215f0f2e3a3716c867aa31887f827"},   "layers": [{         "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",         "size": 44144090,         "digest": "sha256:e80174c8b43b97abb6bf8901cc5dade4897f16eb53b12674bef1eae6ae847451"},      {"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",         "size": 529,         "digest": "sha256:d1072db285cc5eb2f3415891381631501b3ad9b1a10da20ca2e932d7d8799988"},      {"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",         "size": 849,         "digest": "sha256:858453671e6769806e0374869acce1d9e5d97f5020f86139e0862c7ada6da621"},      {"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",         "size": 170,         "digest": "sha256:3d07b1124f982f6c5da7f1b85a0a12f9574d6ce7e8a84160cda939e5b3a1faad"},      {"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",         "size": 8461461,         "digest": "sha256:994dade28a14b2eac1450db7fa2ba53998164ed271b1e4b0503b1f89de44380c"},      {"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",         "size": 22178452,         "digest": "sha256:60a5bd5c14d0f37da92d2a5e94d6bbfc1e2a942d675aee24f055ced76e8a208f"},      {"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",         "size": 22178452,         "digest": "sha256:60a5bd5c14d0f37da92d2a5e94d6bbfc1e2a942d675aee24f055ced76e8a208f"}   ]}

* 接下来是本文最为重要的内容,通过对下面这张图的了解,咱们就能够理解到 Registry 服务端存储的细节。*

  • 图中蓝色的是服务端存储的目录。文字是目录名称,这个名称是固定的。
  • 图中紫色的是服务端存储的文件。文字是文件名称,link文件的内容都是一个 sha256 的哈希值。data文件存储了真正的元文件和镜像层。
  • 图中橙色的是服务端的动静目录。目录的名称和仓库名、镜像标签或者 sha256 无关的。

整个图是从上往下的。举个例子,咱们下面形容的 manifest 如果是存储在服务端的话(文件哈希:sha256:e8b84ce6c245f04e6e453532d676f7c7f0a94b3122f93a89a58f9ae49939e419)。它存储的门路应该是:/docker/registry/v2/blobs/sha256/e8/e8b84ce6c245f04e6e453532d676f7c7f0a94b3122f93a89a58f9ae49939e419/data。对应图上应该是沿着左侧始终向下。

咱们开始拆解剖析其构造细节。

  • 左侧是镜像所有内容的理论存储,其简直占据的绝大部分贮存的空间,包含了镜像层和镜像元信息 Manifest。
    • 例如镜像层 sha256:e80174c8b43b97abb6bf8901cc5dade4897f16eb53b12674bef1eae6ae847451 的存储地位,应该在/docker/registry/v2/blobs/sha256/e8/e80174c8b43b97abb6bf8901cc5dade4897f16eb53b12674bef1eae6ae847451/data

  • 右侧是镜像元信息存储的中央。镜像元信息是依照命名空间和仓库名称分两级目录存储的。
    • 每一个仓库上面又分为 _layers_manifests 两个局部
    • _layers负责记录该仓库援用了哪些镜像层文件。
    • _manifests负责记录镜像的元信息
      • revisions蕴含了仓库下已经上传过的所有版本的镜像元信息
      • tags蕴含了仓库中的所有标签
        • current记录了以后标签指向的镜像
        • index目录则记录了标签指向的历史镜像。
    • 对上述提供的 manifest 计算 sha256,会失去元信息文件的哈希值sha256:e8b84ce6c245f04e6e453532d676f7c7f0a94b3122f93a89a58f9ae49939e419,这个元信息的存储地位应该在/docker/registry/v2/blobs/sha256/e8/e8b84ce6c245f04e6e453532d676f7c7f0a94b3122f93a89a58f9ae49939e419/data

举个镜像下载的例子:

咱们想要晓得 ccr.ccs.tencentyun.com/paas/service-controller:7b1c981c 这个镜像当初的元信息,如何在服务端存储中找到。

  1. 找到 /docker/registry/v2/paas/service-controller/_manifests/tags/7b1c981c/current/link 文件。外面有元信息的 sha256 信息。内容应该是sha256:e8b84ce6c245f04e6e453532d676f7c7f0a94b3122f93a89a58f9ae49939e419
  2. 找到理论存储文件(/docker/registry/v2/blobs/sha256/e8/e8b84ce6c245f04e6e453532d676f7c7f0a94b3122f93a89a58f9ae49939e419/data)。前文中给出了该文件的 json 内容。
  3. 依据源文件信息,客户端顺次下载对应文件就能够了。(鉴权过程参考参考文档)
  • ImageConfig

    sha256:785f4150a5d9f62562f462fa2d8b8764df4215f0f2e3a3716c867aa31887f827

  • ImageLayer

    sha256:e80174c8b43b97abb6bf8901cc5dade4897f16eb53b12674bef1eae6ae847451 sha256:d1072db285cc5eb2f3415891381631501b3ad9b1a10da20ca2e932d7d8799988 sha256:858453671e6769806e0374869acce1d9e5d97f5020f86139e0862c7ada6da621 sha256:3d07b1124f982f6c5da7f1b85a0a12f9574d6ce7e8a84160cda939e5b3a1faad sha256:994dade28a14b2eac1450db7fa2ba53998164ed271b1e4b0503b1f89de44380c sha256:60a5bd5c14d0f37da92d2a5e94d6bbfc1e2a942d675aee24f055ced76e8a208f

Tips:

  1. 很显著同样的镜像层文件在存储中只会有一个正本。应用雷同根底镜像将节俭大量的存储老本。
  2. 如果想要算上述元信息文件的哈希值,请保障你复制的文件内容尾部没有 EOL。[noeol]

基于存储的几个问题

镜像构建如何优化?

依据 UnionFS 的个性,针对性的进行优化:

  1. 构建时,一个构建指令会生成一个镜像层,尽量避免在镜像层中呈现垃圾文件,例如在装置软件之后删除软件包。
  2. 删除敏感资源并不能使得该内容真正隐没,防止敏感内容造成的平安问题。例如编译镜像最初删除代码是有效的坑骗本人的行为。
  3. 通过多阶段构建,缩小两头产物以及编译环境中的依赖内容。

上传到服务端镜像,再上传到其余仓库须要从新上传吗?

须要,在 Registry 的设计中仓库是权限的最小单位,用户是依据仓库进行权限治理与隔离的。思考如果这里疏忽了这一块的设计,镜像层存在就防止反复上传的话,客户端能够通过结构虚伪镜像元信息的形式,越权获取到其余用户的镜像。_layers中记录了仓库有权限获取的所有镜像层的目标就在于此。

镜像复制场景下如何优化?

复制镜像的场景和上传场景的区别在于,源镜像是用户的确曾经领有的。这里能够通过上述问题的思考进行复制的优化,当镜像层在目标地址曾经存在时,间接标记仓库领有该层防止不必要的上传。

镜像历史版本

依据存储构造的特点,能够较为轻松的答复这个问题。实践上只有不做 Registry GC,不删除仓库元信息,仓库历史版本的镜像都会在仓库中始终保留的。

怎么拿到镜像的元信息?

这里不做具体解说了,有趣味的同学能够参考以下文档:

  • DockerRegistry API V2——https://docs.docker.com/regis…
  • Docker Registry TokenAuthentication Specification——https://docs.docker.com/regis…
  • DockerImage Manifests V2 Schema2——https://docs.docker.com/regis…

云服务的存储对接

Registry 作为一个开源软件,适配各种云存储产品属于标配性能了。Registry 提供了规范的存储驱动接口,只有实现了这一套接口就能适配运行起来了。

相干文章

  • 为什么有了 Docker registry 还须要 Harbor?——https://cloud.tencent.com/dev…
  • 了解 Docker 镜像分层——https://www.cnblogs.com/woshi…
  • Docker Registry 存储驱动——https://docs.docker.com/regis…

【腾讯云原生】云说新品、云研新术、云游新活、云赏资讯,扫码关注同名公众号,及时获取更多干货!!

退出移动版