软硬件融合 · 2021年05月20日

Docker容器、虚拟机和裸机运行的性能比较

编者按:

大家一直对虚拟机和容器的性能损耗都有一定的认识,但具体多少应该没有几个人能说清楚。这篇文章详细的评测了相关的应用场景在容器、虚拟机、裸机三种场景下的性能对比,相信会对大家有所帮助。

参考链接:

An Updated Performance Comparison of Virtual Machines and Linux Containers, IBM Research
https://dominoweb.draco.res.ibm.com/reports/rc25482.pdf

摘要

云计算大量使用虚拟机(VM),因为可以通过VM把工作负载彼此隔离,并在一定程度上控制资源使用。然而,虚拟化所涉及的额外抽象代价降低了工作负载的性能,并将其作为成本和性能降低转嫁给了客户。基于容器的虚拟化简化了应用程序的部署,同时仍允许对分配给不同应用程序的资源进行控制。

在本文中,我们探讨了传统虚拟机部署的性能,并将其与Linux容器的使用进行了对比。我们使用一组工作负载,对CPU、内存、存储和网络资源造成压力。我们使用KVM作为虚拟化的Hypervisor,使用Docker作为容器管理器。我们的结果显示,在几乎所有情况下,容器的性能都与VM相同或更好。VM和容器都需要优化以支持IO密集型应用程序。我们还讨论了性能结果对未来云架构的影响。

1 介绍

虚拟机广泛应用于云计算中。特别是,基础设施即服务(Infrastructure as a Service, IaaS)中的最新技术在很大程度上等同于虚拟机。像Amazon EC2这样的云平台让用户可以使用VM,也可以在VM中运行数据库这样的服务。许多“平台即服务”(PaaS)和“软件即服务”(SaaS)提供商都构建在IaaS之上,它们的所有工作负载都运行在VM中。由于目前几乎所有的云工作负载都在VM中运行,所以VM性能是整体云性能的关键部分。一旦Hypervisor的开销增加,没有任何其他层的手段可以删除它。这样的开销将成为云工作负载性能的普遍负担。有许多研究显示了VM执行与裸机执行的对比情况,此类研究已经成为普遍提高VM技术质量的一个激励因素。

基于容器的虚拟化为云中的虚拟机提供了一个有趣的替代方案。虚拟专用服务器提供商,可能被视为云计算的先驱,已经使用容器超过十年了。但他们中的许多人转向VM以提供更一致的性能。尽管很好地理解了名称空间等容器的基础概念,但是容器技术一直处于停滞状态,直到因为需要快速部署的需要,PaaS提供者采用并标准化了容器,使得采用容器来提供隔离和资源控制的复兴。Linux由于其免费、庞大的生态系统、良好的硬件支持、良好的性能和可靠性而成为云计算的首选操作系统。在Linux中实现容器所需的内核名称空间特性很早前就被讨论,但直到最近几年才变得成熟。在过去的两年中,Docker已经成为Linux容器的标准运行时、镜像格式和构建系统。

本文着眼于目前实现资源控制的两种不同方式,即容器和虚拟机,并比较了两种环境中一组工作负载的性能,以及与无虚拟化环境的性能。除了一系列强调计算、内存带宽、内存延迟、网络带宽和I/O带宽等不同方面的基准测试外,我们还探讨了两个真实应用程序的性能,即Redis和MySQL在不同环境下的性能。

我们的目标是隔离并理解虚拟机(特别是KVM)和容器(特别是Docker)相对于非虚拟化Linux所带来的开销。我们希望其他管理程序,如Xen、VMware ESX和Microsoft Hyper-V,能够提供与KVM类似的性能,因为它们使用相同的硬件加速特性。同样地,当其他容器工具使用相同的机制时,它们应该具有与Docker相同的性能。我们不评估在VM中运行容器或在容器中运行VM的情况,因为我们认为这种双重虚拟化是冗余的(至少从性能角度来看)。Linux可以同时承载VM和容器,这一事实为这两种技术之间的相互比较创造了机会——与以前的许多比较相比,混淆变量更少一些。

我们做出了如下贡献:

  • 我们提供了一个本地、容器和虚拟机环境的最新比较,使用最新的硬件和软件,以及与云相关的有趣的基准测试和工作负载。
  • 我们确定了当前虚拟化选项对高性能计算和服务器工作负载的主要性能影响。
  • 我们详细阐述了一些影响虚拟化性能的不明显的实际问题。
  • 我们证明,即使在整个服务器的规模下,容器也是可行的,且对性能的影响很小。

本文的其余部分组织如下。第二节描述Docker和KVM,为理解论文的其余部分提供必要的背景。第三节描述并评估这三个环境上的不同工作负载。第四部分对相关工作进行了回顾,第五部分对全文进行了总结。

2 背景

2.1 云虚拟化的动机和要求

Unix传统上并没有强烈地实现最小权限原则,即“系统的每个程序和每个用户都应该使用完成工作所需的最小权限集进行操作。”以及最不常见的机制原则,即“每个共享机制……代表了用户之间的潜在信息路径,在设计时必须非常小心,以确保它不会无意地危及安全性。”Unix中的大多数对象,包括文件系统、进程和网络堆栈,对所有用户都是全局可见的。

Unix的共享全局文件系统造成的一个问题是缺乏配置隔离。多个应用程序可能对系统范围的配置设定有冲突的要求。共享库依赖关系尤其有问题,因为现代应用程序使用许多库,而且不同的应用程序通常需要相同库的不同版本。当在一个操作系统上安装多个应用程序时,系统管理的成本可能超过软件本身的成本。

普通服务器操作系统中的这些弱点导致管理员和开发人员通过将每个应用程序安装在单独的操作系统副本上(或者在专用服务器上,或者在虚拟机上)来简化部署。与共享服务器相比,这种隔离与在应用程序之间共享任何代码、数据或配置所需的显式操作恰恰相反。

不管环境如何,客户都想要得到他们为之付费的性能。与基础设施和工作负载属于同一家公司的企业整合场景不同,在IaaS和PaaS中,提供者和客户之间存在一种独立的关系。这使得解决性能异常变得困难,所以*aaS提供商通常提供固定的容量单位(CPU内核和RAM),而没有超额订阅。虚拟化系统需要强制执行这种资源隔离,以适应云基础设施的使用。

2.2 KVM

内核虚拟机(KVM)是Linux的一个特性,它允许Linux充当类型1的Hypervisor,在Linux进程中运行未经修改的Guest操作系统(OS)。KVM在较新的处理器中使用硬件虚拟化特性来降低复杂性和开销;例如,Intel VT-x硬件消除了对复杂的Ring压缩方案的需要,而这些方案是由早期的hypervisor,如Xen和VMware,所开创的。KVM既支持通过QEMU模拟I/O设备,也支持使用virtio的半虚拟(paravirtual)I/O设备。硬件加速和半虚拟I/O的结合是为了将虚拟化开销降低到非常低的水平。KVM支持实时迁移,允许腾出物理服务器甚至整个数据中心进行维护,而不会中断Guest操作系统。KVM也很容易通过libvirt等工具来管理。

因为虚拟机有静态数量的虚拟CPU (vCPU)和固定数量的RAM,它的资源消耗自然是有限的。一个vCPU不能使用一个以上的实际CPU周期,vRAM的每一页最多映射到物理RAM的一页(加上嵌套的页表)。KVM可以在运行时通过“热插拔”和“气球”vCPU和vRAM来调整虚拟机的大小,尽管这需要客户操作系统的支持,并且在云中很少使用。

因为每个虚拟机都是一个进程,所以所有普通的Linux资源管理工具,如调度和cgroups(稍后将详细描述)都适用于虚拟机。这简化了Hypervisor的实现和管理,但使Guest操作系统内的资源管理变得复杂。操作系统通常假定CPU一直在运行,内存具有相对固定的访问时间,但在KVM下,vCPU可以在不通知的情况下被取消调度,虚拟RAM可以被换出,从而导致难以调试的性能异常。虚拟机也有两个级别的分配和调度:一个在hypervisor中,另一个在Guest操作系统中。许多云提供商通过不过度使用资源、将每个vCPU固定到一个物理CPU、将所有虚拟RAM锁定到实际RAM来解决这些问题。(不幸的是,OpenStack还没有启用vCPU钉住功能,导致与私有公有云相比性能参差不齐。)这基本上消除了系统管理程序中的调度。这种固定的资源分配也简化了计费。

由于狭窄的接口,虚拟机自然的提供了一定级别的隔离和安全;VM与外部世界通信的唯一方式是通过有限数量的超hypercall或模拟设备,它们都由hypervisor控制。这不是灵丹妙药,因为已经发现了一些Hypervisor特权升级漏洞,它们可能允许Guest操作系统“打破”其VM“沙箱”。

虽然VM擅长隔离,但当在Guest之间或Guest和Hypervisor之间共享数据时,它们增加了开销。通常,这种共享需要相当昂贵的编组和hypercall。在云中,虚拟机通常通过映像文件支持的模拟块设备访问存储;创建、更新和部署这样的磁盘映像可能非常耗时,而包含大部分重复内容的磁盘映像集合可能会浪费存储空间。

2.3 Linux容器

与虚拟硬件上运行完整操作系统不同,基于容器的虚拟化通过修改操作系统以提供额外的隔离。通常,这包括向每个进程添加一个容器ID,并向每个系统调用添加新的访问控制检查。因此,容器可以看作是除了用户和组权限系统之外的另一个级别的访问控制。在实践中,Linux使用下面描述的更复杂的实现。

Linux容器是一个建立在内核命令空间特性上的概念,最初是用于处理高性能计算集群场景而产生的。这个特性可以被clone()系统调用访问,允许创建之前全局名称空间的独立实例。Linux实现了文件系统、PID、网络、用户、IPC和主机名名称空间。例如,每个文件系统名称空间都有自己的根目录和挂载表,类似于chroot(),但功能更强大。

名称空间可以通过许多不同的方式使用,但最常见的方法是创建一个隔离的容器,该容器对容器外部的对象屏蔽可见性或访问权。运行在容器内的进程看起来像是运行在普通的Linux系统上,尽管它们与位于其他名称空间中的进程共享底层内核。容器可以分层嵌套,尽管这种功能还没有得到充分的挖掘。

与运行完整操作系统的VM不同,容器可以只包含单个进程。像一个完整的操作系统一样运行init、inetd、sshd、syslogd、cron等的容器称为系统容器,而只运行应用程序的容器称为应用程序容器。这两种类型在不同的情况下都有用。由于应用程序容器不会在冗余管理进程上浪费RAM,因此它通常比同等的系统容器或VM消耗更少的RAM。应用程序容器通常没有独立的IP地址,这在地址缺乏的环境中是一个优势。

如果不希望完全隔离,那么很容易在容器之间共享一些资源。例如,绑定挂载允许一个目录出现在多个容器中,可能在不同的位置。这是在Linux VFS层中有效实现的。容器之间或容器与主机(实际上可能只是父名称空间)之间的通信与普通Linux IPC一样高效。

Linux控制组(cgroups)子系统用于对进程进行分组并管理它们的总资源消耗。它通常用于限制容器的内存和CPU消耗。一个容器可以通过简单地改变其相应的cgroup的限制来调整大小。Cgroups还提供了一种终止容器内所有进程的可靠方法。因为一个容器化的Linux系统只有一个内核,并且内核对容器有完全的可见性,所以只有一个级别的资源分配和调度。

容器资源管理一个未解决的问题是,在容器中运行的进程不知道它们的资源限制。例如,一个进程可以看到系统中所有的CPU,即使它只被允许运行在其中一个子集上运行;这问题,同样适用于内存。如果应用程序试图根据可用的系统总资源分配资源来自动调优自身,那么在资源受限的容器中运行时,它可能会过度分配资源。随着容器的成熟,这一限制可能会得到解决。

安全容器往往比管理Unix权限更简单,因为容器不能访问它不能看到的内容,因此意外的权限越界的可能性大大降低。当使用用户名称空间时,容器内的根用户不会被视为容器外的根用户,这增加了额外的安全性。容器中的主要安全漏洞类型是不感知名称空间的系统调用,因此可能会在容器之间引入意外泄漏。因为Linux系统调用API集是巨大的,审计与名称空间相关BUG的每个系统调用的工作仍在进行中。这样的错误可以通过使用seccomp]将系统调用列入白名单来减轻(以潜在的应用程序不兼容性为代价)。

有几个管理工具可用于Linux容器,包括LXC、systemd-nspawn、lmctfy、Warden和Docker。(有些人将Linux容器称为“LXC”,但这会引起混淆,因为LXC只是管理容器的众多工具之一)。由于其特性集和易用性,Docker迅速成为容器的标准管理工具和镜像格式。Docker的一个关键特性在大多数其他容器工具中都不存在,那就是分层文件系统映像,通常由AUFS (Another UnionFS)提供支持。AUFS提供了一个分层的文件系统堆栈,并允许在容器之间重用这些层,减少了空间使用,简化了文件系统管理。一个操作系统映像可以作为许多容器的基础,同时允许每个容器有自己的修改文件覆盖(例如,应用程序二进制文件和配置文件)。在许多情况下,Docker容器镜像比同等的VM磁盘镜像需要更少的磁盘空间和I/O。这使得在云中部署的速度更快,因为在虚拟机或容器启动之前,映像通常必须通过网络复制到本地磁盘。

尽管本文主要关注稳定状态的性能,但是其他的测试显示,容器的启动速度比VM快得多(在我们的硬件上容器启动速度小于1秒,而VM启动速度为11秒),因为与VM不同,容器不需要启动操作系统的另一个副本。理论上CRIU可以执行容器的动态迁移,但杀死一个容器并启动一个新的可能更快。

3 评价

性能有许多方面。我们关注的是与非虚拟化环境下执行相比的开销问题,因为它减少了用于生产工作的可用资源。因此,我们研究了一个或多个硬件资源被充分利用的场景,并测量了吞吐量和延迟等工作负载指标,以确定虚拟化的开销。

我们的所有测试都是在一台IBM System x3650 M4服务器上执行的,该服务器拥有两个2.4-3.0 GHz Intel Sandy Bridge-EP Xeon E5-2665处理器,共16核(加上超线程)和256 GB RAM。两个处理器/插槽通过QPI链路连接,使其成为一个NUMA系统。这是一个主流服务器配置,与流行的云提供商使用的配置非常相似。我们使用Ubuntu 13.10 (Saucy) 64位,Linux内核为3.11.0,Docker 1.0, QEMU 1.5.0和libvirt 1.1.1。为了一致性,所有的Docker容器使用Ubuntu 13.10的基础镜像,所有的虚拟机使用Ubuntu 13.10的云镜像。

通过使用性能cpufreq调控器为测试禁用电源管理。Docker容器不受cgroups的限制,所以它们可以消耗被测试系统的全部资源。同样,VM配置了32个vCPU和足够的RAM来容纳基准测试的工作集。在一些测试中,我们研究了普通KVM(类似于默认OpenStack配置)和高度调优的KVM配置(类似于EC2等公共云)之间的差异。我们使用微基准测试来分别测量CPU、内存、网络和存储开销。我们还测量了两个实际的服务器应用程序:Redis和MySQL。

3.1 CPU—PXZ

