經典KVM詳解,太詳細太深入了

KVM 介紹(1):簡介及安裝

http://www.cnblogs.com/sammyliu/p/4543110.html

學習 KVM 的系列文章: 

1. KVM 介紹

1.0 虛擬化簡史

其中,KVM 全稱是 基於內核的虛擬機(Kernel-based Virtual Machine),它是一個 Linux 的一個內核模塊,該內核模塊使得 Linux 變成了一個 Hypervisor:

  • 它由 Quramnet 開發,該公司於 2008年被 Red Hat 收購。
  • 它支持 x86 (32 and 64 位), s390, Powerpc 等 CPU。
  • 它從 Linux 2.6.20 起就作爲一模塊被包含在 Linux 內核中。
  • 它需要支持虛擬化擴展的 CPU。
  • 它是完全開源的。官網

本文介紹的是基於 X86 CPU 的 KVM。

1.1 KVM 架構

  KVM 是基於虛擬化擴展(Intel VT 或者 AMD-V)的 X86 硬件的開源的 Linux 原生的全虛擬化解決方案。KVM 中,虛擬機被實現爲常規的 Linux 進程,由標準 Linux 調度程序進行調度;虛機的每個虛擬 CPU 被實現爲一個常規的 Linux 進程。這使得 KMV 能夠使用 Linux 內核的已有功能。
  但是,KVM 本身不執行任何硬件模擬,需要客戶空間程序通過 /dev/kvm 接口設置一個客戶機虛擬服務器的地址空間,向它提供模擬的 I/O,並將它的視頻顯示映射回宿主的顯示屏。目前這個應用程序是 QEMU。
 
Linux 上的用戶空間、內核空間和虛機:
 
                  
  • Guest:客戶機系統,包括CPU(vCPU)、內存、驅動(Console、網卡、I/O 設備驅動等),被 KVM 置於一種受限制的 CPU 模式下運行。
  • KVM:運行在內核空間,提供CPU 和內存的虛級化,以及客戶機的 I/O 攔截。Guest 的 I/O 被 KVM 攔截後,交給 QEMU 處理。
  • QEMU:修改過的爲 KVM 虛機使用的 QEMU 代碼,運行在用戶空間,提供硬件 I/O 虛擬化,通過 IOCTL /dev/kvm 設備和 KVM 交互。

KVM 是實現攔截虛機的 I/O 請求的原理:

    現代 CPU 本身了對特殊指令的截獲和重定向的硬件支持,甚至新的硬件會提供額外的資源來幫助軟件實現對關鍵硬件資源的虛擬化從而提高性能。以 X86 平臺爲例,支持虛擬化技術的 CPU  帶有特別優化過的指令集來控制虛擬化過程。通過這些指令集,VMM 很容易將客戶機置於一種受限制的模式下運行,一旦客戶機視圖訪問物理資源,硬件會暫停客戶機的運行,將控制權交回給 VMM 處理。VMM 還可以利用硬件的虛級化增強機制,將客戶機在受限模式下對一些特定資源的訪問,完全由硬件重定向到 VMM 指定的虛擬資源,整個過程不需要暫停客戶機的運行和 VMM 的參與。由於虛擬化硬件提供全新的架構,支持操作系統直接在上面運行,無需進行二進制轉換,減少了相關的性能開銷,極大簡化了VMM的設計,使得VMM性能更加強大。從 2005 年開始,Intel 在其處理器產品線中推廣 Intel Virtualization Technology 即 IntelVT 技術。

QEMU-KVM:

  其實 QEMU 原本不是 KVM 的一部分,它自己就是一個純軟件實現的虛擬化系統,所以其性能低下。但是,QEMU 代碼中包含整套的虛擬機實現,包括處理器虛擬化,內存虛擬化,以及 KVM需要使用到的虛擬設備模擬(網卡、顯卡、存儲控制器和硬盤等)。
 
 
 
 
 
  爲了簡化代碼,KVM 在 QEMU 的基礎上做了修改。VM 運行期間,QEMU 會通過 KVM 模塊提供的系統調用進入內核,由 KVM 負責將虛擬機置於處理的特殊模式運行。遇到虛機進行 I/O 操作,KVM 會從上次的系統調用出口處返回 QEMU,由 QEMU 來負責解析和模擬這些設備。
 
 
 
從 QEMU 的角度看,也可以說是 QEMU 使用了 KVM 模塊的虛擬化功能,爲自己的虛機提供了硬件虛擬化加速。除此以外,虛機的配置和創建、虛機運行說依賴的虛擬設備、虛機運行時的用戶環境和交互,以及一些虛機的特定技術比如動態遷移,都是 QEMU 自己實現的。
 
 

KVM:

    KVM 內核模塊在運行時按需加載進入內核空間運行。KVM 本身不執行任何設備模擬,需要 QEMU 通過 /dev/kvm 接口設置一個 GUEST OS 的地址空間,向它提供模擬的 I/O 設備,並將它的視頻顯示映射回宿主機的顯示屏。它是KVM 虛機的核心部分,其主要功能是初始化 CPU 硬件,打開虛擬化模式,然後將虛擬客戶機運行在虛擬機模式下,並對虛機的運行提供一定的支持。以在 Intel 上運行爲例,KVM 模塊被加載的時候,它:
  1. 首先初始化內部的數據結構;
  2. 做好準備後,KVM 模塊檢測當前的 CPU,然後打開 CPU 控制及存取 CR4 的虛擬化模式開關,並通過執行 VMXON 指令將宿主操作系統置於虛擬化模式的根模式;
  3. 最後,KVM 模塊創建特殊設備文件 /dev/kvm 並等待來自用戶空間的指令。
 
    接下來的虛機的創建和運行將是 QEMU 和 KVM 相互配合的過程。兩者的通信接口主要是一系列針對特殊設備文件 dev/kvm 的 IOCTL 調用。其中最重要的是創建虛機。它可以理解成KVM 爲了某個特定的虛機創建對應的內核數據結構,同時,KVM 返回一個文件句柄來代表所創建的虛機。
 
    針對該句柄的調用可以對虛機做相應地管理,比如創建用戶空間虛擬地址和客戶機物理地址、真實物理地址之間的映射關係,再比如創建多個 vCPU。KVM 爲每一個 vCPU 生成對應的文件句柄,對其相應地 IOCTL 調用,就可以對vCPU進行管理。其中最重要的就是“執行虛擬處理器”。通過它,虛機在 KVM 的支持下,被置於虛擬化模式的非根模式下,開始執行二進制指令。在非根模式下,所有敏感的二進制指令都被CPU捕捉到,CPU 在保存現場之後自動切換到根模式,由 KVM 決定如何處理。
 
    除了 CPU 的虛擬化,內存虛擬化也由 KVM 實現。實際上,內存虛擬化往往是一個虛機實現中最複雜的部分。CPU 中的內存管理單元 MMU 是通過頁表的形式將程序運行的虛擬地址轉換成實際物理地址。在虛擬機模式下,MMU 的頁表則必須在一次查詢的時候完成兩次地址轉換。因爲除了將客戶機程序的虛擬地址轉換了客戶機的物理地址外,還要將客戶機物理地址轉化成真實物理地址。 
 

2. KVM 的功能列表

KVM 所支持的功能包括:

  • 支持CPU 和 memory 超分(Overcommit)
  • 支持半虛擬化I/O (virtio)
  • 支持熱插拔 (cpu,塊設備、網絡設備等)
  • 支持對稱多處理(Symmetric Multi-Processing,縮寫爲 SMP )
  • 支持實時遷移(Live Migration)
  • 支持 PCI 設備直接分配和 單根I/O 虛擬化 (SR-IOV)
  • 支持 內核同頁合併 (KSM )
  • 支持 NUMA (Non-Uniform Memory Access,非一致存儲訪問結構 )

3. KVM 工具集合

  • libvirt:操作和管理KVM虛機的虛擬化 API,使用 C 語言編寫,可以由 Python,Ruby, Perl, PHP, Java 等語言調用。可以操作包括 KVM,vmware,XEN,Hyper-v, LXC 等 Hypervisor。
  • Virsh:基於 libvirt 的 命令行工具 (CLI)
  • Virt-Manager:基於 libvirt 的 GUI 工具
  • virt-v2v:虛機格式遷移工具
  • virt-* 工具:包括 Virt-install (創建KVM虛機的命令行工具), Virt-viewer (連接到虛機屏幕的工具),Virt-clone(虛機克隆工具),virt-top 等
  • sVirt:安全工具

4. RedHat Linux KVM 安裝

RedHat 有兩款產品提供 KVM 虛擬化:
1. Red Hat Enterprise Linux:適用於小的環境,提供數目較少的KVM虛機。最新的版本包括 6.5 和 7.0.
2. Red Hat Enterprise Virtualization (RHEV):提供企業規模的KVM虛擬化環境,包括更簡單的管理、HA,性能優化和其它高級功能。最新的版本是 3.0.
 
 RedHat Linux KVM:
  • KVM 由 libvirt API 和基於該 API的一組工具進行管理和控制。
  • KVM 支持系統資源超分,包括內存和CPU的超分。RedHat Linux 最多支持物理 CPU 內核總數的10倍數目的虛擬CPU,但是不支持在一個虛機上分配超過物理CPU內核總數的虛擬CPU。
  • 支持 KSM (Kenerl Same-page Merging 內核同頁合併)

 RedHat Linux KVM 有如下兩種安裝方式:

4.1 在安裝  RedHat Linux 時安裝 KVM

選擇安裝類型爲 Virtualizaiton Host :

可以選擇具體的 KVM 客戶端、平臺和工具:

4.2 在已有的 RedHat Linux 中安裝 KVM

這種安裝方式要求該系統已經被註冊,否則會報錯:

[root@rh65 ~]# yum install qemu-kvm qemu-img
Loaded plugins: product-id, refresh-packagekit, security, subscription-manager
This system is not registered to Red Hat Subscription Management. You can use subscription-manager to register.
Setting up Install Process
Nothing to do

你至少需要安裝 qemu-kvm qemu-img 這兩個包。

# yum install qemu-kvm qemu-img

你還可以安裝其它工具包:

# yum install virt-manager libvirt libvirt-python python-virtinst libvirt-client

4.3 QEMU/KVM 代碼下載編譯安裝

4.3.1 QEMU/KVM 的代碼結構

QEMU/KVM 的代碼包括幾個部分:

(1)KVM 內核模塊是 Linux 內核的一部分。通常 Linux 比較新的發行版(2.6.20+)都包含了 KVM 內核,也可以從這裏得到。比如在我的RedHat 6.5 上:

[root@rh65 isoimages]# uname -r 2.6.32-431.el6.x86_64
[root@rh65 isoimages]# modprobe -l | grep kvm
kernel/arch/x86/kvm/kvm.ko
kernel/arch/x86/kvm/kvm-intel.ko
kernel/arch/x86/kvm/kvm-amd.ko

(2)用戶空間的工具即 qemu-kvm。qemu-kvm 是 KVM 項目從 QEMU 新拉出的一個分支(看這篇文章)。在 QEMU 1.3 版本之前,QEMU 和 QEMU-KVM 是有區別的,但是從 2012 年底 GA 的 QEMU 1.3 版本開始,兩者就完全一樣了。

(3)Linux Guest OS virtio 驅動,也是較新的Linux 內核的一部分了。

(4)Windows Guest OS virtio 驅動,可以從這裏下載。

4.3.2 安裝 QEMU

RedHat 6.5 上自帶的 QEMU 太老,0.12.0 版本,最新版本都到了 2.* 了。

(1). 參考 這篇文章,將 RedHat 6.5 的 ISO 文件當作本地源

複製代碼
mount -o loop soft/rhel-server-6.4-x86_64-dvd.iso /mnt/rhel6/

vim /etc/fstab
=> /root/isoimages/soft/RHEL6.5-20131111.0-Server-x86_64-DVD1.iso /mnt/rhel6 iso9660 ro,loop

[root@rh65 qemu-2.3.0]# cat /etc/yum.repos.d/local.repo
[local]
name=local
baseurl=file:///mnt/rhel6/
enabled=1
gpgcjeck=0

1
yum clean all
yum update
複製代碼

(2). 安裝依賴包包

yum install gcc
yum install autoconf
yum install autoconf automake libtool
yum install -y glib* yum install zlib*

(3). 從 http://wiki.qemu.org/Download 下載代碼,上傳到我的編譯環境 RedHat 6.5.

tar -jzvf qemu-2.3.0.tar.bz2
cd qemu-2.3.0 ./configure
make -j 4 make install

(4). 安裝完成

[root@rh65 qemu-2.3.0]# /usr/local/bin/qemu-x86_64 -version
qemu-x86_64 version 2.3.0, Copyright (c) 2003-2008 Fabrice Bellard

(5). 爲方便起見,創建一個link

ln -s /usr/bin/qemu-system-x86_64 /usr/bin/qemu-kvm

4.3.3 安裝 libvirt

可以從 libvirt 官網下載安裝包。最新的版本是 0.10.2. 

5. 創建 KVM 虛機的幾種方式

5.1 使用 virt-install 命令

複製代碼
virt-install \ --name=guest1-rhel5-64 \ --file=/var/lib/libvirt/images/guest1-rhel5-64.dsk \ --file-size=8 \ --nonsparse --graphics spice \ --vcpus=2 --ram=2048 \ --location=http://example1.com/installation_tree/RHEL5.6-Serverx86_64/os \ --network bridge=br0 \ --os-type=linux \ --os-variant=rhel5.4
複製代碼

5.2 使用 virt-manager 工具

使用 VMM GUI 創建的虛機的xml 定義文件在 /etc/libvirt/qemu/ 目錄中。

5.3 使用 qemu-img 和 qemu-kvm 命令行方式安裝

(1)創建一個空的qcow2格式的鏡像文件

qemu-img create -f qcow2 windows-master.qcow2 10G

(2)啓動一個虛機,將系統安裝盤掛到 cdrom,安裝操作系統

qemu-kvm  -hda  windows-master.qcow2  -m 512 -boot d  -cdrom /home/user/isos/en_winxp_pro_with_sp2.iso

(3)現在你就擁有了一個帶操作系統的鏡像文件。你可以以它爲模板創建新的鏡像文件。使用模板的好處是,它會被設置爲只讀所以可以免於破壞。

qemu-img create -b windows-master.qcow2 -f  qcow2   windows-clone.qcow2

(4)你可以在新的鏡像文件上啓動虛機了

qemu-kvm  -hda  windows-clone.qcow2  -m 400

5.4 通過 OpenStack Nova 使用 libvirt API 通過編程方式來創建虛機 (後面會介紹)

分類: NovaKVM

KVM 介紹(2):CPU 和內存虛擬化

學習 KVM 的系列文章: 

1. 爲什麼需要 CPU 虛擬化

X86 操作系統是設計在直接運行在裸硬件設備上的,因此它們自動認爲它們完全佔有計算機硬件。x86 架構提供四個特權級別給操作系統和應用程序來訪問硬件。 Ring 是指 CPU 的運行級別,Ring 0是最高級別,Ring1次之,Ring2更次之…… 就 Linux+x86 來說, 
  • 操作系統(內核)需要直接訪問硬件和內存,因此它的代碼需要運行在最高運行級別  Ring0上,這樣它可以使用特權指令,控制中斷、修改頁表、訪問設備等等。 
  • 應用程序的代碼運行在最低運行級別上ring3上,不能做受控操作。如果要做,比如要訪問磁盤,寫文件,那就要通過執行系統調用(函數),執行系統調用的時候,CPU的運行級別會發生從ring3到ring0的切換,並跳轉到系統調用對應的內核代碼位置執行,這樣內核就爲你完成了設備訪問,完成之後再從ring0返回ring3。這個過程也稱作用戶態和內核態的切換。
 
 
那麼,虛擬化在這裏就遇到了一個難題,因爲宿主操作系統是工作在 ring0 的,客戶操作系統就不能也在 ring0 了,但是它不知道這一點,以前執行什麼指令,現在還是執行什麼指令,但是沒有執行權限是會出錯的。所以這時候虛擬機管理程序(VMM)需要避免這件事情發生。 虛機怎麼通過 VMM 實現 Guest CPU 對硬件的訪問,根據其原理不同有三種實現技術:
1. 全虛擬化
2. 半虛擬化
3. 硬件輔助的虛擬化 
 

1.1 基於二進制翻譯的全虛擬化(Full Virtualization with Binary Translation)

 
客戶操作系統運行在 Ring 1,它在執行特權指令時,會觸發異常(CPU的機制,沒權限的指令會觸發異常),然後 VMM 捕獲這個異常,在異常裏面做翻譯,模擬,最後返回到客戶操作系統內,客戶操作系統認爲自己的特權指令工作正常,繼續運行。但是這個性能損耗,就非常的大,簡單的一條指令,執行完,了事,現在卻要通過複雜的異常處理過程。
 
異常 “捕獲(trap)-翻譯(handle)-模擬(emulate)” 過程:
 

1.2. 超虛擬化(或者半虛擬化/操作系統輔助虛擬化 Paravirtualization) 

  半虛擬化的思想就是,修改操作系統內核,替換掉不能虛擬化的指令,通過超級調用(hypercall)直接和底層的虛擬化層hypervisor來通訊,hypervisor 同時也提供了超級調用接口來滿足其他關鍵內核操作,比如內存管理、中斷和時間保持。
  這種做法省去了全虛擬化中的捕獲和模擬,大大提高了效率。所以像XEN這種半虛擬化技術,客戶機操作系統都是有一個專門的定製內核版本,和x86、mips、arm這些內核版本等價。這樣以來,就不會有捕獲異常、翻譯、模擬的過程了,性能損耗非常低。這就是XEN這種半虛擬化架構的優勢。這也是爲什麼XEN只支持虛擬化Linux,無法虛擬化windows原因,微軟不改代碼啊。
 

1.3. 硬件輔助的全虛擬化 

    2005年後,CPU廠商Intel 和 AMD 開始支持虛擬化了。 Intel 引入了 Intel-VT (Virtualization Technology)技術。 這種 CPU,有 VMX root operation 和 VMX non-root operation兩種模式,兩種模式都支持Ring 0 ~ Ring 3 共 4 個運行級別。這樣,VMM 可以運行在 VMX root operation模式下,客戶 OS 運行在VMX non-root operation模式下。
 
 
 
  而且兩種操作模式可以互相轉換。運行在 VMX root operation 模式下的 VMM 通過顯式調用 VMLAUNCH 或 VMRESUME 指令切換到 VMX non-root operation 模式,硬件自動加載 Guest OS 的上下文,於是 Guest OS 獲得運行,這種轉換稱爲 VM entry。Guest OS 運行過程中遇到需要 VMM 處理的事件,例如外部中斷或缺頁異常,或者主動調用 VMCALL 指令調用 VMM 的服務的時候(與系統調用類似),硬件自動掛起 Guest OS,切換到 VMX root operation 模式,恢復 VMM 的運行,這種轉換稱爲 VM exit。VMX root operation 模式下軟件的行爲與在沒有 VT-x 技術的處理器上的行爲基本一致;而VMX non-root operation 模式則有很大不同,最主要的區別是此時運行某些指令或遇到某些事件時,發生 VM exit。
 
也就說,硬件這層就做了些區分,這樣全虛擬化下,那些靠“捕獲異常-翻譯-模擬”的實現就不需要了。而且CPU廠商,支持虛擬化的力度越來越大,靠硬件輔助的全虛擬化技術的性能逐漸逼近半虛擬化,再加上全虛擬化不需要修改客戶操作系統這一優勢,全虛擬化技術應該是未來的發展趨勢。
 
 
利用二進制翻譯的全虛擬化
硬件輔助虛擬化
操作系統協助/半虛擬化
實現技術
BT和直接執行
遇到特權指令轉到root模式執行
Hypercall
客戶操作系統修改/兼容性
無需修改客戶操作系統,最佳兼容性
無需修改客戶操作系統,最佳兼容性
客戶操作系統需要修改來支持hypercall,因此它不能運行在物理硬件本身或其他的hypervisor上,兼容性差,不支持Windows
性能
全虛擬化下,CPU需要在兩種模式之間切換,帶來性能開銷;但是,其性能在逐漸逼近半虛擬化。
好。半虛擬化下CPU性能開銷幾乎爲0,虛機的性能接近於物理機。
應用廠商
VMware Workstation/QEMU/Virtual PC
VMware ESXi/Microsoft Hyper-V/Xen 3.0/KVM
Xen

2. KVM CPU 虛擬化

KVM 是基於CPU 輔助的全虛擬化方案,它需要CPU虛擬化特性的支持。

2.1. CPU 物理特性

這個命令查看主機上的CPU 物理情況:

複製代碼
[s1@rh65 ~]$ numactl --hardware
available: 2 nodes (0-1) //2顆CPU node 0 cpus: 0 1 2 3 4 5 12 13 14 15 16 17 //這顆 CPU 有8個內核 node 0 size: 12276 MB
node 0 free: 7060 MB
node 1 cpus: 6 7 8 9 10 11 18 19 20 21 22 23 node 1 size: 8192 MB
node 1 free: 6773 MB
node distances:
node 0 1 0: 10 21 1: 21 10 
複製代碼

要支持 KVM, Intel CPU 的 vmx 或者 AMD CPU 的 svm 擴展必鬚生效了:

[root@rh65 s1]# egrep "(vmx|svm)" /proc/cpuinfo
flags        : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx pdpe1gb rdtscp lm constant_tsc arch_perfmon pebs bts rep_good xtopology nonstop_tsc aperfmperf pni pclmulqdq dtes64 monitor ds_cpl vmx smx est tm2 ssse3 cx16 xtpr pdcm pcid dca sse4_1 sse4_2 popcnt aes lahf_lm arat epb dts tpr_shadow vnmi flexpriority ept vpid

2.2 多 CPU 服務器架構:SMP,NMP,NUMA

從系統架構來看,目前的商用服務器大體可以分爲三類:

  • 多處理器結構 (SMP : Symmetric Multi-Processor):所有的CPU共享全部資源,如總線,內存和I/O系統等,操作系統或管理數據庫的複本只有一個,這種系統有一個最大的特點就是共享所有資源。多個CPU之間沒有區別,平等地訪問內存、外設、一個操作系統。SMP 服務器的主要問題,那就是它的擴展能力非常有限。實驗證明, SMP 服務器 CPU 利用率最好的情況是 2 至 4 個 CPU 。
  • 海量並行處理結構 (MPP : Massive Parallel Processing) :NUMA 服務器的基本特徵是具有多個 CPU 模塊,每個 CPU 模塊由多個 CPU( 如 4 個 ) 組成,並且具有獨立的本地內存、 I/O 槽口等。在一個物理服務器內可以支持上百個 CPU 。但 NUMA 技術同樣有一定缺陷,由於訪問遠地內存的延時遠遠超過本地內存,因此當 CPU 數量增加時,系統性能無法線性增加。
  • MPP 模式則是一種分佈式存儲器模式,能夠將更多的處理器納入一個系統的存儲器。一個分佈式存儲器模式具有多個節點,每個節點都有自己的存儲器,可以配置爲SMP模式,也可以配置爲非SMP模式。單個的節點相互連接起來就形成了一個總系統。MPP可以近似理解成一個SMP的橫向擴展集羣,MPP一般要依靠軟件實現。
  • 非一致存儲訪問結構 (NUMA : Non-Uniform Memory Access):它由多個 SMP 服務器通過一定的節點互聯網絡進行連接,協同工作,完成相同的任務,從用戶的角度來看是一個服務器系統。其基本特徵是由多個 SMP 服務器 ( 每個 SMP 服務器稱節點 ) 通過節點互聯網絡連接而成,每個節點只訪問自己的本地資源 ( 內存、存儲等 ) ,是一種完全無共享 (Share Nothing) 結構。

