当前位置: 首页 > 科技观察

容器化之路:谁偷走了我的建设时间

时间:2023-03-22 13:09:50 科技观察

随着全云时代的到来,很多公司都走上了容器化之路,老刘的公司也不例外。作为一家初创的互联网公司,容器化确实给公司带来了很多便利,降低了成本,但是老刘有一个顾虑。以前天天跟他一起工作的小王,自从公司上云后,每天都比他早。下班一小时,大家手上的工作都是一样的,这应该不太合理。经过多次试验、跟踪、调查,老刘终于发现了其中的秘密。作为开发者,每天需要发布N个测试版本进行调试。容器化后,每个版本都需要镜像。老刘发现自己做一面镜子需要20分钟,而小王只需要10分钟。比较比较,唯有这东西不一样!什么是存储驱动器?为什么它会导致构建时间不同?现在让我们来看看。在回答这个问题之前,我们需要先回答三个问题——什么是镜像?什么是镜像构建?什么是存储驱动程序?什么是镜子?镜像图片和容器讲解:看完是不是感觉更糊涂了?我们可以这么简单粗暴的理解。镜像是一堆只读层。只读层中有什么?另一种简单粗暴的解释:里面有一堆改动过的文件。这个解释在不同的存储驱动下可能不准确,但是我们可以先这么简单的理解一下。那是不对的。当容器被执行时,显然可以修改和删除容器中的文件。如果都是只读的,如何修改呢?事实上,当我们运行容器时,我们在一堆只读层之上添加。建立一个读写层,所有的操作都在这个读写层进行。当一个文件需要修改的时候,我们会将要修改的文件从底层复制到读写层,然后进行修改。如果是删除,那我们不就没有办法删除底层文件了吗?是的,没有办法删除,但是我们只需要在上层隐藏这个文件就可以达到删除的效果。正式来说,这是Docker的写时复制策略。为了加深大家对镜像层的理解,举个栗子,使用如下Dockerfile构建一个etcd镜像:构建完成后,生成如下层文件:每次进入容器,感觉好像你进入了一个虚拟机。包含linux的各种系统目录。是否有一层目录包含所有的Linux系统目录?宾果是对的!最上层的layer目录确实包含了Linux所有的系统目录文件。在上面的Dockerfile中,有这么一步操作ADD./go/src/github.com/coreos/etcd将外部目录下的文件复制到镜像中,那么镜像这一层保存的是什么呢?打开它,发现只有/go目录/src/github.com/coreos/etcd存放复制的文件。是不是有种在这里窥豹一斑的感觉?接下来,我们来了解一下什么是镜像构建,这样我们就基本可以看清全貌了。什么是镜像构造?通过***节的内容,我们知道镜像是由一堆层目录组成的,每一层目录都包含了这一层修改的文件。构建镜像简单来说就是制作生成镜像层,这个过程是如何实现的呢?以下流程图为例:DockerDaemon首先使用基础镜像ubuntu:14.04创建容器环境。通过***节的内容,我们知道容器最上层是读写层,在这一层我们可以进行写入和修改。DockerDaemon首先执行RUNapt-updateget命令。执行完成后,通过Docker的commit操作层文件,将这个读写层的内容保存为只读镜像。接下来在这一层的基础上继续执行ADDrun.sh命令。执行完成后,继续commit,形成镜像层文件。如此反复,直到所有的Dockerfile命令都提交完毕,镜像就完成了。这里可以解释为什么etcd的某一层目录下只有一个go目录,因为构建过程是逐层提交的,每一层只会保存涉及到这一层运行的变化文件。貌似构建镜像就是一个反复启动容器根据Dockerfile执行命令并保存为只读文件的过程,那为什么速度不一样呢?接下来,我们不得不说说存储驱动。什么是存储驱动程序?我们再回顾一下这张图:我们已经知道图像是由图层目录一层层叠加而成的。容器在运行的时候,只是在上面加了一个读写层,同时还有写时复制。该策略保证了底层文件的内容可以在顶层进行修改,那么这些原则是如何实现的呢?这取决于存储驱动程序!简单介绍三种常用的storage-driver:AUFSAUFS通过联合挂载将多层文件堆叠在一起,形成一个统一的整体,提供统一的视图。在读写层读写时,先检查该层是否存在该文件,如果不存在则逐层向下查找。aufs的操作都是基于文件的。当一个文件需要修改时,它会将整个文件从只读层复制到读写层,不管文件大小。因此,如果要修改的文件太大,容器的执行速度会变慢。建议是通过mount方式挂载大文件,而不是放在镜像层。OverlayFSOverlayFS可以看作是AUFS的升级版。容器运行时,镜像层的文件通过硬链接形成一个下层目录,而容器层工作在上层目录。上级目录可读写,下级目录只读。是的,因为使用了大量的硬链接,OverlayFS可能会跑完inode。Overlay2后面会对这个问题进行优化,性能会有很大的提升。不过,Overlay2与AUFS也有相似之处。同样的缺点——大文件的运行速度比较慢。DeviceMapperDeviceMapper和前两个Storage-drivers的实现有很大区别。首先,DeviceMapper的每一层都保存了上一层的快照,其次,DeviceMapper的数据操作不再是基于文件,而是基于数据块。下图是devicemapper在容器层读取一个文件的过程:首先在容器层的快照中找到该文件指向下层文件的指针。然后从下层0xf33位置指针指向的数据块中读取数据到容器的存储区***返回数据给app。写入数据时,还需要根据数据的大小申请1~N个64K的容器快照,用于保存复制的块数据。DeviceMapper的块操作看起来很漂亮,但实际上存在很多问题。比如在频繁操作小文件时,需要不断从资源池中分配数据库并映射到容器中,这样效率会变得很低,而DeviceMapper镜像运行的时候,所有的镜像层信息都需要复制到内存中。启动多张图片时,会占用大量内存空间。针对不同的存储驱动,我们使用上面的etcddockerfile进行了一组构建测试DevivceMapper在时间上明显不如AUFS和Overlay2,AUFS和Overlay2基本一致。当然,这个数据只能作为参考。实际构建还受到具体Dockerfile内容、操作系统、文件系统、网络环境等的影响,那么如何才能最大限度地缩短构建时间,提高我们的工作效率呢?且看下一章分解!