关于程序员:镜像格式二十年从-Knoppix-到-OCIImagev2

4次阅读

共计 8293 个字符,预计需要花费 21 分钟才能阅读完成。

简介: 家喻户晓,Docker 始于 2013 年的 dotCloud,迄今刚刚七年,如果你刚好在圈中经验了 2013-2015 年这段晚期岁月的话,天然应该晓得,最后的 Docker = LXC + aufs,前者就是所谓的 Linux 容器了,而后者则是明天要聊的镜像。

家喻户晓,Docker 始于 2013 年的 dotCloud,迄今刚刚七年,如果你刚好在圈中经验了 2013-2015 年这段晚期岁月的话,天然应该晓得,最后的 Docker = LXC + aufs,前者就是所谓的 Linux 容器了,而后者则是我明天要聊的镜像。

千禧年:惊艳的 Live CD

说到 Linux distro,除了做差异化的界面主题之外,外围差别个别都在于:

  • 如何更不便地装置;
  • 如何更不便地降级;

而在 distro 界却有一股清流,超脱于这两件事件之外,它们就是 Live CD,它们装在一张光盘里,或者是一个 U 盘上,不须要装置、也不会扭转。之前守业的时候,我司的运维大佬——彤哥已经说过:

第一次见到 liveCD 时我心田是震惊的。。

这我当然是同意的,那时我也是震惊的同学之一,要晓得 Knoppix 在 2000 千禧年就来到了世界,而它所基于的驰名的 Debian,直到 2005 年 6 月,Sarge (3.1) 公布的时候才正式在 stable release 里带上了图形界面的安装程序 debian-installer(简称 d-i),此前版本的装置还在用文本菜单。就在这个年代,这样一个开光盘即用、启动起来就是图形界面的零碎,给咱们这些玩家带来的震撼,当然是可想而知的。那时候的 Live CD 就是十三年后的 Docker,相对配得上“惊艳”两个字。

要晓得,一张 700MB 左右的光盘里塞下个残缺的操作系统并不容易(当然有人结尾之后也不太难,起初我爱的 DSL 能够做到 50MB)。Knoppix 有个很棒的想法——把装好的操作系统给压缩一下,放在光盘里,随用随解压,这样,一张 700MB 光盘里能够放下大概 2GB 的根文件系统,这样就跑 KDE 桌面也就没啥问题了,当是时,distrowatch.com 上能够看到,一大片 distro 都是基于 Knoppix 魔改的,足见其影响力。

进化:可读写层与 UnionFS

Knoppix 在诞生之初的一个执念是“绝不碰本地存储一根指头”,而光盘,CD-ROM,所应用的 ISO9600 文件系统也是只读的,这无疑暗合了当今风行的“不可变基础设施”的潮流,然而,即便在明天,没有可写文件系统对于很多 Linux 软件依然是十分艰难的,毕竟随随便便一个程序也要写一点配置文件、状态信息、锁、日志之类的嘛。而诞生之初的 Knoppix 是不可写的,那么,要有什么货色要罗盘,就得手工挖掘出本地硬盘来挂上,或者是挂个 NAS 到 /home 或其余挂载点上来,当你不想只是做个紧急启动盘的时候,这就有点麻烦了。

如果咱们从明天穿梭回去,毫不费力就能够指出,用 overlayfs 加上一个 tmpfs 做可写层嘛。然而,overlayfs 要到 2010 年才首次提交 patchset,2014 年才被合并到 3.18 内核(这两头,过后的淘宝内核组也有不少奉献和踩坑呢)。当然,比 overlay 早的相似的 unionfs 还是有的,Docker 最早采纳的 Aufs 就是其中之一,它是 2006 年呈现的,这里 AUFS 的 A,能够了解成 Advanced,但它最早的意思理论是 Another——是的,“另一个 UFS”,而它的前身就是 UnionFS。

