如何把Docker镜像分发速度提升90%


令人头疼的问题

现在,Docker技术正如狂风暴雨般改变着我们的基础设施架构。在腾讯,我们构建了大规模的容器云平台,其上运行了不同的应用,如广告推荐,消息推送等。其中也包括了像机器学习模型训练这类任务,这类任务包含很多的子任务(可能数百甚至上千个),当部署这种包含很多容器的任务时,会同时从Docker Registry 拉取(Pull)镜像,这种高并发的拉取操作,很容易耗尽Docker Registry的网络资源,这时,Docker Registry的网络出带宽就成了整个部署任务的瓶颈。一旦Docker Registry变的不可用,整个容器平台的可用性也随之降低,甚至导致级联失效(Cascading Failure)。我们虽然水平扩展了Registry,在一定程度解决了高并发问题,但是这种并不优雅的水平扩展方法治标不治本。原因是:多个Registry必须保证后端的数据一致性,所以它们访问的是同一个存储系统(如HDFS,Ceph)这样的话,后端存储的出带宽又成了新的瓶颈,而且随着业务的增长,需要继续水平扩展Registry……陷入一种令人头疼的循环。

因此我们需要一种新的镜像分发方法,优化大规模部署容器时的镜像分发过程。

理想的解决方式

作为一个晚期强迫症加空想社会主义型鹅厂程序鹅(滑稽),怎么可以不幻想一下这个问题的理想型解决方案是什么呢?于是在小本本上列下了几个目标:
  1. 减少大规模部署任务的镜像分发时间,哦对了,这里需要定义一下“分发时间(Distribution Time)“:在一次部署任务中,所有的节点Pull镜像所用的平均时间。这样的定义可以体现出我们的解决方法对分发任务整体的性能提升。
  2. 减少Docker Registry的网络开销。
  3. 避免侵入现有Docker Engine的代码。若改了代码,意味着要想享用我们的系统,必须升级成我们的Docker,估计那时候运维小哥的菜刀已经架到我脖子上了……


总之一句话,新的方法需要“少吃饭,多干活“!听起来好像不太符合热力学第二定律?

现实又是怎样的?

幻想了一通,还是要回归现实的,看了下Registry和部署任务的样子,大概是这样:
01.gif

图1 Docker原生镜像分发

部署任务的pull命令几乎是同时到达Registry,然后Registry把同一个镜像发给N个节点,Registry的出流量就是ImageSize*N。说白了就是下载东西嘛,我想让它下得更快一点,突然想起了以前用BT下载电影(强调一下,是正经电影!),下的那么快,而且号称人越多越快!两者场景很像嘛,那是不是可以把BT和Docker镜像分发结合?BT协议(BitTorrent)是一种广泛使用的P2P协议,下载同一资源的BT Node之间的相互发现靠的是一种叫BT Tracker的服务器,发现彼此后,他俩可以互通有无,从而实现下载加速。这是BT的基本原理。

那么使用BT来分发Docker镜像,大概是这样:
02.gif

图2 使用BT进行镜像分发

需要提及的是,在我们同一私有云内部的节点,都可以直接使用IP地址相互通信,并没有节点隐藏在NAT后面,因此不用做NAT穿透,简化了实现的复杂度。

FID设计与实现

然后我们提出了FID(Faster Image Distribution),下图是FID的系统架构。
03.jpg

图3 FID架构图

图中大体可以分为两部分:

Storage 和P2P Registry

这一部分主要负责镜像存储与管理,我们在Docker Registry[1]的基础上,做了二次开发,给Registry添加了BT模块,但是Registry的BT模块只上传,不下载,因为数据已经在他这里了,无需下载。P2P Registry会向BT Tracker声明自己拥有的资源,这样别的节点通过与tracker通信就能找到P2P Registry了。也就是BT协议的“注册资源-相互发现-互通有无“的过程。

FID Agent和Docker

这一部分运行在每个Docker节点上(图中灰色虚线框表示),为了不侵入Docker的源代码,我们开发了额外的FID Agent负责BT下载,下载后再把数据导入Docker,这里的“导入“有2种方式,会在下文详细叙述。
04.jpg

图4 P2P Registry中的镜像存储结构

众所周知,Docker镜像具有多层的结构。相应地,在Registry里每个层的所有数据被压缩存储在一个静态文件里,称为Blob。实际上,Docker拉取镜像的过程就是从Registry下载一个镜像对应的Blob,然后把Blob“链接“起来,形成镜像。为了结合BT,镜像在P2P Registry中的存储结构如图5所示。P2P Registry为每个Blob生成对应的种子文件(Torrent File)。然后FID Agent从P2P Registry获得种子文件,就可以下载对应的Blob了。
05.jpg

图5 Docker镜像在P2P Registry中的存储结构

FID Agent的工作模式

上文降到Agent把数据导入Docker有2种方式,这多种方式也导致了FID Agent有2种不同的工作模式

1、Load模式

Docker有个接口——Load,用户可以通过“docker load“命令将镜像Load到Docker里,FID Agent通过Load的方式把镜像数据导入到Docker,我们称之为Load模式。我们发现了两个类似于这种模式的相关工作:Docket[2]和VMware Harbor[3]。在FID Agent的Load模式下,拉取镜像主要有四个步骤。
  1. 用户执行“fid-agent pull image-foo“命令
  2. FID Agent把对应的Blob以BT的方式下载下来
  3. 待所有Blob下载完成后,把所有的Blob打包到一个tar包中调用Docker的Load接口,完成镜像导入。
    06.gif

    图6 load模式示意图


