https://mp.weixin.qq.com/s/dGwhhg8FzOxUVmvlyWkN7g
令人头疼的问题
现在,Docker技术正如狂风暴雨般改变着我们的基础设施架构。在腾讯,我们构建了大规模的容器云平台,其上运行了不同的应用,如广告推荐,消息推送等。其中也包括了像机器学习模型训练这类任务,这类任务包含很多的子任务(可能数百甚至上千个),当部署这种包含很多容器的任务时,会同时从Docker Registry 拉取(Pull)镜像,这种高并发的拉取操作,很容易耗尽Docker Registry的网络资源,这时,Docker Registry的网络出带宽就成了整个部署任务的瓶颈。一旦Docker Registry变的不可用,整个容器平台的可用性也随之降低,甚至导致级联失效(Cascading Failure)。我们虽然水平扩展了Registry,在一定程度解决了高并发问题,但是这种并不优雅的水平扩展方法治标不治本。原因是:多个Registry必须保证后端的数据一致性,所以它们访问的是同一个存储系统(如HDFS,CEPH)这样的话,后端存储的出带宽又成了新的瓶颈,而且随着业务的增长,需要继续水平扩展Registry…陷入一种令人头疼的循环。
因此我们需要一种新的镜像分发方法,优化大规模部署容器时的镜像分发过程。
理想的解决方式
作为一个晚期强迫症加空想社会主义型鹅厂程序鹅(滑稽),怎么可以不幻想一下这个问题的理想型解决方案是什么呢?于是在小本本上列下了几个目标:
-
减少大规模部署任务的镜像分发时间,哦对了,这里需要定义一下“分发时间(Distribution Time)“:在一次部署任务中,所有的节点Pull镜像所用的平均时间。这样的定义可以体现出我们的解决方法对分发任务整体的性能提升。
-
减少Docker Registry的网络开销
-
避免侵入现有Docker Engine的代码。若改了代码,意味着要想享用我们的系统,必须升级成我们的Docker,估计那时候运维小哥的菜刀已经架到我脖子上了…
总之一句话,新的方法需要“少吃饭,多干活“!听起来好像不太符合热力学第二定律?
现实又是怎样的?
幻想了一通,还是要回归现实的,看了下Registry和部署任务的样子,大概是这样:
图1 docker原生镜像分发
部署任务的pull命令几乎是同时到达Registry,然后Registry把同一个镜像发给N个节点,Registry的出流量就是ImageSize*N。说白了就是下载东西嘛,我想让它下得更快一点,突然想起了以前用BT下载电影(强调一下,是正经电影!),下的那么快,而且号称人越多越快!两者场景很像嘛,那是不是可以把BT和Docker镜像分发结合?BT协议(BitTorrent)是一种广泛使用的P2P协议,下载同一资源的BT Node之间的相互发现靠的是一种叫BT Tracker的服务器,发现彼此后,他俩可以互通有无,从而实现下载加速。这是BT的基本原理。
那么使用BT来分发Docker镜像,大概是这样:
图2 使用BT进行镜像分发
需要提及的是,在我们同一私有云内部的节点,都可以直接使用IP地址相互通信,并没有节点隐藏在NAT后面,因此不用做NAT穿透,简化了实现的复杂度。
FID设计与实现
然后我们提出了FID(Faster Image Distribution),下图是FID的系统架构。
图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种方式,会在下文详细叙述。
图4 P2P Registry中的镜像存储结构
众所周知,Docker镜像具有多层的结构。相应地,在Registry里每个层的所有数据被压缩存储在一个静态文件里,称为Blob。实际上,Docker拉取镜像的过程就是从Registry下载一个镜像对应的Blob,然后把Blob“链接“起来,形成镜像。为了结合BT,镜像在P2P Registry中的存储结构如图5所示。P2P Registry为每个Blob生成对应的种子文件(Torrent File)。然后FID Agent从P2P Registry获得种子文件,就可以下载对应的Blob了。
图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接口,完成镜像导入。
图6 load模式示意图
明显,整个过程的关键路径是下载时间最长的那个Blob。为了解决这个问题,我们提出了第二种模式。
2. Proxy模式
在Proxy模式中,FID Agent以Docker Engine的一个http代理服务器运行。FID Agent截获那些下载Blob的http请求,然后使用P2P方式下载相关的Blob。最后把下载到的数据写到截获的http请求的返回里。Proxy模式更加轻量级,而且对于不同的Docker版本,都有很强的兼容性。Proxy模式的运行过程如下图所示,每个Layer的下载和导入过程相互独立。
图7 proxy模式示意图
效果是否“令人羡慕”?
为了评价FID的性能,我们设计了两个实验:第一个实验对比了仅在一个节点上的Load Mode,Proxy Mode和Docker原生方案Pull镜像的性能对比。该实验的目的是分析P2P带来的额外开销。使用的镜像我我们自己构造的确定大小和层数的镜像。第二个实验,我们在200台物理节点上部署了FID,选择了4个常用的镜像来做测试
图8 实验设置
实验一
实验1的结果如图4所示,单节点条件下,Docker原生的Pull最快。我们认为,Proxy模式比Docker原生慢的原因有两点:1.Registry必须等到Blob数据全部从后端完全取出后,才能对外提供BT上传服务,在此期间,FID Agent只能等待。2.P2P会带来额外的网络开销,而原生的Pull是通过http下载数据,没有额外的网络开销,由此产生了性能差异。为了减少这种性能差异,我们做了相应的优化。
图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 优化后BT传输示意图
优化后效果显著,再次测试,数据如图5所示,可以看出,Proxy模式已经很接近Docker原生的没有额外网络开销的pull了。
图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 不同并发量下的镜像分发时间,对比了FID 与docker原生分发机制、Docket。右边为对应的CDF
在实验二中,我们统计了所有的P2P流量,在docker原生分发方案中,传输镜像所涉及的全部流量都需要由Registry承担,在图8中被标记为绿色。使用P2P以后,P2P各个节点间会承担一部分流量(在图中标注为蓝色),通过对P2P日志的分析,我们统计出源自P2P Registry与源自FID Agent的流量占比。统计显示源自FID Agent的流量均达到了90%以上,这意味着我们的改进为Registry节省了90%的流量!
图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,他们的建议让本工作变的更好!
参考文献
[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.
本文转载自公众号:PKUFineLab,感兴趣的读者可以搜索微信号“PKUFineLab"或长按识别下方二维码进行关注: