云原生时代的镜像分发工具——Dragonfly简介

欢迎关注微信公众号“云原生手记”

背景

今天要分享的是Dragonfly,这是阿里开源的一款用于镜像分发的工具,大家最早了解到这款工具,可能是因为调研大规模容器镜像分发的解决方案,最初这款工具确实是用于解决镜像分发问题的,当然,现在仍用于镜像分发,但是将来这块工具的定位将是企业级文件工具。

dragonfly解决大规模容器镜像分发问题的关键是采用了p2p技术,p2p翻译为perr to peer,译为端到端。这边稍微介绍下容器镜像分发的背景,大家都知道镜像仓库一般简单的可以使用registry搭建,当然,也有企业级的镜像仓库harbor。正常使用,这两款工具都没什么问题,但是当有大规模拉取镜像需求时(比如1000个节点在拉取镜像),harbor极有可能会支撑不住而挂掉或者获取文件的时间过长导致很多应用都在等待。有人会说,可以水平扩容harbor,但这不是永久性的解决方案,再说harbor的存储(如s3存储)能不能扛住还两说。基于此,阿里推出了dragonfly,而Uber推出了Kraken。本文的重点是dragonfly,Kraken有机会再分享。

dragonfly的原理

dragonfly大规模容器镜像分发问题的关键是采用了p2p技术,那么到底是怎么解决的呢?我们稍微深入分析下大规模容器镜像分发问题,存在以下几个需要解决的问题:

  • 1000个节点都要向镜像仓库拉取镜像,假设仓库服务没有奔溃,但是获取镜像的时间会很长,这个在下面的性能图中会有体现。
  • 1000个节点都要向镜像仓库拉取镜像,其中很多可能拉取的镜像是相同的,或者说他们中很多拉取的层文件是相同的。所以这边的小问题是如何解决同一个镜像/层文件重复拉取的问题。在编程中,相同的代码出现2-3次,就需要抽出来作为一个函数;
  • 高并发拉取镜像时,镜像仓库的存储可能会崩掉,这是由存储系统决定的。存储系统本身有瓶颈。水平扩展harbor,并不能减轻存储的压力。

对于以上两个问题,dragonfly的解决策略如下:

  • 不再对相同镜像/层文件重复拉取,以减轻harbor不必要的并发压力;
  • 使用拉取的客户端节点作为存储,对外提供文件服务,以减轻harbor端存储的压力。
  • 通过去其他客户端节点获取层文件,而不是去镜像仓库获取文件,获取文件的速度会有所提高。

那么dragonfly解决大规模镜像分发的效果如何呢?
对于Dragonfly,无论有多少客户端开始下载文件,平均下载时间几乎都没有增加(实验中为12s,这意味着所有客户端总共只需要12s即可完成文件/图像的下载)。
对于wget,当您有更多客户端时,下载时间会不断增加。当wget客户端数量达到1200(在以下实验中)时,文件源将崩溃,因此它不能为任何客户端提供服务。
从统计结果来看,dragonfly的使用绝对是加快了镜像获取的时间。这是dragonfly的官方的统计结果,样本数是从100个节点开始统计的,确实优化效果很明显,足够说明在100节点及以上的情况下并发拉取镜像,是有效率上的提高的。

测试环境 统计结果
Dragonfly 服务器 2 * (24core 64GB 2000Mb/s)
文件源服务器 2 * (24core 64GB 2000Mb/s)
客户端 4core 8GB 200Mb/s
目标文件大小 200MB

在这里插入图片描述

dragonfly中的概念

