24/03/2011

Devbox KVM+Libvirt perfect setup.

Dans le cadre de mon boulot chez Euro-Web j’ai été amené à monter une plate forme un peu particulière pour mon client Zenexity. Leur principale activité est le développement d’applications web basées sur une technologie maison open source ( framework Play! ).

J’ai donc demandé l’autorisation de faire une documentation publique sur cette installation, car je la trouve intéressante, et je n’ai rien trouvé de similaire pour le moment sur la toile.

Le setup part d’un gros besoin en machines virtuelles :

  • Les développeurs ont besoin de tester leurs applications sous toutes les coutures et sont plus plutôt allergiques à certains systèmes d’exploitation, même sous forme de machines virtuelles.
  • Certaines plates formes déjà en production ont besoin d’environnement de préproduction facilement ré-initialisables. Les développeurs ont besoin d’images/VM “template” afin de pouvoir réaliser leurs tests facilement.
  • Les architectes ont besoin de valider des setups en mode pré-production sur des technologies émergentes
Avertissement : Cet article a pour but de partager des connaissances avec un public avisé.

Préambule

  • Le setup d’origine ayant été réalisé sous Debian 6.0 la documentation présenté tel qu’elle est ici, est bien sur basé sur la machine de production Debian 6.0. Il est à noter que j’ai réalisé très facilement un clone de cette machine sous gentoo, je vous donnerai en annexe les options de compilation des différents programmes utilisés.
  • L’installation + la configuration du serveur OpenVPN ne fait pas l’objet de cet article, vous pouvez donc pour cela vous reporter aux autres articles de Geekfault traitant du VPN.
  • En fait, réaliser ce setup, est chose plutôt aisé pour tout adminsys chevronné, (chose qui vous vous en doutez je suis :p), le plus compliqué a été de trouver des contournements pour les multitudes de soucis rencontrés.

Cahier des charges

Comme expliqué plus haut le système de VM est un système très utilisé chez mon client. J’avais dans le passé monté un serveur XEN pour eux, mais la flexibilité ne convenait plus, notamment pour les grid de test Windows.

Il fallait donc un nouveau serveur de VM. By design, les guest Windows ne seront pas accessible depuis l’internet, et n’auront donc pas d’IP publique. En outre, les développeurs devront pouvoir accéder de façon transparente à leur Windows de test, si possible de manière sécurisée.

Les guests GNU/Linux, quand à eux,  pourront être accessibles via une IP publique, via VPN ou les deux à la fois.

La création / les duplications / la maintenance / le démarrage / l’arrêt des VM devra être très simple. (Le client souhaite  garder la main sur cela, et être maitre de la situation en matière de création de VM).

Choix de la technologie

Nous avons finalement retenu l’idée d’un serveur étant capable de faire cela :

  • N’importe qui doit  pouvoir ré-amorcer un système clean : toutes les VM tourneront sur des fichiers .img stockés dans /var
  • Les gestion des ressources réseau est automatisée au maximum : les VM devront obtenir leurs IP privées ou publiques automatiquement, par DHCP.
  • La technologie de virtualisation sera Qemu/Kvm, épaulée par Libvirt.
  • Certaines VM ne seront accessible que au travers d’un VPN, alors que d’autres devront pouvoir faire office de serveur de préproduction et devront avoir des IP publiques.
  • Ce serveur devra donc être “hub” VPN et permettre la communication entre les clients du VPN, et les VM.
  • Chaque VM qui ne sera que dans le LAN VPN, devra quand même pouvoir accéder à l’internet. Nous pensons donc utiliser un bridge ethernet sur eth0 ainsi que sur l’interface VPN.
  • Chaque VM devra donc être clonable et les developpeurs pourront s’y connecter à travers le VPN,  à l’aide de ssh, ou VNC / rdesktop pour les systèmes d’exploitation de seconde zone.
  • Ce serveur devra pouvoir faire tourner des VM : Gentoo / Debian 5.0 / Debian 6.0 / CentOS / Windows (avec différentes version d’Internet Explorer, nerf de la guerre des tests d’applications web sous plate-forme windows).

