深入理解docker graph driver - DeviceMapper

来源:互联网 发布:saucony跑鞋矩阵 编辑:程序博客网 时间:2024/05/16 06:13
进入正题之前


首先思考个问题,Docker并非第一个Linux容器解决方案,早在Docker之前就有Warden以及LXC等开源项目,为什么它们没这么火,无非都是上层包装和使用内核的ns以及cgroup,个人认为Docker在功能性上要比这两个项目走的更远一点,warden或者lxc总体上所做的工作相对简单,基于这两个开源项目去lanuch一个容器,开发者需要做大量的工作,包括准备/制作/发布镜像等。而这些工作Docker已经帮你做好了,个人认为Docker最大的卖点在于类似git一样的镜像版本管理和发布,而这部分实现的基础在于graphdriver。


graph driver是什么


这个事情我们要从头讲起...


所谓容器是什么,其实不过是一个普通进程,这个进程的特殊之处在于:
1)它可能是位于不同命名空间(ns)的,使进程加入不同的命名空间无非是使用clone/unshare/setns系统调用的其中一个,这个我们后续文章详细说明
2)它对资源的使用(cpu,memory,diskio等)可能受到CGroup资源控制的限制


封装这部分工作的逻辑在docker代码里命名是execdriver,具体实现可以是libcontainer(native方式)或者lxc


这个普通进程通过execdriver运行起来后如果希望他看起来像一个操作系统,那么我们需要一个rootfs,这个rootfs实际就是linux下/dev,/usr,/lib,/bin,/etc等文件目录结构,把这些东西打个包就是我们所说的镜像,Docker的graphdriver负责镜像本地的管理和存储以及对运行中的容器生成镜像等工作。


这个有一个比较重要的问题,如果一台机器上运行100个这样的容器进程,每个进程用的是同一个rootfs,每个容器运行过程中可能对这个rootfs有各自的修改,比如改个/etc下的配置,装个可执行文件到/bin目录,每个进程都是这样的小修改,如果在主机上存储100份这个rootfs的拷贝,那么存储空间的浪费是巨大的,这时我们会希望系统只保存一份rootfs,然后每个容器共享这个拷贝,如果某个容器需要修改,则能够通过CoW机制只保存该容器修改的部分,之后针对这个容器如果需要生成镜像,则只需要计算和保存修改的部分与原有rootfs的差异,这样通过层层叠加的方式来管理整个镜像系统。实际上docker的graphdriver里面除了vfs外的其它驱动都是按这个方式工作的。
graph driver的选择


Docker提供的graphdriver主要有btrfs,aufs,overlayfs(3.18以上kernel支持),devicemapper,vfs
我们逐个分析下:
btrfs文件系统本身支持快照功能,可以实现我们的需求,但是btrfs本身貌似目前还没有productionready


aufs文件系统是叠合文件系统,专门为了解决这种问题而生,可惜因为代码质量问题,一直被linus拒之门外,这样在很多发行版里都不会默认安装,用户使用需要自己编译对非kernel维护人员来说也比较麻烦。


overlayfs文件系统也是叠合文件系统,但是需要3.18以上kernel版本,目前貌似stable的主流Linux发行版都还没到这个内核版本


结论是如果没有自己的kernel团队,那么目前唯一的选择似乎就是devicemapper了。


device mapper是什么


简单的说devicemapper是位于内核通用块blocklayer部分的一个抽象层,帮助上层完成一些IO策略的灵活定制,在内核IO协议栈的层次关系如图:
 实际上devicemapper相当于帮你做好了一个虚拟块设备,盘符路径是在/dev/mapper/下,然后你来写一段内核代码,这段代码来定制你这个虚拟块设备的IO行为,这段代码模块就是devicemapper target,而devicemapper本身相当于一个框架,当然内核本身已经用这个框架实现了一些target,比如dm-raid1镜像target可以帮你完成设备的软raid1,dm-snap可以完成设备的快照,我们后续用的的也是内核实现好的target,分别是dm-thin和dm-pool,另外早期facebook开源的flashcache也是基于devicemapper开发。


现在我们来梳理下dm这个框架做的事情,逻辑如图:




devicemapper设备在内核里通过mapped_device数据结构标识,代表一个虚拟的块设备,上层的读写请求进入blocklayer后会转变成bio数据结构,跟据所写的设备盘符找到相应的mapped_device,然后根据所写入bio的扇区信息查找一个叫dm_table的数据结构,该数据结构维护一个设备IO信息映射表,简单的说就是类似从多少扇区到多少扇区应用什么规则,而这个规则就是dm_target,我们具体写一个dm的target时就是来填充这个dm_target(具体里面有一个target_type的数据结构,提供众多回调方法来灵活处理bio)提供的一些回调方法,之后上层的请求过来就可以按我们的规则完成指定的动作了。


最终我们的代码通过不同返回值来告知dm框架是否已处理完这个bio,如果处理完则框架会向上层返回io处理结果,或者我们可以通过修改bio的设备信息和扇区信息来完成io的重定向,也就是remap。


篇幅所限先写到这里,该篇整体算是一个引言,内容相对粗浅,后续会由浅入深
0 0
原创粉丝点击