Wirtualizacja z KVM
KVM, czyli Kernel-based Virtual Machine to rozwiązanie pozwalające na wirtualizację z wykorzystaniem *hypervisora* działającego jako moduł jądra Linuksa, przez co stawia go na równi z rozwiązaniami typu VMware ESX, jednakże zupełnie za darmo. Było to jedno z kryteriów, jakie musiałem wziąć pod uwagę przy wyborze narzędzi dla stworzenia kilku maszyn wirtualnych w mojej obecnej pracy.
Założenia
Otrzymałem zadanie, by stworzyć platformę pod maszyny wirtualne, na potrzeby projektu, w którym uczestniczę. Mają to być trzy maszyny wirtualne, z czego jedna z nich służyłaby jako serwer HTTP z aplikacją napisaną w Django. Całość ma być (na razie) uruchomiona na zwykłym pececie z Core 2 Duo na pokładzie, 2GB ramu oraz dyskiem SATA 160GB.
Każda maszyna powinna mieć wyjście na świat, co w moim przypadku oznaczało zwyczajnie wyjście na sieć lokalną, oraz zapewnić usługi FTP oraz SSH.
System
Jako system, który będzie nadzorcą (zwanym dalej hostem) dla maszyn wirtualnych, wybrałem dystrybucję Linuksa, którą znam najlepiej, czyli Gentoo. Oczywiście nic nie stoi na przeszkodzie, żeby zainstalować dowolną inną dystrybucję, byleby dość dobrze ją znać, ponieważ będzie potrzeba skompilowania qemu-kvm. Na gościach (ang. Guest), czyli maszynach wirtualnych, które również miały być systemami uniksowymi, również wybrałem Gentoo.
Instalacja Hosta
Sprawdzenie sprzętowego wsparcia
Żeby w pełni wykorzystać fizyczną maszynę, należy najpierw sprawdzić, czy procesor posiada sprzętowe wsparcie dla wirtualizacji. Dla procesorów AMD jest to technologia AMD-V, natomiast dla procesorów Intel - VT-x. To, czy procesor posiada wsparcie dla wirtualizacji można łatwo sprawdzić:
root@puppetmaster ~ # egrep '^flags.*(vmx|svm)' /proc/cpuinfo
Powinno pokazać się coś na kształt:
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 nx lm constant_tsc arch_perfmon pebs bts pni dtes64 monitor ds_cpl vmx est tm2 ssse3 cx16 xtpr pdcm lahf_lm tpr_shadow
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 nx lm constant_tsc arch_perfmon pebs bts pni dtes64 monitor ds_cpl vmx est tm2 ssse3 cx16 xtpr pdcm lahf_lm tpr_shadow
Jeżeli komenda ta nic nie wypisała, a procesor na pewno wspiera wirtualizację, oznacza to, że trzeba włączyć ją w Biosie. Może to być kłopotliwe w płytach, które takowej opcji nie posiadają. Wówczas może się okazać, że trzeba będzie podnieść wersję Biosu (oczywiście, pod warunkiem, że producent dodał taką opcję do nowszej wersji Biosu). Producent mojej płyty głównej (Gigabyte GA-EP35-DS3) na szczęście dołączył w kolejnej wersji Biosu odpowiednią opcję włączenia wirtualizacji procesora.
Teoretycznie można uruchomić KVM bez sprzętowego wsparcia, jednak, przyznam szczerze, że nawet nie brałem takiej opcji pod uwagę, ponieważ chciałem mieć możliwie najlepsze osiągi.
Jedziemy
Pierwsza rzecz to instalacja Hosta. Tu nie ma niespodzianek: lektura handbooka wystarczy by z powodzeniem zainstalować system. Dysk został podzielony w sposób następujący:
root@puppetmaster ~ # fdisk -l /dev/sda Disk /dev/sda: 160.0 GB, 160000000000 bytes 255 heads, 63 sectors/track, 19452 cylinders Units = cylinders of 16065 * 512 = 8225280 bytes Disk identifier: 0x88fe88fe Device Boot Start End Blocks Id System /dev/sda1 1 13 104391 de Dell Utility /dev/sda2 * 14 2445 19535040 83 Linux /dev/sda3 2446 19452 136608727+ 5 Extended /dev/sda5 2446 2943 4000153+ 82 Linux swap / Solaris /dev/sda6 2944 19452 132608511 8e Linux LVM
Na ostatniej partycji został założony wolumin LVM, na którym zakładane dyski dla guestów.
Po instalacji base systemu, zmieniłem jedynie profil z desktop na hardened:
root@puppetmaster ~ # eselect profile list Available profile symlink targets: [1] default/linux/x86/10.0 [2] default/linux/x86/10.0/desktop * [3] default/linux/x86/10.0/developer [4] default/linux/x86/10.0/server [5] hardened/linux/x86/10.0 [6] selinux/2007.0/x86 [7] selinux/2007.0/x86/hardened [8] selinux/v2refpolicy/x86 [9] selinux/v2refpolicy/x86/desktop [10] selinux/v2refpolicy/x86/developer [11] selinux/v2refpolicy/x86/hardened [12] selinux/v2refpolicy/x86/server root@puppetmaster ~ # eselect profile set 5
Konfiguracja kernela
Jedną z czynności, które należy wykonać podczas instalacji Gentoo, to konfiguracja jądra. Ponieważ planuję, by goście mieli możliwość bezpośredniego dostępu do sieci, potrzebne będzie wsparcie dla mostów (ang. bridging support) oraz dla interfejsów TAP/TUN:
-*- Networking support ---> Networking options ---> <*> 802.1d Ethernet Bridging <*> 802.1Q VLAN Support Device Drivers ---> <M> Universal TUN/TAP device driver support
Kolejną rzeczą jest, oczywiście, wsparcie dla KVM:
[*] Virtualization ---> <M> Kernel-based Virtual Machine (KVM) support <M> KVM for Intel processors support < > KVM for AMD processors support
Wybór wsparcia dla konkretnego procesora jest uzależniony oczywiście od posiadanego modelu.
Instalacja oprogramowania
Potrzebne oprogramowania instaluję zwyczajowo z portage:
app-emulation/qemu-kvm-0.12.2
Domyślnie
qemu-kvm
jest zamaskowany (przynajmniej na dzień dzisiejszy), trzeba więc przed wykonaniem emerge dodać tenże pakiet do/etc/portage/package.keywords
:root@puppetmaster ~ # echo qemu-kvm >> /etc/portage/package.keywords root@puppetmaster ~ # echo sys-kernel/linux-headers >> \ /etc/portage/package.keywords
Ograniczyłem też ilość architektur do x86 i x86_64, dopisując do
make.conf
:QEMU_SOFTMMU_TARGETS="i386 x86_64" QEMU_USER_TARGETS="i386 x86_64"
I wreszcie, wyłączyłem niepotrzebne mi na serwerach możliwości:
root@puppetmaster ~ # echo qemu-kvm -alsa -bluetooth -esd -sdl >> \ /etc/portage/package.use
sys-fs/lvm2-2.02.56-r2
Z moich doświadczeń (o czym niżej) wynika, że w kontekście wydajności odczytu/zapisu na dysk, lepsze rezultaty uzyskam wykorzystując urządzenia blokowe, aniżeli plikopartycję. Jednocześnie dzięki LVM uzyskam elastyczność w manipulacji miejscem na dysku.
net-misc/bridge-utils-1.4
Pakiet ten zawiera narzędzie
brctl
służące do administracji mostem.sys-apps/usermode-utilities-20070815
Pakiet zawiera między innymi narzędzie
tunctl
net-fs/nfs-utils-1.1.4-r1
Będę chciał współdzielić katalog
/usr/portage/distfiles
, dlatego potrzebuję narzędzi do obsługi NFS.app-misc/screen-4.0.3
Przy pomocy screena, guesty będą uruchamiane przy starcie hosta.
root@puppetmaster ~ # emerge qemu-kvm lvm2 bridge-utils usermode-utilities \ nfs-utils screen
Przygotowanie guestów i konfiguracja
Konfiguracja sieci
Żeby guesty były widoczne w sieci lokalnej jak każdy inny pecet, trzeba
skonfigurować sieć hosta tak, by przy starcie systemu podnoszony był interfejs
br0
. Edycji trzeba dokonać w pliku /etc/conf.d/net
:
bridge_br0="eth0" brctl_br0=( "setfd 0" "sethello 0" "stp off" ) config_br0=( "192.168.0.122/24" ) routes_br0=( "default via 192.168.0.254" ) config_eth0=( "null" )
W konfiguracji został uwzględniony jedynie most oraz interfejs eth0
.
Interfejsy tapX
(gdzie X to kolejny numer interfejsu tap) będą tworzone
dynamicznie przy starcie maszyny wirtualnej i zwalniane po zamknięciu tejże.
Pozwoli to w elastyczny sposób zarządzać maszynami bez konieczności pamiętania
który interfejs tap jest od której maszyny wirtualnej.
Przygotowanie maszyn wirtualnych
Po instalacji qemu-kvm, utworzona została grupa kvm
, do której
należy dodać użytkownika, który będzie zarządzał maszynami:
root@puppetmaster ~ # usermod -a -G kvm gryf
Do pliku /etc/modules.autoload.d/kernel-2.6
trzeba dodać
moduły kvm oraz tuni, a następnie je załadować:
root@puppetmaster ~ # echo -e "kvm_intel\ntun" >> /etc/modules.autoload.d/kernel-2.6 root@puppetmaster ~ # modprobe kvm_intel; modprobe tun
By automatycznie był tworzony interfejs tap, zmodyfikowałem skrypt
/etc/qemu/qemu-ifup
:
#!/bin/sh sudo /sbin/ifconfig $1 0.0.0.0 promisc up sudo /usr/sbin/brctl addif br0 $1 sleep 2
Dodałem też skrypt, który po zamknięciu maszyny wirtualnej będzie zwalniał
interfejs tap i umieściłem go w pliku /etc/qemu/qemu-ifdown
:
#!/bin/sh sudo tunctl -d $1 sleep 2
Następnie przygotowałem dwie plikopartycje - jedną systemową, drugą dla swap:
gryf@puppetmaster ~ $ qemu-img create -f qcow2 /mnt/data/gentoo.img 8G gryf@puppetmaster ~ $ qemu-img create -f qcow2 /mnt/data/swap.img 1G
Mała uwaga: plikopartycje są wygodne (łatwe kopiowanie, możliwość wykonywania snapshotów, kompresja i szyfrowanie), jednak stosunkowo wolne, dlatego zrezygnowałem z nich na rzecz prawdziwych partycji.
Jako cdrom posłużył mi obraz CD instalacyjnego Gentoo.
Po uruchomieniu skryptu kvm_start.sh
, przeprowadziłem instalację systemu.
Uruchamianie maszyn podczas startu hosta
Do startowania maszyn wirtualnych wykorzystałem program screen. Chcę, żeby
maszyny startowały przy inicjalizacji hosta w związku z tym umieściłem
startowanie maszyn w /etc/conf.d/local.start
:
screen -mdS www_kvm /mnt/data/bin/run_www_vm.sh screen -mdS hg_kvm /mnt/data/bin/run_hg_vm.sh screen -mdS ftp_kvm /mnt/data/bin/run_ftp_vm.sh
Dla własnej wygody, przygotowałem sobie skrypty startowe, które różnią się między sobą tylko wpisami dotyczącymi urządzenia blokowego (czyli woluminu LVM - zmienna HDD_DEVICE), adresem MAC (zmienna IF_MAC_0) i ewentualnie wielkością pamięci (zmienna RAM):
#!/bin/sh # setup {{{ MAIN="/mnt/data/" # directory to change GUESTNAME='www_kvm' HDD_DEVICE='/dev/mapper/vg-www' CDROM='iso/gentoo-install-x86-minimal-20091103.iso' BOOT="-boot d" # '-boot [acdn]' or empty string RAM='512' CPU='2' GRAFIC='-curses' # -nographic -curses or leave empty # If guest machine is intendent as a server, one should setup br0 bridge # inteface on host, and set BR to true. Otherwise NAT will be used. BR=false # eth0 IF_NAME_0='eth0' IF_MODEL_0='rtl8139' # rtl8139 (default), virtio IF_MAC_0='DE:AD:BE:00:00:01' IF_VLAN_0='0' # }}} # invoke KVM. don't touch {{{ cd $MAIN if $BR; then USERID=`whoami` IF=`sudo tunctl -b -u $USERID` sudo /etc/qemu/qemu-ifup $IF NET="-net nic,vlan=$IF_VLAN_0,model=$IF_MODEL_0,macaddr=$IF_MAC_0 \ -net tap,vlan=$IF_VLAN_0,ifname=$IF,script=no,downscript=no" else NET="" fi qemu-system-x86_64 \ -smp $CPU \ -m $RAM \ -drive file=$HDD_DEVICE,cache=none \ -cdrom $CDROM \ $NET \ $BOOT \ $GRAFIC if $BR; then sudo /etc/qemu/qemu-ifdown $IF fi # }}} # vim:ts=4:sw=4:wrap:fdm=marker
Wydajność
Nie miałem możliwości wykonania długotrwałych i wiarygodnych testów dotyczących wydajności wirtualizacji, dlatego też poniższe testy wykonałem wyłącznie po to, by mieć pojęcie, czego mogę się spodziewać po maszynach KVM.
CPU/FPU performance
W sieci znalazłem niewielki program - hardinfo, który między innymi posiada możliwość przeprowadzenia testów wydajności procesora. Na hoście, program ten zainstalowałem z ebuilda, który można znaleźć na bugzilli Gentoo, natomiast na maszynach wirtualnych uruchamiany był z LiveCD dystrybucji Puppy Linux.
Procedura składa się z sześciu testów:
- CPU Blowfish (mniejszy wynik jest lepszy)
- CPU CryptoHash (większy wynik jest lepszy)
- CPU Fibonacci (mniejszy wynik jest lepszy)
- CPU N-Queens (mniejszy wynik jest lepszy)
- FPU FFT (mniejszy wynik jest lepszy)
- FPU Raytracing (mniejszy wynik jest lepszy)
testowa maszyna | CPU Blowfish | CPU CryptoHash | CPU Fibonacci | CPU N-Queens | FPU FFT | FPU Raytracing |
---|---|---|---|---|---|---|
real host | 5,876 | 192,911 | 2,728 | 7,597 | 2,775 | 16,388 |
kvm | 6,572 | 188,773 | 2,987 | 6,843 | 2,751 | 16,523 |
Virtual Box 3.0.1 | 12,749 | 98,479 | 3,029 | 7,822 | 5,479 | 13,858 |
Porównywałem dwa rozwiązania do wirtualizacji - KVM i VirtualBox w odniesieniu do hosta. W tabeli powyżej wytłuściłem wyniki, które były najlepsze. Wyraźnie widać przewagę KVM nad Virtual Boxem, z dwoma wyjątkami: testem na ciąg Fibonacciego, gdzie różnica jest marginalna oraz testem na Raytracing, gdzie wynik dla Virtual Box jest lepszy niż dla hosta :).
Wydajność dysków
Drugi pseudotest jaki przeprowadziłem tyczył się wydajności operacji
odczytu i zapisu na dysk. Ponieważ zalecanym formatem plikopartycji jest
qcow2 (choćby dlatego, że przechowuje on snapshoty guesta), to nie do
końca podobała mi się prędkość, jaką uzyskałem na tym typie nośnika. Istotne
jest też to, że qcow2, jest plikiem, który się "rozciąga" w zależności od
ilości danych na nim przechowywanych - podobnie jak w przypadku virtual drive
z Virtual Boxa. Można oczywiście "wymusić" rozciągnięcie plikopartycji,
poprzez zapełnienie filesystemu guesta choćby przy pomocy wspomnianego niżej
dd
.
test | czas wykonania | wyliczona prędkość |
---|---|---|
real host | 10,0751 s | 107 MB/s |
KVM, qcow2 | 37,1761 s | 28,9 MB/s |
KVM, qcow2, rozciągnięty | 37,9547 s | 28.3 MB/s |
KVM, wolumin LVM | 37,9547 s | 28.3 MB/s |
KVM, wolumin LVM, nocache | 13,6433 s | 78,9 MB/s |
VBox 3.0.1, dynamiczny | 24,5026 s | 43,8 MB/s |
VBox 3.0.1, dynamiczny, rozciągnięty | 21,0771 s | 50,9 MB/s |
VBox 3.1.0, dynamiczny | 18,7751 s | 57,2 MB/s |
VBox 3.1.0, dynamiczny, rozciągnięty | 16,2092 s | 66,2 MB/s |
VBox 3.1.0, stały rozmiar | 21,4612 s | 50,0 MB/s |
Procedura testowa polegała na stworzeniu pliku wielkości 1GB przy pomocy
dd
:
root@guest ~ # $ dd if=/dev/zero of=file bz=1M count=1024
Prędkość zapisu nie zachwyca w przypadku KVM, Virtual Box pod tym względem
znacznie lepiej sobie radzi. Dlatego też postanowiłem zrezygnować z
plikopartycji na rzecz realnych urządzeń LVM, gdzie przy włączonej opcji
cache=none
prędkość zapisu zbliżyła się do osiągów hosta.
Jak do tej pory (maszyny instalowane były pod koniec grudnia ubiegłego roku), maszyny działają przewidywalnie i stabilnie.
TODO
Jedna z rzeczy, których jeszcze nie opanowałem to automatyczne zamykanie guestów podczas zamykania hosta. Na razie nie mam pomysłu jak to osiągnąć.