虚拟化技术性能总结:Zones, KVM, Xen

时间:2021-05-21 06:44:57

[译]虚拟化技术性能总结:Zones, KVM, Xen

翻译源地址: http://dtrace.org/blogs/brendan/2013/01/11/virtualization-performance-zones-kvm-xen/ 
作者:Brendan Gregg,原文标题:Virtualization Performance: Zones, KVM, Xen 
译者:陈晓炜。麻烦转载请注明作者、译者及原始出处,并不吝多多指教!

先说一下,中国香港的 Fengqi.Asia 或大陆的 风起云 所使用的后台技术即是Joyent相关技术,应该也是中国现在唯一使用Joyent技术的公有云(如果有错请不吝指教)。

------------------------------------------------------------

在Joyent我们在两种不同的虚拟化技术( Zones 和 KVM )上运行一个高性能公有云。我们也曾经用过 Xen ,但后来逐步淘汰了它,取而代之,在 SmartOS 上使用KVM。我的任务是让他们运行得更快,所以会用到 DTrace 来分析内核、应用程序和上述的虚拟化技术。这篇文章我会用四种方式来总结一下它们的性能:特点、方框图、内部情况、测试结果。

Attribute Zones Xen KVM
CPU Performance high high (with CPU support) high (with CPU support)
CPU Allocation flexible (FSS + “bursting”) fixed to VCPU limit fixed to VCPU limit
I/O Throughput high (no intrinsic overhead) low or medium (with paravirt) low or medium (with paravirt)
I/O Latency low (no intrinsic overhead) some (I/O proxy overhead) some (I/O proxy overhead)
Memory Access Overhead none some (EPT/NPT or shadow page tables) some (EPT/NPT or shadow page tables)
Memory Loss none some (extra kernels; page tables) some (extra kernels; page tables)
Memory Allocation flexible (unused guest memory used for file system cache) fixed (and possible double-caching) fixed (and possible double-caching)
Resource Controls many (depends on OS) some (depends on hypervisor) most (OS + hypervisor)
Observability: from the host highest (see everything) low (resource usage, hypervisor statistics) medium (resource usage, hypervisor statistics, OS inspection of hypervisor)
Observability: from the guest medium (see everything permitted, incl. some physical resource stats) low (guest only) low (guest only)
Hypervisor Complexity low (OS partitions) high (complex hypervisor) medium
Different OS Guests usually no (sometimes possible with syscall translation) yes yes

(上表就不翻译了,看英文更合适)

配置调优等不同可能会导致上表的一些变化,从而细节可能会有不同。不过至少可以把上表当成一个属性清单来做分析确认,这样的话,当你在考虑其他技术,比如VMware时,可以参考一下此清单。其实Wikipedia上也提供了一个常用的 属性对比表

表中的三列代表三种不同的类型:: 操作系统虚拟化 (Zones)、 硬件虚拟化 的 Type 1  (Xen) and  Type 2  (KVM) 。

它们虚拟化后表现出来的性能是我们最关心的。一般来说,Joyent使用高速的服务器级别的硬件,10 GbE 网络,所用文件系统使用 ZFS ,用 DTrace 做系统分析,尽量使用 Zones 虚拟化。我们使用自己移植的 KVM to illumos ,并在Zones内部运行KVM实例,提供额外的资源控制,和增强的安全措施(“double-hulled virtualization”双层虚拟化).

有很多属性的细节我都想讨论。这篇文章我会谈谈 I/O Path(网络、磁盘)和它的负载情况等。

I/O Path(I/O 路径)

对于传统的Unix和Zones,它们的 I/O 有什么不同呢?

虚拟化技术性能总结:Zones, KVM, Xen

性能完全一样——没有额外开销。Zones分隔OS的方式如同chroot在文件系统里隔离进程一样。在软件栈里没有必要提供额外的一层来让Zones工作。

现在来看看 Xen 和 KVM(简化版):

虚拟化技术性能总结:Zones, KVM, Xen

GK 是 Guest Kernel,在 Xen 里的 domU 运行 guest OS。有些箭头是指 控制路径(control-path ) ,组件相互通信,同步或异步,以传输更多的数据。 数据路径(data-path) 在某些场景下可能会在共享内存或环形缓冲区执行。 配置的方法可以有好几种。比如,Xen 可以使用Isolated Driver Domains(IDD)或stub-domains,在隔离区里运行 I/O Proxy。