明显,整个过程的关键路径是下载时间最长的那个Blob。为了解决这个问题,我们提出了第二种模式。

2、Proxy模式

在Proxy模式中,FID Agent以Docker Engine的一个http代理服务器运行。FID Agent截获那些下载Blob的http请求,然后使用P2P方式下载相关的Blob。最后把下载到的数据写到截获的http请求的返回里。Proxy模式更加轻量级,而且对于不同的Docker版本,都有很强的兼容性。Proxy模式的运行过程如下图所示,每个Layer的下载和导入过程相互独立。
07.gif

图7 Proxy模式示意图

效果是否“令人羡慕”?

为了评价FID的性能,我们设计了两个实验:第一个实验对比了仅在一个节点上的Load Mode,Proxy Mode和Docker原生方案Pull镜像的性能对比。该实验的目的是分析P2P带来的额外开销。使用的镜像我我们自己构造的确定大小和层数的镜像。第二个实验,我们在200台物理节点上部署了FID,选择了4个常用的镜像来做测试。
08.jpg

图8 实验设置

实验一

实验1的结果如图4所示,单节点条件下,Docker原生的Pull最快。我们认为,Proxy模式比Docker原生慢的原因有两点:1. Registry必须等到Blob数据全部从后端完全取出后,才能对外提供BT上传服务,在此期间,FID Agent只能等待。2. P2P会带来额外的网络开销,而原生的Pull是通过http下载数据,没有额外的网络开销,由此产生了性能差异。为了减少这种性能差异,我们做了相应的优化。
09.jpg

图9 三种模式比较

具体的优化方法是:我们优化了从后台取数据和提供BT数据上传(Seeding),使得P2P Registry可以一边从后台取数据,一边Seeding。在BT协议中,一个文件被分成很多块(Block),块是BT传输中的最小单位。如果一个BT Node向P2P Registry请求的block尚未从后端取出,那这个请求就会一直等待,直到P2P Registry得到这个Block。下面的动图显示了优化后的BT下载,为了简化描述,我们假设Block下载并发量为1。P2P Registry从后端读取数据的顺序是顺序的(1,2,3,4,5,6),而BT下载是随机的(为了让下载尽快开始,我们采用局部随机的策略)。而最后返回给Docker的数据必须是顺序的。从图中可以看出,在P2P Registry拿到Block3之前,FID Agent的Block3请求一直处于等待状态,而FID Agent下载完Block1以后,才把数据返回给Docker。
10.gif

图10 优化后BT传输示意图

优化后效果显著,再次测试,数据如图5所示,可以看出,Proxy模式已经很接近Docker原生的没有额外网络开销的pull了。
11.png

图11 优化后的Proxy模式与Docker原生pull在单节点上的对比

实验二,大规模测试

由于Proxy模式的性能优于Load模式,在随后的大规模测试与实际生产环境中,均使用Proxy模式。

从实验数据可以看出,相比于Docker原生的镜像分发方案(图中标记为docker-native)和相关工作Docket(图中标记为Docket),FID的性能是很好的,随着部署任务所涉及的节点数量增加,FID的镜像分发时间并没有显著的线性提升,几乎不受节点数量增加的影响。而Docker原生方案,分发时间的增长就很显著了。从CDF图可以看出,位于长尾的数据点并不多。向200个节点分发hadoop镜像(500M)时,Docker原生方案需要500秒,而FID只需要43秒,FID把分发时间降低了91.35%!
12.jpg

图12 不同并发量下的镜像分发时间,对比了FID与Docker原生分发机制、Docket。右边为对应的CDF

在实验二中,我们统计了所有的P2P流量,在docker原生分发方案中,传输镜像所涉及的全部流量都需要由Registry承担,在图8中被标记为绿色。使用P2P以后,P2P各个节点间会承担一部分流量(在图中标注为蓝色),通过对P2P日志的分析,我们统计出源自P2P Registry与源自FID Agent的流量占比。统计显示源自FID Agent的流量均达到了90%以上,这意味着我们的改进为Registry节省了90%的流量!
13.jpg

图13 P2P流量统计,绿色为源自Registry的流量,蓝色为源自FID Agent的流量(可以理解为帮Registry分担的流量)

总结

为了加速大规模容器部署任务的执行,我们设计和开发了FID(Faster Image Distribution)。FID具有更快的速度,向200个节点分发500M的镜像比docker原生方式的分发时间降低了91%;Registry所在节点具有更低的网络流量,实验数据表明采用FID后,Registry的出流量降低了90%以上;对用户友好,部署FID,只需在每台节点安装FID Agent,然后把Docker Engine的http代理设为FID Agent,上层系统如Kubernets,无需修改任何代码与逻辑,即可享受P2P加速。

致谢

感谢北大与腾讯的同事,他们的建设性意见对本工作帮助很大,实验涉及的物理机,均由腾讯云提供,感谢AMLSC的committees和reviewers,他们的建议让本工作变的更好!
00.png

参考文献

[1]“Docker Distribution”https://github.com/docker/distribution
[2]“Docket” https://github.com/netvarun/docket
[3]“用P2P方法快速分发Docker镜像” https://t.goodrain.com/t/p2p-docker/135
[4] KangjinW, Yong Y, Ying L, et al. FID: A Faster Image Distribution System for DockerPlatform[C]//Foundations and Applications of Self* Systems (FAS* W), 2017 IEEE2nd International Workshops on. IEEE, 2017: 191-198.

原文链接:【腾讯】如何把Docker镜像分发速度提升90+%(作者:王康瑾)

1 个评论

好东西,求开源

要回复文章请先登录注册