Introduction à la virtualisation sous Linux
Petit tour rapide de qemu/KVM/libvirt
Le 03 mars 2015 , Aucun commentaire pour cet articleL'excellent émulateur QEMU offre la possibilité d'exploiter toutes les fonctionnalités des CPU modernes pour la virtualisation d'entreprise, grâce à KVM, fonctionnalité directement intégré dans le noyau. Il peut émuler tellement de périphériques qu'il est difficile d'en faire le tour. Sa syntaxe est assez intuitive, mais peu pratique au quotidien, nous allons également utiliser libvirt, plus souple, et qui amène une couche supplémentaire - les cgroups - afin de contrôler finement notre invité.
Avant de se lancer dans la virtualisation, voici un petit récapitulatif des paramètres de l'hôte:
Au démarrage de l'hôte, nous allons activer les transparent_hugepages pour des pages de 2Mo, désactiver le delay accounting et activer les cgroups pour la mémoire. Attention cependant, les bases de données déconseillent généralement transparent_hugepages, à n'utiliser que sur des hôte qui n'hébergerons pas ce type de service:
cgroup_enable=memory transparent_hugepage=madvice hugepagesz=2M hugepages=1 default_hugepagesz=2M nodelayacct
Pour optimiser les accès disques, on ajoute une règle udev pour désactiver le random entropy contribution sur nos disques et choisir le scheduler deadline:
ACTION=="add|change", KERNEL=="sd[a-z]", ATTR{queue/scheduler}="deadline", ATTR{queue/add_random}="0"
deadline reste le plus performant dans la majorité des cas, mais pas toujours pour les disques ssd.
Dans sysctl.conf, on ajuste les paramètres de gestion de mémoire, comme pour tout serveur: CFS devrait avoir un min_granularity à 10000000ns et wakeup_granularity à 15000000ns, et les dirty_page à 40% et 15% pour le background. Ce sont des valeurs de base conseillé par RH qui conviennent parfaitement pour de la virtualisation. Ensuite, on désactive ulatencyd et ksm si vous n'en avez pas besoin.
Ajout d'un délai suffisant avant la réduction de fréquence du CPU:
SUBSYSTEM=="cpu", ATTR{cpufreq/scaling_governor}="ondemand"
et spécifier une valeure de 10 ou 100, via systemd-tmpfiles:
w /sys/devices/system/cpu/cpufreq/ondemand/sampling_down_factor - - - - 100
QEMU
La machine virtuelle utilisée ici a les caractéristiques suivantes: 2 cpus, 1 disque dur, 1Go de Ram, 2 interfaces réseaux, carte graphique et carte son, ainsi que quelques périphériques comme watchdog, panic, rng ou encore qemu-ga. Une première définition serait:
qemu-system-x86_64 -name Debian -no-user-config -nodefaults -k fr -msg timestamp=on -machine pc-i440fx-2.2,accel=kvm,usb=off,mem-merge=off -bios /usr/lib/roms/bios-256k.bin -L /usr/lib/roms/ -boot menu=off,strict=on -cpu kvm64,+kvm_pv_eoi -smp 2,maxcpus=8,sockets=1,cores=4,threads=2 -object iothread,id=iothread1 -object iothread,id=iothread2 -global PIIX4_PM.disable_s3=1 -global PIIX4_PM.disable_s4=1 -m 1024 -drive file=/srv/MyDisk,if=none,id=drive0,format=qcow2,cache=none,aio=threads -device virtio-blk-pci,iothread=iothread1,ioeventfd=on,event_idx=on,scsi=off,drive=drive0,bootindex=1 -drive if=none,id=drive1,readonly=on,format=raw -device ide-cd,bus=ide.0,unit=0,drive=drive1,bootindex=2 -netdev bridge,br=MyBridge0,helper=/sbin/qemu-bridge-helper,id=bridge0 -device virtio-net-pci,id=bridge0 -netdev bridge,br=MyBridge1,helper=/sbin/qemu-bridge-helper,id=bridge1 -device virtio-net-pci,id=bridge1 -device ich9-usb-ehci1,id=usb,bus=pci.0,addr=0x6.0x7 -device ich9-usb-uhci1,masterbus=usb.0,firstport=0,bus=pci.0,multifunction=on,addr=0x6 -device ich9-usb-uhci2,masterbus=usb.0,firstport=2,bus=pci.0,addr=0x6.0x1 -device ich9-usb-uhci3,masterbus=usb.0,firstport=4,bus=pci.0,addr=0x6.0x2 -device virtio-serial-pci,id=virtio-serial0,bus=pci.0,addr=0x7 -chardev spicevmc,id=charchannel0,name=vdagent -device virtserialport,bus=virtio-serial0.0,nr=1,chardev=charchannel0,id=channel0,name=com.redhat.spice.0 -chardev spicevmc,id=charredir0,name=usbredir -device usb-redir,chardev=charredir0,id=redir0 -chardev spicevmc,id=charredir1,name=usbredir -device usb-redir,chardev=charredir1,id=redir1 -chardev spicevmc,id=charredir2,name=usbredir -device usb-redir,chardev=charredir2,id=redir2 -chardev spicevmc,id=charredir3,name=usbredir -device usb-redir,chardev=charredir3,id=redir3 -spice port=19000,tls-port=20000,addr=192.168.2.1,agent-mouse=on,disable-ticketing,x509-dir=/etc/pki/spice,plaintext-channel=default,plaintext-channel=main,plaintext-channel=display,plaintext-channel=inputs,plaintext-channel=cursor,plaintext-channel=playback,plaintext-channel=record,plaintext-channel=usbredir,image-compression=lz,jpeg-wan-compression=always,zlib-glz-wan-compression=always,playback-compression=on,streaming-video=all,seamless-migration=on -device qxl-vga,id=video0,ram_size=67108864,vram_size=67108864,bus=pci.0,addr=0x2 -chardev socket,id=charchannel1,path=/var/lib/libvirt/qemu/f16x86_64.agent,server,nowait -device virtserialport,bus=virtio-serial0.0,nr=2,chardev=charchannel1,id=channel1,name=org.qemu.guest_agent.0 -device i6300esb,id=watchdog0 -watchdog-action reset -device virtio-balloon-pci,id=balloon0 -object rng-random,id=rng0,filename=/dev/random -device virtio-rng-pci,rng=rng0,max-bytes=1234,period=2000 -device pvpanic,ioport=1285 -realtime mlock=off -rtc base=utc,clock=vm,driftfix=slew -global kvm-pit.lost_tick_policy=discard -monitor stdio
Explications:
cpu: on active kvm avec accel=kvm, un -cpu kvm64 avec PV_EOI pour réduire les VM_EXISTS. Ici je spécifie 2 cpus, avec un max à 8 (j'utilise en fait un i7, et je laisse la possibilité d'ajouter autant de cpu à chaud que nécessaire). désactivation de S3 et S4.
disque: j'utilise des optimisation d'écriture: aio, pas de cache côté hôte/invité, un threads séparé et pilote virtio, qui m'assurent de bonnes performances au détriment de la fiabilité, et un lecteur cdrom ide pour l'installation de l'os.
réseau: 2 interfaces réseaux bridge (penser au setuid bit sur qemu-bridge-helper), pilote virtio.
vidéo: carte qxl et serveur spice offrent d'excellentes performances, et toutes les fonctionnalités nécessaire au vdi, pour un invité graphique, il est fortement conseillé. Un prochain article sera consacré à ce sujet.
le reste est globalement compréhensible, à noter l'utilisation des pilotes virtio partout où cela est possible, il n'y a aucune raison de s'en priver.
Pour lancer un invité Windows, il faut utiliser des disques raw, une horloge localtime au lieu de utc, et on a pas besoin du périphérique panic. Le reste des options sont les même. Penser à préparer l'iso des pilotes virtio et spice.
Libvirt
Libvirt est un toolbox pour la virtualisation. Sa configuration est simple, je donne les paramètres les plus utiles:
listen_tls = 1
tcp_port = "1601"
tls_port = "1602"
listen_addr = "192.168.123.2"
key_file = "/etc/pki/private/host/hypervisor08-key.pem"
cert_file = "/etc/pki/host/hypervisor08.pem"
ca_file = "/etc/pki/CA/uubu.fr.rca.pem"
crl_file = "/etc/pki/CA/uubu.fr.crl.pem"
tls_no_sanity_certificate = 0
tls_no_verify_certificate = 0
tls_allowed_dn_list = ["C=FR,ST=Rhone-Alpes,L=Grenoble,O=uubu.fr,CN=ws-admin02.uubu.fr"]
Sous libvirt, les définitions se font en XML:
Pool
On va créer un pool pour nos disques virtuels. Pour simplifier, je créé des pools de type répertoire:
<pool type='dir'>
<name>MyPool</name>
<target>
<path>/srv/MyPool</path>
</target>
</pool>
Volume
Dans le pool, on créer nos disques virtuels. Pour un disque qcow2 de 8Go:
<volume type='file'>
<name>MyDisk</name>
<capacity unit='GiB'>8</capacity>
<target>
<path>/srv/MyPool/MyDisk</path>
<format type='qcow2'/>
<permissions>
<owner>1000</owner>
<group>1000</group>
<mode>0744</mode>
</permissions>
</target>
</volume>
Network
Les bridges doivent être créés avant la définition, nous utiliserons ces bridges pour les interfaces réseaux:
<network ipv6='yes' trustGuestRxFilters='no'>
<name>MyBridge0</name>
<forward mode='bridge'/>
<bridge name='Bridge0' macTableManager="libvirt"/>
</network>
Domain
Pour définir nos invités, nous allons reprendre les options qemu, et nous allons créer une première définition migrable, c'est à dire en évitant les paramètres qui seraient trop spécifique à notre hôte:
<domain type='kvm' xmlns:qemu='http://libvirt.org/schemas/domain/qemu/1.0'>
<name>MyVM</name>
<os>
<type arch='x86_64'>hvm</type>
<loader readonly='yes' type='rom'>/usr/lib/roms/bios-256k.bin</loader>
<bootmenu enable='no' timeout='0'/>
</os>
<vcpu current="2">8</vcpu>
<cpu match='exact'>
<model>kvm64</model>
<vendor>Intel</vendor>
<feature policy='optional' name='x2apic'/>
</cpu>
<cputune>
<shares>1024</shares>
</cputune>
<memory unit="GiB">2</memory>
<currentMemory unit="MiB">1024</currentMemory>
<memoryBacking>
<nosharepages/>
</memoryBacking>
<on_poweroff>destroy</on_poweroff>
<on_reboot>restart</on_reboot>
<on_crash>restart</on_crash>
<on_lockfailure>poweroff</on_lockfailure>
<pm>
<suspend-to-disk enabled='no'/>
<suspend-to-mem enabled='no'/>
</pm>
<features>
<acpi/>
<apic eoi='on'/>
<hap/>
</features>
<clock offset='utc'>
<timer name='kvmclock'/>
</clock>
<devices>
<emulator>/local/qemu/qemu-system-x86_64</emulator>
<disk type='volume' device='disk'>
<driver name='qemu' type='raw' cache='none'/>
<source pool='Disks' volume='MyDisk'/>
<target dev='vda' bus='virtio'/>
<boot order='1'/>
</disk>
<disk type='file' device='cdrom'>
<source file=''/>
<target dev='hda' bus='ide' try='closed'/>
<boot order='2'/>
<readonly/>
</disk>
<interface type='network'>
<source network='MyBridge'/>
<target dev='net-MyVM'/>
<model type='virtio'/>
</interface>
<controller type='usb' index='0' model='ich9-ehci1'/>
<controller type='usb' index='0' model='ich9-uhci1'>
<master startport='0'/>
</controller>
<controller type='usb' index='0' model='ich9-uhci2'>
<master startport='2'/>
</controller>
<controller type='usb' index='0' model='ich9-uhci3'>
<master startport='4'/>
</controller>
<redirdev bus='usb' type='spicevmc'/>
<redirdev bus='usb' type='spicevmc'/>
<redirdev bus='usb' type='spicevmc'/>
<redirdev bus='usb' type='spicevmc'/>
<graphics type='spice' port='19007' keymap='bepo' defaultMode='insecure' passwd='MyPasswd'>
<listen type='network' network='spice'/>
<channel name='main' mode='insecure'/>
<channel name='display' mode='insecure'/>
<channel name='inputs' mode='insecure'/>
<channel name='cursor' mode='insecure'/>
<channel name='playback' mode='insecure'/>
<channel name='record' mode='insecure'/>
<channel name='usbredir' mode='insecure'/>
<image compression='lz'/>
<zlib compression='always'/>
<jpeg compression='always'/>
<playback compression='on'/>
<streaming mode='all'/>
<clipboard copypaste='yes'/>
<mouse mode='client'/>
<filetransfer enable='yes'/>
<migration seamless='on'/>
</graphics>
<video>
<model type='qxl' ram='65536' vram='65536' heads='1'>
<acceleration accel3d='yes' accel2d='yes'/>
</model>
</video>
<controller type='virtio-serial' index='0'/>
<channel type='spicevmc'>
<target type='virtio' name='com.redhat.spice.0'/>
</channel>
<watchdog model='i6300esb' action='reset'/>
<memballoon model='virtio'/>
<input type='mouse' bus='ps2'/>
<input type='keyboard' bus='ps2'/>
<channel type='unix'>
<source mode='bind' path='/var/lib/libvirt/qemu/f16x86_64.agent'/>
<target type='virtio' name='org.qemu.guest_agent.0'/>
</channel>
<rng model='virtio'>
<rate period="2000" bytes="1234"/>
<backend model='random'>/dev/random</backend>
</rng>
<panic>
<address type='isa' iobase='0x505'/>
</panic>
</devices>
<qemu:commandline>
<qemu:arg value='-L'/>
<qemu:arg value='/usr/lib/roms/'/>
</qemu:commandline>
</domain>
et une définition plus difficile à migrer, mais qui nous assure un contrôle totale des threads, cpus, etc. grâce aux cgroups:
<domain type='kvm' xmlns:qemu='http://libvirt.org/schemas/domain/qemu/1.0'>
<name>MyVM</name>
<os>
<type arch='x86_64'>hvm</type>
<loader readonly='yes' type='rom'>/usr/lib/roms/bios-256k.bin</loader>
<bootmenu enable='no' timeout='0'/>
</os>
<iothreads>2</iothreads>
<vcpu current="2">8</vcpu>
<cpu match='exact'>
<model>kvm64</model>
<vendor>Intel</vendor>
<feature policy='optional' name='x2apic'/>
</cpu>
<cputune>
<vcpupin vcpu="0" cpuset="5"/>
<vcpupin vcpu="1" cpuset="4"/>
<vcpupin vcpu="2" cpuset="3"/>
<vcpupin vcpu="3" cpuset="2"/>
<vcpupin vcpu="4" cpuset="1"/>
<vcpupin vcpu="5" cpuset="7"/>
<vcpupin vcpu="6" cpuset="6"/>
<vcpupin vcpu="7" cpuset="0"/>
<shares>1024</shares>
<iothreadpin iothread="1" cpuset="7"/>
<iothreadsched iothreads='1' scheduler='rr'/>
<iothreadpin iothread="2" cpuset="6"/>
<iothreadsched iothreads='2' scheduler='rr'/>
<period>1000000</period>
<quota>-1</quota>
<emulator_period>1000000</emulator_period>
<emulator_quota>-1</emulator_quota>
<vcpusched vcpus='0-4' scheduler='rr' priority='1'/>
<vcpusched vcpus='5,6' scheduler='batch'/>
<vcpusched vcpus='7' scheduler='idle'/>
</cputune>
<memory unit="GiB">2</memory>
<currentMemory unit="MiB">1024</currentMemory>
<memoryBacking>
<nosharepages/>
</memoryBacking>
<on_poweroff>destroy</on_poweroff>
<on_reboot>restart</on_reboot>
<on_crash>restart</on_crash>
<on_lockfailure>poweroff</on_lockfailure>
<pm>
<suspend-to-disk enabled='no'/>
<suspend-to-mem enabled='no'/>
</pm>
<features>
<acpi/>
<apic eoi='on'/>
<hap/>
</features>
<clock offset='utc'>
<timer name='kvmclock'/>
</clock>
<devices>
<emulator>/local/qemu/qemu-system-x86_64</emulator>
<disk type='volume' device='disk'>
<driver name='qemu' type='raw' cache='none' aio='native' ioeventfd='on' event_idx='on' iothread='1'/>
<source pool='Disks' volume='MyDisk'/>
<target dev='vda' bus='virtio'/>
<boot order='1'/>
</disk>
<disk type='file' device='cdrom'>
<source file=''/>
<target dev='hda' bus='ide' try='closed'/>
<boot order='2'/>
<readonly/>
</disk>
<interface type='network'>
<source network='MyBridge'/>
<target dev='net-MyVM'/>
<model type='virtio'/>
<driver name='vhost' txmode='iothread' ioeventfd='on' event_idx='on' queues='2' iothread='2'>
<host csum='on' gso='on' tso4='on' tso6='on' ecn='on' ufo='on' mrg_rxbuf='on'/>
<guest csum='on' tso4='on' tso6='on' ecn='on' ufo='on'/>
</driver>
</interface>
<controller type='usb' index='0' model='ich9-ehci1'/>
<controller type='usb' index='0' model='ich9-uhci1'>
<master startport='0'/>
</controller>
<controller type='usb' index='0' model='ich9-uhci2'>
<master startport='2'/>
</controller>
<controller type='usb' index='0' model='ich9-uhci3'>
<master startport='4'/>
</controller>
<redirdev bus='usb' type='spicevmc'/>
<redirdev bus='usb' type='spicevmc'/>
<redirdev bus='usb' type='spicevmc'/>
<redirdev bus='usb' type='spicevmc'/>
<graphics type='spice' port='19007' keymap='bepo' defaultMode='insecure' passwd='MyPasswd'>
<listen type='network' network='spice'/>
<channel name='main' mode='insecure'/>
<channel name='display' mode='insecure'/>
<channel name='inputs' mode='insecure'/>
<channel name='cursor' mode='insecure'/>
<channel name='playback' mode='insecure'/>
<channel name='record' mode='insecure'/>
<channel name='usbredir' mode='insecure'/>
<image compression='lz'/>
<zlib compression='always'/>
<jpeg compression='always'/>
<playback compression='on'/>
<streaming mode='all'/>
<clipboard copypaste='yes'/>
<mouse mode='client'/>
<filetransfer enable='yes'/>
<migration seamless='on'/>
</graphics>
<video>
<model type='qxl' ram='65536' vram='65536' heads='1'>
<acceleration accel3d='yes' accel2d='yes'/>
</model>
</video>
<controller type='virtio-serial' index='0'/>
<channel type='spicevmc'>
<target type='virtio' name='com.redhat.spice.0'/>
</channel>
<watchdog model='i6300esb' action='reset'/>
<memballoon model='virtio'/>
<input type='mouse' bus='ps2'/>
<input type='keyboard' bus='ps2'/>
<channel type='unix'>
<source mode='bind' path='/var/lib/libvirt/qemu/f16x86_64.agent'/>
<target type='virtio' name='org.qemu.guest_agent.0'/>
</channel>
<rng model='virtio'>
<rate period="2000" bytes="1234"/>
<backend model='random'>/dev/random</backend>
</rng>
<panic>
<address type='isa' iobase='0x505'/>
</panic>
</devices>
<qemu:commandline>
<qemu:arg value='-L'/>
<qemu:arg value='/usr/lib/roms/'/>
</qemu:commandline>
</domain>
Opérations
Je donne ici quelques opérations courantes:
Déclarer la vm: define --pool MyPool /xml/domains/MyVM
Attacher un iso dans le lecteur cdrom: change-media MyVM hda /iso/debian.iso --insert
Démarrer l'invité: start MyVM
Effectuer des requêtes dans le monitor: qemu-monitor-command --domain MyVM --hmp --cmd info spice
Connaître l'url de connection pour spice: domdisplay MyVM
Dialoguer avec qemu-ga: qemu-agent-command --domain srvpl-WS01 --cmd '{"execute": "guest-info"}'
ajouter de la mémoire/cpu: setmem MyVM 2G / setvcpus MyVM --count 3
Conclusion
L'avantage de libvirt, c'est que toutes les définitions et opérations courantes peuvent être scriptées pour se simplifier la vie.
Une fois la machine virtuelle installée, ne pas oublier d'optimiser l'invité. On peut reprendre les paramètres de l'hôte: scheduler deadline, transparent_hugepages, random entropy contribution, et le paramètres du CFS. Ne pas oublier également le driver qxl et qemu-ga si vous souhaiter les utiliser.