翻译源地址: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分隔OS的方式如同chroot在文件系统里隔离进程一样。在软件栈里没有必要提供额外的一层来让Zones工作。
现在来看看 Xen 和 KVM(简化版):
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 微秒的时间不算短,我们可以再来看看。(我还检查了 pidlock global,现在没什么问题,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 的整体性能、跟踪延迟异常、解释基准测试结果、研究多租户的影响,并提高应用程序和操作系统的性能。