天天看点

你真的了解Docker吗?——Docker插件机制详解

云栖techday活动第十八期中,阿里云容器服务团队的核心成员陈萌辉带来了题为《docker插件机制详解》的分享,分享中,他结合阿里云容器服务实践介绍了docker插件的基本原理、实现方法以及插件机制未来的演进。

以下为现场分享观点整理。

<b>为什么需要docker插件?</b>

你真的了解Docker吗?——Docker插件机制详解

docker之所以这么火并且有很多人愿意使用它,其中涉及到很多方面的因素,例如功能性以及隔离性等各种各样的原因。其中docker的开箱即用功能是一个非常具有特色的优点,docker安装后即可使用,无需再做其他的配置;同时docker自包含的镜像提取出来就是一个完整的系统;此外,它自带网络、进程、文件系统的隔离,使用者可以很方便在一台机器上部署各种互相隔离的系统。因此,docker的应用就非常简单、方便,但是它这种简单方便也带来一些问题。docker把一些场景固化掉了,例如docker的网络模型和存储,固化之后,当特定场景不符合docker预期的要求时,docker就无法满足要求,此时需要重新定制docker。例如在你的网络环境里面,ip地址是预先分配好的,跟docker预留的不匹配或者是你想自定义一个容器间互相互联一个网络,docker预留的那些方式满足不了需求时,需要对它进行扩展。

<b>docker插件机制简介</b>

docker公司对docker的扩展分成三个级别,从低到高分别是:user-facing api,该api是docker提供用于串联出一些场景的api;第二层是插件(plugins),也是今天的主要介绍内容,plugins和docker是两个相互独立的进程,它们之间通过一些预定的通讯协议进行功能扩展;第三层是叫drivers,是docker实现功能的一些驱动,例如一些文件存储的驱动。目前,大多数drivers都是官方出的,当有特定需求或者是用到独特技术时,开发者可以自己写相应的驱动。

你真的了解Docker吗?——Docker插件机制详解

docker的插件是增加docker引擎功能的进程外扩展,也就是说它和docker engine是两个独立分开的进程。插件运行在docker daemon外,通过一些插件的发现机制和预定义协议进行交互通信,这样做的好处是:当更新插件时,无需重新重启更新docker。

目前docker官方提供的插件的类型有四种:

第一种是授权(authz)。可以写一个插件带对每一个请求做验证,例如,可以对公司的通信录或者权限系统绑定,然后给一些请求做授权,使得一部分人能够执行查看类的操作,另外一部分人可以执行创建删除类的操作,这样就可以将权限系统与docker结合起来了。

第二种是数据卷(volumedriver)插件。用于提供不同的存储功能,是应用最多的一类插件。

第三种是网络(networkdriver)插件。该插件用于提供容器之间互联网路模型,这种插件也比较普遍,例如阿里云容器服务提供了针对阿里云的网络的网络插件,在vpc下,所有容器都是互相可以访问的,而且是没有性能损耗的。

第四种是ip地址管理(ipamdriver)插件,当需要对ip地址的分配进行保留时,可能需要该插件。

你真的了解Docker吗?——Docker插件机制详解

下面介绍一下docker插件的发现机制。docker规定了三种插件发现的机制,因为每一个插件其实它本身就是一个http服务器,docker是通过http访问和插件进行通信的。那么docker是如何知道http服务器的地址呢?它规定了三种方式:第一个是.sock文件,在/run/docker/plugins下写一个unix socket文件,则docker认为这是一个插件,而且该文件名就是插件的名字;第二类是.spec文件,它是一个纯文本文件,文件内容是插件访问地址;第三类.json文件,它包含的内容比.spec文件内容多,包括插件的name、addr、tlsconfig。

你真的了解Docker吗?——Docker插件机制详解

上文提到了插件和docker 引擎是两个独立进行,但插件的生命周期是由docker的官方文档所规定的,插件必须先于docker daemon启动,后于docker daemon停止,也就是插件的生命周期必须要长于docker daemon。但在具体是实现时,由于第一次访问插件时才激活,例如一个存储插件,当第一次访问这一类存储或创建这一类储存卷时,才会真正激活插件,因此插件其实可以晚于docker daemon启动,也可以在docker结束之间结束,这也为之后将插件放到容器里面提供了很好的条件。

docker官方提供了插件sdk,使用者所需的发现相关的功能,在sdk中大部分都自带,因此只需简单引用即可,将精力解放于如何实现问题上。