使用 Xen ,hypervisor 为 domains 执行 CPU 调度,每个 domain 对线程调度都有自己 OS 内核。Hypervisor 支持不同的 CPU 调度类,包括 Borrowed Virtual Time (BVT)、Simple Earliest Deadline First (SEDF) 和 Credit-Based。Domain 使用 OS 内核调度器,以及 domain 提供的任何正规的调度类及策略。

多个调度器的额外开销会影响性能。多个调度器可能会在交互中产生复杂的问题,在错误的情景下增加 CPU 延迟。调试它们会非常困难,特别是当 Xen hypervisor 用尽常见的 OS 性能工具资源(用 xentrace )。

通过 I/O proxy 进程(一般是 qemu)发送 I/O 会带来上下文交换(context-switching)和更多开销。我们需要做大量的工作来尽可能减少开销,包括共享内存传输、缓冲、I/O 合并(coalescing)和半虚拟化(paravirtualization)驱动程序等。

使用 KVM ,hypervisor 是一个内核模块(kvm),由 OS 调度器来调度。Hypervisor 可以使用常见的 OS 内核调度器类、策略和优先级等进行调优。KVM 的 I/O 路径要比 Xen 少几步。(最初的 Qumranet KVM 论文 说相比Xen,KVM需要五步,当时的描述里没考虑半虚拟化)

使用 Zones ,就没有上述类似的比较了。它的 I/O 路径(对高速网络很敏感)没有这些多余的步骤。这在Solaris社区(Zones是Solaris的技术)和 FreeBSD 社区(Zones基于 FreeBSD jails 版)应是耳熟能详了。Linux社区也在学习它们并开发属于自己的版本: Linux Containers 。Glauber Costa 在 Linuxcon 2012 大会的演讲里介绍了它,题目是“ The failure of Operating Systems, and how we can fix it ”,并列出了一些用户案例,这些用户之前是用KVM。很多用户案例其实都是可以用Containers技术,并不需要用KVM。

有时你(或我们的客户)的确是需要用到硬件虚拟化技术,因为他们的应用程序依赖于某个特定版本的Linux内核或Windows。针对这种情况,我们提供KVM给客户(我们淘汰了Xen)。

内部情况(Internals)

让我们深入研究看看它们如何工作的(经常使用DTrace)

Network I/O, Zones

下面的两个 stack traces 显示了一个网络包如何通过 global zone(host,即裸机安装)传输到一个zone(guest)中:

Global Zone:                            Zone:
mac`mac_tx+0xda mac`mac_tx+0xda
dld`str_mdata_fastpath_put+0x53 dld`str_mdata_fastpath_put+0x53
ip`ip_xmit+0x82d ip`ip_xmit+0x82d
ip`ire_send_wire_v4+0x3e9 ip`ire_send_wire_v4+0x3e9
ip`conn_ip_output+0x190 ip`conn_ip_output+0x190
ip`tcp_send_data+0x59 ip`tcp_send_data+0x59
ip`tcp_output+0x58c ip`tcp_output+0x58c
ip`squeue_enter+0x426 ip`squeue_enter+0x426
ip`tcp_sendmsg+0x14f ip`tcp_sendmsg+0x14f
sockfs`so_sendmsg+0x26b sockfs`so_sendmsg+0x26b
sockfs`socket_sendmsg+0x48 sockfs`socket_sendmsg+0x48
sockfs`socket_vop_write+0x6c sockfs`socket_vop_write+0x6c
genunix`fop_write+0x8b genunix`fop_write+0x8b
genunix`write+0x250 genunix`write+0x250
genunix`write32+0x1e genunix`write32+0x1e
unix`_sys_sysenter_post_swapgs+0x14 unix`_sys_sysenter_post_swapgs+0x14

我用了一些方法以及很多时间,反复检查我没有弄错,因为上面它们两个是完全一样的。的确,右边显示的栈的结果和左边路径一样。

你也可以配置Zones,让它又一些开销,象一般的系统一样。比如,启动为网络I/O设置的防火墙,或者使用lofs挂载一个文件系统而不用直接挂载。这些都是可选的操作,对于某些用户案例也许会值得耗费一些额外的性能开销。

