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:

  1. 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
    
  2. 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.

  3. net-misc/bridge-utils-1.4

    Pakiet ten zawiera narzędzie brctl służące do administracji mostem.

  4. sys-apps/usermode-utilities-20070815

    Pakiet zawiera między innymi narzędzie tunctl

  5. 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.

  6. 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:

  1. CPU Blowfish (mniejszy wynik jest lepszy)
  2. CPU CryptoHash (większy wynik jest lepszy)
  3. CPU Fibonacci (mniejszy wynik jest lepszy)
  4. CPU N-Queens (mniejszy wynik jest lepszy)
  5. FPU FFT (mniejszy wynik jest lepszy)
  6. FPU Raytracing (mniejszy wynik jest lepszy)
Wyniki testów
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.

Wyniki testów
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ąć.


, Etykiety: gentoo, linux, narzędzia