<b>docker数据卷与存储插件</b>

你真的了解Docker吗?——Docker插件机制详解

下面结合数据卷来讲解一下存储的插件。提到数据卷就不得不提docker的分层文件系统,docker之所以风靡全球,分层文件系统可能贡献了其中三分之一的能量。如图所示,分层文件系统的最底层是bootfs,是容器和数据集共享的一层;在其上是base image层和image层,最上面才是可写入的container。对于一个容器来说,它里面所有的文件都是可能处于不同层内,当修改该文件时,其实是修改的拷贝文件。比如说,你修改了一个系统的文件,其实在磁盘上,原来操作系统的文件并没有发生改变,而是拷贝出来的另一份(容器可见的文件)才是被你修改了。但如果其他人引用了同样一个操作系统时,所看到的文件是没有被修改的文件。分层文件系统有一个很大的优点,它可以把软件的构建变成类似可视化的方式。但分层文件系统同时也带来一些问题:第一,它的系统性能差,因为在修改文件时,有一个拷贝过程,势必会导致性能的损耗,同时在读取时,存在一个回溯操作,也就是它要从下面往上找,找到对应文件所在的层,这也导致了它性能比原操作系统差。

另一个问题是它的生命周期和容器相同,也就是说启动一个容器,在容器被写入例如一些日志或应用相关数据,当容器一旦被删除,这些数据也随之消失,因此一个需要长期保留的数据是无法写入容器的。

为了解决上述问题,docker提供了数据卷功能,数据卷是将主机文件夹挂在容器内部,绕过分层系统,相当于容器内部直接读写某主机上的某一文件夹。

你真的了解Docker吗?——Docker插件机制详解

这样一来它的性能和主机性能是一致的,生命周期也脱离了容器的生命周期,在容器挂靠之后,数据依然存在主机的磁盘中。上图形象地展示了这一过程,当用docker run -v建立一个数据卷时,其实是把这一文件夹从主机挂到容器内部,该文件夹其实已经存放在主机上了。

但docker官方提供的数据卷也有一些缺点:第一,仅能够读写本地磁盘,当访问网络数据时,数据卷就无能为力了;另外,数据卷不能随容器迁移,因为它是读写本机的磁盘,如果容器从a机器迁移到b机器,它无法帮你把数据一块迁移过去,无法实现共享数据。

你真的了解Docker吗?——Docker插件机制详解

那为了解决上述问题,阿里云容器服务提供了两类网络存储数据卷:第一类是ossfs数据卷,第二类是叫nas数据卷。

你真的了解Docker吗?——Docker插件机制详解

存储插件的功能是管理数据卷的生命周期,它的主要工作是将第三方存储映射到host本地文件系统中,以便同期使用该数据卷。docker本身可以使用本地的数据卷,如果没有修改docker是无法直接访问网络数据卷的,将网络数据卷映射到本地文件系统的过程如上图所示:通过命令行给docker daemon发送创建数据卷的命令,然后该命令通过plugin apis访问到volume drive插件,然后该插件将第三方的存储映射到宿主机上面,就可以实现网络数据卷的访问。

你真的了解Docker吗?——Docker插件机制详解

docker定义了一些存储插件api,是docker下发给存储插件的,需要按照api的格式返回相应的数据,一共有七个api:

/volumedriver.create:当需要创建数据 volume 时,调用该api

/volumedriver.remove:当需要删除数据volume时,调用该api

/volumedriver.mount:容器每次启动时都会调用该api一次

/volumedriver.path:返回volume在主机上的实际位置

/volumedriver.unmount:容器每次停止时,调用该api

/volumedriver.get:docker volume inspect时,调用该api

/volumedriver.list:激活插件时,调用该api,用于询问当前已有的volume,防止重复创建。

从这些api可以看出,对存储插件而言,docker daemon所做的事情很少,它就是将这些命令转到volume plugin,然后所有的工作,包括所有状态的存储都是由volume plugin自身完成。 

数据的存储插件的需要有一些持久存储的东西。例如某些特定的参数持久化下来,当下次重启时,可以再次还原回来。

写一个存储插件时非常简单的,只需要把上面的七个请求实现即可,熟悉的人两天就可以写写完,不是很熟练二代开发者一周内也可完成。但是功能完成后,还有其他的事情需要考虑。 

你真的了解Docker吗?——Docker插件机制详解