詳細描述可以參考 SMP、NUMA、MPP體系結構介紹

查看你的服務器的 CPU 架構:

[root@rh65 s1]# uname -a
Linux rh65 2.6.32-431.el6.x86_64 #1 SMP Sun Nov 10 22:19:54 EST 2013 x86_64 x86_64 x86_64 GNU/Linux #這服務器是 SMP 架構 

2.2 KVM CPU 虛擬化

2.2.1 KVM 虛機的創建過程

可見:

(1)qemu-kvm 通過對 /dev/kvm 的 一系列 ICOTL 命令控制虛機,比如

複製代碼
open("/dev/kvm", O_RDWR|O_LARGEFILE)    = 3 ioctl(3, KVM_GET_API_VERSION, 0)        = 12 ioctl(3, KVM_CHECK_EXTENSION, 0x19)     = 0 ioctl(3, KVM_CREATE_VM, 0)              = 4 ioctl(3, KVM_CHECK_EXTENSION, 0x4)      = 1 ioctl(3, KVM_CHECK_EXTENSION, 0x4)      = 1 ioctl(4, KVM_SET_TSS_ADDR, 0xfffbd000)  = 0 ioctl(3, KVM_CHECK_EXTENSION, 0x25)     = 0 ioctl(3, KVM_CHECK_EXTENSION, 0xb)      = 1 ioctl(4, KVM_CREATE_PIT, 0xb)           = 0 ioctl(3, KVM_CHECK_EXTENSION, 0xf)      = 2 ioctl(3, KVM_CHECK_EXTENSION, 0x3)      = 1 ioctl(3, KVM_CHECK_EXTENSION, 0)        = 1 ioctl(4, KVM_CREATE_IRQCHIP, 0)         = 0 ioctl(3, KVM_CHECK_EXTENSION, 0x1a)     = 0
複製代碼

(2)一個 KVM 虛機即一個 Linux qemu-kvm 進程,與其他 Linux 進程一樣被Linux 進程調度器調度。

(3)KVM 虛機包括虛擬內存、虛擬CPU和虛機 I/O設備,其中,內存和 CPU 的虛擬化由 KVM 內核模塊負責實現,I/O 設備的虛擬化由 QEMU 負責實現。

(3)KVM戶機系統的內存是 qumu-kvm 進程的地址空間的一部分。

(4)KVM 虛機的 vCPU 作爲 線程運行在 qemu-kvm 進程的上下文中。

vCPU、QEMU 進程、LInux 進程調度和物理CPU之間的邏輯關係:

2.2.2 因爲 CPU 中的虛擬化功能的支持,並不存在虛擬的 CPU,KVM Guest 代碼是運行在物理 CPU 之上

    根據上面的 1.3 章節,支持虛擬化的 CPU 中都增加了新的功能。以 Intel VT 技術爲例,它增加了兩種運行模式:VMX root 模式和 VMX nonroot 模式。通常來講,主機操作系統和 VMM 運行在 VMX root 模式中,客戶機操作系統及其應用運行在 VMX nonroot 模式中。因爲兩個模式都支持所有的 ring,因此,客戶機可以運行在它所需要的 ring 中(OS 運行在 ring 0 中,應用運行在 ring 3 中),VMM 也運行在其需要的 ring 中 (對 KVM 來說,QEMU 運行在 ring 3,KVM 運行在 ring 0)。CPU 在兩種模式之間的切換稱爲 VMX 切換。從 root mode 進入 nonroot mode,稱爲 VM entry;從 nonroot mode 進入 root mode,稱爲 VM exit。可見,CPU 受控制地在兩種模式之間切換,輪流執行 VMM 代碼和 Guest OS 代碼。

  對 KVM 虛機來說,運行在 VMX Root Mode 下的 VMM 在需要執行 Guest OS 指令時執行 VMLAUNCH 指令將 CPU 轉換到 VMX non-root mode,開始執行客戶機代碼,即 VM entry 過程;在 Guest OS 需要退出該 mode 時,CPU 自動切換到 VMX Root mode,即 VM exit 過程。可見,KVM 客戶機代碼是受 VMM 控制直接運行在物理 CPU 上的。QEMU 只是通過 KVM 控制虛機的代碼被 CPU 執行,但是它們本身並不執行其代碼。也就是說,CPU 並沒有真正的被虛級化成虛擬的 CPU 給客戶機使用。

 這篇文章 是關於 vSphere 中 CPU 虛擬化的,我覺得它和 KVM CPU 虛擬化存在很大的一致。下圖是使用 2 socket 2 core 共 4 個 vCPU 的情形:

   幾個概念:socket (顆,CPU 的物理單位),core (核,每個 CPU 中的物理內核),thread (超線程,通常來說,一個 CPU core 只提供一個 thread,這時客戶機就只看到一個 CPU;但是,超線程技術實現了 CPU 核的虛擬化,一個核被虛擬化出多個邏輯 CPU,可以同時運行多個線程)。 

  上圖分三層,他們分別是是VM層,VMKernel層和物理層。對於物理服務器而言,所有的CPU資源都分配給單獨的操作系統和上面運行的應用。應用將請求先發送給操作系統,然後操作系統調度物理的CPU資源。在虛擬化平臺比如 KVM 中,在VM層和物理層之間加入了VMkernel層,從而允許所有的VM共享物理層的資源。VM上的應用將請求發送給VM上的操作系統,然後操縱系統調度Virtual CPU資源(操作系統認爲Virtual CPU和物理 CPU是一樣的),然後VMkernel層對多個物理CPU Core進行資源調度,從而滿足Virtual CPU的需要。在虛擬化平臺中OS CPU Scheduler和Hyperviisor CPU Scheduler都在各自的領域內進行資源調度。 

   KVM 中,可以指定 socket,core 和 thread 的數目,比如 設置 “-smp 5,sockets=5,cores=1,threads=1”,則 vCPU 的數目爲 5*1*1 = 5。客戶機看到的是基於 KVM vCPU 的 CPU 核,而 vCPU 作爲 QEMU 線程被 Linux 作爲普通的線程/輕量級進程調度到物理的 CPU 核上。至於你是該使用多 socket 和 多core,這篇文章 有仔細的分析,其結論是在 VMware ESXi 上,性能沒什麼區別,只是某些客戶機操作系統會限制物理 CPU 的數目,這種情況下,可以使用少 socket 多 core。

2.2.3 客戶機系統的代碼是如何運行的

 一個普通的 Linux 內核有兩種執行模式:內核模式(Kenerl)和用戶模式 (User)。爲了支持帶有虛擬化功能的 CPU,KVM 向 Linux 內核增加了第三種模式即客戶機模式(Guest),該模式對應於 CPU 的 VMX non-root mode。

KVM 內核模塊作爲 User mode 和 Guest mode 之間的橋樑:

  • User mode 中的 QEMU-KVM 會通過 ICOTL 命令來運行虛擬機
  • KVM 內核模塊收到該請求後,它先做一些準備工作,比如將 VCPU 上下文加載到 VMCS (virtual machine control structure)等,然後驅動 CPU 進入 VMX non-root 模式,開始執行客戶機代碼

三種模式的分工爲:

  • Guest 模式:執行客戶機系統非 I/O 代碼,並在需要的時候驅動 CPU 退出該模式
  • Kernel 模式:負責將 CPU 切換到 Guest mode 執行 Guest OS 代碼,並在 CPU 退出  Guest mode 時回到 Kenerl 模式
  • User 模式:代表客戶機系統執行 I/O 操作

(來源)

QEMU-KVM 相比原生 QEMU 的改動:

  • 原生的 QEMU 通過指令翻譯實現 CPU 的完全虛擬化,但是修改後的 QEMU-KVM 會調用 ICOTL 命令來調用 KVM 模塊。
  • 原生的 QEMU 是單線程實現,QEMU-KVM 是多線程實現。

主機 Linux 將一個虛擬視作一個 QEMU 進程,該進程包括下面幾種線程:

  • I/O 線程用於管理模擬設備
  • vCPU 線程用於運行 Guest 代碼
  • 其它線程,比如處理 event loop,offloaded tasks 等的線程

在我的測試環境中(RedHata Linux 作 Hypervisor):

smp 設置的值 線程數 線程
4 8

1 個主線程(I/O 線程)、4 個 vCPU 線程、3 個其它線程

6 10 1 個主線程(I/O 線程)、6 個 vCPU 線程、3 個其它線程

這篇文章 談談了這些線程的情況。

(來源)

客戶機代碼執行(客戶機線程) I/O 線程 非 I/O 線程
虛擬CPU(主機 QEMU 線程) QEMU I/O 線程 QEMU vCPU 線程
物理 CPU 物理 CPU 的 VMX non-root 模式中 物理 CPU 的 VMX non-root 模式中

2.2.4 從客戶機線程到物理 CPU 的兩次調度

要將客戶機內的線程調度到某個物理 CPU,需要經歷兩個過程:

  1. 客戶機線程調度到客戶機物理CPU 即 KVM vCPU,該調度由客戶機操作系統負責,每個客戶機操作系統的實現方式不同。在 KVM 上,vCPU 在客戶機系統看起來就像是物理 CPU,因此其調度方法也沒有什麼不同。
  2. vCPU 線程調度到物理 CPU 即主機物理 CPU,該調度由 Hypervisor 即 Linux 負責。

    KVM 使用標準的 Linux 進程調度方法來調度 vCPU 進程。Linux 系統中,線程和進程的區別是 進程有獨立的內核空間,線程是代碼的執行單位,也就是調度的基本單位。Linux 中,線程是就是輕量級的進程,也就是共享了部分資源(地址空間、文件句柄、信號量等等)的進程,所以線程也按照進程的調度方式來進行調度。

(1)Linux 進程調度原理可以參考 這篇文章 和 這篇文章。通常情況下,在SMP系統中,Linux內核的進程調度器根據自有的調度策略將系統中的一個可運行(runable)進程調度到某個CPU上執行。下面是 Linux 進程的狀態機:

(2)處理器親和性:可以設置 vCPU 在指定的物理 CPU 上運行,具體可以參考這篇文章 和 這篇文章

    根據 Linux 進程調度策略,可以看出,在 Linux 主機上運行的 KVM 客戶機 的總 vCPU 數目最好是不要超過物理 CPU 內核數,否則,會出現線程間的 CPU 內核資源競爭,導致有虛機因爲 vCPU 進程等待而導致速度很慢。

關於這兩次調度,業界有很多的研究,比如上海交大的論文 Schedule Processes, not VCPUs 提出動態地減少 vCPU 的數目即減少第二次調度。

另外,這篇文章 談到的是 vSphere CPU 的調度方式,有空的時候可以研究下並和 KVM vCPU 的調度方式進行比較。

2.3 客戶機CPU結構和模型

KVM 支持 SMP 和 NUMA 多CPU架構的主機和客戶機。對 SMP 類型的客戶機,使用 “-smp”參數:

-smp [,cores=][,threads=][,sockets=][,maxcpus=]

對 NUMA 類型的客戶機,使用 “-numa”參數:

-numa [,mem=][,cpus=]][,nodeid=] 
 
CPU 模型 (models)定義了哪些主機的 CPU 功能 (features)會被暴露給客戶機操作系統。爲了在具有不同 CPU 功能的主機之間做安全的遷移,qemu-kvm 往往不會將主機CPU的所有功能都暴露給客戶機。其原理如下:
 
你可以運行 qemu-kvm -cpu ? 命令來獲取主機所支持的 CPU 模型列表。
複製代碼
[root@rh65 s1]# kvm -cpu ? x86       Opteron_G5  AMD Opteron 63xx class CPU                      
x86       Opteron_G4  AMD Opteron 62xx class CPU                      
x86       Opteron_G3  AMD Opteron 23xx (Gen 3 Class Opteron)          
x86       Opteron_G2  AMD Opteron 22xx (Gen 2 Class Opteron)          
x86       Opteron_G1  AMD Opteron 240 (Gen 1 Class Opteron)           
x86          Haswell  Intel Core Processor (Haswell)                  
x86      SandyBridge  Intel Xeon E312xx (Sandy Bridge)                
x86         Westmere  Westmere E56xx/L56xx/X56xx (Nehalem-C)          
x86          Nehalem  Intel Core i7 9xx (Nehalem Class Core i7)       
x86           Penryn  Intel Core 2 Duo P9xxx (Penryn Class Core 2)    
x86           Conroe  Intel Celeron_4x0 (Conroe/Merom Class Core 2)   
x86      cpu64-rhel5  QEMU Virtual CPU version (cpu64-rhel5)          
x86      cpu64-rhel6  QEMU Virtual CPU version (cpu64-rhel6)          
x86             n270  Intel(R) Atom(TM) CPU N270   @ 1.60GHz          
x86           athlon  QEMU Virtual CPU version 0.12.1 x86         pentium3                                                  
x86         pentium2                                                  
x86          pentium                                                  
x86 486 x86          coreduo  Genuine Intel(R) CPU           T2600  @ 2.16GHz 
x86           qemu32  QEMU Virtual CPU version 0.12.1 x86            kvm64  Common KVM processor                            
x86         core2duo  Intel(R) Core(TM)2 Duo CPU     T7700  @ 2.40GHz 
x86           phenom  AMD Phenom(tm) 9550 Quad-Core Processor         
x86           qemu64  QEMU Virtual CPU version 0.12.1 Recognized CPUID flags:
  f_edx: pbe ia64 tm ht ss sse2 sse fxsr mmx acpi ds clflush pn pse36 pat cmov mca pge mtrr sep apic cx8 mce pae msr tsc pse de vme fpu
  f_ecx: hypervisor rdrand f16c avx osxsave xsave aes tsc-deadline popcnt movbe x2apic sse4.2|sse4_2 sse4.1|sse4_1 dca pcid pdcm xtpr cx16 fma cid ssse3 tm2 est smx vmx ds_cpl monitor dtes64 pclmulqdq|pclmuldq pni|sse3
  extf_edx: 3dnow 3dnowext lm|i64 rdtscp pdpe1gb fxsr_opt|ffxsr fxsr mmx mmxext nx|xd pse36 pat cmov mca pge mtrr syscall apic cx8 mce pae msr tsc pse de vme fpu
  extf_ecx: perfctr_nb perfctr_core topoext tbm nodeid_msr tce fma4 lwp wdt skinit xop ibs osvw 3dnowprefetch misalignsse sse4a abm cr8legacy extapic svm cmp_legacy lahf_lm
[root@rh65 s1]# 
複製代碼

    每個 Hypervisor 都有自己的策略,來定義默認上哪些CPU功能會被暴露給客戶機。至於哪些功能會被暴露給客戶機系統,取決於客戶機的配置。qemu32 和 qemu64 是基本的客戶機 CPU 模型,但是還有其他的模型可以使用。你可以使用 qemu-kvm 命令的 -cpu 參數來指定客戶機的 CPU 模型,還可以附加指定的 CPU 特性。"-cpu" 會將該指定 CPU 模型的所有功能全部暴露給客戶機,即使某些特性在主機的物理CPU上不支持,這時候QEMU/KVM 會模擬這些特性,因此,這時候也許會出現一定的性能下降。 

RedHat Linux 6 上使用默認的 cpu64-rhe16 作爲客戶機 CPU model:

你可以指定特定的 CPU model 和 feature:

qemu-kvm -cpu Nehalem,+aes

 

你也可以直接使用 -cpu host,這樣的話會客戶機使用和主機相同的 CPU model。

2.4 客戶機 vCPU 數目的分配方法

  1. 不是客戶機的 vCPU 越多,其性能就越好,因爲線程切換會耗費大量的時間;應該根據負載需要分配最少的 vCPU。
  2. 主機上的客戶機的 vCPU 總數不應該超過物理 CPU 內核總數。不超過的話,就不存在 CPU 競爭,每個 vCPU 線程在一個物理 CPU 核上被執行;超過的話,會出現部分線程等待 CPU 以及一個 CPU 核上的線程之間的切換,這會有 overhead。
  3. 將負載分爲計算負載和 I/O 負載,對計算負載,需要分配較多的 vCPU,甚至考慮 CPU 親和性,將指定的物理 CPU 核分給給這些客戶機。

這篇文章 (http://my.oschina.net/chape/blog/173981) 介紹了一些指導性方法,摘要如下:

我們來假設一個主機有 2 個socket,每個 socket 有 4 個core。主頻2.4G MHZ 那麼一共可用的資源是 2*4*2.4G= 19.2G MHZ。假設主機上運行了三個VM,VM1和VM2設置爲1socket*1core,VM3設置爲1socket*2core。那麼VM1和VM2分別有1個vCPU,而VM3有2個vCPU。假設其他設置爲缺省設置。

那麼三個VM獲得該主機CPU資源分配如下:VM1:25%; VM2:25%; VM3:50%

 假設運行在VM3上的應用支持多線程,那麼該應用可以充分利用到所非配的CPU資源。2vCPU的設置是合適的。假設運行在VM3上的應用不支持多線程,該應用根本無法同時使用利用2個vCPU. 與此同時,VMkernal層的CPU Scheduler必須等待物理層中兩個空閒的pCPU,纔開始資源調配來滿足2個vCPU的需要。在僅有2vCPU的情況下,對該VM的性能不會有太大負面影響。但如果分配4vCPU或者更多,這種資源調度上的負擔有可能會對該VM上運行的應用有很大負面影響。

確定 vCPU 數目的步驟。假如我們要創建一個VM,以下幾步可以幫助確定合適的vCPU數目

1 瞭解應用並設置初始值

    該應用是否是關鍵應用,是否有Service Level Agreement。一定要對運行在虛擬機上的應用是否支持多線程深入瞭解。諮詢應用的提供商是否支持多線程和SMP(Symmetricmulti-processing)。參考該應用在物理服務器上運行時所需要的CPU個數。如果沒有參照信息,可設置1vCPU作爲初始值,然後密切觀測資源使用情況。

2 觀測資源使用情況

    確定一個時間段,觀測該虛擬機的資源使用情況。時間段取決於應用的特點和要求,可以是數天,甚至數週。不僅觀測該VM的CPU使用率,而且觀測在操作系統內該應用對CPU的佔用率。特別要區分CPU使用率平均值和CPU使用率峯值。

     假如分配有4個vCPU,如果在該VM上的應用的CPU

  • 使用峯值等於25%, 也就是僅僅能最多使用25%的全部CPU資源,說明該應用是單線程的,僅能夠使用一個vCPU (4 * 25% = 1 )
  • 平均值小於38%,而峯值小於45%,考慮減少 vCPU 數目
  • 平均值大於75%,而峯值大於90%,考慮增加 vCPU 數目

3 更改vCPU數目並觀測結果

每次的改動儘量少,如果可能需要4vCPU,先設置2vCPU在觀測性能是否可以接受。

2. KVM 內存虛擬化

2.1 內存虛擬化的概念

    除了 CPU 虛擬化,另一個關鍵是內存虛擬化,通過內存虛擬化共享物理系統內存,動態分配給虛擬機。虛擬機的內存虛擬化很象現在的操作系統支持的虛擬內存方式,應用程序看到鄰近的內存地址空間,這個地址空間無需和下面的物理機器內存直接對應,操作系統保持着虛擬頁到物理頁的映射。現在所有的 x86 CPU 都包括了一個稱爲內存管理的模塊MMU(Memory Management Unit)和 TLB(Translation Lookaside Buffer),通過MMU和TLB來優化虛擬內存的性能。
 
   KVM 實現客戶機內存的方式是,利用mmap系統調用,在QEMU主線程的虛擬地址空間中申明一段連續的大小的空間用於客戶機物理內存映射。
 
圖片來源 HVA 同下面的 MA,GPA 同下面的 PA,GVA 同下面的 VA)
 
在有兩個虛機的情況下,情形是這樣的:
 
 
可見,KVM 爲了在一臺機器上運行多個虛擬機,需要增加一個新的內存虛擬化層,也就是說,必須虛擬 MMU 來支持客戶操作系統,來實現 VA -> PA -> MA 的翻譯。客戶操作系統繼續控制虛擬地址到客戶內存物理地址的映射 (VA -> PA),但是客戶操作系統不能直接訪問實際機器內存,因此VMM 需要負責映射客戶物理內存到實際機器內存 (PA -> MA)。
 
VMM 內存虛擬化的實現方式:
  • 軟件方式:通過軟件實現內存地址的翻譯,比如 Shadow page table (影子頁表)技術
  • 硬件實現:基於 CPU 的輔助虛擬化功能,比如 AMD 的 NPT 和 Intel 的 EPT 技術 
影子頁表技術:
 
 
 

2.2 KVM 內存虛擬化

 KVM 中,虛機的物理內存即爲 qemu-kvm 進程所佔用的內存空間。KVM 使用 CPU 輔助的內存虛擬化方式。在 Intel 和 AMD 平臺,其內存虛擬化的實現方式分別爲:
  • AMD 平臺上的 NPT (Nested Page Tables) 技術
  • Intel 平臺上的 EPT (Extended Page Tables)技術

EPT 和 NPT採用類似的原理,都是作爲 CPU 中新的一層,用來將客戶機的物理地址翻譯爲主機的物理地址。關於 EPT, Intel 官方文檔中的技術如下(實在看不懂...)

EPT的好處是,它的兩階段記憶體轉換,特點就是將 Guest Physical Address → System Physical Address,VMM不用再保留一份 SPT (Shadow Page Table),以及以往還得經過 SPT 這個轉換過程。除了降低各部虛擬機器在切換時所造成的效能損耗外,硬體指令集也比虛擬化軟體處理來得可靠與穩定。

2.3 KSM (Kernel SamePage Merging 或者 Kernel Shared Memory)

KSM 在 Linux 2.6.32 版本中被加入到內核中。

2.3.1 原理

其原理是,KSM 作爲內核中的守護進程(稱爲 ksmd)存在,它定期執行頁面掃描,識別副本頁面併合並副本,釋放這些頁面以供它用。因此,在多個進程中,Linux將內核相似的內存頁合併成一個內存頁。這個特性,被KVM用來減少多個相似的虛擬機的內存佔用,提高內存的使用效率。由於內存是共享的,所以多個虛擬機使用的內存減少了。這個特性,對於虛擬機使用相同鏡像和操作系統時,效果更加明顯。但是,事情總是有代價的,使用這個特性,都要增加內核開銷,用時間換空間。所以爲了提高效率,可以將這個特性關閉。