在深入介绍dragonfly前,需要先向大家介绍dragonfly中的一些概念:

  • SuperNode(超级节点):这是一个长期运行的进程,主要提供以下两个功能:
    • 为每个peer节点调度下载piece的网络路径(你可以理解为种子文件,该文件告诉客户端去哪些节点获取哪些数据),superNode是个调度者也是追踪者, 因为peer节点会通知supernode本节点piece文件的信息,也就是说超级节点拥有其下属所有peer节点上的piece文件信息。
    • CDN 服务器,从源处缓存数据,避免从源处下载重复的数据。deget在下载文件之前,都会注册到超级节点,告诉超级节点dfget需要下载的信息,超级节点会立刻去源处下载这些目标文件。
  • Dfget:是dragonfly中获取文件的工具,负责下载文件数据。类似于wget,同时,他还担任peer角色(dfget server 命令就是peer服务),peer角色的节点可以为p2p网络中的其他使用dfget命令的客户端传输数据。
  • Dfdaemon:是个代理,Dfdaemon在容器引擎(docker daemon)和注册表(registry或者harbor)之间的代理,也是一个本地长期运行的进程。当拉取镜像时,他会拦截docker daemon发送出去的请求,然后对于非层文件的请求直接转发,而对于层文件获取请求会拦截后使用Dfget下载这些层文件。docker需要配置proxy参数,接入dfdaemon,才能让dfdaemon起效。
  • P2P:peer to peer,一种分布式应用程序体系结构。
  • Task:任务将存储有关taskFile的一些元信息,任务将存储有关taskFile,片段(piece)和其他内容的一些元信息。任务与任务ID标识的磁盘上的文件具有一一对应关系,如果要从Supernode下载文件,则应注册一个任务,这意味着在实际执行操作之前先告诉服务器要下载的文件信息。
  • DfgetTask: DfgetTask表示由Dfget或其他客户端启动的下载过程。当Dfget尝试从p2p网络下载文件时,Supernode将创建一个DfgetTask对象,用于管理下载过程的生命周期。
  • Peer: 在P2P网络中,peer节点双方既是资源的提供者又是消费者。因此,在Dfget开始从Supernode进行下载任务之前,Dfget将启动一个Web服务器,该服务器提供下载的文件服务以供其他peer在P2P网络中下载,并向Supernode发送peer/Register请求以加入P2P网络。只有这样,Dfget才能从P2P网络下载文件。
  • Piece: 一个piece是将要下载的文件的一部分,可以解释为一块文件。在dragonfly中,下载文件不是完整传输,而是分段传输。

dragonfly运行原理

dragonfly中有三个组件:supernode,dfdaemon和dfget,下面就这几个组件进行讲解。

dfdaemon

dfdaemon到底做了哪些事情?起到了什么作用?

在使用dragonfly的节点上,都会配置docker的proxy参数,如下:

vi /usr/lib/systemd/system/docker.service
[Service]
Environment="HTTP_PROXY=http://10.154.12.120:65001"

其中http://10.154.12.120:65001地址时dfdaemon服务的地址,一般每个节点上都会启一个dfdaemon的服务,而节点上的docker proxy 参数会指向dfdaemon的服务。docker的proxy参数设置的作用就是docker daemon向外发送的所有请求都会发送到http://10.154.12.120:65001即dfdaemon服务,由dfdaemon处理。

dfdaemon的拦截

在这里插入图片描述

dfdaemon不会拦截docker daemon发出来的所有的请求,他只对含blobs/sha256.*的请求做拦截处理,其他请求它只做转发。也就是说当拉取镜像时,对于获取镜像出blob文件的请求,dfdaemon是制作转发的。

dfdaemon拦截后作甚

dfaemon会将docker daemon的请求进行拦截,但是除了blob文件的请求会被拦截做额外处理外,其他请求只会做转发。比如docker pull 镜像时会需要获取Manifest文件信息,像这个请求dfdaemon是直接转发的,然后根据转发结果返回给docker daemon。而在获取到manifest文件后,docker daemon会请求获取Blob文件,这类请求将被dfdaemon拦截住,去做其他处理。本来docker daemon获取blob文件,一般是去registry或者harbor获取,现在被dfdaemon拦截,由dfdaemon去获取blob文件。那么dfdaemon是如何获取Blob文件的呢?其实dfdaemon并没有直接去获取blob文件,而是使用dfget命令去获取blob文件,执行的dfget命令如下:

"/opt/dragonfly/df-client/dfget" "-u" "http://10.154.12.121:7999/v2/library/rabbitmq/blobs/sha256:04f8f8815c88ec1ed64a013b039aef36b2ebc09c66101a35a916a2f73bab6ae3" "-o" "/root/.small-dragonfly/dfdaemon/data/5e096c2a-a93c-42c1-a2b6-9f88585a3d92" "--node" "10.142.113.43" "--expiretime" "30m0s" "--alivetime" "5m0s" "-f" "Expires&Signature" "--dfdaemon" "-s" "20MB" "--totallimit" "20MB" "--node" "10.154.12.127,10.154.12.127" "--header" "User-Agent:docker/18.09.9 go/go1.11.13 git-commit/039a7df kernel/4.14.78-300.el7.bclinux.x86_64 os/linux arch/amd64 UpstreamClient(Docker-Client/18.09.2 \\(linux\\))" "--header" "X-Forwarded-For:10.154.12.127"

dfdaemon将拉取blob文件的任务交给dfget,在dfget命令执行完后,dfdaemon会从本地读取下载下来的blob文件内容(dfget获取文件后将文件保存在本地),然后将文件内容返回给docker daemon,那么docker daemon就算获取到了Blob文件。

dfget

上面讲到dfdaemon是使用dfget去获取blob文件的,那么dfget是如何获取的呢?跟docker daemon直接去获取文件有什么区别呢?

dfget获取blob过程