第一需要考虑是否可以在容器中运行插件。因为插件和docker daemon是两个完全独立的进程,但如果一个插件是在主机上的一个进程时,是有一定的不便之处。因为它的生命周期中有docker,docker中没有方便的方法进行管理。另外插件执行时需要root权限,对于容器服务的产品,它是不适合的。

目前的设计方式是将存储插件跑到容器中,这样docker就可以获得管理权限和root权限。但是插件在容器内运用存在一个令人头疼的额问题:容器的mount namespace。上图比较简单地演示了该问题,上面是主机的文件系统,它真实对应了硬盘的文件系统。每一个容器都有自己的文件系统。容器的文件系统和主机文件系统是完全隔离的,在容器内做一些mount操作,完全不影响主机上的文件系统,主机上根本无法发现mount过的文件。如果存储插件运行在容器内时,无乱在容器内做任何mount操作,主机和其他容器都无法发现,这样做就没有任何意义。 

你真的了解Docker吗?——Docker插件机制详解

<b>突破mount space有两种方式:</b>

第一种是nsenter,可以在容器中修改主句的mount点;第二种方式是docker1.10开始提供的shared_mount,它的意思是,虽然处于不同的空间,但是一个变化时可以通知另一个,例如容器文件系统中,通过修改目录,当容器里面的插件由变化是,它会通知主机文件系统,使得主机能够看到该变化。

你真的了解Docker吗?——Docker插件机制详解

阿里云容器服务提供了三种数据卷:

ossfs数据卷:基于fuse,将oss bucket映射为本地文件系统。

nas数据卷:基于nfs协议,按需扩容,高性能高可靠性。它的后台是一个高性能、高可靠性的服务。它默认数据是三份,不会丢数据。它是不同用户之间会做隔离的,这样的话a用户的请求在性能上不会影响b用户。

云盘数据卷:将云盘当成一个数据卷挂在容器中。

你真的了解Docker吗?——Docker插件机制详解

上图对比三中数据卷的优缺点和使用范围。

ossfs数据卷的优点在于它能跨主机共享,同时oss本身就是一个比较成熟的服务,所以很快能够使用起来。但是它的缺点同样明显:首先读写/is性能低,读写性能低是由于每次修改文件时,都会导致文件的重写。is性能低是因为在获取文件时,需要服务器将文件罗列出来,当文件较多时,往往会耗费10秒甚至更长的时间。ossfs数据卷的特点决定了它只适合在小数据量、无修改的场景下使用。例如配置文件和附件上传。

nsa数据卷的优点也是跨主机共享的,同时它可以按需扩容,高性能、高可靠性,挂载速度高。nsa数据卷的缺点是成本略高。

它适应于一些共享数据的重io应用,例如文件服务器等需要快速迁移的重io应用。

云盘数据卷目前尚未开放,还在等待中,但应该很快就会开放出来。它的优点就是性能很高;它的缺点是不能跨主机共享,一个云盘只能挂在一台主机上,无法同时挂载不同主机上。此外它的挂率也是比较慢的。因为云盘数据卷适合如数据库、redis等不需要共享数据的应用。

<b>docker插件系统的发展</b><b></b>

你真的了解Docker吗?——Docker插件机制详解

docker的插件的系统从docker1.6版本引入,目前是docker1.10版本。它存在一些历史问题,现在新版本中也在改进这些问题。

第一个问题是它无法控制启动顺序。现在提到的一容器化方式考虑存储、网络类的插件,很大的一个问题就是:插件必须在其他容器启动之间启动,这样才能使得其他容器正常运转;同时必须在其他容器停止之后才能停止,否则就会影响其他容器的使用。在以前的版本内,是没有办法很好控制这个事情,通过同docker的修改,在阿里云容器服务中可以保证插件在用户逻辑前启动。

第二个问题是插件分发混乱,缺乏统一的渠道。例如开发者新写的插件想共享给别人,缺乏统一的方式和行为规范。

在docker1.12中开发了一些特性。第一个是highly available services,它可以把插件独立于其他容器对待,使得插件和docker engine同时启动停止。

同时,它还引入docker store,使得插件以镜像的形式分发。

你真的了解Docker吗?——Docker插件机制详解

在docker1.13以及以后,他们列出了一些后续开发的项目:

per node plugin,保证plugin部署在每个节点上。

swarm-deployed plugins,可以在集群范围内部署plugin,集中管理插件。

提供编排插件,目前编排都是官方提供的,现在计划将该功能开放出来,使得调度可以引入第三方调度策略,完成特定需求定制。

继续阅读