压缩是云工作负载中经常使用的组件。PXZ是一个使用LZMA算法的并行无损数据压缩实用程序。我们使用PXZ 4.999.9beta (build 20130528)来压缩enwik9,这是一个1GB的Wikipedia转储文件,经常用于压缩基准测试。为了专注于压缩而不是I/O,我们使用了32个线程,输入文件缓存在RAM中,输出通过管道传输到/dev/null。我们使用压缩级别2。

WeChat Image_20210520104143.jpg

表1 PXZ、Linkpack、Stream流和随机访问的结果。每个数据点是十次运行的算术平均值。在圆括号"()"中显示与裸机执行的偏差。标准偏差在方括号[]中标识

表1显示了PXZ在不同配置下的吞吐量。正如预期的那样,裸机和Docker的性能非常相似,而KVM的速度要慢22%。我们注意到,通过vCPU固定和暴露缓存拓扑来优化KVM对性能的影响很小。虽然需要进一步实验来确定KVM开销的来源,但我们怀疑这是由于嵌套分页的额外TLB压力造成的。PXZ可能受益于使用大页。

3.2 HPC—Linpack

Linpack解决了一个稠密的线性方程组,使用一种算法,执行LU因子分解与部分枢轴。绝大多数计算操作都是在一个标量与一个向量的双精度浮点乘法中进行的,然后将结果加到另一个向量上。基准测试通常基于线性代数库,该库针对现有的特定机器架构进行了大量优化。我们使用了一个优化的Linpack二进制(版本11.1.2.005),基于Intel Math Kernel Library (MKL)。英特尔MKL具有高度的自适应性,并基于可用的浮点资源(例如,可用的多媒体操作)和系统的缓存拓扑优化自身。默认情况下,KVM不会向VM公开拓扑信息,因此Guest操作系统认为它运行在统一的32个插槽系统上,每个插槽一个核心。

表1显示了Linpack在Linux、Docker和KVM上的性能。在Linux和Docker上的性能几乎是相同的——考虑到在执行过程中很少涉及操作系统,这并不奇怪。然而,未调优的KVM性能明显更差,这说明了采用KVM的工作负载抽象/隐藏硬件细节的成本。由于无法检测系统的确切问题所在,执行采用了更通用的算法,从而导致性能损失。通过调优KVM,将vCPU固定到相应的CPU上,并暴露底层缓存拓扑,可以提高性能,几乎与裸机不相上下。

我们希望这种方法成为其他类似调优、自适应执行的规范,除非系统拓扑被忠实地贯彻到虚拟化环境中。

3.3 内存带宽—STEAM

WeChat Image_20210520104330.jpg
表2 STEAM流组件

STREAM基准测试是一个简单的综合基准测试程序,它在对向量执行简单操作时测量可持续内存带宽。性能由系统的内存带宽决定,而工作集被设计成比缓存大得多。决定性能的主要因素是主存的带宽,以及处理TLB遗漏的成本(我们进一步减少了对大页的使用)。内存访问模式是规则的,硬件预取器通常会锁住访问模式,并进行数据预取。因此,性能取决于内存带宽而不是延迟。基准测试有四个组成部分:COPY、SCALE、ADD和TRIAD,这些在表II中描述。

表1显示了在三个执行环境中STREAM的性能。STREAM的所有四个组件都执行常规内存访问,一旦在TLB中保存了一个页表项,就会访问该页中的所有数据,然后再转到下一页。硬件TLB预取对于这种工作负载也非常有效。因此,在Linux、Docker和KVM上的性能几乎是相同的,中间数据显示在三个执行环境中只有1.4%的差异。

3.4 随机内存访问——随机访问

STREAM基准测试以常规的方式压测内存子系统,允许硬件预取器在将数据用于计算之前从内存中读取数据到缓存。相反,随机访问基准测试是专门为强调随机内存性能而设计的。基准测试将一大块内存初始化为它的工作集,这比缓存或TLB所能达到的范围大很多个数量级。读取、修改(通过一个简单的异或操作)并写回内存中的随机8字节单词。随机位置是通过使用不需要存储器操作的线性反馈移位寄存器产生的。因此,连续操作之间不存在依赖关系,允许多个独立操作在系统中流动。随机访问代表了具有大工作集和最小计算量的工作负载的行为,比如那些具有内存哈希表和内存数据库的工作负载。

和STREAM一样,随机访问使用大页面来减少TLB未命中的开销。因为它的随机内存访问模式和一个大于TLB范围的工作集,随机访问显著地使用硬件页表遍历器来处理TLB失败。如表1所示,在我们的双路服务器系统上,这对于虚拟化和非虚拟化环境都有相同的开销。

3.5 网络带宽——nuttcp

我们使用nuttcp工具来测量系统的网络性能,待测系统与另一台机器之间通过两张Mellanox ConnectX-2 EN网卡直连,CX2网卡提供10Gbps以太网链路连接的网络带宽。我们对10Gbps网络应用了标准的网络调优,比如启用TCP窗口扩展和增加Socket缓冲区大小。如图1所示,Docker将主机上的所有容器连接到网桥上,并通过NAT将网桥连接到网络上。在我们的KVM配置中,我们使用Virtio和vHost来最小化虚拟化开销。

WeChat Image_20210520104509.jpg
图1 网络配置