2.3.2 好處

其好處是,在運行類似的客戶機操作系統時,通過 KSM,可以節約大量的內存,從而可以實現更多的內存超分,運行更多的虛機。 

2.3.3 合併過程

(1)初始狀態:

(2)合併後:

(3)Guest 1 寫內存後:

2.4  KVM Huge Page Backed Memory (巨頁內存技術)

這是KVM虛擬機的又一個優化技術.。Intel 的 x86 CPU 通常使用4Kb內存頁,當是經過配置,也能夠使用巨頁(huge page): (4MB on x86_32, 2MB on x86_64 and x86_32 PAE)

使用巨頁,KVM的虛擬機的頁表將使用更少的內存,並且將提高CPU的效率。最高情況下,可以提高20%的效率!

使用方法,需要三部:

mkdir /dev/hugepages
mount -t hugetlbfs hugetlbfs /dev/hugepages
#保留一些內存給巨頁
sysctl vm.nr_hugepages=2048 (使用 x86_64 系統時,這相當於從物理內存中保留了2048 x 2M = 4GB 的空間來給虛擬機使用)
#給 kvm 傳遞參數 hugepages
qemu-kvm - qemu-kvm -mem-path /dev/hugepages

也可以在配置文件里加入:



驗證方式,當虛擬機正常啓動以後,在物理機裏查看:

cat /proc/meminfo |grep -i hugepages

 老外的一篇文檔,他使用的是libvirt方式,先讓libvirtd進程使用hugepages空間,然後再分配給虛擬機。

 

參考資料:

http://www.cnblogs.com/xusongwei/archive/2012/07/30/2615592.html

https://www.ibm.com/developerworks/cn/linux/l-cn-vt/

http://www.slideshare.net/HwanjuKim/3cpu-virtualization-and-scheduling

http://www.cse.iitb.ac.in/~puru/courses/autumn12/cs695/classes/kvm-overview.pdf

http://www.linux-kvm.com/content/using-ksm-kernel-samepage-merging-kvm

http://blog.csdn.net/summer_liuwei/article/details/6013255

http://blog.pchome.net/article/458429.html

http://blog.chinaunix.net/uid-20794164-id-3601787.html

虛擬化技術性能比較和分析,周斌,張瑩

http://wiki.qemu.org/images/c/c8/Cpu-models-and-libvirt-devconf-2014.pdf

http://frankdenneman.nl/2011/01/11/beating-a-dead-horse-using-cpu-affinity/


KVM 介紹(3):I/O 全虛擬化和準虛擬化 [KVM I/O QEMU Full-Virtualizaiton Para-virtualization]

在 QEMU/KVM 中,客戶機可以使用的設備大致可分爲三類:
1. 模擬設備:完全由 QEMU 純軟件模擬的設備。
2. Virtio 設備:實現 VIRTIO API 的半虛擬化設備。
3. PCI 設備直接分配 (PCI device assignment) 。

1. 全虛擬化 I/O 設備

KVM 在 IO 虛擬化方面,傳統或者默認的方式是使用 QEMU 純軟件的方式來模擬 I/O 設備,包括鍵盤、鼠標、顯示器,硬盤 和 網卡 等。模擬設備可能會使用物理的設備,或者使用純軟件來模擬。模擬設備只存在於軟件中。 

1.1 原理

 
過程:
  1. 客戶機的設備驅動程序發起 I/O 請求操作請求
  2. KVM 模塊中的 I/O 操作捕獲代碼攔截這次 I/O 請求
  3. 經過處理後將本次 I/O 請求的信息放到 I/O 共享頁 (sharing page),並通知用戶空間的 QEMU 程序。
  4. QEMU 程序獲得 I/O 操作的具體信息之後,交由硬件模擬代碼來模擬出本次 I/O 操作。
  5. 完成之後,QEMU 將結果放回 I/O 共享頁,並通知 KMV 模塊中的 I/O 操作捕獲代碼。
  6. KVM 模塊的捕獲代碼讀取 I/O 共享頁中的操作結果,並把結果放回客戶機。 
注意:當客戶機通過DMA (Direct Memory Access)訪問大塊I/O時,QEMU 模擬程序將不會把結果放進共享頁中,而是通過內存映射的方式將結果直接寫到客戶機的內存中共,然後通知KVM模塊告訴客戶機DMA操作已經完成。
 
這種方式的優點是可以模擬出各種各樣的硬件設備;其缺點是每次 I/O 操作的路徑比較長,需要多次上下文切換,也需要多次數據複製,所以性能較差。 

1.2 QEMU 模擬網卡的實現

Qemu 純軟件的方式來模擬I/O設備,其中包括經常使用的網卡設備。Guest OS啓動命令中沒有傳入的網絡配置時,QEMU默認分配 rtl8139 類型的虛擬網卡類型,使用的是默認用戶配置模式,這時候由於沒有具體的網絡模式的配置,Guest的網絡功能是有限的。 全虛擬化情況下,KVM虛機可以選擇的網絡模式包括:

  1. 默認用戶模式(User);
  2. 基於網橋(Bridge)的模式;
  3. 基於NAT(Network Address Translation)的模式;

分別使用的 qemu-kvm 參數爲:

  • -net user[,vlan=n]:使用用戶模式網絡堆棧,這樣就不需要管理員權限來運行.如果沒有指 定-net選項,這將是默認的情況.-net tap[,vlan=n][,fd=h]
  • -net nic[,vlan=n][,macaddr=addr]:創建一個新的網卡並與VLAN n(在默認的情況下n=0)進行連接。作爲可選項的項目,MAC地址可以進行改變.如果 沒有指定-net選項,則會創建一個單一的NIC.
  • -net tap[,vlan=n][,fd=h][,ifname=name][,script=file]:將TAP網絡接口 name 與 VLAN n 進行連接,並使用網絡配置腳本文件進行 配置。默認的網絡配置腳本爲/etc/qemu-ifup。如果沒有指定name,OS 將會自動指定一個。fd=h可以用來指定一個已經打開的TAP主機接口的句柄。

網橋模式是目前比較簡單,也是用的比較多的模式,下圖是網橋模式下的 VM的收發包的流程。

如圖中所示,紅色箭頭表示數據報文的入方向,步驟:

  1. 網絡數據從 Host 上的物理網卡接收,到達網橋;
  2. 由於 eth0 與 tap1 均加入網橋中,根據二層轉發原則,br0 將數據從 tap1 口轉發出去,即數據由 Tap設備接收;
  3. Tap 設備通知對應的 fd 數據可讀;
  4. fd 的讀動作通過 tap 設備的字符設備驅動將數據拷貝到用戶空間,完成數據報文的前端接收。

