当前位置: 首页 > Linux

镜像格式二十年:从Knoppix到OCI-Image-v2

时间:2023-04-07 01:19:56 Linux

众所周知,Docker从2013年开始dotCloud,到现在也才七年。如果你刚刚经历过2013-2015早年的圈子,自然应该知道,原来Docker=LXC+aufs,前者就是所谓的Linux容器,后者就是我要做的镜像说说今天。Millennium:AmazingLiveCD说到Linux发行版,除了在界面主题上做差异化之外,核心差异一般在于:如何安装更方便;如何升级更方便;但是在发行版世界中有一个清晰的趋势,除了这两个东西之外,它们是LiveCD,它们以光盘或USB棒的形式提供,它们不需要安装,也不会改变。之前创业的时候,我们公司的运维负责人童哥曾经说过:第一次看到liveCD的时候惊呆了。.我当然同意这一点,我也是当时震惊的学生之一。要知道Knoppix在2000年就问世了,它所基于的大名鼎鼎的Debian直到2005年6月Sarge(3.1)才正式推出图形界面的安装程序debian-installer(简称d-i)在发布时的稳定版本中。以前版本的安装仍然使用文本菜单。在这个时代,这样一个开盘即用,图形化界面启动的系统,当然给我们玩家带来的震撼是可想而知的。当时的LiveCD就是十三年后的Docker,绝对配得上“惊艳”二字。要知道,在一张700MB左右的CD中装下一个完整的操作系统并不容易(当然,有人上手后也不是太难,然后我最喜欢的DSL可以达到50MB)。Knoppix有一个好主意——压缩已安装的操作系统,将其放在CD上,并在需要时解压缩。这样一来,一张700MB的光盘可以装下大约2GB的根文件系统,这样你运行KDE桌面就没有问题了。当时在distrowatch.com上可以看到大量的发行版都是基于Knoppix的魔改,可见其影响力。进化:可读可写层与UnionFSKNoppix在诞生之初的执念是“绝对不要在本地存储上动一根手指”,而光盘和光驱所使用的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,此后,包括大家熟悉的UbuntuLiveCD、GentooLiveCD全部使用aufs。可以说,正是LiveCDs提前8年为Docker和DockerImage的诞生做好了存储准备。给不懂的朋友简单说一句。所谓unionfs是指将多个不同的文件系统组合(堆叠)起来,呈现为一个文件系统。不同于一般FHS规定的树组织方式。如下图所示,对于左边的标准目录树结构,任何文件系统都可以挂载到树上的一个挂载点上。根据路径,可以指向某个文件系统。例如下图中,所有/usr/local/下的路径都在一个文件系统上,而其他的/usr会在另一个文件系统上;而UnionFS是多层堆叠的,你写的文件会留在顶层,如图,你修改的/etc/passwd会在最上层可写,其他文件在下层,也就是说,它允许同一目录下的不同文件处于不同的层次,因此,LiveCD操作系统像真正的本地操作系统一样运行,可以读写所有路径。块或文件:Cloop和SquashFS让我们关注只读层,这是LiveCD的基础。这个只读rootfs在LiveCD没有unionFS分层的时候就已经存在了。对于Knoppix,这一层不能直接放一个完整的、未压缩的操作系统,因为在21世纪初,大家还在使用24x到40x速度的光驱,KnoppixLiveCD面临的一个大问题就是700MB之间的出入CD-ROM和笨重的桌面操作系统。正如我们开头提到的,Knoppix的思路是“将安装好的操作系统压缩,放入光盘,随用随解”,这样就可以将精挑细选的2GBDistro压缩成一张CD,但“随用随解”并不意味着有一张。文件系统访问块设备以查找块的偏移量。压缩后偏移量就没那么容易找到了,完全解压到内存中。再次找到偏移量并不是那么容易。回到2000年,当时还是2.2内核的时候,Knoppix的作者KlausKnopper在创立Knoppix的时候就引入了一种叫做cloop的压缩(compressed)循环设备。这个设备是一种特殊的格式,包含一个索引被创建,这样解压过程对用户来说是透明的。Knoppix的cloop设备看起来像块设备,大小约为2GB。应用程序在读写有偏移量的数据时,只需要使用索引解压对应的数据块返回给用户,而不用解压整个磁盘。尽管Knoppix为LiveCD船带来了很多发行版,但许多后继者,如arch、Debian、Fedora、Gentoo、Ubuntu等发行版LiveCD,以及在熟悉的路由器上播放的OpenWrt,都没有选择cloop文件,他们选择了一个更接近应用程序语义的文件系统级解决方案——Squashfs。Squashfs压缩文件、索引节点和目录,支持从4K到1M的压缩单元大小。同样,它也是根据索引按需解压。与cloop不同的是,当用户访问一个文件时,根据索引解压对应文件所在的block,而不是经过一层本地文件系统来压缩block寻址更简单直接。事实上,Knoppix中已经有人呼吁改用squashfs。例如,在2004年,一些开发人员将knoppix转换为squashfs。此外,一些测试数据似乎表明Squashfs的性能似乎更好,尤其是在元数据操作方面。wiki上对cloop的缺点评价如下:cloop驱动的设计要求压缩块要从磁盘中整块读取。当有许多分散的读取时,这使得cloop访问本质上变慢,如果系统内存不足或启动具有许多共享库的大型程序时可能会发生这种情况。一个大问题是CD-ROM驱动器的寻道时间(~80毫秒),它大大超过了硬盘的寻道时间(~10毫秒)。另一方面,由于文件被打包在一起,读取压缩块可能会因此将多个文件带入缓存。众所周知,尾部打包的效果可以缩短寻道时间(参见reiserfs、btrfs),尤其是对于小文件。已经进行了一些与cloop相关的性能测试。让我多翻译一下:cloop旨在以压缩块的形式从磁盘读取数据。因此,当有许多随机读取时,cloop可能会显着变慢,这可能发生在系统内存不足或启动具有许多共享库的大型程序时。cloop面临的一个大问题是CD-ROM的寻道时间(约80ms),大大超过了硬盘的寻道时间(约10ms)。另一方面,由于文件可以打包在一起,读取压缩块实际上可能会将多个文件带入缓存。这样,那些支持尾部打包的文件系统(如reiserfs、btrfs)可能会显着改善seek操作时间,尤其是对于小文件。已经有一些与cloop相关的性能测试来证明这几点。当然,尽管有这些争论,cloop仍然存在于Knoppix上。不过这场争论最终在2009年用squashfs合并到2.6.29主线内核中,应该算是赢家了。开箱即用换来压倒性的市场份额和更好的支持,Squashfs的优势不仅是上面提到的大量发行版用户,还有各种压缩算法的支持,仅在不同的应用中使用场景。Docker:让UnionfsGreatAgain星光更迭,不再年轻的LiveCD火爆到不新鲜。不过,科技圈也有轮回。曾经被LiveCD普及的UnionFS,被Docker再次普及,又迎来了第二春。一般来说,虽然aufs支持多个只读层,但普通的LiveCD只需要一个只读镜像和一个用户可写层即可。然而以Solomon为首的dotCloud的小伙伴们发挥了出色的想象力,将整个文件系统变成了“包”的基本单元,从而实现了#MUGA(MakeUnionfsGreatAgain)。回想一下,从1993年的Slackware到今天的RHEL,Distro(服务器端)做的无非就是我开篇提到的两件事——安装和升级。从rpm到APT/deb再到Snappy,初始化系统后的工作本质在于如何更顺利地安装和升级,保证没有依赖问题,不占用过多的额外空间。解决这个问题的基础是rpm/deb等包和包之间的依赖关系。但是,诸如“A依赖B和C,但B与C冲突”这样的问题还是层出不穷,人们也在不断地努力解决。二十年。但是Docker跳出了一个软件包的想法。他们是这样看的——一个完整的操作系统是一个包,必须是自成一体的,如果在开发、测试、部署环境的过程中都维护着同一个完整的、不变的操作系统,那么就不会造成那么多问题通过依赖;这个完整的操作系统是不可变的,就像一张LiveCD,我们称它为镜像,你可以使用像aufs这样的unionFS放一个writableLayer,应用程序可以在运行时向writablelayer写入东西,以及一些动态生成的配置也可以放在可写层;如果一些应用软件镜像共享基础系统的相同部分,那么将这些公共部分放在Unionfs的下层作为只读层,让它们可以被不同的应用程序使用;当然,如果两个应用依赖的东西不同,那么它们使用的基础层不同,就没有必要互相迁就,自然也就没有上面的依赖有矛盾了;一个镜像可以包含多个图层,方便应用共享部分数据,节省存储和传输成本;大致示意图如下:这样,如果这三个应用程序(容器)运行在同一台机器上),那么这些共享的只读层就不需要重复下载了。此外,Docker的分层结构的另一个优点是它对开发人员非常友好。可以看到下面是一个Dockerfile的示意图。FROM代表最底层的基础层,然后进行RUN、ADD这样改变rootfs的操作,将结果保存为新的中间层,最后形成镜像。这样,开发者对软件依赖关系的组织就可以在图像的层级关系中得到清晰的展示。比如下面的Dockerfile就是一个打包的镜像。它首先安装软件的依赖包和语言环境,然后初始化打包运行用户环境,然后拉取源代码,最后将制作软件包的脚本放到镜像中。这种组织方式是从一般任务到具体任务。图像制作者希望使这些图层尽可能通用。底层的内容可以在其他图像中使用,而上层的内容与本图像的工作最直接相关。其他开发者看到这个Dockerfile,基本可以知道这个镜像里面有什么,要做什么,能不能借鉴。这个镜像的设计是Docker设计中最巧妙的地方之一,这也是为什么大家愿意认同Solomon要做的就是开发者体验(DX,DeveloperExperiences)。FROMdebian:jessieMAINTAINERHyperDevelopersRUNapt-getupdate&&\apt-getinstall-yautoconfautomakepkg-configdh-makecpiogit\libdevmapper-devlibsqlite3-devlibvirt-devpython-pip&&\pipinstallawscli&&\apt-getclean&&rm-fr/var/lib/apt/lists/*/tmp/*/var/tmp/*RUNcurl-sLhttps://storage.googleapis.com/golang/go1.8.linux-amd64.tar.gz|tar-C/usr/local-zxf-RUNuseraddmakedeb&&mkdir-p~makedeb/.aws&&chown-Rmakedeb.makedeb~makedeb&&chmodog-rw~makedeb/.awsRUNmkdir-p/hypersrc/hyperd/../hyperstart&&\cd/hypersrc/hyperd&&gitinit&&git远程添加源https://github.com/hyperhq/hyperd.git&&\cd/hypersrc/hyperstart&&gitinit&&git远程添加源https://github.com/hyperhq/hyperstart.git&&\chownmakedeb.makedeb-R/hypersrcENVPATH/usr/local/go/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/binENVUSERmakedebADDentrypoint.sh/USERmakedebWORKDIR/hypersrcENTRYPOINT["/entrypoint.sh"]一个标准的DockerImage或者由它诞生的OCIImage其实就是一组元数据和一些层数据。这些层数据中的每一个都是文件系统内容的一个包。从某种意义上说,一个典型的LiveCDOS基本上可以理解为一个只读层加上一个可写层。DockerContainer的rootfs就在Docker里面,UnionFS可以说是GreatAgain了。未来:一个现代的镜像系统然而,DockerImage(或OCIImage)的设计虽然包含了“一个完整的操作系统是一个包”的优秀思想,但它也使用了UnionFS来实现“分层”,这是又美又美。开发者体验是一个可以节省时间和空间的精妙设计,但随着时间的推移,还是会暴露出一些问题。从去年(2019年)开始,OCI社区就开始有人讨论下一代图像格式的问题。在这场热烈的讨论中,讨论了一些关于OCIv1(实际上是Docker)图像格式的问题。AleksaSarai还专门写了一篇文章,具体来说,除了tar格式本身的标准化之外,大家对当前图像的主要不满是:内容冗余:不同层之间的相同信息在传输和存储时是冗余的,不可能不看内容就判断这些冗余的存在;不可并行化:单层是一个整体,同一层不能并行传输或提取;无法验证小块数据,只有完整层下载完成后,才能验证整个层的数据完整性;其他问题:比如跨层数据删除难以完美处理;上述问题可以用一句话概括为“层是镜像的基本单位”,但镜像数据的实际使用率却很低。比如Cern在这篇论文中提到,一般镜像只有6%的内容会被实际使用,这导致镜像数据结构大幅升级,不再以层作为权力的基本单位.可以看出,下一代镜像的一个趋势就是打破层结构,进一步优化这些只读。是的,反应快的同学可能还记得前面提到的LiveCD中常用的Squashfs。可以根据来读取,需要解压对应的文件块,放到内存中,供应用程序使用。是不是可以把这个扩展一下,然后去远端拉回镜像的内容,供应用需要的时候使用——从文件FromLazydecompresstoLazyLoad,一步之遥,水到渠成。是的,蚂蚁的图像加速实践就是采用了这样的架构。过去,巨大的镜像不仅拖慢了拉取过程,而且如果这个过程也有风险,还会导致一半以上的Pod启动失败率。今天,当我们介绍lazy-loadedrootfs时,这些故障几乎被完全消除了。在去年底的第十届中国开源黑客马拉松上,我们也展示了通过virtio-fs将这个系统连接到KataContainers安全容器上的实现。如图所示,与Squashfs类似,在这种新的Image格式中,压缩后的数据块是基本单位,一个文件可以对应0到多个数据块。除了数据块之外,还引入了一些额外的元数据,使得目录树到数据块的映射关系,从而可以在不下载完整图像数据的情况下,将完整的文件系统结构呈现给应用程序,并在发生特定读取时,可以根据索引得到相应的数据,提供给应用程序。该图像系统可以带来这些好处:按需加载,无需在启动时完整下载图像,同时可以对加载的不完整图像内容进行完整性验证,作为全链路信任的一部分;对于runC容器,通过Fuse可以提供不依赖宿主机内核变化的用户态解决方案;对于Kata等虚拟化容器,镜像数据直接发送到Pod沙箱内部使用,不加载到主机上;用户体验与之前的DockerImage没有明显区别,开发者体验也不会差;而且,在本系统设计之初,我们发现由于可以获取到应用文件的数据访问方式,基于文件的好处之一就是即使镜像升级了,其数据访问方式也不会趋于变化太大,可以利用应用文件数据的访问方式,做一些有针对性的操作,比如文件预读。可以看出,系统存储领域在过去二十年经历了螺旋式演进。发生在LiveCD上的进化,在容器里也再次来到这里,仿佛过了一辈子。目前,我们正在积极参与OCIImagev2的标准推进,并将我们的参考实现与DragonFlyP2P分发相结合,成为CNCF开源项目Dragonfly的一部分。我们希望未来能和OCI社区进一步互动,让我们的需求和优势成为社区规范的一部分,这也能让我们在OCI-Image-v2镜像下保持与社区的一致性,平滑过渡,统一将来。作者介绍王旭,蚂蚁金服资深技术专家,开源项目KataContainers架构委员会创始成员。近几年一直活跃于国内开源开发社区和标准化工作。在加入蚂蚁金服之前,他是SonicProdigy的联合创始人兼CTO。他们在2015年开源了基于虚拟化技术的容器引擎runV。2017年12月,他们和Intel宣布合并runV和ClearContainers项目,成为KataContainers项目,2019年4月获得董事会批准,成为KataContainers项目。OpenStack基金会自2012年以来第一个新的开放基础设施顶级项目。在创立SonicProdigy之前,王旭曾在盛大云计算和中国移动研究院云计算团队工作。2011年,王旭主持杭州QCon的云计算主题。同时,他还是一位活跃的技术作者、翻译家和资深博主。