我们使用nuttcp来测量单个TCP连接上标准1500字节MTU的单向批量数据传输的实际吞吐量。在客户机到服务器的情况下,被测系统(SUT)充当发送器,在服务器到客户机的情况下,SUT充当接收器;有必要测量两个方向,因为TCP有不同的发送和接收代码路径。这三种配置在发送和接收方向上都达到9.3 Gbps,非常接近因为包头而产生的9.41 Gbps的理论极限。由于分片卸载,即使使用不同形式的虚拟化创建的额外层,批量数据传输也非常高效。这个测试中的瓶颈是NIC,使得其他资源大部分空闲。在这样一个I/O受限的场景中,我们通过测量发送和接收数据所需的CPU周期量来确定开销。图2显示了此测试的系统范围CPU利用率,使用perf stat -a进行测量。Docker使用桥接和NAT显著地增加了传输路径长度;Vhost-net在传输方面是相当有效的,但在接收端有很高的开销。不使用NAT的容器具有与本地Linux相同的性能。在实际的网络密集型工作负载中,我们预计这种CPU开销会降低整体的性能。

WeChat Image_20210520104505.jpg
图2 TCP批量传输效率

过去,Xen和KVM一直难以提供线速率的网络,因为迂回的I/O路径将每个数据包发送到用户空间。这导致了对诸如轮询驱动程序或管理程序旁路等复杂网络加速技术的大量研究。我们的结果表明,vHost允许虚拟机直接与主机内核通信,直接解决了网络吞吐量问题。如果有更多的网卡,我们预计该服务器可以在不使用任何外来技术的情况下驱动超过40Gbps的网络流量。

3.6 网络延迟——netperf

我们使用netperf请求-响应基准测试来测量往返网络延迟,使用与前一节中的nuttcp测试类似的配置。在这种情况下,被测试的系统运行netperf服务器(netserver),而另一台机器运行netperf客户机。客户端发送一个100字节的请求,服务器发送一个200字节的响应,客户端在发送另一个请求之前等待响应。因此,一次只有一个事务在运行中。

WeChat Image_20210520104652.jpg
Fig. 3. 网络往返延时(µs)

图3显示了基准测试的TCP和UDP变体的事务延迟。在这个测试中,与Docker中使用的NAT一样,将延迟增加了一倍。KVM为每个事务增加30µs的开销,比非虚拟化的网络堆栈增加80%。TCP和UDP具有非常相似的延迟,因为在这两种情况下,一个事务都由每个方向上的单个包组成。与吞吐量测试不同,在这种情况下虚拟化开销不能被摊销。

3.7 块I/O——fio

类似于SAN的块存储通常用于云中,以提供高性能和强一致性。为了测试虚拟化块存储的开销,我们使用两个8Gbps光纤通道连接到基于QLogic ISP2532的双端口HBA卡,并使用dm\_multipath来合并这两个连接,将一个20TB的IBM FlashSystem 840闪存SSD连接到我们的测试服务器。我们使用默认设置在它上面创建了一个ext4文件系统。在裸机情况下,文件系统是正常安装的,而在Docker测试中,使用-v选项将它映射到容器中(避免AUFS开销)。在VM的情况下,块设备使用Virtio映射到VM,并挂载到VM中。这些配置如图4所示。我们使用fio 2.0.8和libaio后端在O\_DIRECT模式下对存储在SSD上的16GB文件运行几个测试。使用O\_DIRECT允许访问绕过操作系统缓存。

WeChat Image_20210520104706.jpg
图4 采用fio的存储配置

WeChat Image_20210520104733.jpg
图5 顺序I/O吞吐量(MB/s)

图5显示了使用典型的1 MB I/O大小的连续读写性能,平均时间超过60秒。在这种情况下,Docker和KVM引入的开销可以忽略不计,尽管KVM的性能差异大约是其他情况的四倍。与网络类似,光纤通道HBA卡似乎是测试的瓶颈。

图6显示了使用4KB块大小和128并发随机读、写和混合(70%读、30%写)工作负载的性能,我们通过实验确定,这为特定的SSD提供了最大的性能。正如我们所料,与Linux相比,Docker没有带来任何开销,但是KVM只提供了一半的IOPS,因为每个I/O操作都必须经过QEMU。虽然VM的绝对性能仍然相当高,但它在每个I/O操作中使用了更多的CPU周期,而应用程序则只有相对更少的CPU周期。图7显示了KVM将读取延迟增加2-3倍,这是一些实际工作负载的关键指标。

WeChat Image_20210520104910.jpg
图6 随机I/O吞吐量(IOPS)

WeChat Image_20210520104935.jpg
图7 随机读延迟CDF(累计分布曲线),并发16 (µs),裸机和容器线几乎是重叠的

我们还注意到,这种硬件配置在顺序I/O中应该能够超过1.5 GB/s,在随机I/O中应该能够超过35万IOPS,因此即使是裸机情况下也有很大的未发掘的潜力,而我们在借用硬件时没有设法利用这些潜力。

3.8 Redis

在云中,基于内存的键值存储通常用于缓存、存储会话信息,并作为维护热门非结构化数据集的便捷方式。操作在本质上趋向于简单,并且需要客户机和服务器之间的网络往返。这种业务模型使得应用程序对网络延迟非常敏感。考虑到并发客户机的数量很大,每个客户机都向许多服务器发送非常小的网络数据包,挑战就更复杂了。因此,服务器在网络堆栈中花费了相当多的时间。