其实当前版本的dragonfly主要用于镜像分发,它默认只会对blob文件的请求进行拦截并处理,dragobfly的dfget负责去获取blob文件,下面这张图就是dfget获取Blob文件的全过程:
在这里插入图片描述

  • dfget命令启动后,先注册到supernode,告知本地需要哪个blob文件,并且通知supernode本地起了一个peer服务,地址多少,服务端口多少。supernode会返回一个taskId给dfget;这边需要注意一个细节,一个dfclient本地可能存在多个Peer服务,peer服务是会占用端口的,那么k8s中使用时是否应该注意?
  • dfget根据taskId再次请求supernode,获取下载blob文件的种子文件,种子文件就是告诉你去哪些peer节点获取哪些piece,把这些piece全下载下来,你就可以拼成一个完成的blob文件。然后将blob文件存储到临时目录,外部程序会将临时目录的blob文件转移到正确的目标目录(dfdaemon会去目标目录读取文件并返回给docker daemon)。

注意:这边是否有个疑问,万一supernode挂了,是不是dfget就获取不到Blob文件了呢?这个问题已经被考虑到了,源码中已经解决该问题了,要是dfget通过dragonfly的方式无法获取完整的数据,那么dfget会直接去源仓库处获取blob文件,并保存到临时目录。

supernode

超级节点在整个dragnfly的系统的,起着中央调度作用。dfget都需要从超级节点获取种子文件,失去超级节点,dfget只能使用原始的方式去获取blob文件,回到原始的请求。那么超级节点会做哪些操作呢:

  • 将自己注册成为peer服务,supernode使用nginx作为自己的文件服务器。
  • 提供注册、获取种子文件、汇报节点状态等服务,当然还有相关的监控接口。

注册接口

上面一个了解到,dfget每次启动时,都会向supernode注册,告知超级节点本地启动的peer服务信息,超级节点在这个接口中会去做什么操作呢?

  • dfget注册信息中会带有期望下载的文件信息,supernode会解析出来;
  • supernode根据需要下载的文件信息拼成一个唯一的taskId返回给客户端。
  • supernode会获取期望下载的文件大小,并计算piece大小。
  • supernode会异步去下载期望的文件:
    • 检查本地是否已经有该文件了,有即退出;
    • 本地不存在该文件,那就去源仓库处获取该文件,并存储在supernode本地。
  • dfget获取到taskId后,会再次请求supernode获取种子文件,超级节点会根据文件的分块大小和编号,最后形成一个种子文件:每个块去哪个peer节点下载。一个Blob文件若是196M,那么每个piece有4M,总共分为49个piece。超级节点会告诉deget 这49个piece去哪边下载。
  • dfget下载完每个piece都会向超级节点汇报下载情况,超级节点就知道其所管辖的peer节点处都有哪些piece了。

在这里插入图片描述

以上这张是我画的有关dragobfly的交互图,下面说明下其中的交互。先介绍下部署环境:

  • 1个节点上使用docker部署supernode;
  • 2个节点上均使用docker部署了dfclient,dfclient由dfdaemon和dfget组成。两个节点的docker 都配置proxy参数。

获取Blob文件的描述:

  • 在一个节点上执行docker pull 命令。
  • 节点的docker daemon将请求转发给本地的dfdaemon
  • dfdaemon根据过滤条件选择转发或者拦截处理;
  • dfdaemon拦截blob文件请求;
  • dfdaemon使用dfget去获取Blob文件;
  • dfget注册成为Peer;
  • dfget请求supernode获取种子文件;
  • dfget根据种子文件去获取Blob文件,并保存在临时目录;
  • dfget将下载情况汇报给supernode;
  • dfget经临时文件移动到目标目录,dfget执行完;
  • dfdaemon从目标目录获取文件内容,并返还给docker daemon;
  • 一个blob文件下载完,开始下一个blob文件下来。

总结

dragonfly的优点:

  • 很明显加快拉取速度了,尤其是客户端多的情况下,优化效果很明显,所以优点就不多说了。

dragonfly的风险点:

  • 我们可以看到dragonfly存在很多数据落盘行为,且有很多数据从磁盘读取,那么对磁盘的性能要求就高了。
  • 频繁的落盘和读取行为,阻碍了性能的进一步提升。
  • 因为使用到了本地磁盘,那么磁盘容量管理也很重要,完了撑爆了。当然dragonfly都会定时定量做磁盘清理的。

注意点:dragondly适用于大规模并发场景,要是你的使用场景没有大搞100个节点在并发拉取镜像,建议不要使用Dragonfly,使用水平扩展的harbor就足够了。使用dragonfly毕竟是会带来一定的复杂度的。

对于以上的风险点,会在dragonfly2.0中解决,到时会有个stream模式。此外,dragonfly2.0开始将会有预热功能。总之,该组件的发展刚刚踏上正轨,路还很长。

猜你喜欢

转载自blog.csdn.net/u013276277/article/details/113102551