引言

通常我们在使用集群或者容器的时候,都会接触到存储在本地的镜像,也或多或少对本地镜像存储有一定的了解。但是服务端的镜像存储细节呢?本文主要介绍容器镜像的服务端存储结构,对于自建镜像服务或是对容器镜像底层原理或优化有兴趣的同学可以了解一下。

相关开源项目

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

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,不删除仓库元信息,仓库历史版本的镜像都会在仓库中一直保存的。

怎么拿到镜像的元信息?

这里不做详细讲解了,有兴趣的同学可以参考以下文档:

云服务的存储对接

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

相关文章

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

Registry 容器镜像服务端细节的更多相关文章

  1. 云原生交付加速!容器镜像服务企业版支持 Helm Chart

    2018 年 6 月,Helm 正式加入了 CNCF 孵化项目:2018 年 8 月,据 CNCF 的调研表明,有百分之六十八的开发者选择了 Helm 作为其应用包装方案:2019 年 6 月,阿里云 ...

  2. .Net Core in Docker - 使用阿里云Codepipeline及阿里云容器镜像服务实现持续集成(CI)

    前面已经介绍过了 .Net Core 程序发布到 Docker 容器的内容.但是每次通过 SSH 链接到服务器敲命令,运行脚本也是挺麻烦的一件事.程序员是最懒的,能让电脑解决的问题绝不手动解决,如果当 ...

  3. 容器镜像服务联手 IDE 插件,实现一键部署、持续集成与交付

    容器技术提供了一种标准化的交付方式,将应用的代码以及代码环境依赖都打包在一起,成为一个与环境无关的交付物,可以被用在软件生命周期的任何阶段,彻底改变了传统的软件交付方式. 甚至可以说,是在容器技术之后 ...

  4. 容器镜像服务 联手 IDE 插件,实现一键部署、持续集成与交付

    容器技术提供了一种标准化的交付方式,将应用的代码以及代码环境依赖都打包在一起,成为一个与环境无关的交付物,可以被用在软件生命周期的任何阶段,彻底改变了传统的软件交付方式. 甚至可以说,是在容器技术之后 ...

  5. [Docker镜像] 关于阿里云容器镜像服务的使用(以天池比赛提交镜像为例)

    最近在参加天池比赛,由于比赛需要使用阿里云容器镜像服务完成线上预测任务,所以花费了3-4天的时间了解并使用Docker完成相关镜像操作,在此分享下我学习的内容,以下是本文的目录结构: 介绍 镜像 容器 ...

  6. 如何使用容器镜像服务 TCR 轻松实现容器 DevOps

    作者周明,腾讯云容器产品工程师.目前主要负责腾讯云TKE.TCR等产品控制台的相关研发工作. 概述 当你使用云厂商提供的容器服务部署业务服务后,是否对交付部署全链路的效率有更高的需求,例如实现基于容器 ...

  7. 使用Aliyun Docker 容器镜像/注册表服务

    1.前往阿里云容器镜像服务创建相关资源. 2.登录你的仓库,账户名+公共地址 docker login --username=xxxxxxxxx@aliyun.com registry.cn-hang ...

  8. 正式开放 | 阿里云 10 亿级镜像服务正式支持 Helm Charts,云原生交付再加速!

    作者 | 阿里巴巴高级开发工程师 谢于宁(予栖) 2018 年 6 月,Helm 正式加入了 CNCF 孵化项目: 2018 年 8 月,据 CNCF 的调研表明,有百分之六十八的开发者选择了 Hel ...

  9. 阿里云开源 image-syncer 工具,容器镜像迁移同步的终极利器

    为什么要做这个工具? 由于阿里云上的容器服务 ACK 在使用成本.运维成本.方便性.长期稳定性上大大超过公司自建自维护 Kubernets 集群,有不少公司纷纷想把之前自己维护 Kubernetes ...

随机推荐

  1. python 基础-文件读写'r' 和 'rb'区别

    原文链接: python基础-文件读写'r' 和 'rb'区别 一.Python文件读写的几种模式: r,rb,w,wb 那么在读写文件时,有无b标识的的主要区别在哪里呢? 1.文件使用方式标识 'r ...

  2. Bagging与随机森林(RF)算法原理总结

    Bagging与随机森林算法原理总结 在集成学习原理小结中,我们学习到了两个流派,一个是Boosting,它的特点是各个弱学习器之间存在依赖和关系,另一个是Bagging,它的特点是各个弱学习器之间没 ...

  3. Pytorch中torch.load()中出现AttributeError: Can't get attribute

    原因:保存下来的模型和参数不能在没有类定义时直接使用. Pytorch使用Pickle来处理保存/加载模型,这个问题实际上是Pickle的问题,而不是Pytorch. 解决方法也非常简单,只需显式地导 ...

  4. 重学c#系列——盛派自定义异常源码分析(八)

    前言 接着异常七后,因为以前看过盛派这块代码,正好重新整理一下. 正文 BaseException 首先看下BaseException 类: 继承:public class BaseException ...

  5. Java多线程_生产者消费者模式1

    生产者消费者模型       具体来讲,就是在一个系统中,存在生产者和消费者两种角色,他们通过内存缓冲区进行通信,生产者生产消费者需要的资料,消费者把资料做成产品.生产消费者模式如下图.(图片来自网络 ...

  6. Java面试题(Java基础篇)

    Java 基础 1.JDK 和 JRE 有什么区别? JDK:Java Development Kit 的简称,java 开发工具包,提供了 java 的开发环境和运行环境. JRE:Java Run ...

  7. 性能提升40%: 腾讯 TKE 用 eBPF 绕过 conntrack 优化 K8s Service

    Kubernetes Service 用于实现集群中业务之间的互相调用和负载均衡,目前社区的实现主要有userspace,iptables和IPVS三种模式.IPVS模式的性能最好,但依然有优化的空间 ...

  8. Mybatis通用Join的实现(最终版)

    你是否还在为mybatis的多表关联查询而写xml烦恼,是否还在为动态组装查询条件烦恼,是否还在为此没有合适的解决方案烦恼? mybatis-extension插件,解决开发过程中需要多表关联时需手写 ...

  9. JetBrain破解

    https://blog.csdn.net/u014044812/article/details/78727496 https://jetlicense.nss.im/ https://zhile.i ...

  10. 【转】Android 5.0 : Parsing Data for android-21 failed unsupported major.minor version 51.0

    http://code2care.org/pages/parsing-data-for-android-21-failed-unsupported-major.minor-version-51.0/ ...