在我们的评估中,我们选择了Redis,因为它的高性能,丰富的API和广泛使用的PaaS提供商(例如Amazon Elasticache,谷歌计算引擎)。我们从主要的GitHub仓库中获得了Redis 2.8.13,并在我们的Ubuntu 13.10平台上构建了它。然后,生成的二进制文件将用于每种部署模式:裸机、Docker和KVM。为了提高性能,考虑到Redis是一个单线程应用程序,我们将容器或VM绑定到靠近网络接口的核心上。测试由许多向服务器发出请求的客户端组成。使用了50%读和50%写的混合。每个客户机都维护到服务器的持久TCP连接,并可以通过该连接传输多达10个并发请求。因此,运行中的请求总数是客户端数量的10倍。键的长度为10个字符,生成的值平均为50个字节。这个数据集代表了Steinberg等人描述的生产环境Redis用户数据的典型特征。对于每次运行,都将清除数据集,然后发出确定的操作序列,之后逐步创建1.5亿个键。Redis服务器在执行过程中的内存消耗峰值为11GB。

WeChat Image_20210520105059.jpg
图8 在多个部署场景的NoSQL Redis性能评估(请求/秒),每个数据点是10次运行的算术平均值。

WeChat Image_20210520105112.jpg
图9 不同Redis部署上的平均操作延迟(毫秒),每个数据点是10次运行的算术平均值

图8显示了与不同部署模型的客户机连接数相关的吞吐量(以每秒请求数为单位)。图9显示了每个实验对应的平均时延(用µs表示)。在裸机部署中,网络子系统足以处理负载。所以,当我们扩展客户端连接的数量时,限制Redis服务器吞吐量的主要因素是CPU的饱和——注意Redis是一个单线程的,基于事件的应用程序。在我们的平台中,这很快就会以每秒110k的请求速度到达CPU饱和。添加更多的客户端会导致请求排队,并增加平均延迟。

Redis服务器给网络和内存子系统带来了很大的压力。当将Docker与主机网络堆栈一起使用时,我们可以看到吞吐量和延迟实际上与裸机情况相同。当使用启用了NAT的Docker时,情况就大不相同了,如图1所示。在这种情况下,引入的延迟随着通过网络接收的数据包数量的增加而增加。虽然它可以与4个并发连接的裸机相提并论(51个µs或裸机的1.05倍),但一旦连接数量增加,它就会迅速增长(100个连接时超过1.11倍)。此外,NAT会消耗CPU周期,从而阻止Redis的部署达到使用裸机网络堆栈时的最高性能。

类似地,在KVM中运行时,Redis看起来是网络绑定的。KVM为每个事务增加大约83µs的延迟。我们看到VM在低并发时具有较低的吞吐量,但随着并发性的增加,其性能渐近接近裸机性能。超过100个连接后,两个部署的吞吐量实际上是相同的。这可以用利特尔定律来解释;由于KVM下的网络延迟更高,Redis需要更多的并发来充分利用系统性能。这可能是一个问题,具体取决于云场景中最终用户期望的并发级别。

3.9 MySQL

MySQL是一种流行的关系数据库,在云中广泛使用,通常强调内存、IPC、文件系统和网络子系统。我们针对MySQL 5.5.37的一个实例运行了SysBench oltp基准测试。MySQL被配置为使用InnoDB作为后端存储,并且启用了3GB的缓存;这个缓存足以缓存基准测试运行期间的所有读取。oltp基准测试使用一个预加载了200万条记录的数据库,并执行一组固定的读/写事务,在5个SELECT查询、2个UPDATE查询、1个DELETE查询和1个INSERT查询之间进行选择。SysBench提供的度量值是事务延迟和每秒事务吞吐量的统计数据。客户端数量不断变化,直到达到饱和,每个数据点使用10次运行。测量了五种不同的配置:MySQL在Linux上正常运行(本机),MySQL在Docker下使用主机组网和卷(Docker net=主机卷),使用卷但正常Docker组网(Docker NAT卷),将数据库存储在容器文件系统(Docker NAT AUFS)和MySQL在KVM下运行;表3总结了这些不同的配置。虽然MySQL访问文件系统,我们的配置有足够的缓存,它几乎没有执行实际的磁盘I/O,使得不同的KVM存储选项没有意义。

WeChat Image_20210520105300.jpg

表3 MySQL配置

WeChat Image_20210520105315.jpg
图10 MySQL吞吐量(事务/秒) vs并发

