本文由星云 Clustar 编译并授权 InfoQ 发布,原文链接:https://mp.weixin.qq.com/s/jHSaBHJdikJCZZiMjM4m5Q
为实现资源的高效利用和轻量隔离,很多流行的大型云应用都在逐渐使用容器化。同时,很多数据密集型应用(例如,数据分析和深度学习框架)正在或希望采用RDMA来提高网络性能。行业趋势表明,这两种场景不可避免地会发生冲突。在本文中,我们介绍了FreeFlow,一个为容器云设计的基于软件的RDMA虚拟化框架。FreeFlow纯粹使用基于软件的方法,利用商用RDMA NICs实现了虚拟RDMA网络。与现有的RDMA虚拟化解决方案不同,FreeFlow完全满足来自云环境的需求,例如多租户的隔离、容器迁移的可移植性 、以及控制和数据平面策略的可控制性。FreeFlow对应用程序也是透明的,且使用很小的CPU开销提供了接近裸机RDMA的网络性能。在我们对TensorFlow和Spark的评估中,FreeFlow提供了几乎与裸机RDMA相同的应用性能。
大型云应用开发者一直在追求高性能、低运维成本和高资源利用率,导致容器化和远程直接内存访问(RDMA)网络技术被越来越多的采用。
容器[7,11,6]提供的轻量级隔离和可移植性,降低了部署和管理云应用的复杂性(从而降低了成本)。因此,现在容器成为管理和部署大型云应用的实际方式。
因为与基于标准TCP/IP的网络相比,RDMA网络能够提供更高的吞吐量、更低的延迟和更少的CPU占用,所以许多数据密集型应用(例如,深度学习和数据分析框架)都会采用RDMA [24,5,18,17]。
但不幸的是,这两种技术趋势在云环境中是相互矛盾的。容器化的核心价值是为应用提供高效灵活的管理。为此容器云需要容器在网络中具有以下三个属性:
隔离:每个容器都应该有其专用的网络命名空间(包括端口空间、路由表、接口等),以消除与同一主机上其他容器的冲突。
可移植性:容器应该使用虚拟网络与其他容器通信,并且与它的虚拟IP保持关联,而不管它被调度到哪个主机上或迁移到哪个主机上。
可控性:编排器可以容易地执行控制平面策略(例如,准入控制、路由)和数据平面策略(例如,QoS、计费)。此属性在(多租户)云环境中尤其需要。
对于在云环境中,自由放置、迁移容器、以及控制每个容器可使用的资源,这些属性都是必需的。为此,在基于TCP/IP的操作中,网络可以通过软件(虚拟)交换机来实现完全虚拟化[15]。
但是,很难对基于RDMA的网络进行完全虚拟化。 RDMA通过将网络处理卸载到硬件NIC中,绕过内核软件栈,实现了高网络性能。 在共享的云环境中难以修改硬件中的控制平面状态(例如,路由),同时由于流量直接通过PCIe总线在RAM和NIC之间传输,也难以控制数据路径。
因此,采用这两种技术的几个数据密集型应用(例如,TensorFlow [24],CNTK [5],Spark [18],Hadoop [17])仅在专用裸机群集中运行时才使用RDMA; 当在共享的云环境中运行时,不得不完全摒弃RDMA提供的性能优势。 当然,使用专用集群来运行应用对于提供商或客户来说都是不划算的。
本文的目的很简单:我们希望基于云环境中的容器化应用能够像在专用裸机集群中那样高效地使用RDMA,同时也能实现容器云中的隔离、可移植性和可控性的要求。
目前还没有成熟的容器RDMA虚拟化解决方案。表1总结了可能通过扩展来支持容器的一些重要选项,尽管它们未能达到关键要求,也可能需要以很大的性能成本为代价进行实现。
例如,基于硬件的I/O虚拟化技术,如SR-IOV[21],具有根本的移植性限制[39,28],因为它们需要重新配置硬件网卡和交换机来支持容器的迁移。控制路径虚拟化解决方案,例如HyV[39],只操作控制平面命令来实现隔离和可移植性,它们不具有数据流的可见性或可控制性。因此,它们不能灵活地支持云提供商所需的数据平面策略。软件仿真RDMA,如SoftRoCE[36],通过在UDP网络栈上运行RDMA并使用现有的虚拟IP网络解决方案,可以轻松实现隔离、可移植性和可控性,但其性能将受到UDP的限制。
本文提出了一种基于软件的容器云虚拟RDMA网络框架FreeFlow,该框架实现了隔离、可移植性和可控性,并提供了接近裸机RDMA的性能。FreeFlow的核心是运行在每个服务器上的软件虚拟交换机,来实现在商业RDMA 网卡上虚拟化RDMA。FreeFlow不需要专门的硬件或基于硬件的I/O虚拟化。软件虚拟交换机具有对容器间通信的控制路径(如地址、路由)和数据路径(如数据流量)的完全访问权。这种设计理念类似于容器云中用于TCP / IP网络的现有软件虚拟交换机,例如Open vSwitch(OvS)[15],尽管由于RDMA的特性,FreeFlow的实际设计与OvS截然不同。
FreeFlow的设计解决了两个关键挑战。首先,我们希望FreeFlow对应用程序完全透明。这很有挑战性,因为RDMA需要网卡来操作内存缓冲区和文件描述符,而容器内的应用程序由于网络虚拟化而不能直接与网卡交互。我们解决这一问题的关键点是,容器本质上是进程,它们可以使用FreeFlow轻松共享内存和文件描述符等资源。如果FreeFlow和容器共享相同的内存(§4.3)和文件描述符(§4.4),则底层物理RDMA NIC上的任何操作都将自动在容器内生效。另一个问题是,鉴于应用程序不会协作地创建可共享的资源,因此共享对应用程序透明的资源并不简单。 我们设计了将资源从不可共享转换为可共享的方法,对应用程序代码无需修改或只需很少的修改。
其次,FreeFlow必须提供与裸机RDMA相当的吞吐量和延迟。我们将吞吐量和延迟中的性能瓶颈分别定义为内存复制和进程间通信。我们利用一个零拷贝设计吞吐量(§4.3), 一个共享内存进程间通道与CPU旋转延迟(§5.2)。同时还优化了FreeFlow来限制CPU开销。
我们使用标准的微基准测试工具和真实的数据密集型应用程序、Spark和TensorFlow来评估FreeFlow的性能,不需要对它们进行任何修改或只需很小的修改。FreeFlow的性能可与裸机RDMA相媲美,无需太多的CPU开销。同时,与使用传统TCP/IP虚拟网络相比,FreeFlow显著提高了实际应用程序的性能,吞吐量提高了14.6倍,延迟降低了98%。FreeFlow吸引了多个RDMA解决方案提供商的兴趣,并已开放源代码https://github.com/Microsoft/Freeflow。
本章简要介绍了容器和RDMA网络,以及引出对容器的基于软件的RDMA虚拟化的需求。
容器正在成为打包和部署数据中心应用程序的实际选择 [30, 27, 25]。容器使用如chroot等机制 [4],将应用程序的可执行文件和依赖捆绑在独立的命名空间中,从而提供轻量级隔离和可移植的解决方案。
大
多数容器应用使用微服务架构,并由多个容器组成。例如,Spark [2]中的每个mapper和reducer节点都是一个单独的容器,TensorFlow[22]中的每个ps节点或worker节点也是一个单独的容器。容器通过网络解决方案交换数据。网络解决方案的设计会影响隔离度和可移植性。
例如,在主机模式网络中,容器使用其主机的IP和端口空间,并像主机操作系统中的常规进程一样进行通信。此模式有较差隔离性(例如,端口冲突)和可移植性(例如,迁移到其他主机后,必须更改IP地址和端口)。
因此,许多应用程序使用虚拟模式网络。在此模式下,容器的网络命名空间完全隔离,容器通过由主机上的软件虚拟交换机组成的虚拟(覆盖)网络进行通信。因为可以在软件虚拟交换机中控制到虚拟IP的路由,所以容器的虚拟IP是高度可移植的。由于所有数据流量必须通过虚拟交换机,因此它们可以访问流量,从而为容器网络提供完全可控性。这种隔离和可移植性使编排器在容器部署和迁移方面具有完全的灵活性,并且这种可控性使云提供商能够在控制和数据层面上实施其策略。
实际上,像Kubernetes [11]这样的编排器要求使用虚拟网络模式[12]。许多软件解决方案可用于为容器提供虚拟网络结构,例如Weave [23]和Docker Overlay [7]。
许多现代应用程序(例如深度学习和数据分析框架)已采用RDMA网络 [18,17,25,5]来获得比传统TCP / IP堆栈更高的吞吐量和更低的延迟。RDMA通过将大部分网络功能卸载到NIC中实现性能的提升,有效地绕过操作系统内核。
表2显示了使用RDMA进行通信的深度学习应用的性能改进 - 训练Recurrent Neural Network(RNN)语音识别模型。该应用程序首先在具有8个GPU的单台机器上进行基准测试。当应用程序在具有16个GPU的两台计算机上运行时,传统的TCP/IP网络成为瓶颈,性能的提升下降。但是使用了RDMA,额外GPU可以显著提高性能。
上述现象的原因是,RNN训练任务包含数千个步骤。在每个步骤中,所有GPU都必须对训练模型参数进行shuffle(即随机重排列),总流量大约在100MB到10GB。花在通信上的时间实际上是浪费GPU的时间,因为GPU在shuffle期间处于空闲状态。TCP在这些频繁和突发的工作负载中表现不佳,而RDMA可以在每次shuffle开始时瞬间爬升到全带宽。
对于基于软件的RDMA虚拟化的需求:
我们已经注意到了虚拟模式网络对容器应用程序的好处,即增强的隔离性、可移植性和可控性。同时,RDMA还可以为许多具有微服务架构的应用程序提供显著的性能提升。
那么问题是,我们如何将RDMA网络与需要虚拟模式网络的容器应用程序结合使用,尤其是在云环境中。
正如我们之前看到的,RDMA网络依赖于将大多数网络功能卸载到NIC。“虚拟化”RDMA网络的一种可能方法是使用基于硬件的解决方案,例如SR-IOV[21],但是这会限制虚拟模式网络提供的可移植性。如图1(a)所示,使用SR-IOV,NIC运行一个简单的第2层交换机,仅仅执行VLAN转发。因此,必须在底层物理网络中直接路由从虚拟网络生成并发往虚拟网络的所有分组。同时,将容器C1迁移到主机Host2需要重新配置物理交换机,以将C1的数据包路由到Host2,而不是Host1。此外,在生产中,物理交换机需要维护大量的路由表来管理虚拟网络中所有容器的路由,这在大规模云环境中是不可行的。
因此,我们认为,为容器虚拟化RDMA网络的正确方法是使用软件交换机,正如它用于虚拟化传统TCP/IP网络一样。如图1(b)所示,物理网络只负责提供针对不同主机的数据包,而虚拟网络路由完全在每个主机内部的软件交换机中实现,这些交换机与物理网络无关。软件交换机可以控制所有寻址和路由,因此在控制层面上提供良好的隔离和可移植性。它还可以在数据层面上实现网络功能,例如QoS和计量。
FreeFlow的目标是在每个容器内部提供虚拟接口,应用程序可以通过虚拟接口上的虚拟网络以未修改的方式使用RDMA。理想情况下,虚拟网络的性能应接近裸机RDMA,并且控制和数据路径上的策略都可以灵活地配置为纯软件。在本节中,我们将介绍系统架构以及FreeFlow设计中的主要挑战。
在原生RDMA中,如图2(a)所示,应用程序利用RDMA API直接向硬件NIC发送命令,以实现控制和数据路径功能。FreeFlow接收应用程序和物理网卡之间的通信,并在软件FreeFlow路由器内执行控制平面和数据平面策略,该路由器作为主机上的另一个容器运行。特别的是,为了控制数据路径,FreeFlo wrouter只允许物理NIC直接从自己的内存中读取和写入(图2(b)中的虚拟内存),并负责将数据复制到应用程序的内存中。请注意,内部容器中的内存容器和FreeFlow router中的影子内存可以是零拷贝的同一块物理内存(§4.3 )。
有多种方法可以拦截应用程序和物理网卡之间的通信,但我们必须选择有效的方法。 目前有许多支持RDMA的商业技术,包括Infiniband [9]、RoCE [8]和iWarp [19]。应用程序还可以使用几种不同的高级API来访问RDMA功能,例如MPI和rsocket [20]。如图3所示,这些API事实上的“窄腰”是IB Verbs API(Verbs)。因此,我们有意识地选择在FreeFlow中支持Verbs,这样我们就可以自然地支持所有更高级别的API。
Verbs使用“队列对”(QP)的概念进行数据传输。对于每个连接,两个端点中的每一个都有一个sendqueue(SQ)和一个接收队列(RQ),它们一起称为QP。发送队列保存有关需要发送的内存缓冲区的信息,而接收队列保存有关接收传入数据的缓冲区的信息。
每个端点还具有单独的完成队列(CQ),NIC使用该队列来通知端点发送或接收请求的完成。Verbs库和相关驱动程序允许读取、写入和监视三个队列的应用程序。数据的实际传输,包括打包和错误恢复,由NIC处理。为了透明的支持Verbs ,FreeFlow创建虚拟NIC中的虚拟QP和CQ,以及将它们的操作与物理NI中的实际QP和CQ的操作联系起来。
FreeFlow的体系结构如图4所示。我们修改或引入的容器网络堆栈的三个组件以灰色显示:(i)FreeFlow network library(FFL),(ii)FreeFlow software路由器(FFR),以及(iii)FreeFlow network orchestrator(FFO)。
FFL位于容器内部,是使FreeFlow对应用程序透明的关键。从应用程序的角度来看,它与标准的RDMA Verbs库无法区分[16]。构建Verbs API的所有应用程序和中间件都可以在没有(或可忽略的)修改的情况下运行,FF与FFR协作。
FFR在每个主机上运行单个实例,并与同一主机上的所有容器一起提供虚拟网络。在数据平面中,FFR与同一主机上的容器共享内存缓冲区,并隔离不同容器的共享内存缓冲区。FFR通过NIC在共享内存中发送和接收数据,依靠FFL在应用程序的专用数据缓冲区和共享内存缓冲区之间同步数据。FFR通过控制容器和FFR之间的共享存储器通道来实现数据平面资源策略,例如QoS。它还可以与FFO一起处理诸如IP地址分配之类的簿记任务。
FFO根据用户定义的配置和集群的实时监控,为其集群中的所有容器做出控制平面决策。它还维护集中式存储器映射,我们将在下文中讨论。
在设计FreeFlow时,我们需要解决两个关键挑战。首先,FreeFlow应该提供一个RDMA接口,它透明地支持所有类型的现有RDMA操作。存在各种类型的RDMA操作,包括用于数据传输的单侧和双侧操作,用于工作完成通知的轮询和基于事件的机制,以及用于连接建立的TCP/IP和RDMA-CM。我们观察到,由于RDMA操作的复杂性,透明地支持它们并不简单。其次,FreeFlow应该提供接近裸机的RDMA性能,同时最小化CPU和内存开销。由于FFR通过FFL接受来自应用程序的Verbs调用,所以我们需要仔细设计FFR和FFL之间的通信通道。我们将分别针对§4和§5中的每个挑战提出我们的方法。
Verbs 支持多种操作和机制。使用WRITE/READ单方操作,读取(写入)方可以读取(写入)远端服务器特定内存地址的数据,而不需要事先通知对方。使用SEND/RECV双方操作,发送方必须在接收方准备好接收数据之后才能发送数据。此外,程序也可以使用基于拉取或者基于事件的机制来处理任务完成通知。不同的程序根据自己的需要选择不同的操作方式,我们发现在常用的应用程序中,这几种操作都被使用到。[32、18、17、22]
FreeFlow完全透明地支持这些不同的RDMA操作。主要的挑战是支持单方操作和基于事件的完成通知,因为,RDMA网卡会悄无声息的修改FFR使用的内存或者文件描述符。FFR如果不持续的频繁检查内存或者文件描述符的话是不会马上发现变更的,所以,及时的将物理网卡上的操作转换到容器中的虚拟网卡上是非常困难的。但是,容器终归是进程,FFL和FFR可以共享内存和文件描述符,借助这个特点我们解决了这个问题,让物理网卡所做的操作可以自动的传入容器中。将应用程序的内存在FFL和FFR之间共享也不是很容易,因为,容器内的应用程序不会在IPC共享内存区申请内存,所以,我们需要将容器中的内存转换成共享内存。
两个RDMA通信端首先需要建立连接。它们在各自的网卡上创建QP,并为QP申请一个内存缓存,然后与远端的QP进行配对。当连接建立后,应用程序会要求NIC将本地缓存中的内容发送到远端,或者将收到的内容放到本地缓存中。
图5中的1~7步展示了使用Verbs创建连接的基本流程。左边一列显示了应用程序使用Verbs的顺序。右边阴影中的两列展示了FreeFlow如何捕获应用程中的Verbs调用,以及如何在发送端FFR和接受端之间建立连接。
第一步:应用程序查找支持Verbs的网卡。FFL截获这个调用并返回容器的虚拟网卡。(即通知应用程序容器的虚拟网卡支持Verbs操作。)
第二步:应用程序在虚拟网卡上建立QP和CQ,同时FFR在物理网卡上创建对应的队列(QP’和CQ’)。当FFR创建好这些队列后,由FFL将QP-ID等队列的元信息返回给应用程序。
第三步:应用程序注册一块内存(mem)到QP。FFR在自己的共享内存内部处理交互区(IPC)分配一块同样大小的内存(s-mem),并将其注册到QP’上。FFR返回它创建的s-mem的ID,这个ID是主机上IPC内存区的唯一标示。FFL可以用这个ID将s-mem映射到自己的虚拟内存区中。
第四步:应用程序查询本地QP的地址(在RDMA中叫GID)。这个地址信息会同时共享给本地和远程的QP。最后,FFR返回QP’的真实GID。
第五步:应用程序与远端交换GID和QP-ID。应用程序可以通过任何方式交换这些信息,例如TCP/IP,或RDMA-CM。
第六步:应用程序通过接收者的GID将本地的QP和远端容器的QP配对。FFL将此GID转发给FFR。FFR再用这个GID配对QP’。
第七步:应用程序将本地QP的状态改为Ready,准备发送接收数据,同时FFR修改QP’的状态。
第七步之后,从应用程序的角度来看,它已经可以发送或接收数据了 – 它已经创建好了QP和CQ,在QP上注册了mem,与远端的QP建立了连接,并完成了与远端QP的配对。
从FreeFlow的角度来看,它创建了与QP关联的QP、与CQ关联的CQ’,注册了与mem关联的s-mem,并且与远端FFR中的QP’进行了配对。它也准备好接收并处理应用程序中的Verbs调用了。
由于FFR、FFL之间额外的交互,Freeflow可能会增加连接建立的延迟。但是它对总体延迟影响不大,因为建立连接是一次性行为;大部分的RDMA应用会重用之前建立好的连接。
在执行数据传输之前,发送者和接受者都需要进行两步操作。第一步是用QP发送接收数据,第二步是用CQ来处理完成通知。图5中的8、9步展示了这个过程。
第八步:应用程序执行SEND调用,并传入了指向mem的指针。FFL首先复制mem中的数据到s-mem,然后FFR调用SEND指令,将s-mem中的数据发送到远端的FFR。我们使用§4.3讨论到的零复制机制来实现数据复制,而不是简单的将数据从mem复制到s-mem。请注意,此时在远端会触发一个对应的RECV调用。
第九步:应用程序会通过检测CQ或者等待通知来获知send是否完成。同时FFR会检测与CQ’并将结果发送给FFL。
对于同一个QP中的后续SEND操作,应用程序只需要重复第八、九步。RECV操作的工作流程除了第九步以外基本相同,FFL会在QP’接受完数据后将数据从s-mem复制到mem,就像SEND操作的第八步一样。
对于应用程序来说,FFL和FFR是完全透明的。应用程序就像是在自己的虚拟网卡上执行一个普通Verbs操作。图5展示的流程也是开发中使用Verbs的标准方法。这里描述的FreeFlow的行为说明它可以完全支持SEND和RECV操作。
在单方操作中,客户端不止需要服务端的GID,同时还需要远端内存缓存的地址,以及访问内存所需的密钥。这些信息会在图5的第五步时进行交换,然后在第八步执行读写操作时使用。
与双方操作相比,单方操作的透明支持更加有挑战性。在FreeFlow中支持单方操作需要面临两个问题。
首先,目标存储器地址mem位于远程容器的虚拟存储器中。但是,本地FFR不知道另一方的相应s-mem。例如,在图6(a)中,当发送方试图将mem-1中的数据写入远程存储器mem-2时,它在阶段3)就失败了,因为接收器上的FFR无法访问目标存储器地址mem-2 。
为了解决这个问题,FreeFlow在FFO中为所有FFR构建了一个中心键值存储,以保存应用程序虚拟内存空间中mem指针与FFR虚拟内存空间中相应s-mem指针之间的映射。当应用程序将内存注册到其虚拟NIC时,更新此表会增加图5中步骤3的延迟。但是,数据平面性能不会受到影响,因为FFR可以在本地缓存映射。
其次,即使我们知道远程端的内存映射,WRITE和READ也可以远程修改或复制数据而不通知远程端的CPU,因此,FFR不知道何时复制到应用程序的存储器或从应用程序的存储器复制。例如,在图6(b)中,发送方找到s-mem-2的正确地址并将数据发送给它。但是,在s-mem-2中有数据后,没有通知接收器的FFR知道何时将s-mem-2复制到mem-2。解决此问题的一种方法是不断同步s-mem-2和mem-2。 当然,这会消耗大量的CPU和内存总线带宽。
为了解决这个问题,我们在FreeFlow中设计了一个基于零拷贝的机制来有效地支持单方操作。高级想法是使mem和s-mem具有相同的物理内存,因此FFR不需要进行任何复制,数据将自然地呈现给应用程序。图6(c)说明了这种设计。通过删除内存副本,我们还可以提高FreeFlow的性能。
这里的关键是使应用程序直接分配和使用共享内存与FFR进行数据传输。为此,FreeFlow提供了两个选项:
选项1 - 创建新的API来分配共享内存:我们创建两个新的Verbs函数,ibv _malloc和ibv_free,让应用程序将内存创建和删除委托给FreeFlow。这允许FFL直接在共享内存区域中分配这些缓冲区(与FFR共享),从而避免复制。这个选项的缺点是需要修改应用程序代码,尽管修改应该只是数据缓冲区创建的几行。
选项2 - 将应用程序的虚拟内存地址重新映射到共享内存:当应用程序将虚拟内存地址va作为数据缓冲区注册到私有内存块时(例如,图5中的步骤3),FFL释放物理内存记忆片后面的va,并从FFR到va分配一个共享的物理内存片。在Linux中,此操作仅在va是内存页面开头的地址时有效。为了强制应用程序始终在页面的开头分配内存,FFL拦截C语言中的malloc之类的调用,并使其始终返回页面对齐的内存地址。虽然此选项可以在不修改应用程序代码的情况下实现零内存复制,但它会强制应用程序中的所有内存分配进行页面对齐,这会导致主机上的内存效率降低。
在实践中,我们建议使用第一个选项,因为它更清洁,更高效。但是,由于许多RDMA应用程序已经使它们的数据缓冲区页面对齐以获得更好的性能(例如,RDMA-Spark [18]),我们可以直接使用第二个选项而不拦截malloc,因此副作用有限。请注意,不管开发人员选择使用选项1修改应用程序,还是应用程序本来就支持页面对齐缓冲区,FreeFlow都不会在实际内存使用中产生任何开销。
从CQs(完成队列)中获取信息有两种方式。一种是让应用定时检查队列中是否有结束的操作。另一种是基于事件的,由应用创建一个事件管道,然后将CQ加入管道。当有操作完成时可以通过管道中的文件描述符触发相应的事件。
在FreeFlow中,由于原始文件描述符是由物理网卡创建的,FFR需要将文件描述符传递给FFL,否则后者无法感知与文件操作符相关的事件。但是FFL、FFR本质上共享同一个内核的两个进程,借助此特点也可以实现进程间文件描述符的传递,由此我们实现了将事件管道从FFR传递给了FFL。
由于FreeFlow通过FreeFlow网络库(FFL)截取每个Verbs调用,解释并通过FreeFlow软件路由器(FFR)将它们转发到物理网卡,因此,在FFL和FFR之间建立有效通道以提供高RDMA性能,同时最小化系统资源消耗至关重要。在本章中,我们提出了两种这样的通信信道设计,它们允许用RDMA性能来换取资源消耗量,反之亦然,这取决于应用的要求。
在FFL和FFR之间传递Verbs调用的直接方法是使用RPC:FFL将API名称和参数传递给FFR,FFR适当修改参数后执行API并将API调用的结果返回给FFL。然而,由于Verbs调用复杂的输入数据结构,这种简单的RPC方法在FreeFlow中并不能很好地工作。如图7(a)所示,Verbs中的典型函数调用(例如,ibv_post_send)含有输入数据(qp,wr)和输出数据(bad_wr),它们是指向复杂数据结构的指针。由于FFL和FFR是两个不同的进程,所以,FFL中的指针在FFR中无效。
也许有人提倡“深度复制”,它会记下复杂的输入/输出数据结构,并在FFL和FFR之间的所有指针下传输数据对象。然而,这种方法有两个严重的缺点。首先,Verbs中的数据结构层级非常深(如多层的指针和嵌套),这样的深度复制会损害性能。其次,对于用户自定义的数据结构,其深度复制方法不能由FreeFlow预定义。
为了解决这个问题,我们利用了当前Verbs库的结构。如图7(b)所示,Verbs库由三层组成。顶层是最复杂的层,如上所述那样难以处理。但是,当涉及与网卡文件描述符进行通信的中间层时,Verbs库必须准备一个足够简单(无指针)的数据结构,使网卡硬件可以处理。
因此,我们转发对网卡文件描述符的请求,而不是转发Verbs的原始函数调用。我们用一个一端连接FFR的Unix套接字文件描述符替换容器中的网卡文件描述符,如图7(c)所示。通过这样做,FFR可以获取应用程序发送的命令和提供的参数。FFR会将在容器中对虚拟队列的操作映射到在物理网卡中对实际队列的相应操作。然后,它将来自物理网卡的回复转换为虚拟网卡对虚拟队列的回复,并通过Unix套接字将新的回复返回给FFL。FFL中的网卡驱动程序通信层将正常处理该回复,但不会知道Unix套接字文件描述符背后的操作。
虽然这种基于Unix套接字的方法能消耗很少的CPU,但由于通过套接字进行通信的固有延迟,它可能会产生额外的延迟。我们的测量结果表明,在商品服务器中,Unix套接字(以及信号量共享内存)的往返时间很容易超过5μs。因此,图7(c)中的Unix套接字通信通道可能成为延迟敏感应用程序的性能瓶颈。而这些应用程序则需要超低的延迟(比如小于5μs)。
对于需要低延迟通信的应用,我们将在下一节中介绍Fastpath的设计,它通过用CPU资源来换取通信延迟的优化。
为了加速FFR和FFL之间的通信,我们设计了一个与它们之间基于Unix套接字的通道并行的Fastpath。如图8所示,FFL和FFR共同拥有一块专用的共享内存。使用Fastpath,FFR在CPU内核上自旋并持续检查是否有来自FFL的新请求被写入共享内存块。一旦检测到请求,FFR将立即执行它,同时FFL开始在CPU内核上自旋以检查是否完成响应。在读取到响应后,FFL将停止它的CPU自旋。
正如我们将在第8.1.2节中看到的,Fastpath可以显著减少延迟。但代价就是用于读取请求和响应的自旋所花费的CPU开销。为了限制Fastpath带来的CPU开销,我们做出两个设计决定:
(1)对于同一主机上具有FFL的所有Fastpath通道,FFR仅在一个CPU内核上自旋。
(2)Fastpath仅用于数据通路上的非阻塞功能,使得FFL上等待响应的CPU旋转时间会很短(几微秒)。
总的来说,Fastpath平均每个主机仅消耗一个CPU内核,以显著缩短消息传递的延迟(第8.1.2节)。 另外,如果FFO知道主机上没有对延迟敏感的应用程序(根据运行的容器镜像),它可以禁用Fastpath和CPU自旋。
我们通过修改libibverbs (v1.2.1)、 libmlx4 (v1.2.1) 和librdmacm(v1.1.0)来实现FreeFlow网络库(FFL)[注4]。添加了大约4000行C代码来实现FreeFlow的逻辑。我们大约通过2000行C ++代码实现了FreeFlow软件路由器(FFR)。对于FreeFlow网络协调器(FFO),我们使用ZooKeeper来存储用户定义的信息。比如,IP分配、访问控制、资源共享策略和单侧操作的内存映射信息。由于篇幅限制,我们接下来只展示三个代表性的实现细节。
由于FreeFlow可以控制容器请求的控制和数据层面操作,因此它可以支持常见的控制和数据层面策略,包括带宽执行策略、流优先级划分策略和资源使用执行策略。
作为控制层面策略的一个例子,在我们的原型中,FreeFlow对每个容器可以创建的QP(queue pair)数量进行限额,因为大量的QP是RDMA网卡性能下降的主要原因[32]。此控制层面策略可防止容器创建太多QP影响同一主机上的其他容器。
我们在FFR中实现了一个简单的令牌桶数据结构。当应用程序创建新QP时,我们检查存储在FFO中的策略,并将有预设速率限制的令牌桶关联到QP。在每个应用程序发送请求时,路由器会检查QP是否有足够的令牌去发送请求消息。如果有,则立即将发送请求转发给物理网卡。否则,FFR将通知FFL并将其延迟,直到有足够的令牌。请注意,它只是实施QoS策略的一个示例。FreeFlow提供了灵活的API,用于在FFR中实现复杂的QoS算法,而由于篇幅限制,我们省略了细节部分。
在Fastpath实现中,我们使用汇编代码显式强制地将FFL和FFR写入的请求和响应的缓存行立即刷新到主存储器中。这是必要的,否则CPU会将新写入的行保留一段时间,以等待更多写入行,从而减慢Fastpath上的信息交换速度。
由于应用程序可以创建多个QP,并使用多个线程来并行传输数据,因此FFL和FFR之间的每个Unix域套接字都需要锁定。为了提高性能,我们在FFL和FFR之间创建了多个Unix域套接字。为避免排头阻塞,我们将更多套接字专用于数据层面操作和事件通知,并仅使用一小部分套接字用于创建、设置和删除操作。在FFR上,我们为每个传入的Unix域套接字连接使用专用线程。我们还为每个容器创建专用数据结构,并为每个已注册的内存缓冲区创建专用共享内存区域,以保持数据路径无锁。
注4:libibverbs和librdmacm是允许用户空间进程分别使用InfiniBand / RDMA Verbs和RDMA通信管理器接口的库。libmlx4是libibverbs的用户空间驱动程序,允许用户空间进程使用Mellanox硬件。
在本节中,我们将讨论当前FreeFlow设计中的一些主要问题和潜在扩展。
CPU开销:与基于软件的TCP / IP虚拟网络解决方案类似,FreeFlow会产生CPU开销。特别是,FreeFlow使用CPU内核在FFL和FFR之间轮询控制消息,以支持低延迟IPC通道(第5.2节)。我们承认,这个是在当前商品硬件之上进行网络虚拟化的成本。解决这个问题的一种可能方法是利用支持卸载CPU任务的硬件,例如FPGA、ARM协处理器或RDMA网卡[1]。我们如何消除Fastpath中的CPU开销,将作为我们未来的工作。
安全性:一个问题是由于FFR与容器共享其内存,因此一个容器是否可以通过扫描IPC空间来读取同一主机上其他容器的通信。这不是FreeFlow的问题,因为FFR为每个单独的QP创建了专用的共享内存缓冲区。只有属于容器的共享内存缓冲区才会映射到容器的虚拟内存空间。另一个问题是存储键的安全性,如果可以通过窃听看到密钥,则后续通信可能会受到影响。这个问题与原始RDMA工作中的单边操作有关,并且FreeFlow不会使其变得更糟糕。
使用外部传统对等体:FreeFlow中的容器可以自然地与外部RDMA对等体通信,因为每个FFR都能独立工作。FFR不会区分远程对等体是另一个FFR,还是外部RDMA对等体。
容器迁移:FreeFlow自然支持离线迁移。如果在另一台主机上捕获、关闭、移动和重新启动容器,则其IP地址不会更改,以便其对等方重新建立RDMA连接,就像它刚刚重新启动一样。如今,离线迁移通常用于容器集群中,以进行资源打包或故障转移。FreeFlow不支持实时迁移,因为现在RDMA的移动性还很差[39]。
VM主机:我们的原型(和评估)是基于在裸机主机上运行的容器。但是,如果VM使用SR-IOV访问物理NIC,则FreeFlow可以直接用于部署在VM中的容器。
拥塞控制:RDMA网卡已经拥有拥塞控制机制,并且FreeFlow依赖于它们。
我们评估了FreeFlow的性能和开销。我们将从微基准和FreeFlow上的实际应用程序的性能开始进行评估。
设置:我们在两个测试台上运行微基准测试。一个测试平台运行在InfiniBand,这是一种传统的RDMA专用结构。这些服务器配备两个Intel Xeon E5-2620 2.10GHz 8核CPU、64GB RAM和56Gbps Mellanox FDR CX3 NIC。操作系统是Ubuntu 14.04,内核版本为3.13.0-129-generic。
另一个测试平台运行RoCE(RDMA over Converged Ethernet)。顾名思义,RoCE只需要传统的以太网交换机(在我们的例子中,Arista 7050QX作为ToR交换机)。此测试平台群集中的服务器具有Intel Xeon E5-2609 2.40GHz 4核CPU、64GB RAM、40Gbps Mellanox CX3 NIC和Ubuntu 14.04以及内核版本4.4.0-31-generic。
我们使用Docker(v1.13.0)[7]运行容器,并使用Weave(v1.8.0)[23]设置基本的TCP / IP虚拟网络,并启用Open vSwitch内核模块。除非另有说明,否则我们运行Fastpath(§5.2)启用FreeFlow。
我们主要将FreeFlow与裸机RDMA进行比较,后者是“最佳”性能的替代品。我们将展示FreeFlow为容器启用虚拟RDMA网络,而性能损失最小。在8.1.4中,我们还将演示在FreeFlow上将TCP套接字调用转换为RDMA的性能,这样传统的TCP应用程序也可以从FreeFlow中受益。我们还将FreeFlow与裸机TCP和Weave进行了比较,后者支持容器的虚拟TCP / IP虚拟网络。
我们专注于两个基本性能指标,即吞吐量和延迟。我们使用Mellanox perftest [13]提供的基准测试工具:ib_send_lat和ib_send_bw测量延迟和通过双边操作(SEND),ib_write_lat和ib_write_bw进行单向操作(WRITE)。这些工具可以在FreeFlow上运行而无需任何修改,如4.3中所述。一般而言,FreeFlow不区分主机间设置(在不同主机上运行的发送方和接收方)和主机内设置。这里我们只显示主机间性能值。
吞吐量:我们测量两个测试平台上的单线程RDMA SEND / WRITE吞吐量,并在图9中显示RDMA SEND结果。每次运行都传输1GB数据,不同大小的消息范围从2KB到1MB。 FreeFlow RDMA WRITE结果实际上略好于SEND,为简洁起见省略。我们看到,当消息大小等于或大于8KB时,FreeFlow将获得全吞吐量作为裸机RDMA(InfiniBand为46.9Gbps,RoCE为34.5Gbps)。此外,当我们将并发容器对(流量)的数量增加到512时,所有流量的聚合吞吐量仍然接近最优(图11)。我们还通过计算Jain的公平性指数[31](平均0.97)来验证带宽在不同流量之间的公平分配。
通常,需要大量带宽的应用程序倾向于使用比几KB更大的消息大小。例如,在我们使用RDMA的一个内部存储群集中,典型的消息大小为1MB或更多。在这种情况下,FreeFlow将没有吞吐量损失(关于CPU开销,请参见8.1.2)。
即使消息很小,如2KB,FreeFlow仍然可以达到全部吞吐量的一半以上。我们验证了在这种情况下,吞吐量受单个FFR Fastpath线程(第5.2节)的限制。通过为FFR分配一个CPU核心,并在两个核心之间平衡RDMA请求负载,可以轻松消除此瓶颈。虽然我们将此选项保持打开状态,但开发人员通常不希望使用小消息来满足整个带宽。相反,对于小消息,开发人员通常关心延迟。
延迟:我们分别测量发送64B、256B、1KB和4KB消息的延迟。与吞吐量基准测试一样,两个容器在通过相同ToR交换机连接的不同主机上运行。对于每个消息大小,我们测量延迟1000次。我们绘制了中位数、第10和第99百分位潜伏期值。
图10显示了perftest工具报告的单向延迟。我们可以看到,单面WRITE操作的延迟低于双面SEND操作,FreeFlow和裸机RDMA之间的间隙也较小。但是,即使进行双面操作,FreeFlow也会导致额外延迟小于1.5μs。额外的延迟主要是由于FFL和FFR之间的IPC。单面操作将仅触发IPC一次,而双面操作将触发两次和一次内存复制。这就解释了双面操作的较大延迟差距。
为了将这些延迟值放入透视图中,网络中的一跳(即硬件交换机)具有0.55μs的延迟[3]。因此,FreeFlow延迟开销与网络中额外的交换跳跃相当。相比之下,主机TCP堆栈延迟至少为10μs(8.1.4),然后TCP / IP虚拟网络容量更大(超过40μs)。这意味着FreeFlow在为容器启用虚拟网络的同时保留了RDMA的延迟优势。
FreeFlow以低CPU开销实现了良好的性能。FreeFlow有两种模式:Fastpath和非Fastpath(或LowCPU,§5.1)。默认情况下,启用Fastpath并在延迟方面提供最佳性能。在此模式下,FFR在一个CPU核心上旋转,并尽快为动词请求提供服务。一个CPU核心能够为一个主机上的所有容器提供服务,这要归功于FFR只处理消息级事件,而不是像Open vSwitch那样处理数据包级别。在具有许多CPU内核的商用服务器上,这是可以接受的。
此外,用户可以选择LowCPU模式,它使用Unix套接字作为信号机制而不是核心旋转。这会影响延迟性能(从2.4μs增加到17.0μs),如表3所示。在图12中,我们在测量主机间吞吐量时记录了每进程的CPU利用率。图中所有三种情况的吞吐量是相同的(全带宽)。它显示了LowCPU模式的CPU优势,特别是在FFR上。在LowCPU模式下,FFR CPU开销随实际负载而变化。
我们建议根据工作负载要求选择模式。延迟敏感或非重CPU(例如,GPU-heavy)应用程序应使用快速路径模式运行,而其余应用程序可以使用LowCPU模式运行。但是,即使使用Fastpath,FFR最多也只消耗一个CPU内核,而FFL的额外开销低于全带宽吞吐量的30%。
我们演示了§6中提到的速率限制器的性能。在图13中,我们在InfiniBand测试平台上的不同主机上的两个容器之间启动单个流。我们限制流量并将不同的带宽上限从1Gbps设置为40Gbps。我们看到受控带宽(y轴)接近我们设定的带宽上限(x轴)。FreeFlow只需6%的CPU开销即可实现这一目标。
FreeFlow可以使用速率限制器隔离不同容器的性能(即吞吐量)。为了证明这一点,我们在容器对之间运行了10个并发流,并对每个流应用了不同的速率限制(从1到10Gbps)。我们确认每个流量的吞吐量都是准确的上限。
启用虚拟RDMA还可以提高基于套接字的应用程序的性能。下面我们展示FreeFlow在rsocket(现有的socketto-Verbs转换层)的帮助下,提供比传统TCP / IP虚拟网络更好的性能。
我们在InfiniBand和RoCE集群上进行实验。通过在运行时动态链接rsocket,应用程序套接字调用被透明地转换为RDMA Verbs调用。我们运行iperf [10]来测量TCP吞吐量,NPtcp [14]用于TCP延迟,而不对这些工具进行任何修改。我们与在虚拟和主机模式网络上运行的相同工具进行比较。
如图14所示,FreeFlow总是优于Weave。特别是对于小消息延迟,FreeFlow始终低于主机TCP / IP,高达98%。对于吞吐量,由于套接字到动词转换的开销,FreeFlow有时比主机TCP差,并且无法像原始RDMA那样实现全吞吐量。但是,它仍然比使用大消息的Weave大6.8到13.4倍。
这是FreeFlow具有良好性能的两个原因。首先,RDMA堆栈和FreeFlow架构只能在用户空间中工作,并避免在内核TCP堆栈中进行上下文切换。这个优势并不是唯一的;定制用户空间网络堆栈也可以实现这一目标。FreeFlow优于Weave的第二个原因是根本。现有的TCP / IP虚拟网络解决方案执行从虚拟网络到主机网络的逐包地址转换。但是,FreeFlow执行从虚拟连接到物理连接的基于消息的转换。因此,FreeFlow总是优于Weave,尽管rsocket引入了一些socket-to-Verbs转换开销。
在本节中,我们将展示TensorFlow和Spark的性能,这是一个在容器中运行的代表性机器学习和数据分析框架。我们将FreeFlow的应用程序性能与Host-RDMA、HostTCP和Weave进行比较。
由于TensorFlow需要我们的RoCE集群没有的GPU,我们在InfiniBand集群上运行所有实验。基于微基准测试,我们认为如果配备GPU,RoCE集群将具有类似的趋势。
我们在InfiniBand集群中的三台服务器上运行支持RDMA的TensorFlow(v1.3.0)。我们修改了TensorFlow的一行源代码,用我们的自定义内存分配器(第4.3节)替换原始的内存分配函数。每台服务器都有8个NVIDIA GTX 1080 Ti GPU。其中一个服务器是主节点,也是参数服务器,而其他两个服务器是工作服务器。我们针对深度学习运行两种主要类型的训练工作量,即基于卷积神经网络(CNN)的图像识别和基于递归神经网络(RNN)的语音识别。
对于图像识别,我们运行三个特定模型,ResNet-50 [29]、Inception-v3 [42]和AlexNet [33]。我们使用合成的Image Netdata作为训练数据。图15(a)显示了具有10百分位数和99百分位数值的每秒中值训练速度。根据所有三种不同模型的结果,我们得出结论,首先,网络性能确实是分布式培训的瓶颈。将主机RDMA与主机TCP进行比较,主机RDMA在训练速度方面的性能提高了1.8到3.0倍。容器覆盖层上FreeFlow和Weave之间的差距更大。例如,FreeFlow在AlexNet上的运行速度提高了14.6倍。其次,FreeFlow性能非常接近主机RDMA。差异小于4.9%,FreeFlow有时甚至更快。我们推测这是由于测量噪声造成的。
对于语音识别,我们运行一个私有语音RNN模型,该模型由双向编码器和完全连接的解码器层组成,隐藏层维度为1024,词汇量大小为100k。数据集大小为4GB,包括1860万个样本。在每个训练步骤中,GPU从一小块“学习”并相互通信以进行同步。图15(b)显示了每个训练步骤所花费时间的CDF,包括GPU时间和网络时间。同样,FreeFlow非常接近主机RDMA。中位数训练时间比Weave快8.7倍。
我们在两台服务器上运行Spark(v2.1.0)。其中一个服务器运行一个主容器,用于调度从属容器上的作业。两个服务器都运行从属容器。Spark [18]的RDMA扩展是由闭源实现的。我们从他们的官方网站下载了二进制文件,并没有进行任何修改。
我们演示了Spark发行版附带的基本基准测试 - GroupBy和SortBy。每个基准测试运行262,144个键值对,值为2 KB。我们将Spark映射器和Reducer的数量设置为8,并且每个都是单个线程。图16显示了结果。我们总结了与运行TensorFlow相似的观察结果。网络的性能确实会显著影响应用程序的端到端性能。使用FreeFlow运行时,性能非常接近在主机RDMA上运行,优于主机TCP,并且比使用Weave运行容器的性能高出1.8倍。
容器中的RDMA虚拟化:Mellanox正在努力扩展Linux内核中的网络namespace和cgroup,以适应RDMA的网络隔离[34,35]。使用MACVLAN将物理接口拆分为多个虚拟接口,为每个容器插入一个或多个接口,并依靠VLAN路由将流量传递到正确的虚拟接口。 显然,它在云环境中会有可移植性问题,因为移动IP意味着更新硬件中的VLAN路由。此外,它不提供灵活的可控性,因为它允许容器直接访问物理NIC。
另一种方法是使用可编程硬件来处理容器的RDMA虚拟化,例如智能NIC [26]或FPGA [38]。与基于硬件的解决方案相比,FreeFlow的优势在于使用商用硬件降低了成本,并且可以更灵活地定制网络功能。
VM的RDMA虚拟化:HyV [39]是最接近FreeFlow的解决方案。它还拦截应用程序和NIC驱动程序之间的通信,并提供地址转换、QP/CQ映射和内存映射。HyV和FreeFlow的主要区别在于HyV不控制数据路径以在私有集群中提供裸机性能,而FreeFlow适用于云环境。这给FreeFlow带来了更多挑战,例如使性能仍然接近裸机质量,同时保持数据路径中应用程序的透明性。VMM旁路I/O [37]具有与HyV类似的设计和问题。VMware一直致力于对称为vRDMA的RDMA设备进行半虚拟化[40]。vRDMA专为VMware的虚拟机管理程序和虚拟机而设计,因此,它本身并不适用于容器。
在本文中,我们介绍了FreeFlow,这是一种虚拟RDMA网络解决方案,可提供容器云所需的隔离性、可移植性和可控性。FreeFlow对应用程序是透明的,并且可接受的开销实现接近裸机的RDMA性能。使用实际应用程序和微基准测试进行的评估表明,FreeFlow可以支持与裸机RDMA相媲美的性能,并且比现有的TCP / IP虚拟网络解决方案更好。我们开源了FreeFlow的原型。
参考文献请查看原文链接,原文地址:
https://www.usenix.org/conference/nsdi19/presentation/kim
来源:NSDI 19
作者:Daehyeok Kim and Tianlong Yu, Carnegie Mellon University; Hongqiang Harry Liu, Alibaba; Yibo Zhu, Microsoft and Bytedance; Jitu Padhye and Shachar Raindel, Microsoft; Chuanxiong Guo, Bytedance; Vyas Sekar and Srinivasan Seshan, Carnegie Mellon University
参与编译:张春海、冉玫美、王泽旺、孙夏、张磊
出品:Clustar