在 2005 年 5 月,也就是十五年前,Knoppix 创造性地引入了 UnionFS,而在一年半当前的 5.1 版本中,Knoppix 引入了当年诞生的更稳固的 aufs,尔后,包含大家相熟的 Ubuntu LiveCD、Gentoo LiveCD 全都应用了 aufs。能够说,正是 Live CD 们,提前了 8 年,为 Docker 和 Docker Image 的诞生,做好了存储上的筹备。

这里简略说一句给不理解的人听,所谓 union fs,是指多个不同文件系统联结(重叠)在一起,出现为一个文件系统,它和个别的 FHS 规定的那种树装组织形式是不同的,如下图,对于右边的规范的目录树结构,任何一个文件系统,挂载在树上的一个挂载点,依据门路,就能够指到一个确定的文件系统,比方,下图中,所有的 /usr/local/ 上面的门路都在一个文件系统上,而其余 /usr 就会在另一个文件系统上;而 UnionFS 是多层重叠的,你写的文件会停留在最上层,比方图中,你批改过的 /etc/passwd 就会在最上的可写层,其余的文件就要去上面的层中去找,也就是说,它容许同一个目录中的不同文件在不同层中,这样,Live CD 操作系统跑起来就像真正的本地操作系统一样能够读写所有门路了。

块或文件:Cloop 与 SquashFS

让咱们把眼光放在只读层上,这一层是 Live CD 的根底,在 Live CD 还没有 union FS 来做分层的时候就曾经存在这个只读 rootfs 了。对 Knoppix 来说,这一层是不能间接放残缺、无压缩的操作系统的,因为在 21 世纪初,大家都还在用 24x 到 40x 速光驱的时代,Knoppix Live CD 面临的一个大问题是 700MB 的光盘和宏大的桌面操作系统之间的矛盾。

结尾咱们提到了,Knoppix 的想法就是“把装好的操作系统给压缩一下,放在光盘里,随用随解压”,这样精心抉择后的 2GB 的 Distro 就能够压到一张光盘里了,不过“随用随解压“不是说有就有的,文件系统拜访块设施,是要找到块的偏移量的,压缩了之后,这个偏移量就并不那么好找了,全解压到内存里再找偏移并不那么容易。

回到 2000 年,那时候还是 2.2 内核,Knoppix 的作者 Klaus Knopper 在创建 Knoppix 之初就引入了一种压缩的(compressed) loop 设施,称为 cloop,这种设施是一种非凡的格局,它蕴含了一个索引,从而让解压缩的过程对用户来说是通明的,Knoppix 的 cloop 设施看起来就是一个大概 2GB 大的块设施,当利用读写一个偏移的数据的时候,它只须要依据索引解压缩对应的数据块,并返回数据给用户就能够了,无需全盘解压缩。

只管 Knoppix 把泛滥 distro 带上了 Live CD 的船,然而,泛滥后继者,诸如 arch、Debian、Fedora、Gentoo、Ubuntu 等等 distro 的 LiveCD,以及大家相熟的路由器上玩的 OpenWrt,都并没有抉择 cloop 文件,它们抉择了和利用语义更靠近的文件系统级的解决方案——Squashfs。Squashfs 压缩了文件、inode 和目录,并反对从 4K 到 1M 的压缩单元尺寸。同样,它也是依据索引按需解压的,和 cloop 的不同之处是,当用户拜访一个文件的时候,它来依据索引解压相应的文件所在的块,而非再通过一层本地文件系统到压缩块的寻址,更加简略间接。事实上,Knoppix 里也始终有呼声想要切换到 squashfs,比方,2004 年就有开发者在转换 knoppix 到 squashfs 上,而且,一些测试数据仿佛也表明 Squashfs 的性能仿佛要更好一些,尤其在元数据操作方面。

在 wiki 上是这么评估 cloop 的毛病的:

The design of the cloop driver requires that compressed blocks be read whole from disk. This makes cloop access inherently slower when there are many scattered reads, which can happen if the system is low on memory or when a large program with many shared libraries is starting. A big issue is the seek time for CD-ROM drives (~80 ms), which exceeds that of hard disks (~10 ms) by a large factor. On the other hand, because files are packed together, reading a compressed block may thus bring in more than one file into the cache. The effects of tail packing are known to improve seek times (cf. reiserfs, btrfs), especially for small files. Some performance tests related to cloop have been conducted.

我来画龙点睛地翻译一下:

cloop 的设计要求从磁盘上以压缩块为单位读取数据。这样,当有很多随机的读操作时,cloop 就会显著地变慢,当零碎内存不足或者一个有很多共享库的大型程序启动的时候都很可能产生。cloop 面临的一个很大的问题是 CD-ROM 的寻道工夫(约 80 毫秒),这在很大水平上超过了硬盘的查找时间(约 10 毫秒)。另一方面,因为文件能够被打包在一起,因而读取压缩块理论可能能够将多个文件带入缓存。这样,那些反对 tail packing 的文件系统(比方 reiserfs,btrfs)可能能够显著改善 seek 操作的工夫,尤其是对于小文件更是如此。曾经有一些与 cloop 相干的性能测试也证实了这些观点。

当然,只管有这些争执,cloop 也依然在 Knoppix 上存在,不过,这个争执最终随着 2009 年 squashfs 被并入 2.6.29 主线内核,应该算是分出输赢了,进入 kernel 带来的开箱即用换来的是压倒性的占有率和更好的反对,Squashfs 的劣势不仅在上述的 distro 用户之多,也在于反对了了各种的压缩算法,只用于不同的场景。

Docker: Make Unionfs Great Again

斗转星移,不再年老的 Live CD 也因为如此遍及,而让人感觉并不离奇了。然而,技术圈也有轮回个别,当年被 Live CD 带红的 Union FS 们再一次被 Docker 捧红,焕发了第二春。一般地说,尽管 aufs 们反对多个只读层,但一般的 Live CD 只有一个只读镜像和一个可写层留给用户就能够了。然而,以 Solomon 为首的 dotCloud 的敌人们充分发挥了他们卓越的想象力,把整个文件系统变成了“软件包”的根本单位,从而做到了 #MUGA (Make Unionfs Great Again)。

回忆一下,从 1993 年的 Slackware 到明天的 RHEL,(服务端的)Distro 的所作所为,不外乎我结尾提到的两件事件——装置和降级。从 rpm 到 APT/deb 再到 Snappy,初始化好零碎之后的工作的精华就在于怎么更平滑地装置和降级、确保没有依赖问题,又不额定占据太多的空间。解决这个问题的根底就是 rpm/deb 这样的包以及包之间的依赖关系,然而,相似“A 依赖 B 和 C,但 B 却和 C 抵触”这样的事件依然层出不穷,让人们不停地尝试解决了二十年。

但 Docker 跳出了软件包这个思路,他们是这么看的——

  • 残缺的操作系统就是一个包,它必然是自蕴含的,而且如果在开发、测试、部署时都放弃同样残缺、不变的操作系统环境,那么也就没有那么多依赖性导致的问题了;
  • 这个残缺的操作系统都是不可变的,就像 Live CD 一样,咱们叫它镜像,能够用 aufs 这样的 union FS 在下面放一个可写层,利用能够在运行时写货色到可写层,一些动静生成的配置也能够放在可写层;
  • 如果一些应用软件镜像,它们共用同一部分根底零碎,那么,把这些公共局部,放在 Unionfs 的上层作为只读层,这样他们能够给不同的利用应用;当然,如果两个利用依赖的货色不一样,那它们就用不同的根底层,也不须要相互迁就了,天然没有下面的依赖性矛盾存在了;
  • 一个镜像能够蕴含多个层,这样,更不便利用能够共享一些数据,节俭存储和传输开销;

大略的示意图是这样的:

这样,如果在同一台机器上跑这三个利用(容器),那么这些共享的只读层都不须要反复下载。