图10显示了事务吞吐量作为SysBench模拟的用户数量的函数。右边的Y轴显示了与裸机相比的吞吐量损失。这条曲线的大致形状与我们的预期一致:吞吐量随着负载的增加而增加,直到机器饱和,然后在超载时由于争抢冲突而略有损失而趋于平稳。Docker具有与裸机相似的性能,在更高的并发性下差异渐近接近2%。KVM的开销要高得多,在所有测量的情况下都高于40%。AUFS引入了显著的开销,这并不奇怪,因为I/O要经过好几层,通过比较Docker NAT卷和Docker NAT AUFS结果可以看出。AUFS开销演示了在文件系统上面进行虚拟化(如Docker所做的)和在文件系统下面进行虚拟化(如KVM所做的)之间的区别。我们测试了不同的KVM存储协议,发现对于类似于此测试的缓存内工作负载,它们对性能没有影响。NAT也会带来一些开销,但这种工作负载不是网络密集型的。KVM显示了一个有趣的结果,在网络中达到了饱和,但在CPU中没有达到(图11)。基准测试生成大量的小数据包,因此即使网络带宽很小,网络堆栈也无法维持每秒所需的数据包数量。由于基准测试使用同步请求,在给定并发级别下,延迟的增加也会降低吞吐量。

WeChat Image_20210520105332.jpg
图11 MySQL吞吐量(事务/秒) vs. CPU利用率

WeChat Image_20210520105342.jpg
图12 MySQL延迟(以毫秒为单位) vs. 并发

图12显示了SysBench模拟的用户数量与延迟的关系。正如预期的那样,延迟随着负载的增加而增加,但有趣的是,Docker在中等负载水平下增加延迟的速度更快,这解释了低并发水平下的低吞吐量。图中扩展的部分显示,本机Linux能够实现更高的CPU峰值利用率,而Docker无法达到同样的水平,差异约为1.5%。这一结果表明,Docker有一个较小但可测量的影响。

图11描绘了吞吐量与CPU利用率之间的关系。比较图10到图12,我们注意到在相同并发情况下,Docker和使用NAT的Docker的低吞吐量并没有产生同等的CPU消耗增加。当使用相同数量的CPU时,吞吐量的差异很小。在其他方面,由于Docker的并发性值较低,因此其延迟就大大增加了,我们把这种行为归因于互斥锁争用。互斥也阻止MySQL在所有情况下充分利用CPU,但这在Docker情况下更明显,因为事务需要更长的时间。图11清楚地显示,在VM的情况下,限制不是CPU,而是网络,但即使在较低的客户机数量下,KVM的开销也很明显。

WeChat Image_20210520105430.jpg
图13 MySQL吞吐量(事务/秒) vs. 延迟

图13中的吞吐量-延迟曲线便于比较给定目标延迟或吞吐量的备选方案。这条曲线的一个有趣方面是,当饱和后引入更多客户机时,在几种情况下,更高的上下文切换导致吞吐量降低。由于在Docker中有更多的空闲时间,因此更高的开销不会影响基准测试中使用的客户机数量的吞吐量。

3.10 讨论

我们从这些结果中看到了几个普遍的趋势。正如我们所期望的那样,容器和虚拟机对CPU和内存的使用几乎没有任何开销;它们只影响I/O和OS交互。这种开销是以每个I/O操作的额外周期的形式出现的,所以小I/O比大I/O遭受的损失要大得多。这种开销增加了I/O延迟,减少了用于有用工作的CPU周期,从而限制了吞吐量。不幸的是,真正的应用程序通常不能批量处理大型I/O。

Docker添加了一些特性,如分层镜像和NAT,这使得它比LXC风格的原始容器更容易使用,但这些特性是以性能为代价的。因此,使用默认设置的Docker可能不会比KVM快。文件系统或磁盘密集型的应用程序应该通过使用卷绕过AUFS。使用-net =host可以很容易地消除NAT开销,但这放弃了网络名称空间的好处。最终,我们相信Kubernetes项目提出的每个容器一个IP地址的模型可以提供灵活性和性能。

虽然KVM可以提供非常好的性能,但它的可配置性是一个弱点。良好的CPU性能需要仔细配置大页面、CPU模型、vCPU固定和缓存拓扑;这些特性的文档说明很差,需要反复调优才能配置妥当。我们建议读者避免直接使用Qemu-KVM,而是使用libvirt,因为它简化了KVM配置。即使在最新版本的Ubuntu上,我们也无法让vHost-scsi工作,所以仍然有改进的空间。这种复杂性对任何有抱负的云运营商来说都是一个进入的障碍,无论是公共的还是私有的。

4 相关工作

Multics项目在20世纪60年代开始建立公用事业计算基础设施。尽管Multics从未得到广泛的应用,云计算直到互联网普及后才得以起飞,但该项目产生了像端到端论证和一套安全原则这样的想法,这些理念在今天仍然适用。

虚拟机在20世纪70年代的IBM大型机上被引入,然后在90年代后期由VMware在x86上重新发明。Xen和KVM在2000年将VM带到了开源世界。虚拟机的开销最初很高,但由于硬件和软件的优化,这些年来稳步减少了。

操作系统级虚拟化也有很长的历史。在某种意义上,操作系统的目的是虚拟化硬件资源,以便共享它们,但是由于文件系统、进程和网络的全局名称空间,Unix传统上提供了较差的隔离。基于容量的操作系统由于没有任何全局名称空间而提供了类似容器的隔离,但它们在20世纪80年代从商业上消失了。Plan 9引入了每个进程的文件系统名称空间和bind挂载,启发了支持Linux容器的名称空间机制。

Unix chroot()特性长期以来一直用于实现基本的“监狱”,而BSD监狱特性扩展了这个概念。Solaris 10在2004年引入并大力推广了Zones,这是一种现代的容器实现。云提供商Joyent从2008年开始提供基于zone的IaaS,尽管对整个云市场没有影响。