(引用自 http://luoye.me/2014/07/17/netdev-virtual-1/

 1.3 RedHat Linux 6 中提供的模擬設備

  • 模擬顯卡:提供2塊模擬顯卡。
  • 系統組件:
    • ntel i440FX host PCI bridge
    • PIIX3 PCI to ISA bridge
    • PS/2 mouse and keyboard
    • EvTouch USB Graphics Tablet
    • PCI UHCI USB controller and a virtualized USB hub
    • Emulated serial ports
    • EHCI controller, virtualized USB storage and a USB mouse
  • 模擬的聲卡:intel-hda
  • 模擬網卡:e1000,模擬 Intel E1000 網卡;rtl8139,模擬 RealTeck 8139 網卡。
  • 模擬存儲卡:兩塊模擬 PCI IDE 接口卡。KVM  限制每個虛擬機最多隻能有4塊虛擬存儲卡。還有模擬軟驅。

注意:RedHat Linux KVM 不支持 SCSI 模擬。

在不顯式指定使用其它類型設備的情況下,KVM 虛機將使用這些默認的虛擬設備。比如上面描述的默認情況下 KVM 虛機默認使用rtl8139網卡。比如,在 RedHat Linxu 6.5 主機上啓動KVM RedHat Linux 6.4 虛機後,登錄虛機,查看 pci 設備,可以看到這些模擬設備:
 
當使用 “-net nic,model=e1000” 指定網卡model 爲 e1000 時,

1.4 qemu-kvm 關於磁盤設備和網絡的主要選項

類型 選項
磁盤設備(軟盤、硬盤、CDROM等)
-drive option[,option[,option[,...]]]:定義一個硬盤設備;可用子選項有很多。
    file=/path/to/somefile:硬件映像文件路徑;
    if=interface:指定硬盤設備所連接的接口類型,即控制器類型,如ide、scsi、sd、mtd、floppy、pflash及virtio等;
    index=index:設定同一種控制器類型中不同設備的索引號,即標識號;
    media=media:定義介質類型爲硬盤(disk)還是光盤(cdrom);  format=format:指定映像文件的格式,具體格式可參見qemu-img命令;
-boot [order=drives][,once=drives][,menu=on|off]:定義啓動設備的引導次序,每種設備使用一個字符表示;不同的架構所支持的設備及其表示字符不盡相同,在x86 PC架構上,a、b表示軟驅、c表示第一塊硬盤,d表示第一個光驅設備,n-p表示網絡適配器;默認爲硬盤設備(-boot order=dc,once=d)
網絡
-net nic[,vlan=n][,macaddr=mac][,model=type][,name=name][,addr=addr][,vectors=v]:創建一個新的網卡設備並連接至vlan n中;PC架構上默認的NIC爲e1000,macaddr用於爲其指定MAC地址,name用於指定一個在監控時顯示的網上設備名稱;emu可以模擬多個類型的網卡設備;可以使用“qemu-kvm -net nic,model=?”來獲取當前平臺支持的類型;
-net tap[,vlan=n][,name=name][,fd=h][,ifname=name][,script=file][,downscript=dfile]:通過物理機的TAP網絡接口連接至vlan n中,使用script=file指定的腳本(默認爲/etc/qemu-ifup)來配置當前網絡接口,並使用downscript=file指定的腳本(默認爲/etc/qemu-ifdown)來撤消接口配置;使用script=no和downscript=no可分別用來禁止執行腳本;
-net user[,option][,option][,...]:在用戶模式配置網絡棧,其不依賴於管理權限;有效選項有:
    vlan=n:連接至vlan n,默認n=0;
    name=name:指定接口的顯示名稱,常用於監控模式中;
    net=addr[/mask]:設定GuestOS可見的IP網絡,掩碼可選,默認爲10.0.2.0/8;
    host=addr:指定GuestOS中看到的物理機的IP地址,默認爲指定網絡中的第二個,即x.x.x.2;
    dhcpstart=addr:指定DHCP服務地址池中16個地址的起始IP,默認爲第16個至第31個,即x.x.x.16-x.x.x.31;
    dns=addr:指定GuestOS可見的dns服務器地址;默認爲GuestOS網絡中的第三個地址,即x.x.x.3;
    tftp=dir:激活內置的tftp服務器,並使用指定的dir作爲tftp服務器的默認根目錄;
    bootfile=file:BOOTP文件名稱,用於實現網絡引導GuestOS;如:qemu -hda linux.img -boot n -net user,tftp=/tftpserver/pub,bootfile=/pxelinux.0

 對於網卡來說,你可以使用 modle 參數指定虛擬網絡的類型。 RedHat Linux 6 所支持的虛擬網絡類型有:

[root@rh65 isoimages]# kvm -net nic,model=? qemu: Supported NIC models: ne2k_pci,i82551,i82557b,i82559er,rtl8139,e1000,pcnet,virtio 

2. 準虛擬化 (Para-virtualizaiton) I/O 驅動 virtio

在 KVM 中可以使用準虛擬化驅動來提供客戶機的I/O 性能。目前 KVM 採用的的是 virtio 這個 Linux 上的設備驅動標準框架,它提供了一種 Host 與 Guest 交互的 IO 框架。

2.1 virtio 的架構

 KVM/QEMU 的 vitio 實現採用在 Guest OS 內核中安裝前端驅動 (Front-end driver)和在 QEMU 中實現後端驅動(Back-end)的方式。前後端驅動通過 vring 直接通信,這就繞過了經過 KVM 內核模塊的過程,達到提高 I/O 性能的目的。
 
 
純軟件模擬的設備和 Virtio 設備的區別:virtio 省去了純模擬模式下的異常捕獲環節,Guest OS 可以和 QEMU 的 I/O 模塊直接通信。
 
 
使用 Virtio 的完整虛機 I/O流程:
 
 
Host 數據發到 Guest:
1. KVM 通過中斷的方式通知 QEMU 去獲取數據,放到 virtio queue 中
2. KVM 再通知 Guest 去 virtio queue 中取數據。

2.2 Virtio 在 Linux 中的實現

 Virtio 是在半虛擬化管理程序中的一組通用模擬設備的抽象。這種設計允許管理程序通過一個應用編程接口 (API)對外提供一組通用模擬設備。通過使用半虛擬化管理程序,客戶機實現一套通用的接口,來配合後面的一套後端設備模擬。後端驅動不必是通用的,只要它們實現了前端所需的行爲。因此,Virtio 是一個在 Hypervisor 之上的抽象API接口,讓客戶機知道自己運行在虛擬化環境中,進而根據 virtio 標準與 Hypervisor 協作,從而客戶機達到更好的性能。
  • 前端驅動:客戶機中安裝的驅動程序模塊
  • 後端驅動:在 QEMU 中實現,調用主機上的物理設備,或者完全由軟件實現。
  • virtio 層:虛擬隊列接口,從概念上連接前端驅動和後端驅動。驅動可以根據需要使用不同數目的隊列。比如 virtio-net 使用兩個隊列,virtio-block只使用一個隊列。該隊列是虛擬的,實際上是使用 virtio-ring 來實現的。
  • virtio-ring:實現虛擬隊列的環形緩衝區
 
Linux 內核中實現的五個前端驅動程序:
  • 塊設備(如磁盤)
  • 網絡設備
  • PCI 設備
  • 氣球驅動程序(動態管理客戶機內存使用情況)
  • 控制檯驅動程序
Guest OS 中,在不使用 virtio 設備的時候,這些驅動不會被加載。只有在使用某個 virtio 設備的時候,對應的驅動纔會被加載。每個前端驅動器具有在管理程序中的相應的後端的驅動程序。
 
以 virtio-net 爲例,解釋其原理:

(1)virtio-net 的原理:

它使得:
  1. 多個虛機共享主機網卡 eth0
  2. QEMU 使用標準的 tun/tap 將虛機的網絡橋接到主機網卡上
  3. 每個虛機看起來有一個直接連接到主機PCI總線上的私有 virtio 網絡設備
  4. 需要在虛機裏面安裝 virtio驅動

(2)virtio-net 的流程:

 
總結 Virtio 的優缺點:
  • 優點:更高的IO性能,幾乎可以和原生系統差不多。
  • 缺點:客戶機必須安裝特定的 virtio 驅動。一些老的 Linux 還沒有驅動支持,一些 Windows 需要安裝特定的驅動。不過,較新的和主流的OS都有驅動可以下載了。Linux 2.6.24+ 都默認支持 virtio。可以使用 lsmod | grep virtio 查看是否已經加載。

2.3 使用 virtio 設備 (以 virtio-net 爲例)

使用 virtio 類型的設備比較簡單。較新的 Linux 版本上都已經安裝好了 virtio 驅動,而 Windows 的驅動需要自己下載安裝。

(1)檢查主機上是否支持 virtio 類型的網卡設備

[root@rh65 isoimages]# kvm -net nic,model=? qemu: Supported NIC models: ne2k_pci,i82551,i82557b,i82559er,rtl8139,e1000,pcnet,virtio

(2)指定網卡設備model 爲 virtio,啓動虛機

(3)通過 vncviewer 登錄虛機,能看到被加載了的 virtio-net 需要的內核模塊

(4)查看 pci 設備

其它 virtio 類型的設備的使用方式類似 virtio-net。

2.4 vhost-net (kernel-level virtio server)

  前面提到 virtio 在宿主機中的後端處理程序(backend)一般是由用戶空間的QEMU提供的,然而如果對於網絡 I/O 請求的後端處理能夠在在內核空間來完成,則效率會更高,會提高網絡吞吐量和減少網絡延遲。在比較新的內核中有一個叫做 “vhost-net” 的驅動模塊,它是作爲一個內核級別的後端處理程序,將virtio-net的後端處理任務放到內核空間中執行,減少內核空間到用戶空間的切換,從而提高效率。

根據 KVM 官網的這篇文章,vhost-net 能提供更低的延遲(latency)(比 e1000 虛擬網卡低 10%),和更高的吞吐量(throughput)(8倍於普通 virtio,大概  7~8 Gigabits/sec )。

vhost-net 與 virtio-net 的比較:

vhost-net 的要求:

  • qemu-kvm-0.13.0 或者以上
  • 主機內核中設置 CONFIG_VHOST_NET=y 和在虛機操作系統內核中設置 CONFIG_PCI_MSI=y (Red Hat Enterprise Linux 6.1 開始支持該特性)
  • 在客戶機內使用 virtion-net 前段驅動
  • 在主機內使用網橋模式,並且啓動 vhost_net

qemu-kvm 命令的 -net tap 有幾個選項和 vhost-net 相關的: -net tap,[,vnet_hdr=on|off][,vhost=on|off][,vhostfd=h][,vhostforce=on|off]

  • vnet_hdr =on|off:設置是否打開TAP設備的“IFF_VNET_HDR”標識。“vnet_hdr=off”表示關閉這個標識;“vnet_hdr=on”則強制開啓這個標識,如果沒有這個標識的支持,則會觸發錯誤。IFF_VNET_HDR是tun/tap的一個標識,打開它則允許發送或接受大數據包時僅僅做部分的校驗和檢查。打開這個標識,可以提高virtio_net驅動的吞吐量。
  • vhost=on|off:設置是否開啓vhost-net這個內核空間的後端處理驅動,它只對使用MIS-X中斷方式的virtio客戶機有效。
  • vhostforce=on|off:設置是否強制使用 vhost 作爲非MSI-X中斷方式的Virtio客戶機的後端處理程序。
  • vhostfs=h:設置爲去連接一個已經打開的vhost網絡設備。

vhost-net 的使用實例:

(1)確保主機上 vhost-net 內核模塊被加載了

(2)啓動一個虛擬機,在客戶機中使用 -net 定義一個 virtio-net 網卡,在主機端使用 -netdev 啓動 vhost

(3)在虛擬機端,看到 virtio 網卡使用的 TAP 設備爲 tap0。

(4)在宿主機中看 vhost-net 被加載和使用了,以及 Linux 橋 br0,它連接物理網卡 eth1 和 客戶機使用的 TAP 設備 tap0


一般來說,使用 vhost-net 作爲後端處理驅動可以提高網絡的性能。不過,對於一些網絡負載類型使用 vhost-net 作爲後端,卻可能使其性能不升反降。特別是從宿主機到其中的客戶機之間的UDP流量,如果客戶機處理接受數據的速度比宿主機發送的速度要慢,這時就容易出現性能下降。在這種情況下,使用vhost-net將會是UDP socket的接受緩衝區更快地溢出,從而導致更多的數據包丟失。故這種情況下,不使用vhost-net,讓傳輸速度稍微慢一點,反而會提高整體的性能。

使用 qemu-kvm 命令行,加上“vhost=off”(或沒有vhost選項)就會不使用vhost-net,而在使用libvirt時,需要對客戶機的配置的XML文件中的網絡配置部分進行如下的配置,指定後端驅動的名稱爲“qemu”(而不是“vhost”)。

   

   


2.6 virtio-balloon

另一個比較特殊的 virtio 設備是 virtio-balloon。通常來說,要改變客戶機所佔用的宿主機內存,要先關閉客戶機,修改啓動時的內存配置,然後重啓客戶機纔可以實現。而 內存的 ballooning (氣球)技術可以在客戶機運行時動態地調整它所佔用的宿主機內存資源,而不需要關閉客戶機。該技術能夠:

  • 當宿主機內存緊張時,可以請求客戶機回收利用已分配給客戶機的部分內存,客戶機就會釋放部分空閒內存。若其內存空間不足,可能還會回收部分使用中的內存,可能會將部分內存換到交換分區中。
  • 當客戶機內存不足時,也可以讓客戶機的內存氣球壓縮,釋放出內存氣球中的部分內存,讓客戶機使用更多的內存。

目前很多的VMM,包括 KVM, Xen,VMware 等都對 ballooning 技術提供支持。其中,KVM 中的 Ballooning 是通過宿主機和客戶機協同來實現的,在宿主機中應該使用 2.6.27 及以上版本的 Linux內核(包括KVM模塊),使用較新的 qemu-kvm(如0.13版本以上),在客戶機中也使用 2.6.27 及以上內核且將“CONFIG_VIRTIO_BALLOON”配置爲模塊或編譯到內核。在很多Linux發行版中都已經配置有“CONFIG_VIRTIO_BALLOON=m”,所以用較新的Linux作爲客戶機系統,一般不需要額外配置virtio_balloon驅動,使用默認內核配置即可。
 
原理:
  1. KVM 發送請求給 VM 讓其歸還一定數量的內存給KVM。
  2. VM 的 virtio_balloon 驅動接到該請求。
  3. VM 的驅動是客戶機的內存氣球膨脹,氣球中的內存就不能被客戶機使用。
  4. VM 的操作系統歸還氣球中的內存給VMM
  5. KVM 可以將得到的內存分配到任何需要的地方。
  6. KM 也可以將內存返還到客戶機中。

優勢和不足:

 
優勢 不足
  1. ballooning 可以被控制和監控
  2. 對內存的調節很靈活,可多可少。
  3. KVM 可以歸還內存給客戶機,從而緩解其內存壓力。
  1. 需要客戶機安裝驅動
  2. 大量內存被回收時,會降低客戶機的性能。
  3. 目前沒有方便的自動化的機制來管理 ballooning,一般都在 QEMU 的 monitor 中執行命令來實現。
  4. 內存的動態增加或者減少,可能是內存被過度碎片化,從而降低內存使用性能。

在QEMU monitor中,提供了兩個命令查看和設置客戶機內存的大小。

  • (qemu) info balloon    #查看客戶機內存佔用量(Balloon信息)
  • (qemu) balloon num   #設置客戶機內存佔用量爲numMB
使用實例:

 (1)啓動一個虛機,內存爲 2048M,啓用 virtio-balloon

(2)通過 vncviewer 進入虛機,查看 pci 設備

(3)看看內存情況,共 2G 內存

(4)進入 QEMU Monitor,調整 balloon 內存爲 500M

(5)回到虛機,查看內存,變爲 500 M

 2.7 RedHat 的 多隊列 Virtio (multi-queue)

    目前的高端服務器都有多個處理器,虛擬使用的虛擬CPU數目也不斷增加。默認的 virtio-net 不能並行地傳送或者接收網絡包,因爲 virtio_net 只有一個TX 和 RX 隊列。而多隊列 virtio-net 提供了一個隨着虛機的虛擬CPU增加而增強網絡性能的方法,通過使得 virtio 可以同時使用多個 virt-queue 隊列。
 
它在以下情況下具有明顯優勢:
  1. 網絡流量非常大
  2. 虛機同時有非常多的網絡連接,包括虛擬機之間的、虛機到主機的、虛機到外部系統的等
  3. virtio 隊列的數目和虛機的虛擬CPU數目相同。這是因爲多隊列能夠使得一個隊列獨佔一個虛擬CPU。
注意:對隊列 virtio-net 對流入的網絡流工作得非常好,但是對外發的數據流偶爾會降低性能。打開對隊列 virtio 會增加中的吞吐量,這相應地會增加CPU的負擔。 在實際的生產環境中需要做必須的測試後才確定是否使用。
 
在 RedHat 中,要使用多隊列 virtio-net,在虛機的 XML 文件中增加如下配置:
然後在主機上運行下面的命令:
ethtool -L eth0 combined M ( 1 <= M <= N)

2.8 Windows 客戶機的 virtio 前端驅動

Windows 客戶機下的 virtio 前端驅動必須下載後手工安裝。 RedHat Linux 這篇文章 說明了在 Windows 客戶機內安裝virtio 驅動的方法。
 
參考文檔:
 

KVM 介紹(4):I/O 設備直接分配和 SR-IOV [KVM PCI/PCIe Pass-Through SR-IOV]

學習 KVM 的系列文章: 

本文將分析 PCI/PCIe 設備直接分配(Pass-through)和 SR-IOV, 以及三種 I/O 虛擬化方式的比較。

1. PCI/PCI-E 設備直接分配給虛機 (PCI Pass-through)

設備直接分配 (Device assignment)也稱爲 Device Pass-Through。


先簡單看看PCI 和 PCI-E 的區別(AMD CPU):

(簡單點看,PCI 卡的性能沒有 PCI-E 高,因爲 PCI-E 是直接連在 IOMMU 上,而 PCI 卡是連在一個 IO Hub 上。)

主要的 PCI 設備類型:

  • Network cards (wired or wireless)
  • SCSI adapters
  • Bus controllers: USB, PCMCIA, I2C, FireWire, IDE
  • Graphics and video cards
  • Sound cards

1.1 PCI/PCIe Pass-through 原理

這種方式,允許將宿主機中的物理 PCI 設備直接分配給客戶機使用。較新的x86平臺已經支持這種類型,Intel 定義的 I/O 虛擬化技術成爲 VT-d,AMD 的稱爲 AMD-V。KVM 支持客戶機以獨佔方式訪問這個宿主機的 PCI/PCI-E 設備。通過硬件支持的 VT-d 技術將設備分給客戶機後,在客戶機看來,設備是物理上連接在PCI或者PCI-E總線上的,客戶機對該設備的I/O交互操作和實際的物理設備操作完全一樣,不需要或者很少需要 KVM 的參與。運行在 VT-d 平臺上的 QEMU/KVM,可以分配網卡、磁盤控制器、USB控制器、VGA 顯卡等設備供客戶機直接使用。
 
幾乎所有的 PCI 和 PCI-E 設備都支持直接分配,除了顯卡以外(顯卡的特殊性在這裏)。PCI Pass-through 需要硬件平臺 Intel VT-d 或者 AMD IOMMU 的支持。這些特性必須在 BIOS 中被啓用。Red Hat Enterprise Linux 6.0 及以上版本支持熱插拔的 PCI 設備直接分配到虛擬機。
 
網卡直接分配:
 

硬盤直接分配:

  • 一般 SATA 或者 SAS 等類型的硬盤的控制器都是直接接入到 PCI 或者  PCI-E 總線的,所以也可以將硬盤作爲普通的PCI設備直接分配個客戶機。需要注意的是,當分配硬盤時,實際上將其控制器作爲一個整體分配到客戶機中,因此需要在硬件平臺上至少有另兩個或者多個SATA或者 SAS控制器。 

1.2 在 RedHat Linux 6 上使用 virt-manger 分配一個光纖卡給虛機

準備工作:

(1)在 BIOS 中打開 Intel VT-d 

(2)在 Linux 內核中啓用 PCI Pass-through

添加 intel_iommu=on 到 /boot/grub/grub.conf 文件中。(在我的 RedHat Linux 6上,該文件是 /boot/grub.conf)

(3)重啓系統,使得配置生效

實際分配:

(1)使用 lspci -nn 命令找到待分配的 PCI 設備。這裏以一個 FC 卡爲例:

使用 lspci 命令得到的 PCI 數字的含義,以後使用 libvirt API 分配設備時會用到:

(2)使用 virsh nodedev-list 命令找到該設備的 PCI 編號

(3)將設備從主機上解除

(4)使用 virt-manager 將設備直接分配給一個啓動了的虛擬機

 

(5)添加好了後的效果

(6)在虛機中查看該PCI設備

(7)不再使用的話,需要在 virt-manager 中首先將該設備移除,然後在主機上重新掛載該設備

  

1.3 在 RedHat Linux 6 上使用 qemu-kvm 分配一個光纖卡給虛機

除了步驟(4),其他步驟同上面。


1.4 設備直接分配讓客戶機的優勢和不足

  • 好處:在執行 I/O 操作時大量減少甚至避免 VM-Exit 陷入到 Hypervisor 中,極大地提高了性能,可以達到幾乎和原生系統一樣的性能。VT-d 克服了 virtio 兼容性不好和 CPU 使用頻率較高的問題。
  • 不足:(1)一臺服務器主板上的空間比較有限,因此允許添加的 PCI 和 PCI-E 設備是有限的。大量使用 VT-d 獨立分配設備給客戶機,讓硬件設備數量增加,這會增加硬件投資成本。(2)對於使用 VT-d 直接分配了設備的客戶機,其動態遷移功能將受限,不過也可以使用熱插拔或者libvirt 工具等方式來緩解這個問題。
  • 不足的解決方案:(1)在一臺物理宿主機上,僅少數 I/O 如網絡性能要求較高的客戶機使用 VT-d直接分配設備,其他的使用純模擬或者 virtio 已達到多個客戶機共享同一個設備的目的 (2)對於網絡I/O的解決辦法,可以選擇 SR-IOV 是一個網卡產生多個獨立的虛擬網卡,將每個虛擬網卡分配個一個客戶機使用。

2. SR-IOV 設備分配

2.1 原理

    VT-d 的性能非常好,但是它的物理設備只能分配給一個客戶機使用。爲了實現多個虛機共享一個物理設備,並且達到直接分配的目的,PCI-SIG 組織發佈了 SR-IOV (Single Root I/O Virtualization and sharing) 規範,它定義了一個標準化的機制用以原生地支持實現多個客戶機共享一個設備。不過,目前 SR-IOV (單根 I/O 虛擬化)最廣泛地應用還是網卡上。 
 
SR-IOV 使得一個單一的功能單元(比如,一個以太網端口)能看起來像多個獨立的物理設備。一個帶有 SR-IOV 功能的物理設備能被配置爲多個功能單元。SR-IOV 使用兩種功能(function):
  • 物理功能(Physical Functions,PF):這是完整的帶有 SR-IOV 能力的PCIe 設備。PF 能像普通 PCI 設備那樣被發現、管理和配置。
  • 虛擬功能(Virtual Functions,VF):簡單的 PCIe 功能,它只能處理I/O。每個 VF 都是從 PF 中分離出來的。每個物理硬件都有一個 VF 數目的限制。一個 PF,能被虛擬成多個 VF 用於分配給多個虛擬機。
 
Hypervisor 能將一個或者多個 VF 分配給一個虛機。在某一時刻,一個 VF 只能被分配給一個虛機。一個虛機可以擁有多個 VF。在虛機的操作系統看來,一個 VF 網卡看起來和一個普通網卡沒有區別。SR-IOV 驅動是在內核中實現的。
 
網卡 SR-IOV 的例子:

 

光纖卡 SR-IOV 的例子:

2.2 SR-IOV 的條件

  1. 需要 CPU 支持 Intel VT-x 和 VT-D (或者 AMD 的 SVM 和 IOMMU) 
  2. 需要有支持 SR-IOV 規範的設備:目前這種設備較多,比如Intel的很多中高端網卡等。
  3. 需要 QEMU/KAM 的支持。
 RedHat Linux 6.0 官方只完整測試了下面的幾款 SR-IOV 網卡:
  • Intel? 82576NS Gigabit Ethernet Controller ( igb 驅動)
  • Intel? 82576EB Gigabit Ethernet Controller ( igb 驅動)
  • Intel? 82599ES 10 Gigabit Ethernet Controller ( ixgbe 驅動)
  • Intel? 82599EB 10 Gigabit Ethernet Controller ( ixgbe 驅動)

2.3 分配 SR-IOV 設備的步驟

手頭沒有支持SR-IOV的設備。這是 RedHat 上 SR-IOV 的配置步驟: Using SR-IOV

簡單來說,SR-IOV 分配步驟和設備直接分配相比基本類似,除了要使 PF 虛擬化成多個 VF 以外。

2.4 優勢和不足

優勢 不足
  1. 真正實現設備共享 (多個客戶機共享一個 SR-IOV 設備的物理端口)
  2. 接近原生性能
  3. 相比 VT-d, SR-IOV 可以使用更少的設備來支持更多的客戶機,可以提高數據中心的空間利用率。
  1. 對設備有依賴,目前只有部分設備支持 SR-IOV。RedHat Linux 只是測試了 Intel 的幾款高端網卡。
  2. 使用 SR-IOV 時不方便動態遷移客戶機。 這是因爲這時候虛機直接使用主機上的物理設備,因此虛機的遷移(migiration)和保存(save)目前都不支持。這個在將來有可能被改變。

3. 各種設備虛擬化方式的比較

3.1 架構上的比較(以網卡爲例)

3.2 性能上的比較 (以網卡爲例)

純模擬網卡和物理網卡的比較:

(來源:Evaluating and Optimizing I/O Virtualization in Kernel-based Virtual Machine (KVM), Binbin Zhang, Xiaolin Wang, Rongfeng Lai, Liang Yang, Zhenlin Wang,Yingwei Luo, Xiaoming Li)

(測試環境:兩臺物理服務器 HostA 和 HostB,都使用GB以太網。HostA 使用 82566DC 網卡,HostB 使用 82567LM-2 網卡,一臺虛機運行在 HostB 上,使用 KVM-76.)

結論:

  • 純模擬網卡的性能只有物理網卡的四成到六成
  • 純模擬網卡的 UDP 性能比 TCP 性能高 50% 到 100%
  • 在虛擬網卡上使用 NAPI,不但不會提高性能,反而會是性能下降
  • e1000 的性能比 rt18139 的性能高不少(爲什麼 RedHat Linux KVM 上默認的網卡是 rt18139 呢?)

Virtio 和 vhost_net 的吞吐量比較:

  • 來源:CANONICAL, KVM Performance Optimization, Paul Sim,Cloud Consultant, [email protected]
  • 結論: vhost_net 比 virtio 的 UDP 和 TCP 性能高 20% 左右。

RedHat Linux 6 上 virtio,vhost_net,SR-IOV 和物理設備網絡延遲的比較:

(來源:RedHat 官網

RedHat Linux 6 上 virtio 和 vhost_net 所消耗的主機CPU資源的比較:

(來源同上)

使用 virtio 的 KVM 與物理機的 TCP 吞吐量對比:

(數據來源:RedHat 官網

物理機與使用 SR-IOV 的 KVM 的網絡性能對比:

(來源:同上)

物理機與使用 Pass-through 的KVM 的 TCP 性能對比:


(資料來源:Open Source Virtualization: KVM and Linux, Chris Wright, Principal Software Engineer, Red Hat,September 4, 2009)

3.3  Virtio 和 Pass-Through 的詳細比較


(來源:Reconnaissance of Virtio: What’s new and how it’s all connected? by Mario Smarduch)

 4. 綜合結論

KVM 依賴的Intel/AMD 處理器的各種虛擬化擴展:

處理器 CPU 虛擬化 內存虛擬化 PCI Pass-through
Intel VT-x VPID,EPT VT-d
AMD AMD-V ASID,NPT IOMMU


I/O 虛擬化方案的選擇:

  • I/O設備儘量使用準虛擬化(virtio 和 vhost_net)
  • 如果需要實時遷移,不能使用 SR-IOV
  • 對更高I/O要求又不需要實時遷移的,可以使用 SR-IOV
  • 每種方案都有優勢和不足,在特定環境下其性能有可能反而下降,因此在生產環境中使用各種虛擬化方式前需要經過完整測試


其它參考資料:

分類: KVM虛擬化

KVM 介紹(5):libvirt 介紹 [ Libvrit for KVM/QEMU ]

 學習 KVM 的系列文章: 

1. Libvirt 是什麼

爲什麼需要Libvirt?

  1. Hypervisor 比如 qemu-kvm 的命令行虛擬機管理工具參數衆多,難於使用。
  2. Hypervisor 種類衆多,沒有統一的編程接口來管理它們,這對雲環境來說非常重要。
  3. 沒有統一的方式來方便地定義虛擬機相關的各種可管理對象。

Libvirt提供了什麼?

  1. 它提供統一、穩定、開放的源代碼的應用程序接口(API)、守護進程 (libvirtd)和和一個默認命令行管理工具(virsh)。
  2. 它提供了對虛擬化客戶機和它的虛擬化設備、網絡和存儲的管理。
  3. 它提供了一套較爲穩定的C語言應用程序接口。目前,在其他一些流行的編程語言中也提供了對libvirt的綁定,在Python、Perl、Java、Ruby、PHP、OCaml等高級編程語言中已經有libvirt的程序庫可以直接使用。
  4. 它對多種不同的 Hypervisor 的支持是通過一種基於驅動程序的架構來實現的。libvirt 對不同的 Hypervisor 提供了不同的驅動,包括 Xen 的驅動,對QEMU/KVM 有 QEMU 驅動,VMware 驅動等。在 libvirt 源代碼中,可以很容易找到 qemu_driver.c、xen_driver.c、xenapi_driver.c、vmware_driver.c、vbox_driver.c 這樣的驅動程序源代碼文件。
  5. 它作爲中間適配層,讓底層 Hypervisor 對上層用戶空間的管理工具是可以做到完全透明的,因爲 libvirt 屏蔽了底層各種 Hypervisor 的細節,爲上層管理工具提供了一個統一的、較穩定的接口(API)。
  6. 它使用 XML 來定義各種虛擬機相關的受管理對象。

     目前,libvirt 已經成爲使用最爲廣泛的對各種虛擬機進行管理的工具和應用程序接口(API),而且一些常用的虛擬機管理工具(如virsh、virt-install、virt-manager等)和雲計算框架平臺(如OpenStack、OpenNebula、Eucalyptus等)都在底層使用libvirt的應用程序接口。

                           (SLE 11)

1.1 Libvirt C API

1.1.1 Libvirti API 所管理的主要對象

 
對象 解釋
Domain (域) 指運行在由Hypervisor提供的虛擬機器上的一個操作系統實例(常常是指一個虛擬機)或者用來啓動虛機的配置。
Hypervisor 一個虛擬化主機的軟件層
Node (主機) 一臺物理服務器。
Storage pool (存儲池) 一組存儲媒介的集合,比如物理硬盤驅動器。一個存儲池被劃分爲小的容器稱作卷。卷會被分給一個或者多個虛機。
Volume (卷) 一個從存儲池分配的存儲空間。一個卷會被分給一個或者多個域,常常成爲域裏的虛擬硬盤。
 

1.1.2 對象的管理模型

對象名稱 對象 Python 類 描述
Connect 與 Hypervisor的連接
virConnectPtr
在調用任何 API 去管理一個本地或者遠端的Hypervisor前,必須建立和這個Hypervisor的連接。
Domain Guest domain
virDomainPtr
用於列舉和管理已有的虛機,或者創建新的虛機。唯一標識:ID,Name,UUID。一個域可能是暫時性的或者持久性的。暫時性的域只能在它運行期間被管理。持久性的域在主機上保存了它的配置。
Virtual Network 虛擬網絡
virNetworkPtr
用於管理虛機的網絡設備。唯一標識:Name,UUID。一個虛擬網絡可能是暫時性的或者持久性的。每個主機上安裝libvirt後,它都有一個默認的網絡設備“default”。它向該主機上運行的虛機提供DHCP服務,以及通過NAT連接到主機上。
Storage Pool 存儲池
virStoragePoolPtr
用於管理虛擬機內的所有存儲,包括 local disk, logical volume group, iSCSI target, FibreChannel HBA and local/network file system。唯一標識:Name,UUID。一個存儲池可能是暫時性的或者持久性的。Pool 的 type 可以是  dir, fs, netfs, disk, iscsi, logical, scsi,mpath, rbd, sheepdog, gluster 或者 zfs。
Storage Volume   存儲卷
virStorageVolPtr
用於管理一個存儲池內的存儲塊,包括一個池內分配的塊、磁盤分區、邏輯卷、SCSI/iSCSI Lun,或者一個本地或者網絡文件系統內的文件等。唯一標識:Name,Key,Path。
Host device  主機設備
virNodeDevPtr
用於管理主機上的物理硬件設備,包括 the physical USB or PCI devices and logical devices these provide, such as a NIC, disk, disk
controller, sound card, etc。唯一標識:Name。
 

1.1.3 API 的簡單分類

Libvirt API 就是對各種對象的各種操作,包括基本的增、刪、改、查操作和其它操作。

對象 其它
Connect virConnectOpen
virConnectOpenAuth
virConnectOpenReadOnly
virConnectClose virConnectSetKeepAlive
Strorage pool virStoragePoolBuild
virStoragePoolCreate
virStoragePoolCreateXML
virStoragePoolDefineXML
virStoragePoolDelete
virStoragePoolDestroy
virStoragePoolFree
virStoragePoolUndefine
virStoragePoolRefresh
virStoragePoolSetAutostart
virConnectFindStoragePoolSources
virConnectListAllStoragePools
virConnectListDefinedStoragePools
virConnectListStoragePools
virConnectNumOfDefinedStoragePools
virConnectNumOfStoragePools

virStoragePoolGetInfo/Name/UUID/UUIDString/XMLDesc
 virStoragePoolIsActive/Persistent
virStoragePoolLookupByName/UUID/UUIDString/Volume
virStoragePoolRef
virStoragePoolGetAutostart
virStoragePoolGetConnect

virStoragePoolNumOfVolumes
virStoragePoolListAllVolumes
virStoragePoolListVolumes
Storage volume virStorageVolCreateXML
virStorageVolCreateXMLFrom
virStorageVolDelete
virStorageVolFree
virStorageVolResize
virStorageVolUpload
virStorageVolWipe
virStorageVolWipePattern
virStorageVolGetConnect/Info/Key/Name/Path/XMLDesc
 virStorageVolLookupByKey/Name/Path
virStorageVolRef
virStorageVolDownload
Network virNetworkCreate
virNetworkCreateXML
virNetworkDefineXML
virNetworkDestroy
virNetworkFree
virNetworkUndefine
virNetworkSetAutostart
virNetworkUpdate
virConnectListAllNetworks
virConnectListDefinedNetworks
virConnectListNetworks
virConnectNumOfDefinedNetworks
virConnectNumOfNetworks

virNetworkGetBridgeName/DHCPLeases/Name/UUID/UUIDString/XMLDesc
virNetworkIsActive/Persistent
 virNetworkLookupByName/UUID/UUIDString
virNetworkGetAutostart
virNetworkGetConnect
virConnectNetworkEventDeregisterAny
virConnectNetworkEventGenericCallback
virNetworkDHCPLeaseFree
Domain snapshot virDomainSnapshotCreateXML virDomainSnapshotDelete
virDomainSnapshotFree
virDomainRevertToSnapshot virDomainHasCurrentSnapshot
virDomainListAllSnapshots
virDomainSnapshotCurrent
virDomainSnapshotGetConnect/Domain/Name/Parent/XMLDesc
virDomainSnapshotHasMetadata
virDomainSnapshotIsCurrent
virDomainSnapshotListAllChildren
virDomainSnapshotListChildrenNames
virDomainSnapshotListNames
virDomainSnapshotLookupByName
virDomainSnapshotNum
virDomainSnapshotNumChildren
virDomainSnapshotRef
Host
virInitialize
virNodeSetMemoryParameters
virNodeSuspendForDuration
virConnectBaselineCPU
virConnectCompareCPU
virConnectGetCPUModelNames/Capabilities/Hostname/LibVersion/MaxVcpus/Sysinfo/Type/URI/Version
virConnectIsAlive
virConnectIsEncrypted
virConnectIsSecure
virGetVersion
virNodeGetCPUMap/CPUStats /CellsFreeMemory/FreeMemory/Info/MemoryParameters/MemoryStats /
 virNodeGetSecurityModel
virTypedParamsAddBoolean
virTypedParamsAddDouble
virTypedParamsAddFromString
virTypedParamsAddInt
virTypedParamsAddLLong
virTypedParamsAddString
virTypedParamsAddUInt
virTypedParamsAddULLong
virTypedParamsClear
virTypedParamsFree
virTypedParamsGet

Interface virInterfaceCreate
virInterfaceDefineXML
virInterfaceDestroy
virInterfaceFree
virInterfaceUndefine
virInterfaceChangeBegin
virInterfaceChangeCommit
virInterfaceChangeRollback
virConnectListAllInterfaces
virConnectListDefinedInterfaces
virConnectListInterfaces
virConnectNumOfDefinedInterfaces
virConnectNumOfInterfaces
virInterfaceGetConnect
virInterfaceGetMACString
virInterfaceGetName
virInterfaceGetXMLDesc
virInterfaceIsActive
virInterfaceLookupByMACString
virInterfaceLookupByName
virInterfaceRef
Net Filter virNWFilterDefineXML
virNWFilterFree
virNWFilterUndefine
virConnectListAllNWFilters
virConnectListNWFilters
virConnectNumOfNWFilters
virNWFilterGetName
virNWFilterGetUUID
virNWFilterGetUUIDString
virNWFilterGetXMLDesc
virNWFilterLookupByName
virNWFilterLookupByUUID
virNWFilterLookupByUUIDString
virNWFilterRef
Domain Event virConnectDomainEventDeregister
virConnectDomainEventDeregisterAny
virConnectDomainEventDeviceAddedCallback
virConnectDomainEventDeviceRemovedCallback
virConnectDomainEventAgentLifecycleCallback
virConnectDomainEventBalloonChangeCallback
virConnectDomainEventBlockJobCallback
virConnectDomainEventCallback
virConnectDomainEventDiskChangeCallback
virConnectDomainEventGenericCallback
virConnectDomainEventGraphicsCallback
virConnectDomainEventIOErrorCallback
virConnectDomainEventIOErrorReasonCallback
virConnectDomainEventPMSuspendCallback
virConnectDomainEventPMSuspendDiskCallback
Domain virDomainCreate
virDomainCreateLinux
virDomainCreateWithFiles
virDomainCreateWithFlags
virDomainCreateXML
virDomainCreateXMLWithFiles
virDomainDefineXML
virDomainDefineXMLFlags
virDomainDestroy
virDomainDestroyFlags

virDomainFree
virDomainUndefine
virDomainUndefineFlags
virDomainUpdateDeviceFlags
virDomainAbortJob
virDomainAddIOThread
virDomainDelIOThread
virDomainAttachDevice
virDomainAttachDeviceFlags
virDomainDetachDevice
virDomainDetachDeviceFlags

virDomainBlockCommit
virDomainBlockCopy
virDomainBlockJobAbort
virDomainBlockJobSetSpeed
virDomainBlockPeek
virDomainBlockPull
virDomainBlockRebase
virDomainBlockResize

virDomainCoreDump
virDomainCoreDumpWithFormat

virDomainFSFreeze
virDomainFSInfoFree
virDomainFSThaw
virDomainFSTrim
virDomainInjectNMI

virDomainInterfaceFree

virDomainManagedSave
virDomainManagedSaveRemove

virDomainMigrate
virDomainMigrate2
virDomainMigrate3

virDomainMigrateSetCompressionCache
virDomainMigrateSetMaxDowntime
virDomainMigrateSetMaxSpeed
virDomainMigrateToURI
virDomainMigrateToURI2
virDomainMigrateToURI3

virDomainOpenChannel
virDomainOpenConsole
virDomainOpenGraphics
virDomainOpenGraphicsFD
virDomainPMSuspendForDuration

virDomainPMWakeup

virDomainReboot
virDomainReset
virDomainRestore
virDomainRestoreFlags
virDomainResume
virDomainSave
virDomainSaveFlags
virDomainSaveImageDefineXML
virDomainScreenshot
virDomainSendKey
virDomainSendProcessSignal
virDomainShutdown
virDomainShutdownFlags
virDomainSuspend
virDomainSetAutostart
virDomainSetBlkioParameters
virDomainSetBlockIoTune
virDomainSetInterfaceParameters
virDomainSetMaxMemory
virDomainSetMemory
virDomainSetMemoryFlags
virDomainSetMemoryParameters
virDomainSetMemoryStatsPeriod
virDomainSetMetadata
virDomainSetNumaParameters
virDomainSetSchedulerParameters
virDomainSetSchedulerParametersFlags
virDomainSetTime
virDomainSetUserPassword
virDomainSetVcpus
virDomainSetVcpusFlags
virConnectGetAllDomainStats
virConnectGetDomainCapabilities
virConnectListAllDomains
virConnectListDefinedDomains
virConnectListDomains
virConnectNumOfDefinedDomains
virConnectNumOfDomains

virDomainBlockStats
virDomainBlockStatsFlags

virDomainGetAutostart
virDomainGetBlkioParameters
virDomainGetBlockInfo
virDomainGetBlockIoTune
virDomainGetBlockJobInfo
virDomainGetCPUStats
virDomainGetConnect
virDomainGetControlInfo
virDomainGetDiskErrors
virDomainGetEmulatorPinInfo

virDomainGetFSInfo
virDomainGetHostname
virDomainGetID
virDomainGetIOThreadInfo
virDomainGetInfo

virDomainGetInterfaceParameters
virDomainGetJobInfo
virDomainGetJobStats
virDomainGetMaxMemory
virDomainGetMaxVcpus
virDomainGetMemoryParameters
virDomainGetMetadata
virDomainGetName

virDomainGetNumaParameters
virDomainGetOSType
virDomainGetSchedulerParameters
virDomainGetSchedulerParametersFlags
virDomainGetSchedulerType

virDomainGetSecurityLabel
virDomainGetSecurityLabelList
virDomainGetState
virDomainGetTime
virDomainGetUUID
virDomainGetUUIDString
virDomainGetVcpuPinInfo
virDomainGetVcpus
virDomainGetVcpusFlags

virDomainGetXMLDesc
virDomainHasManagedSaveImage
virDomainIOThreadInfoFree

virDomainInterfaceAddresses
virDomainInterfaceStats

virDomainIsActive
virDomainIsPersistent
virDomainIsUpdated
virDomainListGetStats

virDomainLookupByID
virDomainLookupByName
virDomainLookupByUUID
virDomainLookupByUUIDString

virDomainMemoryPeek
virDomainMemoryStats

virDomainMigrateGetCompressionCache
virDomainMigrateGetMaxSpeed

virDomainPinEmulator ?
virDomainPinIOThread
virDomainPinVcpu
virDomainPinVcpuFlags

virDomainSaveImageGetXMLDesc
virDomainStatsRecordListFree
virConnectDomainXMLFromNative
virConnectDomainXMLToNative
Secret virSecretDefineXML virSecretFree
virSecretUndefine
virSecretSetValue virConnectListAllSecrets
virConnectListSecrets
virConnectNumOfSecrets
virSecretGetConnect/UUID/UUIDString/UsageID/UsageType/Value/XMLDesc
virSecretLookupByUUID/UUIDString/Usage
virSecretRef
Stream virStreamNew virStreamFree virStreamFinish
virStreamAbort
virStreamRecv
virStreamRecvAll
virStreamSend
virStreamSendAll
virStreamSinkFunc
virStreamSourceFunc

1.2 Libvirt XML 定義

Libvirt 使用 XML 來定義各種對象,其中,與 OpenStack Nova 關係比較密切的有:

disk (磁盤) 任何磁盤設備,包括軟盤(floppy)、硬盤(hard disk)、光驅(cdrom)或者半虛擬化驅動都使用 元素來定義。 方式:
。其中:
  • ”type“ 用來指定device source 的類型:"file", "block", "dir", "network", 或者 "volume"。具體的 source  由 標籤定義。
  • ”device“ 用來指定 device target 的類型:"floppy", "disk", "cdrom", and "lun", 默認爲 "disk" 。具體的 target 由 標籤定義。

(1)”volume“ 類型的 disk
    'volume' device='disk'>
      'qemu' type='raw'/>
      'blk-pool0' volume='blk-pool0-vol0'/>
      'hdk' bus='ide'/>
    
(2)”file“ 類型的 disk
    'file' snapshot='external'>
      "tap" type="aio" cache="default"/>
      '/var/lib/xen/images/fv0' startupPolicy='optional' />
      'hda' bus='ide'/>
    
(3)”block“ 類型的 disk
   'block' device='cdrom'>
      'qemu' type='raw'/>
      'hdd' bus='ide' tray='open'/>
      <readonly/>
    
(4)”network“ 類型的 disk
複製代碼
 'network' device='cdrom'>
      'qemu' type='raw'/>
      "http" name="url_path">
        "hostname" port="80"/>
      
      'hde' bus='ide' tray='open'/>
      <readonly/>
     
複製代碼
 
Host device assignment (主機設備分配)
複製代碼
   'subsystem' type='usb'> #USB 設備直接分配 'optional'>
        '0x1234'/>
        '0xbeef'/>
      
      '2'/>
    
    'subsystem' type='pci' managed='yes'> #PCI 設備直接分配 
        
												
													'0x0000' bus='0x06' slot='0x02' function='0x0'/>
      
      '1'/>
      'on' file='/etc/fake/boot.bin'/>
    
												
複製代碼
 
Network interface (網卡)
有幾種 interface 類型:
(1)type = ‘network’ 定義一個連接 Virtual network 的 interface
複製代碼
    <interface type='network'>
      'default'/> #虛擬網絡的名稱爲 ‘default’ interface> ... <interface type='network'>
      'default' portgroup='engineering'/>
      'vnet7'/>
      "00:11:22:33:44:55"/>
      
        '09b11c53-8b5c-4eeb-8f00-d84eaa0aaa4f'/>
      

    interface>
  
複製代碼
#virsh:attach-interface --domain d-2 --type network --source isolatednet1 --mac 52:53:00:4b:75:6f --config
(2)type=‘birdge’ 定義一個 Bridge to LAN(橋接到物理網絡)的interface:前提是主機上存在一個 bridge,該 bridge 已經連到物理LAN。
複製代碼
    <interface type='bridge'> #連接到 br0 'br0'/>
    interface>
    <interface type='bridge'> #連接到br1 'br1'/>
      'vnet7'/>
      "00:11:22:33:44:55"/>
    interface>
    <interface type='bridge'> #連接到 Open vSwithc bridge ovsbr 'ovsbr'/>
      'openvswitch'>
        'menial' interfaceid='09b11c53-8b5c-4eeb-8f00-d84eaa0aaa4f'/>
      
    interface>
複製代碼
#virsh:attach-interface --domain d-2 --type bridge --source virbr0 --mac 52:22:33:44:55:66 --config
(3)type=‘ethernet’ 定義一個使用指定腳本連接到 LAN 的 interface
    <interface type='ethernet'>
      'vnet7'/>
'/etc/qemu-ifup-mynet'/>
    interface>
  
(4)type=‘direct’ 定義一個直接連到物理網卡(Direct attachment to physical interface)的 interface:需要 Linux macvtap 驅動支持
    <interface type='direct' trustGuestRxFilters='no'>
      'eth0' mode='vepa'/>
    interface>
(5)type=‘hostdev’ 定義一個由主機PCI 網卡直接分配(PCI Passthrough)的 interface: 分配主機上的網卡給虛機
複製代碼
    <interface type='hostdev' managed='yes'>
      'vfio'/>
      
        
'pci' domain='0x0000' bus='0x00' slot='0x07' function='0x0'/> '52:54:00:6d:90:02'/> '802.1Qbh'> 'finance'/> interface>
複製代碼
 
network (網絡)


1. bridge:定義一個用於構造該虛擬網絡的網橋。 

2. domain:定義 DHCP server 的 DNS domain。

3. forward: 定義虛擬網絡直接連到物理 LAN 的方式. ”mode“指轉發模式。

 (1) mode=‘nat’:所有連接到該虛擬網絡的虛擬的網絡都會經過物理機器的網卡,並轉換成物理網卡的地址。

複製代碼
        default
        "virbr0" />
        "nat"/>
        "192.168.122.1" netmask="255.255.255.0">
          
            "192.168.122.2" end="192.168.122.254" />
          
        
        "ipv6" address="2001:db8:ca2:2::1" prefix="64" />
      
複製代碼

也可以指定公共的IP地址和端口號。

'nat'>
'1.2.3.4' end='1.2.3.10'/> 'nat'>'500' end='1000'/>
(2) mode=‘route’:類似於 NAT,但是不使用NAT,而是使用routing table。  
複製代碼
      
        local
        "virbr1" />
        "route" dev="eth1"/>
        "192.168.122.1" netmask="255.255.255.0">
          
            "192.168.122.2" end="192.168.122.254" />
          
        
        "ipv6" address="2001:db8:ca2:2::1" prefix="64" />
      
複製代碼
(3) mode=‘bridge’:使用不受libvirt管理的bridge,比如主機上已有的bridge;open vswitch bridge;使用 macvtap's "bridge"  模式
      
        host-bridge
        "bridge"/>
        "br0"/>
      
(4) mode=‘passthrough’:使用 a macvtap "direct" connection in "passthrough" mode 指定主機上的特定網卡用於虛擬網絡   
複製代碼
 'passthrough'>
    <interface dev='eth10'/>
    <interface dev='eth11'/>
    <interface dev='eth12'/>
    <interface dev='eth13'/>
    <interface dev='eth14'/>
  
複製代碼
(5) mode=‘hostdev’:直接分配主機上的網絡設備。
'hostdev' managed='yes'>
    'vfio'/>
    
'pci' domain='0' bus='4' slot='0' function='1'/>
'pci' domain='0' bus='4' slot='0' function='2'/>
'pci' domain='0' bus='4' slot='0' function='3'/>
 

詳細的 XML 定義說明在 https://libvirt.org/format.html

1.3 Libvirt API 的實現

libvirt API 的實現是在各個 Hypervisor driver 和 Storage dirver 內。Hypervisor 驅動包括:

1.4 Libvirt 的 Python 綁定

python-libvirt 包含 Libvirt 的 Python 語言綁定。安裝 libvirt 時,默認會安裝 python-libvirt 。 來源: https://libvirt.org/python.html  https://pypi.python.org/pypi/libvirt-python 
Python API 和 C API 之間幾乎是一對一的映射關係,比如:
複製代碼
#C API int virConnectNumOfDomains (virConnectPtr conn); int virDomainSetMaxMemory (virDomainPtr domain, unsigned long memory);

#Python API virConnect::numOfDomains(self) virDomain::setMaxMemory(self, memory)
複製代碼

 因此,libvirt 官網並沒有提供詳細的 python API 描述。

2. QEMU/KVM libvirt 驅動

2.1 架構

 

?Libvirtd 是一個 daemon 進程,可以被本地的virsh調用,也可以被遠程的virsh調用
?Libvirtd 調用 qemu-kvm 操作KVM 虛擬機
這裏有一個 virsh 命令、Libvirt C API、 QEMU driver 方法 和 QEMU Monitor 命令的對照表(部分):
virsh command Public API QEMU driver function Monitor command
virsh create XMLFILE virDomainCreateXML() qemudDomainCreate() info cpus, cont, change vnc password, balloon (all indirectly)
virsh suspend GUEST virDomainSuspend() qemudDomainSuspend() stop
virsh resume GUEST virDomainResume() qemudDomainResume() cont
virsh shutdown GUEST virDomainShutdown() qemudDomainShutdown() system_powerdown
virsh setmem GUEST MEM-KB virDomainSetMemory() qemudDomainSetMemory() balloon (indirectly)
virsh dominfo GUEST virDomainGetInfo() qemudDomainGetInfo() info balloon (indirectly)
virsh save GUEST FILENAME virDomainSave() qemudDomainSave() stop, migrate exec
virsh restore FILENAME virDomainRestore() qemudDomainRestore() cont
virsh dumpxml GUEST virDomainDumpXML() qemudDomainDumpXML() info balloon (indirectly)
virsh attach-device GUEST XMLFILE virDomainAttachDevice() qemudDomainAttachDevice() change, eject, usb_add, pci_add (all indirectly)
virsh detach-device GUEST XMLFILE virDomainDetachDevice() qemudDomainDetachDevice() pci_del (indirectly)
virsh migrate GUEST DEST-URI virDomainMigrate() qemudDomainMigratePerform() stop, migrate_set_speed, migrate, cont
virsh domblkstat GUEST virDomainBlockStats() qemudDomainBlockStats() info blockstats
- virDomainBlockPeek() qemudDomainMemoryPeek() memsave

2.2 安裝

有三種方式來安裝 libvirt:

(1)下載 libvirt 的源代碼,然後編譯和安裝

(2)從各 Linux 的發行版中直接安裝,比如 Ubuntu 上運行 apt-get install libvirt-bin

(3)從 git 上克隆 libvirt 的代碼,然後編譯和安裝

2.3 libvirt log

這篇文章 描述了 livbirt log。設置所有日誌的方法是在 /etc/libvirt/libvirtd.conf 中添加下面的配置然後重啓 libvirt:

log_filters="1:libvirt 1:util 1:qemu" log_outputs="1:file:/var/log/libvirt/libvirtd.log"

3 使用 libvirt 編程來管理 KVM 虛機的實例

 這裏只描述基本的過程。具體的過程,下一篇文章會具體分析 Nova 中 libvirt 的使用。

  1. 定義虛機的基本配置,包括 vCPU、內存、磁盤或者cdrom以及啓動順序,生成 xml 配置,調用 virDomainCreateXML API 啓動一個虛機
  2. 使用 Domain 相關的 API 來管理虛機的生命週期。我的這篇文章有虛機生命週期的詳細介紹。
  3. 添加磁盤:定義一個 disk 的 xml 配置,使用 virDomainAttachDevice API 將它掛載到虛機上。如果不是本地的源磁盤,需要提前準備好。
  4. 添加interface:使用 Network API 定義一個虛擬網絡(需要提前準備好物理網絡),然後定義一個 interface 的 XML 配置,使用 virDomainAttachDevice API 將它加到虛機。
  5. 按照需要,重複2、3、4步驟。 


分類: KVM虛擬化

KVM 介紹(6):Nova 通過 libvirt 管理 QEMU/KVM 虛機 [Nova Libvirt QEMU/KVM Domain]

學習 KVM 的系列文章:

1. Libvirt 在 OpenStack 架構中的位置

在 Nova Compute 節點上運行的 nova-compute 服務調用 Hypervisor API 去管理運行在該 Hypervisor 的虛機。Nova 使用 libvirt 管理 QEMU/KVM 虛機,還使用別的 API 去管理別的虛機。

       

libvirt 的實現代碼在 /nova/virt/libvirt/driver.py 文件中。

這裏是 OpenStack Hypervisor Matrix

這裏是 每個 Linux 發行版裏面 libvirt, QEMU/KVM 的版本號

請注意Juno 版本 Nova 對 libvirt 和 QEMU 的各種最低版本要求:

功能 最低 libvirt 版本 最低 QEMU 版本 不支持的後果
所有  0.9.11    Nova 不能使用 libvirt driver

支持 device callback

 1.1.1   不支持的話,就無法支持 Detach PCI/SR-IOV 設備
Live snapshot 1.3.0 1.3.0  只能使用 Clod Snapshot
掛載卷時設置卷的 block 大小(Block IO) 0.10.2   不能使用的話,就不能設置卷的特定 block size,只能使用其默認的 block size。
Block Job Info 1.1.1   不能在線刪除卷的快照 (online deletion of volume snapshots
Discard 1.0.6 1.6.0
不支持 image 設置 hw_disk_discard 屬性,具體參考 BluePrint
NUMA topology 1.0.4    無法獲取 node 的 NUMA topology 信息,就無法將虛機的 vCPU 指定到特定的 node CPU 上,會影響虛機的性能


2. Nova 中 libvirt 的使用

Nova 使用 libvirt 來管理虛機,包括:

  • 創建虛機
  • 虛機的生命週期管理(參考這篇文檔
  • 添加和刪除連接到別的網絡的網卡 (interface)
  • 添加和刪除 Cinder 卷 (volume)

2.1 創建 QEMU/KVM 虛機

創建虛機的配置有幾個來源:

  • 用戶的選項,包括虛機的基本信息,比如 name,flavor,image,network,disk等。
  • image 的屬性,比如 hw_vif_model,hw_scsi_model 等。完整的供 libvirt API 使用的屬性列表 在這裏
  • 管理員在 nova.conf 中的配置

(注意:image 的元數據屬性的優先級高於 nova.conf 中的配置。只有在沒有property的情況下才使用nova.conf中的配置)

創建虛機的過程的幾個主要階段:

(1)消息由 nova-api 路由到某個 nova compute 節點 (API -> Scheduler -> Compute (manager) -> Libvirt Driver)

(2)調用 Neutron REST API 去準備網絡。其返回的數據類似:

[VIF({'profile': {}, 'ovs_interfaceid': u'59cfa0b8-2f5c-481a-89a8-7a8711b368a2', 'network': Network({'bridge': 'br-int', 'subnets': [Subnet({'ips': [FixedIP({'meta': {}, 'version': 4, 'type': 'fixed', 'floating_ips': [], 'address': u'10.0.10.14'})], 'version': 4, 'meta': {'dhcp_server': u'10.0.10.11'}, 'dns': [], 'routes': [], 'cidr': u'10.0.10.0/24', 'gateway': IP({'meta': {}, 'version': 4, 'type': 'gateway', 'address': u'10.0.10.1'})})], 'meta': {'injected': False, 'tenant_id': u'74c8ada23a3449f888d9e19b76d13aab'}, 'id': u'a924e87a-826b-4109-bb03-523a8b3f6f9e', 'label': u'demo-net2'}), 'devname': u'tap59cfa0b8-2f', 'vnic_type': u'normal', 'qbh_params': None, 'meta': {}, 'details': {u'port_filter': True, u'ovs_hybrid_plug': True}, 'address': u'fa:16:3e:e0:30:e7', 'active': False, 'type': u'ovs', 'id': u'59cfa0b8-2f5c-481a-89a8-7a8711b368a2', 'qbg_params': None})]

(3)從 image 啓動話,nova 會調用 Glane REST API 後者 image metadata 和準備本地啓動盤

image metadata:

{u'status': u'active', u'deleted': False, u'container_format': u'bare', u'min_ram': 0, u'updated_at': u'2015-04-26T04:34:40.000000', u'min_disk': 0, u'owner': u'74c8ada23a3449f888d9e19b76d13aab', u'is_public': False, u'deleted_at': None, u'properties': {}, u'size': 13167616, u'name': u'image', u'checksum': u'64d7c1cd2b6f60c92c14662941cb7913', u'created_at': u'2015-04-26T04:34:39.000000', u'disk_format': u'qcow2', u'id': u'bb9318db-5554-4857-a309-268c6653b9ff'}

本地啓動盤:

{'disk_bus': 'virtio', 'cdrom_bus': 'ide', 'mapping': {'disk': {'bus': 'virtio', 'boot_index': '1', 'type': 'disk', 'dev': u'vda'}, 'root': {'bus': 'virtio', 'boot_index': '1', 'type': 'disk', 'dev': u'vda'}, 'disk.local': {'bus': 'virtio', 'type': 'disk', 'dev': 'vdb'}, 'disk.swap': {'bus': 'virtio', 'type': 'disk', 'dev': 'vdc'}}} 

本地啓動盤的文件信息:

複製代碼
root@compute2:/home/s1# qemu-img info /var/lib/nova/instances/02699155-940f-4401-bc01-36220db80639/disk.local
image: /var/lib/nova/instances/02699155-940f-4401-bc01-36220db80639/disk.local
file format: qcow2
virtual size: 1.0G (1073741824 bytes) #由 flavor.ephemeral_disk 指定其 size disk size: 324K
cluster_size: 65536
backing file: /var/lib/nova/instances/_base/ephemeral_1_default
Format specific information:
    compat: 1.1
    lazy refcounts: false

root@compute2:/home/s1# qemu-img info /var/lib/nova/instances/02699155-940f-4401-bc01-36220db80639/disk.swap
image: /var/lib/nova/instances/02699155-940f-4401-bc01-36220db80639/disk.swap
file format: qcow2
virtual size: 30M (31457280 bytes) # 由 flavor.swap_disk 指定其size disk size: 196K
cluster_size: 65536
backing file: /var/lib/nova/instances/_base/swap_30
Format specific information:
    compat: 1.1
    lazy refcounts: false
複製代碼

(4)根據這些信息,生成 domain xml,然後生成其配置使得它是一個持久性虛機 (調用 libvirt Python DefineXML API)。

一個從 image 啓動的 Domain 的配置 XML 實例(藍色部分是註釋說明):

複製代碼
"qemu">
  8352e969-0a25-4abf-978f-d9d0ec4de0cd
  instance-0000002f
  51200 # guest.memory = flavor.memory_mb * units.Ki 即 50 * 1024 = 51200 "0">1 #flavor.vcpus 
    "http://openstack.org/xmlns/libvirt/nova/1.0">
      "2014.2.2"/>
      vm11 #input.name 2015-06-09 23:54:04
      "tiny2"> #input.flavor 50
        1
        30
        1
        1
      
      
        "bcd37e6272184f34993b4d7686ca4479">admin
        "74c8ada23a3449f888d9e19b76d13aab">admin
      
      "image" uuid="bb9318db-5554-4857-a309-268c6653b9ff"/> #input.source 
  
  "smbios"> # Nova 中寫死的 
      "manufacturer">OpenStack Foundation
      "product">OpenStack Nova
      "version">2014.2.2
      "serial">03bb1a0f-ae04-4765-9f3c-d200a2540675
      "uuid">8352e969-0a25-4abf-978f-d9d0ec4de0cd
    
  
  
    hvm #表示 Guest OS 需要 full virtualiaiton 支持 "hd"/> #指定啓動盤 "sysinfo"/> #去讀取  的定義 
  
     # Soft Reboot 需要 ACPI 的支持,否則只能使用 Hard reboot。 https://bugs.launchpad.net/horizon/+bug/1346741  # 沒 APIC 的話,Windows Guest 會在 Xen 或者 KVM 上崩潰。 https://bugs.launchpad.net/nova/+bug/1086352 
  "utc"/> #如果Guest OS 是 MS,則是 localtime,否則都是 utc "host-model" match="exact"> # 對於 KVM,如果 CONF.libvirt.cpu_mode 是 none,mode 則設爲 "host-model"。具體可參考 https://wiki.openstack.org/wiki/LibvirtXMLCPUModel  "1" cores="1" threads="1"/> #默認的時候,sockets 數目設爲 vcpu 的數目,cores 和 threads 都設爲 1. 可以通過設置 image 的 hw_cpu_topology 屬性來改變這裏的設置,具體請參考 https://blueprints.launchpad.net/nova/+spec/support-libvirt-vcpu-topology 以及 https://wiki.openstack.org/wiki/VirtDriverGuestCPUMemoryPlacement 
  
    "file" device="disk"> # 從 image 啓動時候的啓動盤(flavor.root_disk) "qemu" type="qcow2" cache="none"/>
      "/var/lib/nova/instances/8352e969-0a25-4abf-978f-d9d0ec4de0cd/disk"/>
      "virtio" dev="vda"/> #對於 KVM,disk 的 bus 爲 "virtio",cdrom 的 bus 爲 "ide",floppy 的 bus 爲 "fdc" 
    "file" device="disk"> #臨時分區 (falvor.ephemeral_disk) "qemu" type="qcow2" cache="none"/>
      "/var/lib/nova/instances/8352e969-0a25-4abf-978f-d9d0ec4de0cd/disk.local"/>
      "virtio" dev="vdb"/>
    
    "file" device="disk"> #swap 分區 (flavor.swap_disk) "qemu" type="qcow2" cache="none"/>
      "/var/lib/nova/instances/8352e969-0a25-4abf-978f-d9d0ec4de0cd/disk.swap"/>
      "virtio" dev="vdc"/>
    
    <interface type="bridge"> # 虛機通過網橋連接到 OVS "fa:16:3e:e0:30:e7"/>
      "virtio"/> #該 type 可以由 image metadata hw_vif_type 指定。未指定的話,如果配置了 conf.libvirt.use_virtio_for_bridges = true (默認就是 true)的話,QEMU/KVM 會使用 virtio 類型。 "qemu"/>
      "qbr59cfa0b8-2f"/> #qbr59cfa0b8-2f 連接虛機的 vNIC tap59cfa0b8-2f 和 qvb59cfa0b8-2f ,而 qvb59cfa0b8-2f 練到 OVS 的 br-int 上。 "tap59cfa0b8-2f"/>
    interface>
    "file"> 當 CONF.serial_console.enabled = true 時,type 爲 "tcp",使用 config 配置,其 XML 爲     ;當爲 false 時,使用 console.log 文件,type 爲 file。 "/var/lib/nova/instances/8352e969-0a25-4abf-978f-d9d0ec4de0cd/console.log"/>
    
    "pty"/> #每個domain都有 type 爲 "pty" 的 serial 配置。 "tablet" bus="usb"/> #當 CONF.vnc_enabled = true 或者 CONF.spice.enabled = true 並且 CONF.spice.agent_enabled = false 時添加 tablet,type 和 bus 都是固定的。 "vnc" autoport="yes" keymap="en-us" listen="0.0.0.0"/> #如果 CONF.vnc_enabled = true,那麼 keymap=CONF.vnc_keymap;listen=CONF.vncserver_listen  #如果 CONF.vnc_enabled 或者 CONF.spice.enabled,則添加該 video 配置 "cirrus"/> #如果 CONF.spice.enabled,則 type 爲 qxl;否則爲 cirrus。 
    "virtio"> #如果 CONF.libvirt.mem_stats_period_seconds >0 則添加 memballoon;對 KVM,model 固定爲 "virtio" "10"/>
    
  
複製代碼

從 bootable volume 啓動的話,disk 部分爲:

複製代碼
"file" device="disk">
      "qemu" type="qcow2" cache="none"/>
      "/var/lib/nova/instances/02699155-940f-4401-bc01-36220db80639/disk.local"/>
      "virtio" dev="vdb"/>
    
    "file" device="disk">
      "qemu" type="qcow2" cache="none"/>
      "/var/lib/nova/instances/02699155-940f-4401-bc01-36220db80639/disk.swap"/>
      "virtio" dev="vdc"/>
     
     
     
     
     26446902-5a56-4c79-b839-a8e13a66dc7a
    
複製代碼

(5). 啓動 domain (調用 libvirt Python createWithFlags API)

2.2 添加 volume 到虛機 (nova volume-attach

(1)使用 volume id 通過 volume driver 找到指定的 volume

(2)調用 volume driver 來建立主機和 Volume 之間的連接

主機信息爲: 

 {'ip': '192.168.1.15', 'host': 'compute2', 'initiator': 'iqn.1993-08.org.debian:01:a9f2b45c24f9'} 

建立的 iSCSI 連接信息爲:

{u'driver_volume_type': u'iscsi', u'data': {u'access_mode': u'rw', u'target_discovered': False, u'encrypted': False, u'qos_specs': None, u'target_iqn': u'iqn.2010-10.org.openstack:volume-51da0d1f-0a17-4e7f-aeff-27438963348a', u'target_portal': u'10.0.2.41:3260', u'volume_id': u'51da0d1f-0a17-4e7f-aeff-27438963348a', u'target_lun': 1, u'auth_password': u'hXG64qrzEjNt8MDKnERA', u'auth_username': u'fKSAe6vhgyeG88U9kcBV', u'auth_method': u'CHAP'}} 

volume 在主機上的磁盤爲:

複製代碼

root@compute2:/home/s1# ls /dev/disk/by-path/ -ls
total 0
0 lrwxrwxrwx 1 root root 9 Jun 10 12:18 ip-10.0.2.41:3260-iscsi-iqn.2010-10.org.openstack:volume-51da0d1f-0a17-4e7f-aeff-27438963348a-lun-1 -> ../../sdc


Disk /dev/sdc: 1073 MB, 1073741824 bytes
34 heads, 61 sectors/track, 1011 cylinders, total 2097152 sectors
Units = sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disk identifier: 0x00000000

Disk /dev/sdc doesn't contain a valid partition table

複製代碼

(3)通過 domain name 來找到指定 domain 對象 (通過調用 lookupByName API)

(4)生成 volume 連接的配置 xml,比如:

複製代碼


   
   
   
    51da0d1f-0a17-4e7f-aeff-27438963348a

複製代碼

(5)調用 attachDeviceFlags API 將 volume 掛載到該虛機

2.3 添加連接到新的網絡的 interface 給虛機 (nova interface-attach

(1)運行 nova  interface-attach,傳入 network-id,Neutron 會分配如下network info 給 Nova

VIF({'profile': {}, 'ovs_interfaceid': u'0142efee-7382-43ef-96e8-d0084ecc893c', 'network': Network({'bridge': u'br-int', 'subnets': [Subnet({'ips': [FixedIP({'meta': {}, 'version': 4, 'type': u'fixed', 'floating_ips': [], 'address': u'10.0.0.40'})], 'version': 4, 'meta': {u'dhcp_server': u'10.0.0.3'}, 'dns': [], 'routes': [], 'cidr': u'10.0.0.0/24', 'gateway': IP({'meta': {}, 'version': 4, 'type': u'gateway', 'address': u'10.0.0.1'})})], 'meta': {u'injected': False, u'tenant_id': u'74c8ada23a3449f888d9e19b76d13aab'}, 'id': u'01630966-b21f-4a6d-95ff-10c4575f1fe2', 'label': u'demo-net'}), 'devname': u'tap0142efee-73', 'vnic_type': u'normal', 'qbh_params': None, 'meta': {}, 'details': {u'port_filter': True, u'ovs_hybrid_plug': True}, 'address': u'fa:16:3e:14:32:d9', 'active': True, 'type': u'ovs', 'id': u'0142efee-7382-43ef-96e8-d0084ecc893c', 'qbg_params': None})

(2)執行下面的命令,將 Neutron 分配的 port 連接到 OVS 

複製代碼
#添加 linux bridge
brctl addbr qbr0142efee-73 #名字是 devname 的後半部分 brctl setfd qbr0142efee-73 0 brctl stp qbr0142efee-73 off
tee /sys/class/net/qbr0142efee-73/bridge/multicast_snooping

ip link add qvb0142efee-73 type veth peer name qvo0142efee-73 ip link set qvb0142efee-73 ip link set qvb0142efee-73 promisc on

#在 OVS 上添加端口
ovs-vsctl --timeout=120 -- --if-exists del-port qvo0142efee-73 -- add-port br-int qvo0142efee-73 -- set Interface qvo0142efee-73 external-ids:iface-id=0142efee-7382-43ef-96e8-d0084ecc893c external-ids:iface-status=active external-ids:attached-mac=fa:16:3e:14:32:d9 external-ids:vm-uuid=8352e969-0a25-4abf-978f-d9d0ec4de0cd
複製代碼

(3)生成 interface 配置的xml,比如:

複製代碼


   
   
    
    
    

複製代碼

(4)調用 attachDeviceFlags API 來掛載該 interface 到虛機


 至於其他的虛機操作,會在另一篇文章中描述。 

KVM 介紹(7):使用 libvirt 做 QEMU/KVM 快照和 Nova 實例的快照 (Nova Instances Snapshot Libvirt)

學習 KVM 的系列文章:

本文將梳理 QEMU/KVM 快照相關的知識,以及在 OpenStack Nova 中使用 libvirt 來對 QEMU/KVM 虛機做快照的過程。

1. QEMU/KVM 快照

1.1 概念

QEMU/KVM 快照的定義:

  • 磁盤快照:磁盤的內容(可能是虛機的全部磁盤或者部分磁盤)在某個時間點上被保存,然後可以被恢復。
    • 磁盤數據的保存狀態:
      • 在一個運行着的系統上,一個磁盤快照很可能只是崩潰一致的(crash-consistent) 而不是完整一致(clean)的,也是說它所保存的磁盤狀態可能相當於機器突然掉電時硬盤數據的狀態,機器重啓後需要通過 fsck 或者別的工具來恢復到完整一致的狀態(類似於 Windows 機器在斷電後會執行文件檢查)。
      • 對一個非運行中的虛機來說,如果上次虛機關閉的時候磁盤是完整一致的,那麼其被快照的磁盤快照也將是完整一致的。
    • 磁盤快照有兩種:
      • 內部快照 - 使用單個的 qcow2 的文件來保存快照和快照之後的改動。這種快照是 libvirt 的默認行爲,現在的支持很完善(創建、回滾和刪除),但是隻能針對 qcow2 格式的磁盤鏡像文件,而且其過程較慢等。
      • 外部快照 - 快照是一個只讀文件,快照之後的修改是另一個 qcow2 文件中。外置快照可以針對各種格式的磁盤鏡像文件。外置快照的結果是形成一個 qcow2 文件鏈:original <- snap1 <- snap2 <- snap3。這裏有文章詳細討論外置快照。
  • 內存狀態(或者虛機狀態):只是保持內存和虛機使用的其它資源的狀態。如果虛機狀態快照在做和恢復之間磁盤沒有被修改,那麼虛機將保持一個持續的狀態;如果被修改了,那麼很可能導致數據corruption。
  • 系統還原點(system checkpoint):虛機的所有磁盤的快照和內存狀態快照的集合,可用於恢復完整的系統狀態(類似於系統休眠)。

關於 崩潰一致(crash-consistent)的附加說明:

  • 應該儘量避免在虛機I/O繁忙的時候做快照。這種時候做快照不是可取的辦法。
  • vmware 的做法是裝一個 tools,它是個 PV driver,可以在做快照的時候掛起系統
  • 似乎 KVM 也有類似的實現 QEMU Guest Agent,但是還不是很成熟,可參考 http://wiki.libvirt.org/page/Qemu_guest_agent

快照還可以分爲 live snapshot(熱快照)和 Clod snapshot:

  • Live snapshot:系統運行狀態下做的快照
  • Cold snapshot:系統停止狀態下的快照

libvit 做 snapshot 的各個 API:

snapshot 做快照的 libvirt API 從快照恢復的 libvirt API virsh 命令
磁盤快照 virDomainSnapshotCreateXML(flags = VIR_DOMAIN_SNAPSHOT_CREATE_DISK_ONLY ) virDomainRevertToSnapshot  virsh snapshot-create/snapshot-revert
內存(狀態)快照

virDomainSave

virDomainSaveFlags

virDomainManagedSave

virDomainRestore

virDomainRestoreFlags

virDomainCreate

virDomainCreateWithFlags

virsh save/restore
系統檢查點 virDomainSnapshotCreateXML virDomainRevertToSnapshot  virsh snapshot-create/snapshot-revert

分別來看看這些 API 是如何工作的:

1. virDomainSnapshotCreateXML (virDomainPtr domain, const char * xmlDesc, unsigned int flags)

作用:根據 xmlDesc 指定的 snapshot xml 和 flags 來創建虛機的快照。

flags 包含  虛機處於運行狀態時快照的做法 虛機處於關閉狀態時快照的做法
0 創建系統檢查點,包括磁盤狀態和內存狀態比如內存內容 保持關機時的磁盤狀態
VIR_DOMAIN_SNAPSHOT_CREATE_LIVE 做快照期間,虛機將不會被 paused。這會增加內存 dump file 的大小,但是可以減少系統停機時間。部分 Hypervisor 只在做外部的系統檢查點時才設置該 flag,這意味着普通快照還是需要暫停虛機。  
VIR_DOMAIN_SNAPSHOT_CREATE_DISK_ONLY 只做指定磁盤的快照。對應運行着的虛機,磁盤快照可能是不完整的(類似於突然電源被拔了的情形)。 只做指定磁盤的快照。

其內部實現根據虛機的運行狀態有兩種情形:

  • 對運行着的虛機,API 使用 QEMU Monitor 去做快照,磁盤鏡像文件必須是 qcow2 格式,虛機的 CPU 被停止,快照結束後會重新啓動。
  • 對停止着的虛機,API 調用 qemu-img 方法來操作所有磁盤鏡像文件。

這裏有其實現代碼,可見其基本的實現步驟: 

複製代碼
static virDomainSnapshotPtr qemuDomainSnapshotCreateXML
{
    ....
    call qemuDomainSnapshotCreateDiskActive
    {
        call qemuProcessStopCPUs # 停止 vCPUs for each disk call qemuDomainSnapshotCreateSingleDiskActive
        {
            call qemuMonitorDiskSnapshot # 調用 QEMU Monitor 去爲每個磁盤做snapshot }
        call qemuProcessStartCPUs # 啓動 vCPUs  }  .... } 
複製代碼

2. virDomainSave 相關的幾個 API

這幾個API 功能都比較類似:

virDomainSave  該方法會 suspend 一個運行着的虛機,然後保存期內存內容到一個文件中。成功調用以後,domain 將不會處於 running 狀態。使用 virDomainRestore 來恢復虛機。
virDomainSaveFlags  類似於 virDomainSave API,可使用幾個  flags。一些 Hypervisor 在調用該方法前需要調用  virDomainBlockJobAbort() 方法來停止 block copy 操作。
virDomainManagedSave  也類似於 virDomainSave API。主要區別是 libvirt 將其內存保存到一個受 libvirt 管理的文件中,因此libvirt 可以一直跟蹤 snapshot 的狀態;當調用 virDomainCreate/virDomainCreateWithFlags 方法重啓該 domain的時候,libvirt 會使用該受管文件,而不是一個空白的文件,這樣就可以 restore 該snapshot。

Features/SnapshotsMultipleDevices 這篇文章討論同時對多個磁盤做快照的問題。

1.2 使用 virsh 實驗

1.2.1 virsh save 命令

對運行中的 domain d-2 運行 “virsh save” 命令。命令執行完成後,d-2 變成 “shut off” 狀態。

看看 domain 的磁盤鏡像文件和 snapshot 文件:

內存數據被保存到 raw 格式的文件中。

要恢復的時候,可以運行 “vish restore d-2.snap1” 命令從保存的文件上恢復。

1.2.2 virsh snapshot-create/snapshort-create-as

先看看它的用法:

複製代碼
virsh # help snapshot-create-as
  NAME
    snapshot-create-as - Create a snapshot from a set of args

  SYNOPSIS
    snapshot-create-as  [] [] [--print-xml] [--no-metadata] [--halt] [--disk-only] [--reuse-external] [--quiesce] [--atomic] [--live] [--memspec ] [[--diskspec] ]...

  DESCRIPTION
    Create a snapshot (disk and RAM) from arguments

  OPTIONS
    [--domain]   domain name, id or uuid
    [--name]   name of snapshot
    [--description]   description of snapshot
    --print-xml      print XML document rather than create
    --no-metadata    take snapshot but create no metadata
    --halt           halt domain after snapshot is created
    --disk-only      capture disk state but not vm state
    --reuse-external  reuse any existing external files
    --quiesce        quiesce guest's file systems
    --atomic         require atomic operation
    --live           take a live snapshot
    --memspec   memory attributes: [file=]name[,snapshot=type]
    [--diskspec]   disk attributes: disk[,snapshot=type][,driver=type][,file=name]
複製代碼

其中一些參數,比如 --atomic,在一些老的 QEMU libary 上不支持,需要更新它到新的版本。根據 這篇文章,atomic 應該是 QEMU 1.0 中加入的。

(1)默認的話,該命令創建虛機的所有磁盤和內存做內部快照,創建快照時虛機處於 paused 狀態,快照完成後變爲 running 狀態。持續時間較長。

'internal'/>
  
    'vda' snapshot='internal'/>
    'vdb' snapshot='internal'/>
    'vdc' snapshot='internal'/>
   

每個磁盤的鏡像文件都包含了 snapshot 的信息:

複製代碼
root@compute1:/var/lib/nova/instances/eddc46a8-e026-4b2c-af51-dfaa436fcc7b# qemu-img info disk
image: disk
file format: qcow2
virtual size: 1.0G (1073741824 bytes)
disk size: 43M
cluster_size: 65536
backing file: /var/lib/nova/instances/_base/fbad3d96a1727069346073e51d5bbb1824e76e34 Snapshot list:
ID        TAG                 VM SIZE                DATE       VM CLOCK
1         1433950148              41M 2015-06-10 23:29:08   05:16:55.007 Format specific information:
    compat: 1.1
    lazy refcounts: false 
複製代碼

你可以運行 snapshot-revert 命令回滾到指定的snapshot。

virsh # snapshot-revert instance-0000002e 1433950148

根據 這篇文章,libvirt 將內存狀態保存到某一個磁盤鏡像文件內 (”state is saved inside one of the disks (as in qemu's 'savevm'system checkpoint implementation). If needed in the future,we can also add an attribute pointing out _which_ disk saved the internal state; maybe disk='vda'.)

(2)可以使用 “--memspec” 和 “--diskspec” 參數來給內存和磁盤外部快照。這時候,在獲取內存狀態之前需要 Pause 虛機,就會產生服務的 downtime。

複製代碼
virsh # snapshot-create-as 0000002e livesnap2  --memspec /home/s1/livesnap2mem,snapshot=external --diskspec vda,snapshot=external
Domain snapshot livesnap2 created
virsh # snapshot-dumpxml 0000002e livesnap2     'qcow2'/>
      '/home/s1/testvm/testvm1.livesnap2'/>
    
  
複製代碼

(3)可以使用 “--disk-only” 參數,這時會做所有磁盤的外部快照,但是不包含內存的快照。不指定快照文件名字的話,會放在原來的磁盤文件所在的目錄中。多次快照後,會形成一個外部快照鏈,新的快照使用前一個快照的鏡像文件作爲 backing file。

複製代碼
virsh # snapshot-list instance-0000002e --tree 1433950148 #內部快照 1433950810 #內部快照 1433950946 #內部快照 snap1 #第一個外部快照 |
  +- snap2 #第二個外部快照 |
      +- 1433954941 #第三個外部快照 |
          +- 1433954977 #第四個外部快照
複製代碼

而第一個外部快照的鏡像文件是以虛機的原始鏡像文件作爲 backing file 的:

複製代碼
root@compute1:/var/lib/nova/instances/eddc46a8-e026-4b2c-af51-dfaa436fcc7b# qemu-img info disk.snap1
image: disk.snap1
file format: qcow2
virtual size: 30M (31457280 bytes)
disk size: 196K
cluster_size: 65536
backing file: /var/lib/nova/instances/eddc46a8-e026-4b2c-af51-dfaa436fcc7b/disk.swap #虛機的 swap disk 原始鏡像文件 backing file format: qcow2
Format specific information:
    compat: 1.1
    lazy refcounts: false
複製代碼

目前還不支持回滾到某一個extrenal disk snapshot。這篇文章 談到了一個workaround。

[root@rh65 osdomains]# virsh snapshot-revert d-2 1434467974
error: unsupported configuration: revert to external disk snapshot not supported yet

(4)還可以使用 “--live” 參數創建系統還原點,包括磁盤、內存和設備狀態等。使用這個參數時,虛機不會被 Paused(那怎麼實現的?)。其後果是增加了內存 dump 文件的大小,但是減少了系統的 downtime。該參數只能用於做外部的系統還原點(external checkpoint)。

複製代碼
virsh # snapshot-create-as 0000002e livesnap3  --memspec /home/s1/livesnap3mem,snapshot=external --diskspec vda,snapshot=external --live Domain snapshot livesnap3 created
virsh # snapshot-dumpxml 0000002e livesnap3  'external' file='/home/s1/livesnap3mem'/>
  
    'vda' snapshot='external' type='file'>
      'qcow2'/>
      '/home/s1/testvm/testvm1.livesnap3'/>
    
  
複製代碼

注意到加 “--live” 生成的快照和不加這個參數生成的快照不會被鏈在一起:

複製代碼
virsh # snapshot-list 0000002e --tree livesnap1 #沒加 --live |
  +- livesnap2 #沒加 --live livesnap3 #加了 --live |
  +- livesnap4 #加了 --live
複製代碼

不過,奇怪的是,使用 QEMU 2.3 的情況下,即使加了 --live 參數,虛機還是會被短暫的 Paused 住:


複製代碼
[root@rh65 ~]# virsh snapshot-create-as d-2 --memspec /home/work/d-2/mem3,snapshot=external --diskspec hda,snapshot=external --live
Domain snapshot 1434478667 created

[root@rh65 ~]# virsh list --all
 Id    Name                           State ---------------------------------------------------- 40 osvm1                          running 42 osvm2                          running 43 d-2 running

[root@rh65 ~]# virsh list --all
 Id    Name                           State ---------------------------------------------------- 40 osvm1                          running 42 osvm2                          running 43    d-2 paused # 不是說好我用 --live 你就不pause 虛機的麼?這是腫了麼。。 [root@rh65 ~]# virsh list --all
 Id    Name                           State ---------------------------------------------------- 40 osvm1                          running 42 osvm2                          running 43 d-2 running
複製代碼


綜上所述,對於 snapshot-create-as 命令來說,

參數 結果
  所有磁盤和內存的內部的內部快照
--memspec snapshot=external --diskspec vda,snapshot=external
磁盤和內存的外部快照,虛機需要被暫停
--live  --memspec snapshot=external --diskspec vda,snapshot=external 創建系統檢查點(包括磁盤和內存的快照),而且虛機不會被暫停(?測試結果顯示還是會暫停,只是暫停時間比不使用 --live 要短一些)
--disk-only 創建所有或者部分磁盤的外部快照

 可以使用 sanpshot-revert 命令來回滾到指定的系統還原點,不過得使用 “-force” 參數:


[root@rh65 ~]# virsh snapshot-revert d-2 1434478313 error: revert requires force: Target device address type none does not match source pci

[root@rh65 ~]# virsh snapshot-revert d-2 1434478313 --force

[root@rh65 ~]#

1.3 外部快照的刪除

目前 libvirt 還不支持直接刪除一個外部快照,可以參考 這篇文章 介紹的 workaround。

2. OpenStack 中的快照

 OpenStack Snapshot 可分爲下面的幾種情形:

2.1 對 Nova Instance 進行快照

(1)對從鏡像文件啓動的虛機做快照

  • 只將運行當中的虛機的 Root disk (第一個vd 或者 hd disk) 做成 image,然後上傳到 glance 裏面
  • Live Snapshot:對滿足特定條件(QEMU 1.3+ 和 Libvirt 1.0.0+,以及 source_format not in ('lvm', 'rbd') and not CONF.ephemeral_storage_encryption.enabled and not CONF.workarounds.disable_libvirt_livesnapshot,以及能正常調用 libvirt.blockJobAbort ,其前提條件可參考這文章)的虛機,會進行 Live snapshot。Live Snapshot 允許用戶在虛機處於運行狀態時不停機做快照。
  • Cold Snapshot:對不能做 live snapshot 的虛機做 Cold snapshot。這種快照必須首先 Pause 虛機。

(2)對從卷啓動的虛機做快照

  • 對虛機的每個掛載的 volume 調用 cinder API 做 snapshot。
  • Snapshot 出的 metadata 會保存到 glance 裏面,但是不會有 snapshot 的 image 上傳到 Glance 裏面。
  • 這個 snapshot 也會出現在 cinder 的數據庫裏面,對 cinder API 可見。

2.2 對卷做快照

  • 調用 cinder driver api,對 backend 中的 volume 進行 snapshot。
  • 這個 snapshot 會出現在 cinder 的數據庫裏面,對 cinder API 可見。  

3. 從鏡像文件啓動的 Nova 虛機做快照

    嚴格地說,Nova 虛機的快照,並不是對虛機做完整的快照,而是對虛機的啓動盤(root disk,即 vda 或者 hda)做快照生成 qcow2 格式的文件,並將其傳到 Glance 中,其作用也往往是方便使用快照生成的鏡像來部署新的虛機。Nova 快照分爲 Live Snapshot (不停機快照)和 Clold Snapshot (停機快照)。

3.1 Nova Live Snapshot

滿足 2.1.1 中所述條件時,運行命令  ”nova image-create “ 後,Nova 會執行 Live Snapshot。其過程如下:

  1. 找到虛機的 root disk (vda 或者 hda)。
  2. 在 CONF.libvirt.snapshots_directory 指定的文件夾(默認爲 /var/lib/nova/instances/snapshots)中創建一個臨時文件夾,在其中創建一個 qcow2 格式的 delta 文件,其文件名爲 uuid 字符串,該文件的 backing file 和 root disk 文件的 backing file 相同 (下面步驟 a)。
  3. 調用 virDomainGetXMLDesc 來保存 domain 的 xml 配置。
  4. 調用 virDomainBlockJobAbort 來停止對 root disk 的活動塊操作 (Cancel the active block job on the given disk)。
  5. 調用 virDomainUndefine 來將 domain 變爲 transimit 類型的,這是因爲 BlockRebase API 不能針對 Persistent domain 調用。
  6. 調用 virDomainBlockRebase 來將 root disk image 文件中不同的數據拷貝到 delta disk file 中。(下面步驟 b)
  7. 步驟 6 是一個持續的過程,因爲可能有應用正在向該磁盤寫數據。Nova 每隔 0.5 秒調用 virDomainBlockJobInfo API 來檢查拷貝是否結束。
  8. 拷貝結束後,調用  virDomainBlockJobAbort 來終止數據拷貝。
  9. 調用 virDomainDefineXML 將domain 由 transimisit 該回到 persistent。
  10. 調用 qemu-img convert 命令將 delta image 文件和 backing file 變爲一個 qcow2 文件 (下面步驟 c)
  11. 將 image 的元數據和 qcow2 文件傳到 Glance 中。
複製代碼
(a)執行  qemu-img create -f qcow2 (qemu-img create 創建一個基於鏡像1的鏡像2,鏡像2的文件將基於鏡像1,鏡像2中的文件將基於鏡像1中的。在鏡像2中所作的任何讀寫操作都不會影響到鏡像1. 鏡像1可以被其他鏡像當做backing file. 但是要確保鏡像1不要被修改)。比如: qemu-img create -f qcow2 -o backing_file=/var/lib/nova/instances/_base/ed39541b2c77cd7b069558570fa1dff4fda4f678,size=21474836480 /var/lib/nova/instances/snapshots/tmpzfjdJS/7f8d11be9ff647f6b7a0a643fad1f030.delta 
(b)相當於執行 virsh blockjob   [--abort] [--async] [--pivot] [--info] []
(c)執行 'qemu-img convert -f qcow2 -o dest_fmt' 來將帶 backing file 的 qcow2 image 轉化成不帶 backing file 的 flat image。其中 dest_fmt 由 snapshot_image_format 決定,有效值是 raw, qcow2, vmdk, vdi,默認值是 source image 的 format。比如: qemu-img convert -f qcow2 -O qcow2 /var/lib/nova/instances/snapshots/tmpzfjdJS/7f8d11be9ff647f6b7a0a643fad1f030.delta /var/lib/nova/instances/snapshots/tmpzfjdJS/7f8d11be9ff647f6b7a0a643fad1f030
複製代碼

來看看其中的一個關鍵 API int virDomainBlockRebase (virDomainPtr dom, const char * disk, const char * base, unsigned long bandwidth,unsigned int flags)

該 API 從 backing 文件中拷貝數據,或者拷貝整個 backing 文件到  @base 文件。
Nova 中的調用方式爲:domain.blockRebase(disk_path, disk_delta, 0,libvirt.VIR_DOMAIN_BLOCK_REBASE_COPY |libvirt.VIR_DOMAIN_BLOCK_REBASE_REUSE_EXT |libvirt.VIR_DOMAIN_BLOCK_REBASE_SHALLOW)
默認的話,該 API 會拷貝整個@disk 文件到 @base 文件,但是使用  VIR_DOMAIN_BLOCK_REBASE_SHALLOW 的話就只拷貝差異數據(top data)因爲 @disk 和 @base 使用相同的 backing 文件。 VIR_DOMAIN_BLOCK_REBASE_REUSE_EXT 表示需要使用已經存在的 @base 文件因爲 Nova 會預先創建好這個文件。

簡單的示意圖:

這裏 有個過程的 PoC 代碼描述該過程。

這裏 有該過程的完整 libvirt 日誌分析。

這裏 有文章講 Libvirt Features/SnapshotsMultipleDevices。

3.2 Nova Cold Snapshot

當虛機不在運行中時或者不滿足 live snapshot 的條件的情況下,Nova 會執行 Cold snapshot。其主要過程如下:

(1)當虛機處於 running 或者 paused 狀態時:

  1. detach PCI devices
  2. detach SR-IOV devices
  3. 調用 virDomainManagedSave API 來將虛機 suspend 並且將內存狀態保存到磁盤文件中。

(2)調用 qemu-img convert 命令將 root disk 的鏡像文件轉化一個相同格式的鏡像文件。

(3)調用 virDomainCreateWithFlags  API 將虛機變爲初始狀態

(4)將在步驟1 中卸載的 PCI 和 SR-IOV 設備重新掛載回來

(5)將元數據和 qcow2 文件傳到 Glance 中

4. 從 volume 啓動的 Nova 實例的快照

(0)從卷啓動虛機,並且再掛載一個卷,然後運行 nova image-create 命令。

| image                                | Attempt to boot from volume - no image supplied                                                  |
| key_name                             | -                                                                                                |
| metadata                             | {}                                                                                               |
| name                                 | vm10                                                                                             |
| os-extended-volumes:volumes_attached | [{"id": "26446902-5a56-4c79-b839-a8e13a66dc7a"}, {"id": "de127d46-ed92-471d-b18b-e89953c305fd"}]

(1)從 DB 獲取該虛機的塊設備( Block Devices Mapping)列表。

(2)對該列表中的每一個卷,依次調用 Cinder API 做快照。對 LVM Driver 的 volume 來說,執行的命令類似於 " lvcreate --size 100M --snapshot --name snap /dev/vg00/lvol1“。

複製代碼
s1@controller:~$ cinder snapshot-list +--------------------------------------+--------------------------------------+-----------+------------------------+------+
|                  ID                  |              Volume ID               |   Status  |          Name          | Size |
+--------------------------------------+--------------------------------------+-----------+------------------------+------+
| a7c591fb-3413-4548-abd8-86753da3158b | de127d46-ed92-471d-b18b-e89953c305fd | available | snapshot for vm10-snap | 1 |
| d1277ea9-e972-4dd4-89c0-0b9d74956247 | 26446902-5a56-4c79-b839-a8e13a66dc7a | available | snapshot for vm10-snap | 1 |
+--------------------------------------+--------------------------------------+-----------+------------------------+------+
複製代碼

(3)將快照的 metadata 放到 Glance 中。(注:該 image 只是一些屬性的集合,比如 block device mapping, kernel 和 ramdisk IDs 等,它並沒有 image 數據, 因此其 size 爲 0。)

複製代碼
s1@controller:~$ glance image-show e86cc562-349c-48cb-a81c-896584accde3 +---------------------------------+----------------------------------------------------------------------------------+
| Property                        | Value                                                                            |
+---------------------------------+----------------------------------------------------------------------------------+
| Property 'bdm_v2' | True                                                                             |
| Property 'block_device_mapping' | [{"guest_format": null, "boot_index": 0, "no_device": null, "snapshot_id":       |
| # 分別是該虛機掛載的兩個volume 的   | "d1277ea9-e972-4dd4-89c0-0b9d74956247", "delete_on_termination": null,           |
| snapshot 的信息 | "disk_bus": "virtio", "image_id": null, "source_type": "snapshot",               |
|                                 | "device_type": "disk", "volume_id": null, "destination_type": "volume",          |
|                                 | "volume_size": null}, {"guest_format": null, "boot_index": null, "no_device":    |
|                                 | null, "snapshot_id": "a7c591fb-3413-4548-abd8-86753da3158b",                     |
|                                 | "delete_on_termination": null, "disk_bus": null, "image_id": null,               |
|                                 | "source_type": "snapshot", "device_type": null, "volume_id": null,               |
|                                 | "destination_type": "volume", "volume_size": null}]                              |
| Property 'checksum' | 64d7c1cd2b6f60c92c14662941cb7913                                                 |
| Property 'container_format' | bare                                                                             |
| Property 'disk_format' | qcow2                                                                            |
| Property 'image_id' | bb9318db-5554-4857-a309-268c6653b9ff                                             |
| Property 'image_name' | image                                                                            |
| Property 'min_disk' | 0 |
| Property 'min_ram' | 0 |
| Property 'root_device_name' | /dev/vda                                                                         |
| Property 'size' | 13167616 |
| created_at                      | 2015-06-10T05:52:24 |
| deleted                         | False                                                                            |
| id                              | e86cc562-349c-48cb-a81c-896584accde3                                             |
| is_public                       | False                                                                            |
| min_disk                        | 0 |
| min_ram                         | 0 |
| name                            | vm10-snap                                                                        |
| owner                           | 74c8ada23a3449f888d9e19b76d13aab                                                 |
| protected | False                                                                            |
| size                            | 0 # 這裏 size 是 0,表明該 image 只是元數據, |
| status                          | active                                                                           |
| updated_at                      | 2015-06-10T05:52:24 |
+---------------------------------+----------------------------------------------------------------------------------+ 
複製代碼

5. 當前 Nova snapshot 的侷限

  • Nova snapshot 其實只是提供一種創造系統盤鏡像的方法。不支持回滾至快照點,只能採用該快照鏡像創建一個新的虛擬機。
  • 在虛機是從 image boot 的時候,只對系統盤進行快照,不支持內存快照,不支持系統還原點 (blueprint:https://blueprints.launchpad.net/nova/+spec/live-snapshot-vms)
  • Live Snapshot 需要用戶進行一致性操作:http://www.sebastien-han.fr/blog/2012/12/10/openstack-perform-consistent-snapshots/
  • 只支持虛擬機內置(全量)快照,不支持外置(增量)快照。這與當前快照的實現方式有關,因爲是通過 image 進行保存的。
  • 從 image boot 的虛機的快照以 Image 方式保存到 Glance 中,而非以 Cinder 卷方式保存。
  • 過程較長(需要先通過存儲快照,然後抽取並上傳至 Glance),網絡開銷大。

那爲什麼 Nova 不實現虛機的快照而只是系統盤的快照呢?據說,社區關於這個功能有過討論,討論的結果是不加入這個功能,原因主要有幾點:

  • 這應該是一種虛擬化技術的功能,不是雲計算平臺的功能。
  • openstack 由於底層要支持多種虛擬化的技術,某些虛擬化技術實現這種功能比較困難。
  • 創建的 VM state snapshot 會面臨 cpu feature 不兼容的問題。
  • 目前 libvirt 對 QEMU/KVM 虛機的外部快照的支持還不完善,即使更新到最新的 libvirt 版本,造成兼容性比較差。
 這裏 也有很多的討論。

KVM 介紹(8):使用 libvirt 遷移 QEMU/KVM 虛機和 Nova 虛機 [Nova Libvirt QEMU/KVM Live Migration]

 學習 KVM 的系列文章:


1. QEMU/KVM 遷移的概念

     遷移(migration)包括系統整體的遷移和某個工作負載的遷移。系統整理遷移,是將系統上所有軟件包括操作系統完全複製到另一個物理機硬件機器上。虛擬化環境中的遷移,可分爲靜態遷移(static migration,或者 冷遷移 cold migration,或者離線遷移 offline migration) 和 動態遷移 (live migration,或者 熱遷移 hot migration 或者 在線遷移 online migration)。靜態遷移和動態遷移的最大區別是,靜態遷移有明顯一段時間客戶機中的服務不可用,而動態遷移則沒有明顯的服務暫停時間。

    虛擬化環境中的靜態遷移也可以分爲兩種,一種是關閉客戶機後,將其硬盤鏡像複製到另一臺宿主機上然後恢復啓動起來,這種遷移不能保留客戶機中運行的工作負載;另一種是兩臺宿主機共享存儲系統,這時候的遷移可以保持客戶機遷移前的內存狀態和系統運行的工作負載。

    動態遷移,是指在保證客戶機上應用服務正常運行的同時,讓客戶機在不同的宿主機之間進行遷移,其邏輯步驟和前面的靜態遷移幾乎一直,有硬盤存儲和內存都複製的動態遷移,也有僅複製內存鏡像的動態遷移。不同的是,爲了保證遷移過程中客戶機服務的可用性,遷移過程只能有非常短暫的停機時間。動態遷移允許系統管理員將客戶機在不同物理機上遷移,同時不會斷開訪問客戶機中服務的客戶端或者應用程序的連接。一個成功的遷移,需要保證客戶機的內存、硬盤存儲和網絡連接在遷移到目的主機後任然保持不變,而且遷移的過程的服務暫停時間較短。

1.1 遷移效率的衡量

(1)整體遷移時間

(2)服務器停機時間:這時間是指源主機上的客戶機已經暫停服務,而目的主機上客戶機尚未恢復服務的時間。

(3)對服務性能的影響:客戶機遷移前後性能的影響,以及目的主機上其它服務的性能影響。

  其中,整體遷移時間受很多因素的影響,比如 Hypervisor 和遷移工具的種類、磁盤存儲的大小(是否需要複製磁盤鏡像)、內存大小及使用率、CPU 的性能和利用率、網絡帶寬大小及是否擁塞等,整體遷移時間一般分爲幾秒鐘到幾十分鐘不等。動態遷移的服務停機時間,也有這些因素的影響,往往在幾毫秒到幾百毫秒。而靜態遷移,其暫停時間較長。因此,靜態遷移一般適合於對服務可用性要求不高的場景,而動態遷移適合於對可用性要求高的場景。

動態遷移的應用場景包括:負載均衡、解除硬件依賴、節約能源 和異地遷移。

1.2 KVM 遷移的原理

1.2.1 靜態遷移

    對於靜態遷移,你可以在宿主機上某客戶機的 QEMU monitor 中,用 savevm my_tag 命令保存一個完整的客戶機鏡像快照,然後在宿主機中關閉或者暫停該客戶機,然後將該客戶機的鏡像文件複製到另一臺宿主機中,使用在源主機中啓動該客戶機時的命令來啓動複製過來的鏡像,在其 QEMU monitor 中 loadvm my_tag 命令恢復剛纔保存的快照即可完全加載保存快照時的客戶機狀態。savevm 命令可以保證完整的客戶機狀態,包括 CPU 狀態、內存、設備狀態、科協磁盤中的內存等。注意,這種方式需要 qcow2、qed 等格式的磁盤鏡像文件的支持。

1.2.2 動態遷移

    如果源宿主機和目的宿主機共享存儲系統,則只需要通過網絡發送客戶機的 vCPU 執行狀態、內存中的內容、虛機設備的狀態到目的主機上。否則,還需要將客戶機的磁盤存儲發到目的主機上。共享存儲系統指的是源和目的虛機的鏡像文件目錄是在一個共享的存儲上的。

    在基於共享存儲系統時,KVM 動態遷移的具體過程爲:

  1. 遷移開始時,客戶機依然在宿主機上運行,與此同時,客戶機的內存頁被傳輸到目的主機上。
  2. QEMU/KVM 會監控並記錄下遷移過程中所有已被傳輸的內存頁的任何修改,並在所有內存頁都傳輸完成後即開始傳輸在前面過程中內存頁的更改內容。
  3. QEMU/KVM 會估計遷移過程中的傳輸速度,當剩餘的內存數據量能夠在一個可以設定的時間週期(默認 30 毫秒)內傳輸完成時,QEMU/KVM 會關閉源宿主機上的客戶機,再將剩餘的數據量傳輸到目的主機上,最後傳輸過來的內存內容在目的宿主機上恢復客戶機的運行狀態。
  4. 至此,KVM 的動態遷移操作就完成了。遷移後的客戶機儘可能與遷移前一直,除非目的主機上缺少一些配置,比如網橋等。

注意,當客戶機中內存使用率非常大而且修改頻繁時,內存中數據不斷被修改的速度大於KVM能夠傳輸的內存速度時,動態遷移的過程是完成不了的,這時候只能靜態遷移。

1.3 使用命令行的方式做動態遷移

1.3.1 使用 NFS 共享存儲

(1)在源宿主機上掛載 NFS 上的客戶機鏡像,並啓動客戶機

mount my-nfs:/raw-images/ /mnt/ kvm /mnt/rh1.img -smp 2 -m 2048 -net nic -net tap

(2)在目的宿主機上也掛載鏡像目錄,並啓動一個客戶機用於接收動態遷移過來的內存內容

mount my-nfs:/raw-images/ /mnt/ kvm /mnt/rh1.img -smp 2 -m 2048 -net nic -net tap -incoming tcp:0:6666

注意:(1)NFS 掛載目錄必須一致 (2)“-incoming tcp:0:6666” 參數表示在 6666 端口建立一個 TCP socket 連接用於接收來自源主機的動態遷移的內容,其中 0 表示運行來自任何主機的連接。“-incoming“ 使 qemu-kvm 進程進入到監聽模式,而不是真正以命令行中的文件運行客戶機。

(3)在源宿主機的客戶機的 QEMU monitor 中,使用命令 ” migrate tcp:host2:6666" 即可進入動態遷移的流程。

1.3.2 不使用共享存儲的動態遷移

過程類似,包括使用相同backing file 的鏡像的客戶機遷移,以及完全不同鏡像文件的客戶機的遷移。唯一的區別是,migrate 命令中添加 “-b” 參數,它意味着傳輸塊設備。

1.3.3 其它 QEMU monitor migrate 命令

  • migrate_cancel:取消遷移
  • migrate_set_speed:設置最大遷移速度,單位是 bytes
  • migrate_set_downtime:設置最大允許的服務暫停時間,單位是 秒
  • info migrate:顯示遷移進度

2. OpenStack Nova QEMU/KVM 實例動態遷移的環境配置

除了直接拷貝磁盤鏡像文件的冷遷移,OpenStack 還支持下面幾種虛機熱遷移模式:

  • 不使用共享存儲時的塊實時遷移(Block live migration without shared storage)。這種模式不支持使用只讀設備比如 CD-ROM 和 Config Drive。塊實時遷移不需要 nova compute 節點都使用共享存儲。它使用 TCP 來將虛機的鏡像文件通過網絡拷貝到目的主機上,因此和共享存儲式的實時遷移相比,這種方式需要更長的時間。而且在遷移過程中,主機的性能包括網絡和 CPU 會下降。
  • 基於共享存儲的實時遷移 (Shared storage based live migration):兩個主機可以訪問共享的存儲。
  • 從卷啓動的虛機的實時遷移(Volume backed VM live migration)。這種遷移也是一種塊拷貝遷移。

實時遷移的過程並不複雜,複雜在於環境配置。

2.1 基礎環境配置

2.1.1 SSH 權限配置

這種方式需要配置源(compute1)和目的主機(compute2)之間能夠通過 SSH 相互訪問,以確保能通過 TCP 拷貝文件,已經可以通過 SSH 在目的主機建立目錄。

使用 nova 用戶在compute1 上執行操作:

複製代碼
usermod -s /bin/bash nova
su nova
mkdir -p -m 700 .ssh

#創建 config 文件如下
nova@compute2:~/.ssh$ cat config
Host * StrictHostKeyChecking no
UserKnownHostsFile=/dev/null #產生 key
ssh-keygen -f id_rsa -b 1024 -P "" cat id_rsa.pub >> authorized_keys

#將 id_rsa id_rsa.pub 拷貝到 compute2 上面
cat id_rsa.pub >> authorized_keys
複製代碼

使用 root 用戶在每個主機上進行操作:

root@compute1:/var/lib/nova/.ssh# chown -R nova:nova /var/lib/nova
root@compute1:/var/lib/nova/.ssh# chmod 700 /var/lib/nova/.ssh
root@compute1:/var/lib/nova/.ssh# chmod 600 /var/lib/nova/.ssh/authorized_keys

測試 SSH 無密碼訪問:

複製代碼
nova@compute1:~/.ssh$ ssh nova@compute2 ls
Warning: Permanently added 'compute2,192.168.1.29' (ECDSA) to the list of known hosts.
...

nova@compute2:~/.ssh$ ssh nova@compute1 ls
Warning: Permanently added 'compute1,192.168.1.15' (ECDSA) to the list of known hosts.
...
複製代碼

2.1.2 其它配置

 每個node 上的 DNS 或者 /etc/hosts,確保互聯互通。

2.2 Live migration 環境配置

2.2.1 libvirtd 配置

在 compute1 和 compute2 上做如下配置:

複製代碼
->Edit /etc/libvirt/libvirtd.conf
listen_tls = 0 listen_tcp = 1 auth_tcp = “none” ->Edit /etc/init/libvirt-bin.conf env libvirtd_opts="-d -l" 
->Edit /etc/default/libvirt-bin

  # options passed to libvirtd, add "-l" to listen on tcp
  libvirtd_opts="-d -l"

->Restart libvirtd
service libvirt-bin restart

root     12088     1  2 07:48 ?        00:00:00 /usr/sbin/libvirtd -d -l
複製代碼

做完上述操作後,可以使用如下命令來檢查是否設置正確:

複製代碼
root@compute2:~# virsh -c qemu+tcp://compute1/system list  --all  Id    Name                           State ---------------------------------------------------- 4 instance-0000000d              running 5 instance-00000006 running -     instance-00000005 shut off

root@compute1:~# virsh -c qemu+tcp://compute2/system list  --all  Id    Name                           State ----------------------------------------------------
複製代碼

Nova 設置:

->Edit /etc/nova/nova.conf, add following line:  
  [libvirt]
  block_migration_flag = VIR_MIGRATE_UNDEFINE_SOURCE,VIR_MIGRATE_PEER2PEER, VIR_MIGRATE_LIVE,VIR_MIGRATE_TUNNELLED,VIR_MIGRATE_NON_SHARED_INC
  live_migration_flag = VIR_MIGRATE_UNDEFINE_SOURCE, VIR_MIGRATE_PEER2PEER, VIR_MIGRATE_LIVE,VIR_MIGRATE_TUNNELLED
  live_migration_uri = qemu+tcp://%s/system

2.2.2 共享存儲 Live migration 環境配置

其實共享存儲的實時遷移配置的要求和塊拷貝的實時遷移的配置差不多,除了下面幾點:

  1. Hypervisor 要求:目前只有部分 Hypervisor 支持 live migraiton,可查詢該表
  2. 共享存儲:存放虛機文件的文件夾 NOVA-INST-DIR/instances/ (比如 /var/lib/nova/instances,該路徑可以由 state_path 配置變量來配置) 必須是掛載到共享存儲上的。當Nova 使用 RBD 作爲鏡像的backend時,這個要求不是必須的,具體見下面的說明。
  3. 必須在 nova.conf 中配置 vncserver_listen=0.0.0.0 (關於這個,社區認爲這個配置具有安全風險,會通過這個 ticket 來解決)
  4. 不使用默認配置的話,必須在每個 nova compute 上的 nova.conf 中配置相同的 instances_path 和 state_path 。
  5. 在 Kilo 版本之前,Nova 是默認不支持 live migriation 的。在做實時遷移之前,需要在 nova.conf 中做如下配置
live_migration_flag=VIR_MIGRATE_UNDEFINE_SOURCE,VIR_MIGRATE_PEER2PEER,VIR_MIGRATE_LIVE,VIR_MIGRATE_TUNNELLED

 注意:對於上面第二點,在 Kilo 版本中(前面版本的情況未知),當 Nova 使用 RBD 作爲 image backend 時,Nova 會認爲是在共享存儲上:

def check_instance_shared_storage_local(self, context, instance): """Check if instance files located on shared storage."""  if self.image_backend.backend().is_shared_block_storage(): return None

在 class Rbd(Image): 類中:

 @staticmethod
    def is_shared_block_storage():
        """True if the backend puts images on a shared block storage.""" return True

目前,只有 RBD 作爲 image backend 時,該函數才返回 true。對於其它類型的 backend,Nova 會在目的 host 上的 instance folder 創建一個臨時文件,再在源 host 上查看該文件,通過判斷是否該文件在共享存儲上來判斷是否在使用共享存儲。

常見問題:

(1)在源 host 上,出現  ”live Migration failure: operation failed: Failed to connect to remote libvirt URI qemu+tcp://compute2/system: unable to connect to server at 'compute2:16509': Connection refused“

其原因是 2.1.1 部分的 libvirt 設置不正確。

(2)在目的 host 上,出現 ”libvirtError: internal error: process exited while connecting to monitor: 2015-09-21T14:17:31.840109Z qemu-system-x86_64: -drive file=rbd:vms/6bef8898-85f9-429d-9250-9291a2e4e5ac_disk:id=cinder:key=AQDaoPpVEDJZHhAAu8fuMR/OxHUV90Fm1MhONQ==:auth_supported=cephx\;none:mon_host=9.115.251.194\:6789\;9.115.251.195\:6789\;9.115.251.218\:6789,if=none,id=drive-virtio-disk0,format=raw,cache=writeback,discard=unmap: could not open disk image rbd:vms/6bef8898-85f9-429d-9250-9291a2e4e5ac_disk:id=cinder:key=AQDaoPpVEDJZHhAAu8fuMR/OxHUV90Fm1MhONQ==:auth_supported=cephx\;none:mon_host=9.115.251.194\:6789\;9.115.251.195\:6789\;9.115.251.218\:6789: Could not open 'rbd:vms/6bef8898-85f9-429d-9250-9291a2e4e5ac_disk:id=cinder:key=AQDaoPpVEDJZHhAAu8fuMR/OxHUV90Fm1MhONQ==:auth_supported=cephx\;none:mon_host=9.115.251.194\:6789\;9.115.251.195\:6789\;9.115.251.218\:6789': Operation not permitted“

原因:目的 host 上的用戶操作 RBD 的權限設置不正確,檢查 secret 設置。

3. 遷移過程

3.0 Nova 有關遷移的命令

Nova 有三個與遷移有關的命令:migrate,live-migrate 和 resize。

Nova CLI REST API Action 行爲
nova live-migration  --block-migrate  --disk_over_commit 8352e969-0a25-4abf-978f-d9d0ec4de0cd compute2 os-migrateLive 塊拷貝動態遷移
nova live-migration  8352e969-0a25-4abf-978f-d9d0ec4de0cd compute2 os-migrateLive 共享存儲動態遷移
nova migrate  8352e969-0a25-4abf-978f-d9d0ec4de0cd migrate 靜態遷移
nova resize --poll 8352e969-0a25-4abf-978f-d9d0ec4de0cd 1 resize 靜態遷移並且改變 flavor
nova resize --poll 8352e969-0a25-4abf-978f-d9d0ec4de0cd resize 靜態遷移
nova resize-confirm 9eee079e-0353-44cb-b76c-ecf9be61890d confirmResize 確認 resize 使得完整操作得以完成 
nova resize-revert 9eee079e-0353-44cb-b76c-ecf9be61890d revertResize 取消 resize 使得操作被取消虛機回到原始狀態 

 

3.1 靜態遷移(migrate 或者 resize 不使用新的 flavor)

複製代碼
s1@controller:~$ nova migrate --poll 9eee079e-0353-44cb-b76c-ecf9be61890d

Server migrating... 100% complete
Finished
s1@controller:~$ nova list
+--------------------------------------+-------+---------------+------------+-------------+------------------------------------------+
| ID                                   | Name  | Status        | Task State | Power State | Networks                                 |
+--------------------------------------+-------+---------------+------------+-------------+------------------------------------------+
| 02699155-940f-4401-bc01-36220db80639 | vm10  | ACTIVE        | -          | Running     | demo-net2=10.0.10.17; demo-net=10.0.0.39 |
| 9eee079e-0353-44cb-b76c-ecf9be61890d | vm100 | VERIFY_RESIZE | -          | Running     | demo-net2=10.0.10.20                     |
+--------------------------------------+-------+---------------+------------+-------------+------------------------------------------+ s1@controller:~$ nova resize-confirm 9eee079e-0353-44cb-b76c-ecf9be61890d
s1@controller:~$ nova list
+--------------------------------------+-------+--------+------------+-------------+------------------------------------------+
| ID                                   | Name  | Status | Task State | Power State | Networks                                 |
+--------------------------------------+-------+--------+------------+-------------+------------------------------------------+
| 02699155-940f-4401-bc01-36220db80639 | vm10  | ACTIVE | -          | Running     | demo-net2=10.0.10.17; demo-net=10.0.0.39 |
| 9eee079e-0353-44cb-b76c-ecf9be61890d | vm100 | ACTIVE | -          | Running     | demo-net2=10.0.10.20                     |
+--------------------------------------+-------+--------+------------+-------------+------------------------------------------+ 
複製代碼


3.1.1 遷移過程

 直接使用流程圖來說明:

1. migrate 和 resize 都是執行靜態遷移。

2. 靜態遷移分爲三個階段:

(1)調用 Scheduler 算法選擇目的 node(步驟5),並通過 RPC 遠程調用 prep_resize 做些遷移前的準備工作

(2)在源主機上,調用 libvirt driver 做一系列操作:

  1. 使用 ssh 在目的 node 上建立虛機的鏡像文件的目錄
  2. 將虛機關機
  3. 斷開所有 volume connections
  4. 針對每一個非 swap 分區的磁盤,如果是 qcow2 格式,則執行 qemu-img merge 操作將稀疏文件和backing 文件合併成單個文件,並通過 “rysnc” 或者 “scp”命令將文件拷貝到目的 node 上
  5. 開始遷移需要的網絡工作

(3)通過 RPC,調用目的 node 上的 Nova 的 finish_resize 方法。該方法會在自己本機上設置網絡、結束網絡設置工作,並調用 libvirt driver 來:

  1. 創建 image
  2. 獲取 guest xml
  3. 創建 domain 和 network
  4. 需要的話啓動虛機

至此,虛機已經被拷貝到目的主機上了。接下來,用戶有兩個選擇:resize_confirm 和 resize_revert。

3.1.2 確認遷移 (resize_confirm)

遷移確認後,在源主機上,虛機的文件會被刪除,虛機被 undefine,虛機的 VIF 被從 OVS 上拔出,network filters 也會被刪除。

3.1.3 取消遷移 (resize_revert)

取消遷移的命令首先發到目的 node 上,依次 tear down network,刪除 domain,斷掉 volume connections,然後調用源主機上的方法來重建 network,刪除臨時文件,啓動 domain。這樣,虛機就會需要到 resize 之前的狀態。

 3.2 實時遷移 (Live migration)

可以 Nova client 的 live-migration 命令來做實時遷移,除了要被遷移的 虛機 和 目的 node 外,它可以帶兩個額外的參數:

  • “block-migrate“:使用的話,做 block copy live migration;不使用的話,做共享存儲的 live migration;
  • ”disk_over_commit“:使用的話,計算所有磁盤的 disk_size 來計算目的 node 上所需空間的大小;不使用的話,則計算磁盤的 virtual size。在下面的例子中,如果使用 disk_over_commit,那麼計算在目的主機上需要的空間的時候,該 disk 的 size 爲 324k,否則爲 1G:
複製代碼
root@compute1:/home/s1# qemu-img info /var/lib/nova/instances/8352e969-0a25-4abf-978f-d9d0ec4de0cd/disk.local
image: /var/lib/nova/instances/8352e969-0a25-4abf-978f-d9d0ec4de0cd/disk.local
file format: qcow2 virtual size: 1.0G (1073741824 bytes)
disk size: 324K
cluster_size: 65536 backing file: /var/lib/nova/instances/_base/ephemeral_1_default
Format specific information:
    compat: 1.1 lazy refcounts: false
複製代碼

REST API request body 實例: {"os-migrateLive": {"disk_over_commit": false, "block_migration": true, "host": "compute2"}}

實時遷移的主要步驟如下:

其過程也可以分爲三個階段:

3.2.1 實時遷移前的準備工作 (步驟 2 - 7)

Nova 通過 RPC 調用目的主機上 nova comute manager 的 pre_live_migration 方法,它依次:

(1)準備 instance 目錄:

      (1)創建 instance dir

(2)如果源和目的虛機不共享 instance path:獲取鏡像類型,爲每一個disk,如果不使用 backing file 的話則調用 “qemu-img create” 方法來創建空的磁盤鏡像;否則,依次創建空的 Ephemeral disk 和 Swap disk,以及從  Glance 中獲取 image 來創建 Root disk

(3)如果不是 block migration 而且 不 is_shared_instance_path,則 _fetch_instance_kernel_ramdisk

(2)調用 volumer driver api 爲每一個volume 建立目的主機和 volume 的連接

(3)調用 plug_vifs(instance, network_info) 將每一個 vif plug 到 OVS 上

(4)調用 network_api.setup_networks_on_host 方法,該方法會爲遷移過來的虛機準備 dhcp 和 gateway;

(5)調用 libvirt driver 的 ensure_filtering_rules_for_instance 方法去準備 network filters。

3.2.2 調用 libvirt API 開始遷移虛機 (步驟 8 - 9)

  這部分的實現在 libvirt driver 代碼中。因爲 libvirt 的一個 bug (說明在這裏),當 libvirt 帶有 VIR_DOMAIN_XML_MIGRATABLE flag 時,Nova 會調用 libvirt 的 virDomainMigrateToURI2 API,否則調用 virDomainMigrateToURI API。

首先比較一下 block live migration 和 live migration 的 flags 的區別:

#nova block live migration flags:VIR_MIGRATE_UNDEFINE_SOURCE, VIR_MIGRATE_PEER2PEER, VIR_MIGRATE_LIVE, VIR_MIGRATE_TUNNELLED, VIR_MIGRATE_NON_SHARED_INC #nova live migration flags:      VIR_MIGRATE_UNDEFINE_SOURCE, VIR_MIGRATE_PEER2PEER, VIR_MIGRATE_LIVE, VIR_MIGRATE_TUNNELLED 

各自的含義如下:

  • VIR_MIGRATE_UNDEFINE_SOURCE: 遷移完成後,將源虛機刪除掉。(If the migration is successful, undefine the domain on the source host.)
  • VIR_MIGRATE_PEER2PEER 和 VIR_MIGRATE_TUNNELLED 一起使用:點對點遷移,必須指定目的 URI,QEMU在兩者之間建立 TCP Tunnel 用於數據傳輸
  • VIR_MIGRATE_LIVE: 執行 live migration,不要停機 (Do not pause the VM during migration)
  • VIR_MIGRATE_NON_SHARED_INC: 使用非共享存儲式遷移即 block migration (Migration with non-shared storage with incremental disk copy)

再看看兩個 API 的參數:

複製代碼
int virDomainMigrateToURI2        (virDomainPtr domain, const char * dconnuri, # 格式爲 qemu+tcp:///system const char * miguri, #爲 none const char * dxml, #指定遷移後的虛機的 XML。Nova 對 “/devices/graphics” 部分做了一點點更改。 unsigned long flags, # nova.conf 中的配置 const char * dname, #none unsigned long bandwidth) # 由 CONF.libvirt.live_migration_bandwidth 指定,默認爲 0 表示由 libvirt 自己選擇合適的值
複製代碼

如果 libvirt 不帶 VIR_DOMAIN_XML_MIGRATABLE flag,則調用的 API 是:

int virDomainMigrateToURI        (virDomainPtr domain, const char * duri,
                     unsigned long flags, const char * dname,
                     unsigned long bandwidth)

可見,兩個 API 唯一的區別是不能指定新的虛機使用的 XML 配置。這時候你必須手動配置 VNC 或者 SPICE 地址爲 0.0.0.0 or :: (接收全部)或者 127.0.0.1 or ::1 (只限本機)。

調用 API 後,接下來就是等待其完成。這其中的過程應該主要包括:

(1)根據傳入的 domain xml,啓動一個虛機,它處於等待 TCP incoming 狀態

(2)從源 node 上將 domain 的數據傳過來

(3)快完成時,關閉源 node 上的虛機,傳輸最後一次數據,打開目的 node 上的虛機

(4)將源 node 上的虛機刪除

Nova 每個0.5 秒檢查源虛機的狀態,直到它被刪除。

遷移完成後,需要執行後續的操作(_post_live_migration)。

3.2.3 遷移完成後在源和目的主機上的後續操作(步驟 10 -29)

在源主機上,依次執行下面的操作:

  1. 調用 volume driver 的 disconnect_volume 方法和 terminate_connection 方法,斷開主機和所有 volume 的連接
  2. 調用 firewall driver 的 unfilter_instance 方法,刪除 domain 的 iptables 中的所有 security group ingress 規則 (self.iptables.ipv4['filter'].remove_chain(chain_name))
  3. 調用 network api 的 migrate_instance_start 方法,開始將網絡從源主機上遷移到目的主機上(實際上沒做什麼事情,只是 pass)
  4. 調用 vif driver 的 unplug 方法,將每個 vif 從 OVS 上刪除                                                                                                                                      
    brctl delif qbr59cfa0b8-2f qvb59cfa0b8-2f 
    ip link set qbr59cfa0b8-2f down 
    brctl delbr qbr59cfa0b8-2f 
    ovs-vsctl --timeout=120 -- --if-exists del-port br-int qvo59cfa0b8-2f 
    ip link delete qvo59cfa0b8-2f
  5. 通過 RPC 調用目的主機上的 nova manager 的 post_live_migration_at_destination 方法,該方法會:
    1. 調用 network api 的 setup_networks_on_host 方法來設置網絡(處理 vpn,dhcp,gateway)
    2. 調用 network api 的 migrate_instance_finish 方法
    3. 調用 libvirt driver 的 post_live_migration_at_destination方法,它會調用 libvirt _conn.listDefinedDomains 方法查看遷移過來的主機的 domain是否存在;不存在的話,生成其 xml,然後調用 libvirt API  _conn.defineXML(xml) 去定義該 domain。
    4. 將新的 domain 數據更新到數據庫(包括新的 host,power_state,vm_state,node)
    5. 調用 network api 的 setup_networks_on_host 方法 (不理解爲什麼重複上面第1步)
  6. 調用 libvirt driver 的 driver.cleanup 方法去 _unplug_vifs (如果上面第四步失敗,則再次嘗試刪除所有 vif 相關的 bridge 和 OVS 連接),firewall_driver.unfilter_instance (和上面第2步重複了),_disconnect_volume(斷開 domain 和 所有 volume 的連接),_delete_instance_files (刪除 domain 相關的文件),_undefine_domain (刪除 domain)
  7. 調用 network_api.setup_networks_on_host 去 tear down networks on source host
  8. 至此,live migration 完全結束。

3.2.4 遷移過程中失敗時的回滾

 遷移的三個步驟中,前面第一個和第二個步驟中出現失敗的話,會調用 _rollback_live_migration 啓動回滾操作。該方法

(1)將虛機的狀態由 migrating 變爲 running。

(2)調用 network_api.setup_networks_on_host 方法重做源主機上的網絡設置

(3)通過 RPC 調用,去目的主機上將準備過程中建立的 volume 連接刪除。

(4)通過 RPC 調用,去目的主機上調用 compute_rpcapi.rollback_live_migration_at_destination 函數,該方法會

(1)調用 network_api.setup_networks_on_host 方法去 tear down networks on destination host

(2)調用 libvirt driver 的 driver.rollback_live_migration_at_destination 方法,它會將 domain 刪除,並且清理它所使用的資源,包括  unplug vif,firewall_driver.unfilter_instance,_disconnect_volume, _delete_instance_files, _undefine_domain。

3.2.5 測試

環境:準備兩個虛機 vm1 和 vm2,操作系統爲 cirros。打算將 vm1 遷移到另一個node 上。在 vm2 上查看 vm1 在遷移過程中的狀態。

遷移前:在 vm1 中運行 “ping vm2”,並在 vm2 中 ssh 連接到 vm1。

結果:vm1 遷移過程中,vm2 上 ssh 的連接沒有中斷,vm1 中的 ping 命令繼續執行。在另一次測試結果中,vm2 ping vm1 在整個遷移過程中 time 出現了一次 2ms 的時間增加。

3.3 遇到過的問題

3.3.1 apparmor

將虛機從 compute1 遷移到 compute2 成功,再從 compute2 遷移到 compute1 失敗,報錯如下:

An error occurred trying to live migrate. Falling back to legacy live migrate flow. Error: unsupported configuration: Unable to find security driver for label apparmor

經比較遷移前後的虛機的 xml,發現 compute2 上的虛機的 xml 多了一項: 。

分別在 compute 1 和 2 上運行 “virsh capabilities”,發現 compute1 沒有使用 apparmor,而 compute2 使用了 apparmor。

複製代碼
#compute 1
      none
      0
     #compute2 上  
      apparmor
      0
    
複製代碼

    最簡單的方法是在兩個 node 上都 disable apparmor(在 /etc/libvirt/qemu.conf 中添加 ‘security_driver = “none”  然後重啓 libvirtd),然後 destroy/start 虛機後,它的 xml 配置中的 apparmor 就沒有了。這篇文章 詳細介紹了 apparmor。

3.3.2 當虛機是 boot from volume 時,live migration 失敗。

報錯:

Command: iscsiadm -m node -T iqn.2010-10.org.openstack:volume-26446902-5a56-4c79-b839-a8e13a66dc7a -p 10.0.2.41:3260 --rescan
Exit code: 21 Stdout: u'' Stderr: u'iscsiadm: No session found.\n' to caller

原因是 cinder 代碼中有 bug,導致目的主機無法建立和 volume 的連接。fix 在這裏


參考文檔:

https://www.mirantis.com/blog/tutorial-openstack-live-migration-with-kvm-hypervisor-and-nfs-shared-storage/

http://www.sebastien-han.fr/blog/2015/01/06/openstack-configure-vm-migrate-nova-ssh/

KVM 原理技術 實戰與原理解析 任永傑、單海濤著

OpenStack 官網

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章