更进一步说,Docker 这种分层构造的另一个长处在于,它自身是十分开发者敌对的,能够看到,上面这个是一个 Dockerfile 的示意,FROM 代表最底下的根底层,之后的 RUN, ADD 这样扭转 rootfs 的操作,都会将后果存成一个新的中间层,最终造成一个镜像。这样,开发者对于软件依赖性的组织能够十分清晰地展示在镜像的分层关系中,比方上面这个 Dockerfile 是一个 packaging 用的 image,它先装了软件的依赖包、语言环境,而后初始化了打包操作的用户环境,而后拉源代码,最初把制作软件包的脚本放到镜像里。这个组织形式是从通用到特定工作的组织形式,镜像制作者心愿让这些层能够尽量通用一些,底层的内容能够在其余镜像中也用得上,而下层则是和本镜像的工作最间接相干的内容,其余开发者在看到这个 Dockerfile 的时候曾经能够根本晓得这个镜像里有什么、要干什么,以及本人是否能够借鉴了。这个镜像的设计是 Docker 设计里最奇妙的中央之一,也是为什么大家都违心认同,Solomon 要做的就是开发者体验(DX, Developer Experiences)。

FROM       debian:jessie
MAINTAINER Hyper Developers <dev@hyper.sh>

RUN apt-get update &&\
    apt-get install -y autoconf automake pkg-config dh-make cpio git \
        libdevmapper-dev libsqlite3-dev libvirt-dev python-pip && \
    pip install awscli && \
    apt-get clean && rm -fr /var/lib/apt/lists/* /tmp/* /var/tmp/*
RUN curl -sL https://storage.googleapis.com/golang/go1.8.linux-amd64.tar.gz | tar -C /usr/local -zxf -

RUN useradd makedeb && mkdir -p ~makedeb/.aws && chown -R makedeb.makedeb ~makedeb && chmod og-rw ~makedeb/.aws
RUN mkdir -p /hypersrc/hyperd/../hyperstart &&\
    cd /hypersrc/hyperd && git init && git remote add origin https://github.com/hyperhq/hyperd.git && \
    cd /hypersrc/hyperstart && git init && git remote add origin https://github.com/hyperhq/hyperstart.git && \
    chown makedeb.makedeb -R /hypersrc

ENV PATH /usr/local/go/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
ENV USER makedeb
ADD entrypoint.sh /

USER makedeb
WORKDIR /hypersrc
ENTRYPOINT ["/entrypoint.sh"]

一个标准的 Docker Image 或者脱胎于其中的 OCI Image,实际上就是一组元数据和一些层数据,这些层数据每个都是一个文件系统内容的打包,从某种意义上说,典型的 Live CD 的 OS,基本上能够了解成一个只读层加上一个可写层的 Docker Container 的 rootfs。在 Docker 这里,Union FS 能够说是 Great Again 了。

将来:现代化的镜像零碎

然而,只管 Docker Image(或者说 OCI Image)的设计蕴含了“残缺的操作系统就是一个包”的优良思维,又利用 Union FS 实现了“分层”这种既实现丑陋的开发者体验,又能节约工夫空间的精美设计,但随着工夫的推移,还是裸露进去了一些问题。从去年(2019 年)年初,OCI 社区中开始有人探讨下一代镜像格局的问题,这个热烈的探讨中,集中探讨了 OCIv1(理论也是 Docker 的)镜像格局的一些问题,Aleksa Sarai 也专门写了一篇博客来探讨这个话题,具体说,除了 tar 格局自身的标准化问题外,大家对以后的镜像的次要不满集中在:

  • 内容冗余:不同层之间雷同信息在传输和存储时都是冗余内容,在不读取内容的时候无奈判断到这些冗余的存在;
  • 无奈并行:单一层是一个整体,对同一个层既无奈并行传输,也不能并行提取;
  • 无奈进行小块数据的校验,只有残缺的层下载实现之后,能力对整个层的数据做完整性校验;
  • 其余一些问题:比方,跨层数据删除难以完满解决;

上述这些问题用一句话来总结就是“层是镜像的根本单位 ”,然而,镜像的数据的理论使用率是很低的,比方 Cern 的这篇论文中就提到,个别镜像只有6% 的内容会被理论用到,这就产生了实质性的降级镜像数据结构,不再以层为根本单位的能源。

可见,下一代镜像的一个趋势就是突破层的构造来对这些只读进行更进一步的优化,是的,反馈快的同学可能曾经回想起了后面提到的 Live CD 里罕用的 Squashfs,它能够依据读取的须要来解压相应的文件块来放入内存、供给用应用,这里是不是能够扩大一下,变成在须要的时候再去远端拉回镜像的内容来供给用应用呢——从文件的 Lazy decompress 到 Lazy Load,一步之遥,水到渠城。

是的,蚂蚁的镜像减速实际里就采取了这样的架构。在过来,宏大的镜像不仅让拉取过程变慢,而且如果这一过程同样危险重重,奉献了大半的 Pod 启动失败率,而明天,当咱们把提早加载的 rootfs 引入进来的时候,这些失败简直被齐全毁灭掉了。在去年年末的第 10 届中国开源黑客松里,咱们也演示了通过 virtio-fs 把这套系统对接到 Kata Containers 平安容器里的实现。

如图,相似 Squashfs,这种新的 Image 格局中,压缩数据块是根本单位,一个文件能够对应 0 到多个数据块,在数据块之外,引入了一些附加的元数据来做目录树到数据块的映射关系,从而能够在没有下载残缺镜像数据的时候也给利用出现残缺的文件系统构造,并在产生具体读取的时候,依据索引,去获取相应的数据来提供给利用应用。这个镜像零碎能够带来这些益处:

  • 按需加载,启动时无需齐全下载镜像,同时对加载的不齐全镜像内容能够进行完整性校验,作为全链路可信的一个局部;
  • 对于 runC 容器,通过 fuse 能够提供用户态解决方案,不依赖宿主机内核变动;
  • 对于 Kata 这样的虚拟化容器,镜像数据间接送给 Pod 沙箱外部应用,不加载在宿主机上;
  • 应用体验上和之前的 Docker Image 并没有显著不同,开发者体验不会变差;

而且,这套零碎在设计之初,咱们就发现,因为咱们能够获取到利用文件数据拜访模式的,而基于文件的一个益处是,即便镜像降级了,它的数据拜访模式也是偏向于不太变动的,所以,咱们能够利用利用文件数据的拜访模式做一些文件预读之类的针对性操作。

能够看到,零碎存储这个畛域二十年来产生了一个螺旋式的进化,产生在 Live CD 上的进化,在容器这里也又来了一次,恍如隔世。目前,咱们正在踊跃地参加 OCI Image v2 的规范推动,也正在把咱们的参考实现和 DragonFly P2P 散发联合在一起,并成为 CNCF 的开源我的项目 Dragonfly 的一部分,心愿在将来能够和 OCI 社区进一步互动,让咱们的需要和劣势成为社区标准的一部分,也让咱们能够和社区保持一致、可平滑过渡,将来能够对立在 OCI-Image-v2 镜像之下。

作者介绍

王旭,蚂蚁金服资深技术专家,也是开源我的项目 Kata Containers 的架构委员会开创成员,在过来几年中沉闷在国内的开源开发社区与标准化工作中。在退出蚂蚁金服之前,他是音速神童的联结创始人和 CTO,他们在 2015 年开源了基于虚拟化技术的容器引擎 runV,在 2017 年 12 月,他们和 Intel 一起发表 runV 与 Clear Containers 我的项目合并,成为 Kata Containers 我的项目,该我的项目于 2019 年 4 月被董事会通过成为了 OpenStack 基金会 2012 年以来的首个新凋谢基础设施顶级我的项目。在创建音速神童之前,王旭曾工作于隆重云计算和中国移动研究院的云计算团队。2011 年王旭已经主持过杭州 QCon 的云计算主题,同时,也已经是一位沉闷的技术作者、译者和老 blogger。

正文完
 0