KVM 介紹(1):簡介及安裝
http://www.cnblogs.com/sammyliu/p/4543110.html
學習 KVM 的系列文章:
- (1)介紹和安裝
- (2)CPU 和 內存虛擬化
- (3)I/O QEMU 全虛擬化和準虛擬化(Para-virtulizaiton)
- (4)I/O PCI/PCIe設備直接分配和 SR-IOV
- (5)libvirt 介紹
- (6)Nova 通過 libvirt 管理 QEMU/KVM 虛機
- (7)快照 (snapshot)
- (8)遷移 (migration)
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 架構
- 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 請求的原理:
QEMU-KVM:
KVM:
- 首先初始化內部的數據結構;
- 做好準備後,KVM 模塊檢測當前的 CPU,然後打開 CPU 控制及存取 CR4 的虛擬化模式開關,並通過執行 VMXON 指令將宿主操作系統置於虛擬化模式的根模式;
- 最後,KVM 模塊創建特殊設備文件 /dev/kvm 並等待來自用戶空間的指令。
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 安裝
- 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 通過編程方式來創建虛機 (後面會介紹)
KVM 介紹(2):CPU 和內存虛擬化
學習 KVM 的系列文章:
- (1)介紹和安裝
- (2)CPU 和 內存虛擬化
- (3)I/O QEMU 全虛擬化和準虛擬化(Para-virtulizaiton)
- (4)I/O PCI/PCIe設備直接分配和 SR-IOV
- (5)libvirt 介紹
- (6)Nova 通過 libvirt 管理 QEMU/KVM 虛機
- (7)快照 (snapshot)
- (8)遷移 (migration)
1. 爲什麼需要 CPU 虛擬化
- 操作系統(內核)需要直接訪問硬件和內存,因此它的代碼需要運行在最高運行級別 Ring0上,這樣它可以使用特權指令,控制中斷、修改頁表、訪問設備等等。
- 應用程序的代碼運行在最低運行級別上ring3上,不能做受控操作。如果要做,比如要訪問磁盤,寫文件,那就要通過執行系統調用(函數),執行系統調用的時候,CPU的運行級別會發生從ring3到ring0的切換,並跳轉到系統調用對應的內核代碼位置執行,這樣內核就爲你完成了設備訪問,完成之後再從ring0返回ring3。這個過程也稱作用戶態和內核態的切換。
1.1 基於二進制翻譯的全虛擬化(Full Virtualization with Binary Translation)
1.2. 超虛擬化(或者半虛擬化/操作系統輔助虛擬化 Paravirtualization)
1.3. 硬件輔助的全虛擬化
|
利用二進制翻譯的全虛擬化
|
硬件輔助虛擬化
|
操作系統協助/半虛擬化
|
實現技術 |
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,需要經歷兩個過程:
- 客戶機線程調度到客戶機物理CPU 即 KVM vCPU,該調度由客戶機操作系統負責,每個客戶機操作系統的實現方式不同。在 KVM 上,vCPU 在客戶機系統看起來就像是物理 CPU,因此其調度方法也沒有什麼不同。
- 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=]
[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 數目的分配方法
- 不是客戶機的 vCPU 越多,其性能就越好,因爲線程切換會耗費大量的時間;應該根據負載需要分配最少的 vCPU。
- 主機上的客戶機的 vCPU 總數不應該超過物理 CPU 內核總數。不超過的話,就不存在 CPU 競爭,每個 vCPU 線程在一個物理 CPU 核上被執行;超過的話,會出現部分線程等待 CPU 以及一個 CPU 核上的線程之間的切換,這會有 overhead。
- 將負載分爲計算負載和 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 內存虛擬化的概念
- 軟件方式:通過軟件實現內存地址的翻譯,比如 Shadow page table (影子頁表)技術
- 硬件實現:基於 CPU 的輔助虛擬化功能,比如 AMD 的 NPT 和 Intel 的 EPT 技術
2.2 KVM 內存虛擬化
- 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]
學習 KVM 的系列文章:
- (1)介紹和安裝
- (2)CPU 和 內存虛擬化
- (3)I/O QEMU 全虛擬化和準虛擬化(Para-virtulizaiton)
- (4)I/O PCI/PCIe設備直接分配和 SR-IOV
- (5)libvirt 介紹
- (6)Nova 通過 libvirt 管理 QEMU/KVM 虛機
- (7)快照 (snapshot)
- (8)遷移 (migration)
1. 全虛擬化 I/O 設備
1.1 原理
- 客戶機的設備驅動程序發起 I/O 請求操作請求
- KVM 模塊中的 I/O 操作捕獲代碼攔截這次 I/O 請求
- 經過處理後將本次 I/O 請求的信息放到 I/O 共享頁 (sharing page),並通知用戶空間的 QEMU 程序。
- QEMU 程序獲得 I/O 操作的具體信息之後,交由硬件模擬代碼來模擬出本次 I/O 操作。
- 完成之後,QEMU 將結果放回 I/O 共享頁,並通知 KMV 模塊中的 I/O 操作捕獲代碼。
- KVM 模塊的捕獲代碼讀取 I/O 共享頁中的操作結果,並把結果放回客戶機。
1.2 QEMU 模擬網卡的實現
Qemu 純軟件的方式來模擬I/O設備,其中包括經常使用的網卡設備。Guest OS啓動命令中沒有傳入的網絡配置時,QEMU默認分配 rtl8139 類型的虛擬網卡類型,使用的是默認用戶配置模式,這時候由於沒有具體的網絡模式的配置,Guest的網絡功能是有限的。 全虛擬化情況下,KVM虛機可以選擇的網絡模式包括:
- 默認用戶模式(User);
- 基於網橋(Bridge)的模式;
- 基於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的收發包的流程。
如圖中所示,紅色箭頭表示數據報文的入方向,步驟:
- 網絡數據從 Host 上的物理網卡接收,到達網橋;
- 由於 eth0 與 tap1 均加入網橋中,根據二層轉發原則,br0 將數據從 tap1 口轉發出去,即數據由 Tap設備接收;
- Tap 設備通知對應的 fd 數據可讀;
- fd 的讀動作通過 tap 設備的字符設備驅動將數據拷貝到用戶空間,完成數據報文的前端接收。
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 模擬。
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
2.1 virtio 的架構
2.2 Virtio 在 Linux 中的實現
- 前端驅動:客戶機中安裝的驅動程序模塊
- 後端驅動:在 QEMU 中實現,調用主機上的物理設備,或者完全由軟件實現。
- virtio 層:虛擬隊列接口,從概念上連接前端驅動和後端驅動。驅動可以根據需要使用不同數目的隊列。比如 virtio-net 使用兩個隊列,virtio-block只使用一個隊列。該隊列是虛擬的,實際上是使用 virtio-ring 來實現的。
- virtio-ring:實現虛擬隊列的環形緩衝區
- 塊設備(如磁盤)
- 網絡設備
- PCI 設備
- 氣球驅動程序(動態管理客戶機內存使用情況)
- 控制檯驅動程序
(1)virtio-net 的原理:
- 多個虛機共享主機網卡 eth0
- QEMU 使用標準的 tun/tap 將虛機的網絡橋接到主機網卡上
- 每個虛機看起來有一個直接連接到主機PCI總線上的私有 virtio 網絡設備
- 需要在虛機裏面安裝 virtio驅動
(2)virtio-net 的流程:
- 優點:更高的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 (氣球)技術可以在客戶機運行時動態地調整它所佔用的宿主機內存資源,而不需要關閉客戶機。該技術能夠:
- 當宿主機內存緊張時,可以請求客戶機回收利用已分配給客戶機的部分內存,客戶機就會釋放部分空閒內存。若其內存空間不足,可能還會回收部分使用中的內存,可能會將部分內存換到交換分區中。
- 當客戶機內存不足時,也可以讓客戶機的內存氣球壓縮,釋放出內存氣球中的部分內存,讓客戶機使用更多的內存。
- KVM 發送請求給 VM 讓其歸還一定數量的內存給KVM。
- VM 的 virtio_balloon 驅動接到該請求。
- VM 的驅動是客戶機的內存氣球膨脹,氣球中的內存就不能被客戶機使用。
- VM 的操作系統歸還氣球中的內存給VMM
- KVM 可以將得到的內存分配到任何需要的地方。
- KM 也可以將內存返還到客戶機中。
優勢和不足:
優勢 | 不足 |
|
|
在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)
- 網絡流量非常大
- 虛機同時有非常多的網絡連接,包括虛擬機之間的、虛機到主機的、虛機到外部系統的等
- virtio 隊列的數目和虛機的虛擬CPU數目相同。這是因爲多隊列能夠使得一個隊列獨佔一個虛擬CPU。
ethtool -L eth0 combined M ( 1 <= M <= N)
2.8 Windows 客戶機的 virtio 前端驅動
- http://linux.web.cern.ch/linux/centos7/docs/rhel/Red_Hat_Enterprise_Linux-7-Virtualization_Tuning_and_Optimization_Guide-en-US.pdf
- http://toast.djw.org.uk/qemu.html
- KVM 官方文檔
- KVM 虛擬化技術實戰與解析 任永傑、單海濤 著
- RedHat Linux 6 官方文檔
- http://www.slideshare.net 中關於 KVM 的一些文檔
- http://www.linux-kvm.org/page/Multiqueue
- 以及部分來自於網絡,比如 http://smilejay.com/2012/11/use-ballooning-in-kvm/
KVM 介紹(4):I/O 設備直接分配和 SR-IOV [KVM PCI/PCIe Pass-Through SR-IOV]
學習 KVM 的系列文章:
- (1)介紹和安裝
- (2)CPU 和 內存虛擬化
- (3)I/O QEMU 全虛擬化和準虛擬化(Para-virtulizaiton)
- (4)I/O PCI/PCIe設備直接分配和 SR-IOV
- (5)libvirt 介紹
- (6)Nova 通過 libvirt 管理 QEMU/KVM 虛機
- (7)快照 (snapshot)
- (8)遷移 (migration)
本文將分析 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 原理
硬盤直接分配:
- 一般 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 原理
- 物理功能(Physical Functions,PF):這是完整的帶有 SR-IOV 能力的PCIe 設備。PF 能像普通 PCI 設備那樣被發現、管理和配置。
- 虛擬功能(Virtual Functions,VF):簡單的 PCIe 功能,它只能處理I/O。每個 VF 都是從 PF 中分離出來的。每個物理硬件都有一個 VF 數目的限制。一個 PF,能被虛擬成多個 VF 用於分配給多個虛擬機。
光纖卡 SR-IOV 的例子:
2.2 SR-IOV 的條件
- 需要 CPU 支持 Intel VT-x 和 VT-D (或者 AMD 的 SVM 和 IOMMU)
- 需要有支持 SR-IOV 規範的設備:目前這種設備較多,比如Intel的很多中高端網卡等。
- 需要 QEMU/KAM 的支持。
- 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 優勢和不足
優勢 | 不足 |
|
|
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
- 每種方案都有優勢和不足,在特定環境下其性能有可能反而下降,因此在生產環境中使用各種虛擬化方式前需要經過完整測試
其它參考資料:
- RedHat Linux 6 官方文檔
- KVM 官方文檔
- KVM 虛擬化技術實戰與解析 任永傑、單海濤 著
- KVM 虛擬化技術在 AMD 平臺上的實現
KVM 介紹(5):libvirt 介紹 [ Libvrit for KVM/QEMU ]
學習 KVM 的系列文章:
- (1)介紹和安裝
- (2)CPU 和 內存虛擬化
- (3)I/O QEMU 全虛擬化和準虛擬化(Para-virtulizaiton)
- (4)I/O PCI/PCIe設備直接分配和 SR-IOV
- (5)libvirt 介紹
- (6)Nova 通過 libvirt 管理 QEMU/KVM 虛機
- (7)快照 (snapshot)
- (8)遷移 (migration)
1. Libvirt 是什麼
爲什麼需要Libvirt?
- Hypervisor 比如 qemu-kvm 的命令行虛擬機管理工具參數衆多,難於使用。
- Hypervisor 種類衆多,沒有統一的編程接口來管理它們,這對雲環境來說非常重要。
- 沒有統一的方式來方便地定義虛擬機相關的各種可管理對象。
Libvirt提供了什麼?
- 它提供統一、穩定、開放的源代碼的應用程序接口(API)、守護進程 (libvirtd)和和一個默認命令行管理工具(virsh)。
- 它提供了對虛擬化客戶機和它的虛擬化設備、網絡和存儲的管理。
- 它提供了一套較爲穩定的C語言應用程序接口。目前,在其他一些流行的編程語言中也提供了對libvirt的綁定,在Python、Perl、Java、Ruby、PHP、OCaml等高級編程語言中已經有libvirt的程序庫可以直接使用。
- 它對多種不同的 Hypervisor 的支持是通過一種基於驅動程序的架構來實現的。libvirt 對不同的 Hypervisor 提供了不同的驅動,包括 Xen 的驅動,對QEMU/KVM 有 QEMU 驅動,VMware 驅動等。在 libvirt 源代碼中,可以很容易找到 qemu_driver.c、xen_driver.c、xenapi_driver.c、vmware_driver.c、vbox_driver.c 這樣的驅動程序源代碼文件。
- 它作爲中間適配層,讓底層 Hypervisor 對上層用戶空間的管理工具是可以做到完全透明的,因爲 libvirt 屏蔽了底層各種 Hypervisor 的細節,爲上層管理工具提供了一個統一的、較穩定的接口(API)。
- 它使用 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
virDomainAttachDevicevirDomainAttachDeviceFlags 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
virDomainSetAutostartvirDomainShutdownFlags virDomainSuspend 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)或者半虛擬化驅動都使用 元素來定義。 方式:
。其中:
(1)”volume“ 類型的 disk 'volume' device='disk'> 'qemu' type='raw'/> 'blk-pool0' volume='blk-pool0-vol0'/> 'hdk' bus='ide'/> 'file' snapshot='external'> "tap" type="aio" cache="default"/> '/var/lib/xen/images/fv0' startupPolicy='optional' /> 'hda' bus='ide'/> 'block' device='cdrom'> 'qemu' type='raw'/> 'hdd' bus='ide' tray='open'/> <readonly/> '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'/> 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" /> host-bridge "bridge"/> "br0"/> 'passthrough'> <interface dev='eth10'/> <interface dev='eth11'/> <interface dev='eth12'/> <interface dev='eth13'/> <interface dev='eth14'/> '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 驅動包括:
- LXC - Linux Containers
- OpenVZ
- QEMU
- Test - Used for testing
- UML - User Mode Linux
- VirtualBox
- VMware ESX
- VMware Workstation/Player
- Xen
- Microsoft Hyper-V
- IBM PowerVM (phyp)
- Parallels
- Bhyve - The BSD Hypervisor
1.4 Libvirt 的 Python 綁定
#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 架構
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 的使用。
- 定義虛機的基本配置,包括 vCPU、內存、磁盤或者cdrom以及啓動順序,生成 xml 配置,調用 virDomainCreateXML API 啓動一個虛機
- 使用 Domain 相關的 API 來管理虛機的生命週期。我的這篇文章有虛機生命週期的詳細介紹。
- 添加磁盤:定義一個 disk 的 xml 配置,使用 virDomainAttachDevice API 將它掛載到虛機上。如果不是本地的源磁盤,需要提前準備好。
- 添加interface:使用 Network API 定義一個虛擬網絡(需要提前準備好物理網絡),然後定義一個 interface 的 XML 配置,使用 virDomainAttachDevice API 將它加到虛機。
- 按照需要,重複2、3、4步驟。
KVM 介紹(6):Nova 通過 libvirt 管理 QEMU/KVM 虛機 [Nova Libvirt QEMU/KVM Domain]
學習 KVM 的系列文章:
- (1)介紹和安裝
- (2)CPU 和 內存虛擬化
- (3)I/O QEMU 全虛擬化和準虛擬化(Para-virtulizaiton)
- (4)I/O PCI/PCIe設備直接分配和 SR-IOV
- (5)libvirt 介紹
- (6)Nova 通過 libvirt 管理 QEMU/KVM 虛機
- (7)快照 (snapshot)
- (8)遷移 (migration)
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 中的快照
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。其過程如下:
- 找到虛機的 root disk (vda 或者 hda)。
- 在 CONF.libvirt.snapshots_directory 指定的文件夾(默認爲 /var/lib/nova/instances/snapshots)中創建一個臨時文件夾,在其中創建一個 qcow2 格式的 delta 文件,其文件名爲 uuid 字符串,該文件的 backing file 和 root disk 文件的 backing file 相同 (下面步驟 a)。
- 調用 virDomainGetXMLDesc 來保存 domain 的 xml 配置。
- 調用 virDomainBlockJobAbort 來停止對 root disk 的活動塊操作 (Cancel the active block job on the given disk)。
- 調用 virDomainUndefine 來將 domain 變爲 transimit 類型的,這是因爲 BlockRebase API 不能針對 Persistent domain 調用。
- 調用 virDomainBlockRebase 來將 root disk image 文件中不同的數據拷貝到 delta disk file 中。(下面步驟 b)
- 步驟 6 是一個持續的過程,因爲可能有應用正在向該磁盤寫數據。Nova 每隔 0.5 秒調用 virDomainBlockJobInfo API 來檢查拷貝是否結束。
- 拷貝結束後,調用 virDomainBlockJobAbort 來終止數據拷貝。
- 調用 virDomainDefineXML 將domain 由 transimisit 該回到 persistent。
- 調用 qemu-img convert 命令將 delta image 文件和 backing file 變爲一個 qcow2 文件 (下面步驟 c)
- 將 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 狀態時:
- detach PCI devices
- detach SR-IOV devices
- 調用 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)介紹和安裝
- (2)CPU 和 內存虛擬化
- (3)I/O QEMU 全虛擬化和準虛擬化(Para-virtulizaiton)
- (4)I/O PCI/PCIe設備直接分配和 SR-IOV
- (5)libvirt 介紹
- (6)Nova 通過 libvirt 管理 QEMU/KVM 虛機
- (7)快照 (snapshot)
- (8)遷移 (migration)
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 動態遷移的具體過程爲:
- 遷移開始時,客戶機依然在宿主機上運行,與此同時,客戶機的內存頁被傳輸到目的主機上。
- QEMU/KVM 會監控並記錄下遷移過程中所有已被傳輸的內存頁的任何修改,並在所有內存頁都傳輸完成後即開始傳輸在前面過程中內存頁的更改內容。
- QEMU/KVM 會估計遷移過程中的傳輸速度,當剩餘的內存數據量能夠在一個可以設定的時間週期(默認 30 毫秒)內傳輸完成時,QEMU/KVM 會關閉源宿主機上的客戶機,再將剩餘的數據量傳輸到目的主機上,最後傳輸過來的內存內容在目的宿主機上恢復客戶機的運行狀態。
- 至此,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 環境配置
其實共享存儲的實時遷移配置的要求和塊拷貝的實時遷移的配置差不多,除了下面幾點:
- Hypervisor 要求:目前只有部分 Hypervisor 支持 live migraiton,可查詢該表。
- 共享存儲:存放虛機文件的文件夾 NOVA-INST-DIR/instances/ (比如 /var/lib/nova/instances,該路徑可以由 state_path 配置變量來配置) 必須是掛載到共享存儲上的。當Nova 使用 RBD 作爲鏡像的backend時,這個要求不是必須的,具體見下面的說明。
- 必須在 nova.conf 中配置 vncserver_listen=0.0.0.0 (關於這個,社區認爲這個配置具有安全風險,會通過這個 ticket 來解決)
- 不使用默認配置的話,必須在每個 nova compute 上的 nova.conf 中配置相同的 instances_path 和 state_path 。
- 在 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 做一系列操作:
- 使用 ssh 在目的 node 上建立虛機的鏡像文件的目錄
- 將虛機關機
- 斷開所有 volume connections
- 針對每一個非 swap 分區的磁盤,如果是 qcow2 格式,則執行 qemu-img merge 操作將稀疏文件和backing 文件合併成單個文件,並通過 “rysnc” 或者 “scp”命令將文件拷貝到目的 node 上
- 開始遷移需要的網絡工作
(3)通過 RPC,調用目的 node 上的 Nova 的 finish_resize 方法。該方法會在自己本機上設置網絡、結束網絡設置工作,並調用 libvirt driver 來:
- 創建 image
- 獲取 guest xml
- 創建 domain 和 network
- 需要的話啓動虛機
至此,虛機已經被拷貝到目的主機上了。接下來,用戶有兩個選擇: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)
在源主機上,依次執行下面的操作:
- 調用 volume driver 的 disconnect_volume 方法和 terminate_connection 方法,斷開主機和所有 volume 的連接
- 調用 firewall driver 的 unfilter_instance 方法,刪除 domain 的 iptables 中的所有 security group ingress 規則 (self.iptables.ipv4['filter'].remove_chain(chain_name))
- 調用 network api 的 migrate_instance_start 方法,開始將網絡從源主機上遷移到目的主機上(實際上沒做什麼事情,只是 pass)
- 調用 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
- 通過 RPC 調用目的主機上的 nova manager 的 post_live_migration_at_destination 方法,該方法會:
- 調用 network api 的 setup_networks_on_host 方法來設置網絡(處理 vpn,dhcp,gateway)
- 調用 network api 的 migrate_instance_finish 方法
- 調用 libvirt driver 的 post_live_migration_at_destination方法,它會調用 libvirt _conn.listDefinedDomains 方法查看遷移過來的主機的 domain是否存在;不存在的話,生成其 xml,然後調用 libvirt API _conn.defineXML(xml) 去定義該 domain。
- 將新的 domain 數據更新到數據庫(包括新的 host,power_state,vm_state,node)
- 調用 network api 的 setup_networks_on_host 方法 (不理解爲什麼重複上面第1步)
- 調用 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)
- 調用 network_api.setup_networks_on_host 去 tear down networks on source host
- 至此,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 官網