Prérequis Kernel

Nous avons finalement utilisé le noyau 2.6.32.27. En effet les noyaux 2.6.36 présentent un bug dans le support KVM qui fait freezer la machine à la coupure des VM. (Nous sommes à priori victimes d’une régression dans le kernel Linux).
http://comments.gmane.org/gmane.comp.emulators.kvm.devel/65852

Nous avons donc upgradé sur le 2.6.37 mais, nous nous sommes heurtés à un nouveau bug kernel.

Bug kernel avec le 2.6.37

[76529.274129] rmap_remove: ffff88017deb37f8 1->BUG
[76529.274162] ------------[ cut here ]------------
[76529.274189] kernel BUG at arch/x86/kvm/mmu.c:700!
[76529.274217] invalid opcode: 0000 [#1] SMP
[76529.274247] last sysfs file: /sys/devices/virtual/net/lo/operstate
[76529.274276] CPU 2
[76529.274302] Pid: 27193, comm: kvm Not tainted 2.6.37 #1 01V648/PowerEdge R410
[76529.274333] RIP: 0010:[] [] drop_spte+0xb8/0x179
[76529.274389] RSP: 0018:ffff88080d987b28 EFLAGS: 00010296
[76529.274417] RAX: 000000000000003b RBX: ffff88017deb37f8 RCX: ffff8800000bc380
[76529.274449] RDX: 000000000000c3c3 RSI: 0000000000000046 RDI: ffffffff81918a88
[76529.274480] RBP: ffff88080d987b38 R08: 0000000000000000 R09: 000000000000000a
[76529.274512] R10: ffff88102f819400 R11: ffff88102f819400 R12: ffff880fe7cb0000
[76529.274544] R13: ffff88080d987b98 R14: 0000000000000000 R15: ffff88017deb37f8
[76529.274576] FS: 0000000000000000(0000) GS:ffff88102fc20000(0000) knlGS:0000000000000000
[76529.274623] CS: 0010 DS: 002b ES: 002b CR0: 000000008005003b
[76529.274652] CR2: 00007fb91807f000 CR3: 000000100aa0d000 CR4: 00000000000026e0
[76529.274684] DR0: 0000000000000090 DR1: 00000000000000a4 DR2: 00000000000000ff
[76529.274715] DR3: 000000000000000f DR6: 00000000ffff0ff0 DR7: 0000000000000400
[76529.274747] Process kvm (pid: 27193, threadinfo ffff88080d986000, task ffff8805a1d82630)
[76529.274794] Stack:
[76529.274815] ffff88080da10090 ffff880fe7cb0000 ffff88080d987b88 ffffffff8100ef8f
[76529.274870] ffff8805a1d82630 000000ff8109702a ffff88080d987c08 ffff880fe7cb0000
[76529.274925] ffff88080da10950 ffff88080d987b98 ffff880fe7cb2320 0000000000020d93
[76529.274981] Call Trace:
[76529.275006] [] kvm_mmu_prepare_zap_page+0x74/0x23c
[76529.275038] [] kvm_mmu_zap_all+0x41/0x6b
[76529.275069] [] kvm_arch_flush_shadow+0x11/0x1e
[76529.275101] [] kvm_mmu_notifier_release+0x2c/0x3f
[76529.275134] [] __mmu_notifier_release+0x49/0x74
[76529.275166] [] exit_mmap+0x27/0x147
[76529.275198] [] mmput+0x28/0xb4
[76529.275226] [] exit_mm+0x124/0x131
[76529.275254] [] do_exit+0x20f/0x6a7
[76529.275283] [] do_group_exit+0x71/0x99
[76529.275313] [] get_signal_to_deliver+0x2fa/0x315
[76529.275346] [] do_signal+0x6d/0x673
[76529.275375] [] ? kill_pid_info+0x3a/0x47
[76529.275404] [] ? sys_kill+0x82/0x161
[76529.275433] [] do_notify_resume+0x27/0x51
[76529.275464] [] int_signal+0x12/0x17
[76529.275491] Code: 48 d9 71 81 31 c0 e8 2b 7f 5c 00 0f 0b eb fe 40 f6 c6 01 75 26 48 39 f3 74 15 48 89 de 48 c7 c7 63 d9 71 81 31 c0 e8 0b 7f 5c 00 <0f> 0b eb fe 48 c7 00 00 00 00 00 e9 ac 00 00 00 48 83 e6 fe 31
[76529.275720] RIP [] drop_spte+0xb8/0x179
[76529.275752] RSP
[76529.276109] ---[ end trace 2d814c5436296e34 ]---
[76529.276177] Fixing recursive fault but reboot is needed!

Je vous renvoie à la mailing list de kernel.org à ce sujet. Ça ressemble beaucoup, le gars a le même problème sur un 2.6.36. Le mainteneur des kernel 2.4.x a posté un patch pour 2.6.36 sur le thread.

Voici ce qu’il faut activer dans le noyau pour que notre setup fonctionne :

- Networking support
-- Networking options
--- 802.1d Ethernet Bridging
--- Network Packet Filtering framework (netfilter)
---- Core Netfilter configuration (les options dont vous aurez besoin pour vos propres régles de firewalling)
---- IP: Netfilter Configuration
----- IP tables support
----- IPv4 Connection tracking support (required for NAT)
----- Full NAT
----- MASQUERADE target support

(à compléter, j’ai certainement du oublier un truc).

Installation des composants indispensables

Voici la liste des composants indispensables à installer avec votre package manager (apt-get sous Debian; emerge sous Gentoo) :

  • libvirt-bin
  • libvirt0
  • virt-top
  • virtinst
  • isc-dhcp-server
  • openvpn
  • iproute
  • iptables
  • ifupdown
  • kvm
  • build-essential + libncurses5-dev
  • qemu-kvm
  • rsync
  • munin-libvirt-plugins

Use flags sous Gentoo

/etc/portage/package.use
app-emulation/libvirt libvirtd qemu lvm network nfs virt-network

Configuration du bridge (debian ways)

/etc/network/interfaces
auto lo
iface lo inet loopback
# The primary network interface
auto eth0
iface eth0 inet static
address 91.x.x.x
netmask 255.255.255.0
network 91.x.x.0
broadcast 91.x.x.255
gateway 91.x.x.1
auto br0
iface br0 inet static
address 91.x.x.x
netmask 255.255.255.0
network 91.x.x.0
broadcast 91.x.x.255
gateway 91.x.x.1
bridge_ports eth0
bridge_stp off
bridge_maxwait 5
auto br1
iface br1 inet static
address 172.16.20.1
netmask 255.255.255.0
network 172.16.20.0
broadcast 172.16.20.255
bridge_ports tap0
bridge_stp off
bridge_maxwait 5
pre-up iptables-restore -c /etc/network/iptables
auto eth1
iface eth1 inet static
address 10.191.78.4
netmask 255.255.0.0
network 10.191.0.0
broadcast 10.191.0.255

libvirt setup

/etc/default/libvirt-bin
start_libvirtd="yes"
libvirtd_opts="-d"

Creation du fichier de l’image :

# qemu-img create /var/VM/gentoo-x86_64.img 10G
/etc/sysctl.conf
net.bridge.bridge-nf-call-ip6tables = 0
net.bridge.bridge-nf-call-iptables = 1
net.bridge.bridge-nf-call-arptables = 0
net.ipv4.conf.all.bootp_relay = 1

Attention à l’isolation reseau des VM : https://bugzilla.redhat.com/show_bug.cgi?id=512206

Le DHCP ne doit pas envoyer de requêtes sur le reseau :

# iptables -t filter -A FORWARD -p udp --sport 68 --dport 67 -j DROP

Sécuriser le VNC de qemu/kvm en le forçant sur l’interface sécurisée

/etc/libvirt/qemu.conf
vnc_listen '172.16.20.1'

Problème de keymap bug libvirt

http://www.arnebrodowski.de/blog/keymap-problems-with-virt-manager.html

Iptables avec le bridge et la libvirt

http://ddevnet.net/wiki/index.php/How_KVM_or_libVirt_IPtables_work

Mise en place du dhcpd

/etc/dhcp/dhcpd.conf
ddns-update-style none;
option domain-name "zenexity.fr";
option domain-name-servers 81.93.xxx.xxx, 81.93.xx.xx;
default-lease-time 3600;
max-lease-time 7200;
log-facility local7;
subnet 91.xxx.xx.0 netmask 255.255.255.0 {
range 91.xxx.xx.20 91.xxx.xx.50;
option routers 91.xxx.xx.1;
}
subnet 172.16.20.0 netmask 255.255.255.0 {
range 172.16.20.128 172.16.20.254;
option routers 172.16.20.1
}

Exemple de création de VM

  • Une machine virtuelle gentoo n’ayant qu’une seule interface réseau sur le VPN only
    # virt-install -n gentoo -r 512 -vcpus=1 -f /var/VM/gentoo-x86_64.img -b br1 --vnc --accelerate -v -c /var/iso/gentoo.iso --os-type=linux
  • Une machine virtuelle debian, ayant une interface reseau publique et une interface VPN
    # virt-install -n debian -r 512 -vcpus=1 -f /var/VM/debian-x86_64.img -b br0 -b br1 --vnc --accelerate -v -c /var/iso/debian-5.iso --os-type=linux

libvirt stocke ses fichiers de description de VM dans /etc/libvirt/qemu. Voici un exemple de fichier xml généré par libvirt :

/etc/libvirt/qemu/debian-50.xml
<domain type='kvm'>
  <name>debian-50</name>
  <uuid>ce5fe35a-703b-8291-9508-8a83f74eb110</uuid>
  <memory>524288</memory>
  <currentMemory>524288</currentMemory>
  <vcpu>1</vcpu>
  <os>
    <type arch='x86_64' machine='pc-0.12'>hvm</type>
    <boot dev='hd'/>
  </os>
  <features>
    <acpi/>
    <apic/>
    <pae/>
  </features>
  <clock offset='utc'/>
  <on_poweroff>destroy</on_poweroff>
  <on_reboot>restart</on_reboot>
  <on_crash>restart</on_crash>
  <devices>
    <emulator>/usr/bin/kvm</emulator>
    <disk type='file' device='disk'>
      <driver name='qemu' type='raw'/>
      <source file='/var/VM/debian-50.img'/>
      <target dev='vda' bus='virtio'/>
      <address type='pci' domain='0x0000' bus='0x00' slot='0x04' function='0x0'/>
    </disk>
    <disk type='block' device='cdrom'>
      <driver name='qemu' type='raw'/>
      <target dev='hdc' bus='ide'/>
      <readonly/>
      <address type='drive' controller='0' bus='1' unit='0'/>
    </disk>
    <controller type='ide' index='0'>
      <address type='pci' domain='0x0000' bus='0x00' slot='0x01' function='0x1'/>
    </controller>
    <interface type='bridge'>
      <mac address='22:54:01:03:93:38'/>
      <source bridge='br0'/>
      <model type='virtio'/>
      <address type='pci' domain='0x0000' bus='0x00' slot='0x03' function='0x0'/>
    </interface>
    <serial type='pty'>
      <target port='0'/>
    </serial>
    <console type='pty'>
      <target type='serial' port='0'/>
    </console>
    <input type='mouse' bus='ps2'/>
    <graphics type='vnc' port='-1' autoport='yes' keymap='fr'/>
    <video>
      <model type='cirrus' vram='9216' heads='1'/>
      <address type='pci' domain='0x0000' bus='0x00' slot='0x02' function='0x0'/>
    </video>
    <memballoon model='virtio'>
      <address type='pci' domain='0x0000' bus='0x00' slot='0x05' function='0x0'/>
    </memballoon>
  </devices>
</domain>

Attention, si vous changez à la main un fichier domain.xml vous devez lancer la commande ;

# virsh define domain.xml

Internet pour les VM n’ayant été bind que sur le bridge du VPN

# iptables -t filter -A FORWARD -i br1 -j MARK --set-mark 1
# iptables -t nat -A POSTROUTING -m mark ! --mark 1 -o br0 -j MASQUERADE

Ou, moins fin :

# iptables -t nat -A POSTROUTING -o br0 -j MASQUERADE

Commandes libvirt qui vous serviront avec KVM

  • virsh start domain : Start le domaine spécifié
  • virsh shutdown domain : Shutdown le domaine spécifié
  • virsh reboot domain : just reboot a domain
  • virsh destroy domain : Terminate a domain
  • virsh autostart domain : Autostart a domain
  • virsh list : List domains

Ordre de boot des services

Cette partie est la pierre angulaire de ce setup :

  1. Il faut que dhcpd soit demarré après openvpn ! en effet sinon vous ne serai pas capable de diffuser des adresses ip sur votre interface br1 (bridgé avec l’interface tap0 du vpn) si le dhcpd ne demarre pas après openvpn.
  2. Il faut que la libvirt soit demarré après dhcpd, sinon vous ne serez pas capable de donner des ip à vos VM si celles-ci sont en auto start.
    /etc/init.d/.depend.start
    TARGETS = rsyslog qemu-kvm killprocs openvpn apache2 isc-dhcp-server cron acpid ssh rsync dbus exim4 libvirt-bin bootlogs munin-node single rc.local stop-bootlogd rmnologin
    INTERACTIVE = openvpn apache2
    openvpn: rsyslog
    apache2: rsyslog
    isc-dhcp-server: rsyslog
    cron: rsyslog
    acpid: rsyslog
    ssh: rsyslog
    rsync: rsyslog
    dbus: rsyslog
    exim4: rsyslog
    libvirt-bin: rsyslog openvpn
    munin-node: rsyslog isc-dhcp-server qemu-kvm openvpn libvirt-bin apache2 bootlogs cron acpid ssh rsync dbus exim4
    single: killprocs bootlogs
    rc.local: rsyslog isc-dhcp-server qemu-kvm openvpn libvirt-bin apache2 bootlogs cron acpid ssh rsync dbus exim4
    stop-bootlogd: rsyslog isc-dhcp-server qemu-kvm openvpn libvirt-bin apache2 bootlogs cron acpid ssh rsync dbus exim4
    rmnologin: rsyslog isc-dhcp-server qemu-kvm openvpn libvirt-bin apache2 bootlogs cron acpid ssh rsync dbus exim4
  3. /etc/network/interfaces sur Debian aura toujours tendance à se lancer AVANT la création de tap0 par le service openvpn, or votre interface br1 doit être bridge sur tap0, il est donc nécéssaire de lancer ce petit script (J’ai rajouté ce script au script d’init de openvpn, ainsi celui est lancé juste après que le vpn soit lancé, ainsi le bridge entre br1 et tap0 est fonctionnel lorsque la libvirt se lancera) :
    /etc/zenexity/fixebr1
    sleep 10
    ifdown br1 && echo 'bridge down' >> /var/tmp/bridge
    sleep 1
    ifup br1 && echo 'bridge up' >> /var/tmp/bridge
    sleep 1
    /etc/init.d/isc-dhcp-server restart
  4. Plugin munin pour libvirt + KVM

    virt-cpu
    virt-memory

    Je vous renvoie au site du plugin LibVirt pour Munin.

Schema de l’infrastructure

  1. | #1

    un grand merci pour ce post, il m’est très utile car je suis en cour de renouvellement de mes serveurs..bref bien expliquer et claire.
    Ça mérite d’être dit !

  2. | #2

    Merci 404_crazy

  3. truffe
    | #3

    Merci bien pour ce poste, j’étudie actuellement l’idée de passer à la virtualisation pour ma boite, ça donne quelques points d’entrée 🙂

  1. | #1