Linux容器有着漫长而曲折的历史。Linux-VServer项目是“虚拟私有服务器”在2001年的最初实施,它从未合并到主流Linux中,但在PlanetLab中被成功使用。商业产品Virtuozzo及其开源版本OpenVZ已经广泛用于Web托管,但也没有合并到Linux中。Linux最终在2007年以内核名称空间和LXC用户空间工具的形式添加了本地容器化来管理它们。

像Heroku这样的平台即服务提供商引入了使用容器高效且可重复部署应用程序的思想。Heroku不是将容器视为虚拟服务器,而是将其视为具有额外隔离的进程。由此产生的应用程序容器的开销非常小,提供与VM类似的隔离,但具有与普通进程一样的资源共享。谷歌在其内部基础结构中也广泛采用了应用程序容器。Heroku的竞争对手DotCloud(现在称为Docker Inc.)引入了Docker作为这些应用程序容器的标准镜像格式和管理系统。

对Hypervisor进行了广泛的性能评估,但大多是与其他虚拟管理程序或非虚拟化执行进行比较。

过去VM与容器的比较大多使用旧软件,如Xen和树外容器补丁。

5 总结和未来的工作

VM和容器都是成熟的技术,它们受益于硬件性能提升和软件优化。通常,在我们测试的每一种情况下,Docker的性能都等于或超过KVM。我们的结果表明,KVM和Docker都为CPU和内存性能带来了微不足道的开销(除非在极端情况下)。对于I/O密集型工作负载,应该谨慎使用这两种形式的虚拟化。

我们发现,自创建以来,KVM的性能有了相当大的改善。曾经被认为是非常具有挑战性的工作负载,比如线速率10Gbps的网络,现在使用2013年的硬件和软件,只用一个核心就可以实现。即使使用可用的最快的半虚拟化形式,KVM仍然给每个I/O操作增加了一些开销;当执行较小的I/O时,这一开销非常大,而当它分摊到较大的I/O时,则可以忽略不计。因此,KVM不太适合对延迟敏感或具有高I/O速率的工作负载。这些开销会显著影响我们测试的服务器应用程序。

尽管容器本身几乎没有开销,但Docker并非没有性能问题。Docker卷的性能明显优于存储在AUFS中的文件。Docker的NAT还会带来高包速率工作负载的开销。这些特性代表了易于管理和性能之间的权衡,应该根据具体情况加以考虑。

在某种意义上,容器的比较只会变得更糟,因为它们一开始的开销接近于零,而VM随着时间的推移变得更快。如果容器要被广泛采用,它们必须提供稳态性能以外的优点。我们相信,在不久的将来,便利性、更快的部署、灵活性和性能的结合可能会变得引人注目。

我们的结果可以为如何构建云基础设施提供一些指导。传统观点认为(在最新的云生态系统中存在这样的东西)IaaS是用VM实现的,而PaaS是用容器实现的。我们没有发现技术上的原因,尤其是基于容器的IaaS可以提供更好的性能或更容易的部署。容器还可以消除IaaS和“裸金属”非虚拟化服务器之间的区别,因为它们提供了具有裸金属性能的虚拟机的控制和隔离。无需为虚拟化和非虚拟化服务器维护不同的映像,相同的Docker映像可以有效地部署在从Core的一小部分到整个机器上的任何地方。

我们还质疑在VM中部署容器的做法,因为与直接在非虚拟化Linux上部署容器相比,这会增加VM的性能开销,而没有任何好处。如果必须使用VM,那么在容器中运行VM可以创建额外的安全层,因为利用QEMU的攻击者仍然在容器中。

虽然今天的典型服务器是NUMA架构,但我们认为,试图在云中利用NUMA可能会付出更多的努力但不值得。将每个工作负载限制在单个CPU内,极大地简化了性能分析和调优。考虑到云应用程序通常是为可扩展而设计的,而且每个CPU的Core数量会随着时间的推移而增加,因此扩展的单位可能应该是CPU而不是服务器。这也不利于裸金属,因为每个CPU运行一个容器的服务器实际上可能比跨CPU分散工作负载要快,因为减少了交叉流量。

在本文中,我们创建了消耗整个服务器的单个VM或容器;在云中,更常见的做法是将服务器划分为更小的单元。这就引出了几个值得研究的附加问题:在同一台服务器上运行多个工作负载时的性能隔离、实时调整容器和虚拟机的大小、扩展和向外扩展之间的权衡以及实时迁移和重新启动之间的权衡。

作者:Chaobo
来源:https://mp.weixin.qq.com/s/\_\_2bJidH3y\_RHnz2cjHqSg
作者微信公众号
qrcode_cash-arch_1.jpg

相关文章推荐

更多软硬件技术干货请关注软硬件融合专栏。
推荐阅读
关注数
2803
内容数
104
软硬件融合
目录
极术微信服务号
关注极术微信号
实时接收点赞提醒和评论通知
安谋科技学堂公众号
关注安谋科技学堂
实时获取安谋科技及 Arm 教学资源
安谋科技招聘公众号
关注安谋科技招聘
实时获取安谋科技中国职位信息