虛擬化技術性能總結: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 大會的演講裡介紹了它,題目是“
有時你(或我們的客戶)的確是需要用到硬體虛擬化技術,因為他們的應用程式依賴於某個特定版本的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程序寫到它自己的驅動裡。這種情況,我演示一個帶有 的 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也檢查過)
網路吞吐量結果
一般來說,我都會再三檢查數字是否有問題,再分享效能測試結果。但我現在沒有足夠的時間(因為這篇本來想寫的簡短點)。我會分享一些我幾個月前的資料,那時我仔細地做了測試,有詳細的效能結果:
這兒有一系列的網路吞吐量和用 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 從 調優後達到的)。即使達到了 900 Mbits/sec 的速度,還是隻能到 Zones 的 1/5。
請注意“suspected limiter”列(可能的限制因素)。這對於確認實際測試結果很關鍵,來源於 。這意味著我對於每個結果都做了效能分析(包括為了節省空間沒有列在這裡的)。如果你有疑問,你可以花上一整天來進行所有的測試並分析每一個結果(使用 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 的整體效能、跟蹤延遲異常、解釋基準測試結果、研究多租戶的影響,並提高應用程式和作業系統的效能。