Network I/O, KVM

显示网络I/O的完整代码路径非常复杂。

第一部分是guest进程写到它自己的驱动里。这种情况,我演示一个带有  DTrace-for-Linux 的 Linux  Fedora guest,并且跟踪 paravirt 驱动:

guest# dtrace -n 'fbt:virtio_net:start_xmit:entry { @[stack(100)] = count(); }'
dtrace: description 'fbt:virtio_net:start_xmit:entry ' matched 1 probe
^C
[...]
kernel`start_xmit+0x1
kernel`dev_hard_start_xmit+0x322
kernel`sch_direct_xmit+0xef
kernel`dev_queue_xmit+0x184
kernel`eth_header+0x3a
kernel`neigh_resolve_output+0x11e
kernel`nf_hook_slow+0x75
kernel`ip_finish_output
kernel`ip_finish_output+0x17e
kernel`ip_output+0x98
kernel`__ip_local_out+0xa4
kernel`ip_local_out+0x29
kernel`ip_queue_xmit+0x14f
kernel`tcp_transmit_skb+0x3e4
kernel`__kmalloc_node_track_caller+0x185
kernel`sk_stream_alloc_skb+0x41
kernel`tcp_write_xmit+0xf7
kernel`__alloc_skb+0x8c
kernel`__tcp_push_pending_frames+0x26
kernel`tcp_sendmsg+0x895
kernel`inet_sendmsg+0x64
kernel`sock_aio_write+0x13a
kernel`do_sync_write+0xd2
kernel`security_file_permission+0x2c
kernel`rw_verify_area+0x61
kernel`vfs_write+0x16d
kernel`sys_write+0x4a
kernel`sys_rt_sigprocmask+0x84
kernel`system_call_fastpath+0x16
2015

上面便是 Linux 3.2.6 网络传输的路径。

控制部分由 KVM 传给 qemu I/O proxy,然后用常见的方式(native driver)在host OS上传输。这里是 SmartOS 栈的情况:

host# dtrace -n 'fbt::igb_tx:entry { @[stack()] = count(); }'
dtrace: description 'fbt::igb_tx:entry ' matched 1 probe
^C
[...]
igb`igb_tx_ring_send+0x33
mac`mac_hwring_tx+0x1d
mac`mac_tx_send+0x5dc
mac`mac_tx_single_ring_mode+0x6e
mac`mac_tx+0xda
dld`str_mdata_fastpath_put+0x53
ip`ip_xmit+0x82d
ip`ire_send_wire_v4+0x3e9
ip`conn_ip_output+0x190
ip`tcp_send_data+0x59
ip`tcp_output+0x58c
ip`squeue_enter+0x426
ip`tcp_sendmsg+0x14f
sockfs`so_sendmsg+0x26b
sockfs`socket_sendmsg+0x48
sockfs`socket_vop_write+0x6c
genunix`fop_write+0x8b
genunix`write+0x250
genunix`write32+0x1e
unix`_sys_sysenter_post_swapgs+0x149
1195

上述两个栈开始都非常得复杂。然后有一些相关的会运行在 Linux kernel 和illumos kernel 中,它们会更加复杂。基本上,paravirt 代码路径让这两个 kernel stack 得以“紧密接触”。

当 Joyent 的 Robert Mustacchi 深入研究了上述的代码路径后,他画了如下非常形象的 ASCII 图以帮助理解:

/*
* GUEST # QEMU
* #####################################################################
* #
* +----------+ #
* | start_ | (1) #
* | xmit() | #
* +----------+ #
* || #
* || +-----------+ #
* ||------>|free_old_ | (2) #
* ||------>|xmit_skbs()| #
* || +-----------+ #
* \/ (3) #
* +---------+ +-------------+ + - #--- PIO write to VNIC
* | xmit_ |------->|virtqueue_add| | # PCI config space (6)
* | skb() |------->|_buf_gfp() | | #
* +---------+ +-------------+ | #
* || | # +- VM exit
* || +- iff interrupts | # | KVM driver exit (7)
* \/ | unmasked (4) | # |
* +---------+ | +-----------+(5) | # | +---------+
* |virtqueue|----*---->|vp_notify()|-----*---#-*->| handle | (8)
* |_kick() |----*---->| |-----*---#-*->|PIO write|
* +---------+ +-----------+ # +---------+
* || # ||
* || (13) # ||
* **-----+ iff avail ring # \/ (9)
* || capacity < 20 # +-----------------+
* || else return # |virtio_net_handle|
* || # |tx_timer() |
* \/ (14) # +-----------------+
* +----------+ # ||
* |netif_stop| # || (10)
* |_queue() | # || +---------+
* +----------+ # ||-->|qemu_mod_|
* || # ||-->|timer() |
* || (15) (16) # || +---------+
* +----------------+ +----------+ # ||
* |virtqueue_enable|---->|unmask | # || (11)
* |_cb_delayed() |---->|interrupts| # || +------------+
* +----------------+ +----------+ # |+->|virtio_ |
* || || # +-->|queue_set_ |
* || (18) || (17) # |notification|
* || +-return +-------------------+ # +------------+
* || | iff ---->|check if the number| # |
* **--+ is false |of unprocessed used| # | disable host
* || |ring entries is > | # +- interrupts
* || |3/4s of the avail | # (12)
* \/ (19) |ring index - the | #
* +-----------+ |last freed used | #
* |free_old_ | |ring index | #
* |xmit_skbs()| +-------------------+ #
* +-----------+ #
* || #
* || (20) #
* **-----+ iff avail ring #
* || capacity is #
* || now > 20 #
* \/ #
* +-----------+ #
* |netif_start| (21) #
* |_queue() | #
* +-----------+ #
* || #
* || #
* \/ (22) (23) #
* +------------+ +----------+ #
* |virtqueue_ |----->|mask | #
* |disable_cb()|----->|interrupts| #
* +------------+ +----------+ #
* #
* #
*/
Figure II: Guest / Host Packet TX Part 1

我引用上图只是让你大概感觉里面发生了些什么。而且上面只是一部分(Part 1)。

简单来说,它(KVM)使用在共享内存中的 ring buffer 传输数据,一个提醒机制会告知何时可以准备传输数据。当万事如计划开始工作时,性能就非常不错。它没有裸金属那么快(或者如Zones那么快),但是也没那么差。我会在这篇后面引用一些数字来佐证。

CPU 开销和降低的网络性能是需要注意的。此外因为上面介绍过的复杂性,使得分析和性能调查都比较困难。如果使用 Zones,那么只用研究和调优一个内核 TCP/IP 栈(kernel TCP/IP stack)。因为其复杂性,一个就够啦!如果用 KVM,那么有两个不同的内核 TCP/IP 栈,加上 KVM 和 paravirt。调研性能会耗时十倍于Zones。因为太长,所以一般都被禁止。这是为什么在我的表格里,我要引入“可观察性(Observability)”这个概念作为一个重要的特性。因为越难看到的,就越难去调优。

Network I/O, Xen

Guest 传输和 I/O proxy 传输一样,里面的情况更加复杂。不能使用OS里可观察或调试的工具对hypervisor进行检查,因为它直接运行在裸金属级别。有一个工具叫 xentrace,也许很有用,因为在 Xen 调度器中编写的很多事件类型都使用静态探针(static probes)。(但它不是如 DTrace 那样实时观测且可编程,而且需要我再去学另外一个 Tracer 工具)

/proc, Zones

当 I/O 路径默认是0额外开销时,操作系统虚拟化还有一些开销,通常是因为管理或观测需要,且它们并不在 CPU 或 I/O 的 “热门路径” 中。

比如,在同一个系统中用 prstat(1M) , top(1) 之类的读 /proc,一个 Zone 是看不到其他 guest 系统的 /proc 信息。下面是 usr/src/uts/common/fs/proc/prvnops.c 的片段:

static int
pr_readdir_procdir(prnode_t *pnp, uio_t *uiop, int *eofp)
{
[...]
/*
* Loop until user's request is satisfied or until all processes
* have been examined.
*/
while ((error = gfs_readdir_pred(&gstate, uiop, &n)) == 0) {
uint_t pid;
int pslot;
proc_t *p; /*
* Find next entry. Skip processes not visible where
* this /proc was mounted.
*/
mutex_enter(&pidlock);
while (n < v.v_proc &&
((p = pid_entry(n)) == NULL || p->p_stat == SIDL ||
(zoneid != GLOBAL_ZONEID && p->p_zone->zone_id != zoneid) ||
secpolicy_basic_procinfo(CRED(), p, curproc) != 0))
n++;
[...]

从上看出,完整的进程信息列表被扫描,但只有本地的 Zone 进程信息被返回。听上去有点低效,难道不能在 proc_t 上加个链表,这样 Zone 进程就能直接得到了?当然可以,但让我们用数字来说话。

下面是使用  prstat(1M) 命令从 Zone 里读 /proc ,读取时间用DTrace来测算:

# dtrace -n 'fbt::pr_readdir_procdir:entry /execname == "prstat"/ {
self->ts = timestamp; } fbt::pr_readdir_procdir:return /self->ts/ {
@["ns"] = avg(timestamp - self->ts); self->ts = 0; }'

dtrace: description 'fbt::pr_readdir_procdir:entry ' matched 2 probes
^C
ns 544584

平均而言,需要 544 us (微秒)

现在在另一个 Zone 里有额外的 1000 个进程(代表12个典型的额外的 guest ):

# dtrace -n 'fbt::pr_readdir_procdir:entry /execname == "prstat"/ {
self->ts = timestamp; } fbt::pr_readdir_procdir:return /self->ts/ {
@["ns"] = avg(timestamp - self->ts); self->ts = 0; }'

dtrace: description 'fbt::pr_readdir_procdir:entry ' matched 2 probes
^C
ns 594254

这样增加了 50 微秒。对于一个 /proc 读操作,算不上是热路径(hot path)。如果这样的话,那 50 微秒的时间不算短,我们可以再来看看。(我还检查了 pidlockglobal,现在没什么问题,DTrace也检查过)

网络吞吐量结果

一般来说,我都会再三检查数字是否有问题,再分享性能测试结果。但我现在没有足够的时间(因为这篇本来想写的简短点)。我会分享一些我几个月前的数据,那时我仔细地做了测试,有详细的性能结果: Active Benchmarking 。

这儿有一系列的网络吞吐量和用 iperf 测试 IOPS 的结果,用来测试缺省安装 1 Gbyte SmartOS Zones 和 CentOS KVM 实例的不同表现(没有测试 Xen)。客户机和服务器都在同一个数据中心内,但是没有在同一个物理主机,也就是说用到了所有的网络栈。

我可以明确这些测试并没有为我们的云做最优化的配置,实际上是个最低配置minimum config  (1 Gbyte instances)。如果这是个市场宣传的行为,那么我很可能将测试结果调试到对我们最有利。也就是说,对于 SmartOS 内核,会做大量的工作,可以线性驱动多个 10 GbE 端口,需要客户机生成大量的负载来测试。

对于这些结果,视负载、平台内核型号、调优情况不同,每个人所得最终结果可能不尽相同。如果你要使用它们,请仔细思考如何应用它们到何种程度。如果你的负载是受限于 CPU 或文件系统,那么你最好不要测试它们的性能,可考虑使用网络的测试结果。

在 服务器端 一个典型的调用:

iperf -s -l 128k

在 客户端 :

iperf -c server -l 128k -P 4 -i 1 -t 30

线程计数 (-P) 根据调查情况有所不同。最终结果(每个测试时间平均超过30秒)如下。

吞吐量

搜索最高的 Gbits/sec:

source dest threads result suspected limiter
SmartOS 1 GB SmartOS 1 GB 1 2.75 Gbits/sec client iperf @80% CPU, and network latency
SmartOS 1 GB SmartOS 1 GB 2 3.32 Gbits/sec dest iperf up to 19% LAT, and network latency
SmartOS 1 GB SmartOS 1 GB 4 4.54 Gbits/sec client iperf over 10% LAT, hitting CPU caps
SmartOS 1 GB SmartOS 1 GB 8 1.96 Gbits/sec client iperf LAT, hitting CPU caps
KVM CentOS 1 GB KVM CentOS 1 GB 1 400 Mbits/sec network/KVM latency (dest 60% of the 1 VCPU)
KVM CentOS 1 GB KVM CentOS 1 GB 2 394 Mbits/sec network/KVM latency (dest 60% of the 1 VCPU)
KVM CentOS 1 GB KVM CentOS 1 GB 4 388 Mbits/sec network/KVM latency (dest 60% of the 1 VCPU)
KVM CentOS 1 GB KVM CentOS 1 GB 8 389 Mbits/sec network/KVM latency (dest 70% of the 1 VCPU)

Zones 峰值性能是 4个线程,4.54 Gbits/sec。更多的线程会达到 1 Gbyte (小型实例)的 CPU 瓶颈,那么 CPU 调度延迟会引发 TCP 故障。更大的 SmartOS 实例有更高的 CPU 瓶颈,能够获得更好的性能。

对于 KVM 测试,我们使用默认的 CentOS 实例。我知道有更新的 Linux 内核,对 网络栈的调优也会更好,不过我们可以之后再去提高相应的吞吐量。我能达到的最高值是对于 1 VCPU KVM Linux 可达到 900 Mbits/sec(这是我们用大量的 DTrace 分析后,对 KVM 从  110 Mbits/sec 调优后达到的)。即使达到了 900 Mbits/sec 的速度,还是只能到 Zones 的 1/5。

请注意“suspected limiter”列(可能的限制因素)。这对于确认实际测试结果很关键,来源于 Active Benchmarking 。这意味着我对于 每个结果 都做了性能分析(包括为了节省空间没有列在这里的)。如果你有疑问,你可以花上一整天来进行所有的测试并分析每一个结果(使用 DTrace)。

IOPS

寻找那些最高的 packets/sec:

source dest threads result suspected limiter
SmartOS 1 GB SmartOS 1 GB 1 14000 packets/sec client/dest thread count (each thread about 18% CPU total)
SmartOS 1 GB SmartOS 1 GB 2 23000 packets/sec client/dest thread count
SmartOS 1 GB SmartOS 1 GB 4 36000 packets/sec client/dest thread count
SmartOS 1 GB SmartOS 1 GB 8 60000 packets/sec client/dest thread count
SmartOS 1 GB SmartOS 1 GB 16 78000 packets/sec both client & dest CPU cap
KVM Centos 1 GB KVM Centos 1 GB 1 1180 packets/sec network/KVM latency, thread count (client thread about 10% CPU)
KVM Centos 1 GB KVM Centos 1 GB 2 2300 packets/sec network/KVM latency, thread count
KVM Centos 1 GB KVM Centos 1 GB 4 4400 packets/sec network/KVM latency, thread count
KVM Centos 1 GB KVM Centos 1 GB 8 7900 packets/sec network/KVM latency, thread count (threads now using about 30% CPU each; plenty idle)
KVM Centos 1 GB KVM Centos 1 GB 16 13500 packets/sec network/KVM latency, thread count (~50% idle on both)
KVM Centos 1 GB KVM Centos 1 GB 32 18000 packets/sec CPU (dest >90% of the 1 VCPU)

以上情况下,Zones 比 KVM 快4倍。同理,受限因素是云 CPU,我只测试了小型的 1 Gbyte 的服务器。更大的服务器有更高的 CPU 配额,那么所有的结果会扩展得到的更高的值。

结论

在这篇文章里,我总结了三种虚拟化技术(Zones, Xen, KVM)的性能特征,进一步调研了它们的 I/O 路径。Zones 没有额外开销,但是 Xen 和 KVM 都有。这些开销可能会限制网络吞吐量到原有的1/4。

缺省情况下,我们鼓励用户部署 Zones,为了性能、可观察性、简单(可调式性)。这意味着要为 SmartOS (我们基于 illumos 的操作系统,托管 Zones)编译用户的程序。万一用户必须要用 Linux 或者 Windows,或其之上应用程序,那么可以使用硬件虚拟化(KVM)。

还有更多的性能特征可以考虑,这里我没有探讨,只是简要地给了一些表格,包括 CPU如何分配,VCPU如何工作,内存分配如何工作,文件系统如何缓存等等。这些在后续的日志中会继续讨论。

这篇文章本来想过多地讨论 DTrace,但是它对于高性能计算是一个必备的工具,无法不提及。我们用它来提升 Zones 和 KVM 的整体性能、跟踪延迟异常、解释基准测试结果、研究多租户的影响,并提高应用程序和操作系统的性能。