\documentclass[a4paper,11pt]{report}

	\input{../includes/headers_global.tex}
	
    \def\sitename{Debian GNU/Linux }
	\def\shorttitre{Libvirt - KVM}
	\def\titre{Debian Lenny - Virtualisation avec Libvirt/KVM}
	\def\autheur{Matthieu Vogelweith}
	\def\subject{Mise en place d'un serveur KVM sous Debian GNU/Linux 5.0 (Lenny)}
	\def\keywords{Debian, Linux, Matthieu, Vogelweith, Serveur, Lenny, KVM, virtualisation, libvirt, proxyarp}

	\input{../includes/headers.tex}

\begin{document}

\renewcommand{\bibname}{R\'ef\'erences}

\begin{titlepage}
	\maketitle
\end{titlepage}

\chapter*{Résumé}

	L'objectif de ce document est de détailler l'installation d'un serveur KVM \cite{kvm} (\textbf{K}ernel based \textbf{V}irtual \textbf{M}achine) complet ainsi que les bases de son administration.\\

\input{../includes/licence.tex}

\tableofcontents

\chapter{Préparation}

    \section{Pré requis}

Pour obtenir des performances correcte et pouvoir installer des OS non modifié dans les machines virtuelle, le support des extensions de virtualisation du processeur est indispensable. Il est possible de vérifier l'existance de cette extension sur le processeur à l'aide de la commande suivante :

\vspace{1em}
\begin{lstlisting}
# egrep '^flags.*(vmx|svm)' /proc/cpuinfo >/dev/null && echo OK || echo KO
\end{lstlisting}
\vspace{1em}

Si la commande retourne "OK", le processeur possède les extensions de virtualisation.

Ce document suppose également que le serveur est déjà installé avec une Debian 5.0 (Lenny) \cite{debian} propre. L'installation et la configuration du système de base sont présentée en détail dans un document dédié à cet effet : \cite{debian_install}.

    \section{Installation des paquets}

Comme indiqué précédemment, cette documentation propose d'utiliser la LibVirt pour gérer des machines virtuelles KVM. Dans Lenny, tout s'installe très simplement avec la commande suivante :

\vspace{1em}
\begin{lstlisting}
# aptitude install libvirt-bin kvm qemu virtinst
\end{lstlisting}
\vspace{1em}

Si les extensions de virtualisation sont bien gérées par le processeur, le script d'init de kvm a dû charger automatiquement les modules noyaux. Il est possible de le vérifier avec la commande suivante :

\vspace{1em}
\begin{lstlisting}
# lsmod | grep kvm
kvm_intel              39776  1 
kvm                   127464  1 kvm_intel
\end{lstlisting}
\vspace{1em}

Par défaut, libvirt ne se lance pas automatiquement. Pour ce faire, il suffit d'éditer le fichier /etc/default/libvirt-bin et de mofifier la variable suivante :

\vspace{1em}
\begin{lstlisting}
start_libvirtd="yes"
\end{lstlisting}
\vspace{1em}

Le démon sera alors actif au démarrage ou après avoir exécuté la commande suivante :

\vspace{1em}
\begin{lstlisting}
# /etc/init.d/libvirt-bin  restart
\end{lstlisting}
\vspace{1em}

 
\chapter{Gestion des images disques}
    
    \section{Présentation}

- différents format disponibles\\
    
    \section{Création d'une image disque}
    
\vspace{1em}
\begin{lstlisting}
$ qemu-img create -f qcow2 disk.qcow2 100G
\end{lstlisting}
\vspace{1em}

    \section{Création d'une image disque relative}

- Création d'une image disque en utilisant une autre image comme base. Cette nouvelle image ne contiendra que les diff par rapport à l'image de base.\\
- Utile par exemple si on dispose d'une image clean d'un OS et que l'on souhaite décliner plusieurs machines à partir de cet OS.\\

\vspace{1em}
\begin{lstlisting}
$ qemu-img create -b disk.qcow2 -f qcow2 reldisk.qcow2
\end{lstlisting}
\vspace{1em}

Il suffit ensuite d'utiliser reldisk.qcow2 dans les nouvelles VM.

    \section{Conversion d'images}

Conversion d'une image VMWare :

\vspace{1em}
\begin{lstlisting}
$ qemu-img convert -f vmdk vdisk.vmdk -O qcow2 vdisk.qcow2
\end{lstlisting}
\vspace{1em}

Réduction de la taille d'une image qcow2 :

\vspace{1em}
\begin{lstlisting}
$ qemu-img convert vdisk.qcow2 -O qcow2 vdisk-clean.qcow2
\end{lstlisting}
\vspace{1em}

%\chapter{Gestion des machines virtuelles}
%    
%    \section{Démarrage de la VM}
%
%\vspace{1em}
%\begin{lstlisting}
%$ kvm -cdrom etch.iso -hda disk.qcow2 -daemonize
%\end{lstlisting}
%\vspace{1em}
%
%    \section{Le mode Monitor}
%
%- Passer sur la console avec Ctrl+Alt.2 (ou bien lancer vm avec l'option "-monitor stdio")\\
%- help pour connaitre les commandes dispo\\
%
%
%\chapter{Gestion du réseau}
%
%    \section{NAT}
%
%Par défaut, si aucune option spécifique n'a été fournie lors du lancement de la VM, la machine invitée est NATée derrière la machine hôte. Il suffit de configurer la machine invitée en DHCP et elle obtiendra les paramètres réseaux suivants :
%\begin{itemize}
%    \item address : 10.0.2.xx/24
%    \item gateway : 10.0.2.2
%    \item DNS : 10.0.2.3
%\end{itemize}
%
%Ce mode est très pratique parce qu'il permet d'obtenir un accès réseau dans le VMs sans besoin d'avoir des droits root sur l'hôte. Malheureusement ce mode de fonctionnement montre très rapidement ses limites en terme de performances et ne permet pas de donner un accès simple aux VM par le réseau.
%
%Il est néanmoins possible d'accéder à la VM activant le DNAT sur quelques ports au démarrage de la machine :
%
%\vspace{1em}
%\begin{lstlisting}
%$ kvm -cdrom etch.iso -hda disk.qcow2 -daemonize -redir tcp10022::22
%\end{lstlisting}
%\vspace{1em}
%
%Avec cette configuration, il sera possible de joindre le serveur SSH de la VM en se connecter sur le port 10022 de la machine hôte.
%
%    \section{Bridge}
%
%Pour pouvoir bridger les machines virtuelles sur l'interface réseau réélle de la machine hôte, il faut modifier la configuration réseau de ce dernier. La première étape est d'ajouter une interface de type bridge qui pour être utilisée par les interfaces TUN/TAP associées à chaque VM. Par exemple, pour créer un bridge sur l'interface réélle eth0 et DHCP, modifier /etc/network/interfaces comme indiqué ci-dessous :
%
%\vspace{1em}
%\begin{lstlisting}
%# Lan Card with bridge
%auto br0
%iface br0 inet dhcp
%    bridge_ports eth0
%    bridge_stp off
%    bridge_maxwait 0
%    bridge_fd 0
%\end{lstlisting}
%\vspace{1em}
%
%Cette configuration va donc permettre d'ajouter plusieurs interfaces TUN ou TAP qui seront bridgée directement sur l'interface physique eth0. Le problème principal de ce système est que les interfaces TUN/TAP doivent être créées avec les droits root : les VM ne pourront donc pas obtenir une interface bridgée si elles sont lancée par des simples utilisateurs.
%
%Pour contourner ce problème il est possible de crééer les interfaces TAP à l'avance dans le fichier /etc/network/interfaces et de les associer à un utilisateur particulier. Par exemple, pour créer les interfaces tap0 et tap1 associées respectivement aux utilisateurs <user1> et <user2>, modifier la configuration précédente comme indiqué ci-dessous :
%
%\vspace{1em}
%\begin{lstlisting}
%# Lan Card with bridge
%auto eth0 br0
%iface br0 inet dhcp
%    pre-up tunctl -u <user1> -t tap0
%    pre-up tunctl -u <user2> -t tap1
%    post-down tunctl -d tap0
%    post-down tunctl -d tap1
%    bridge_ports eth0 tap0
%    bridge_stp off
%    bridge_maxwait 0
%    bridge_fd 0
%\end{lstlisting}
%\vspace{1em}
%
%De cette façon, les interfaces tap0 et tap1 seront créées automatiquement au démarrage de la machine et user1 et user2 ne pourront utiliser que l'interface qui leur est associée. Par exemple, <user1> pourra démarrer une VM avec la commande suivante :
%
%\vspace{1em}
%\begin{lstlisting}
%$ kvm -hda disk.qcow2 -daemonize -net nic,model=e1000 -net tap,ifnamme=tap0,script=no
%\end{lstlisting}
%\vspace{1em}
%
%Notons que l'option \textbf{model=e1000} a été ajoutée pour simuler une carte Giga. En réalité, les tests montrent qu'il est possible de monter à environ 20MB/s avec se type de configuration. Le mode bridge permet donc d'offrir un accès réseau complet aux VM avec des performances plus acceptables qu'en mode NAT. Pour améliorer encore les performances réseau il est possible d'utiliser \textbf{virtio}, qui fait l'objet du paragraphe suivant.
%    
%    \section{Virtio}
%
%- Attention, disponible uniquement pour kvm >= 60 donc pas dans les backports de ETCH\\
%- Utilisation de virtio pour améliorer les perfs réseau (89MB/s dans mes tests)\\
%- Virtio nécessite l'installation de modules spécifiques dans le guest : ils sont intégrés dans le kernel > 2.6.25 et disponible pour windows.\\
%
%\vspace{1em}
%\begin{lstlisting}
%$ kvm -hda disk.qcow2 -daemonize -net nic,model=virtio -net tap,ifnamme=tap0,script=no
%\end{lstlisting}
%\vspace{1em}
%    
%    \section{VDE}

\chapter{Gestion des snapshots}

    \section{Mode snapshot}

        Mode snapshot : disk en RO, toute les modifs sont faites dans des fichiers temp dans /tmp. Possibilité d'écriture sur le disk avec "commit" en console.

    \section{Création d'un snapshot}

\vspace{1em}
\begin{lstlisting}
qemu> savevm
\end{lstlisting}
\vspace{1em}


    \section{Affichage de la liste des snapshots}

\vspace{1em}
\begin{lstlisting}
qemu> info snapshots
\end{lstlisting}
\vspace{1em}

    \section{Chargement d'un snapshot}

\vspace{1em}
\begin{lstlisting}
qemu> loadvm
\end{lstlisting}
\vspace{1em}

\chapter{Utilisation de libvirt pour la gestion des VM}

    \section{Création d'une VM}

\vspace{1em}
\begin{lstlisting}
# qemu-img create -f qcow2 lenny.qcow2 100G
# virt-install --ram=1024 --name=lenny \
    --file=/var/lib/libvirt/images/lenny.qcow2 \
    --cdrom=/tmp/debian-LennyBeta2-amd64-netinst.iso \
    --hvm --vnc --noautoconsole --accelerate --network=bridge:br0
\end{lstlisting}
\vspace{1em}
    
    \section{VIRSH : Gestion des VM en console}
    
    \section{Sauvegarde/Restauration automatique}

VIRSH apporte également des commandes simples permettant de suspendre restaurer les machines virtuelles. Par exemple, il est possible de mettre une machine en hibernation avec la commande suivante :

\vspace{1em}
\begin{lstlisting}
# virsh save vm_name vm_name.dump
\end{lstlisting}
\vspace{1em}

La machine sera alors stoppée et les données nécessaires à la restauration de la machine seront stockées dans le dump vm\_name.dump. Pour restaurer la VM, il suffit ensuite d'exécuter la commande ci-dessous :

\vspace{1em}
\begin{lstlisting}
# virsh restore vm_name.dump
\end{lstlisting}
\vspace{1em}

Une utilisation courante de ce type de commandes et la suspension/restauration des VMs lors de l'arrêt/démarrage de la machine hôte. En effet, par défaut, les machines virtuelles sont stoppées brutalement lors de l'arrêt du démon libvirt-bin. Pour corriger ce problème, le paquet disponible fournit depuis la version 0.4.6-4 un script d'init \textbf{libvirt-suspendonreboot} permettant de suspendre les VMs lors de l'arrêt de la machine hôte et de la restaurer lors du démarrage. C'est une solution temporaire mais qui permet d'arrêter correctement les VMs lors d'un reboot de la machine hôte par exemple. Ce script peut être installé en utilisant les commandes ci-dessous :
 
\vspace{1em}
\begin{lstlisting}
# cp /usr/share/doc/libvirt-bin/examples/libvirt-suspendonreboot /etc/init.d/
# chmod 755 /etc/init.d/libvirt-suspendonreboot
# mkdir -p /var/lib/libvirt/autosuspend/
# update-rc.d libvirt-suspendonreboot defaults 21 19
\end{lstlisting}
\vspace{1em}

 
\chapter{Serveur de virtualisation : problématique réseau}
    
    \section{Présentation}

Lors de l'utilisation d'une machine hébergée comme serveur de virtualisation, on peut être amener à rencontrer des problèmes réseaux un peu spécifique. Par machine hébergée, entendez machine qui n'est pas accessible physiquement et ou on ne gère pas les équipements réseaux associés".

Par exemple sur les Dedibox XL \cite{dedibox}, les extensions de virtualisations sont disponibles et il est possible d'avoir plusieurs IP publiques routées sur la même machine. Cependant, il n'est pas possible d'affecter simplement les autres IP publiques à des VMs bridgées sur le réseau en raison des systèmes de sécurités mis en place sur les switches. En effet, lorsqu'on bridge une VM directement sur l'interface physique de la machine hôte, les paquets sortant de la VM sont émis avec une adresse MAC virtuelle différente de l'adresse MAC de la machine hôte. Ce comportement sera immédiatement détecté par le switch qui coupera le port pour des raisons de sécurité parce qu'il n'est pas censé recevoir des paquets avec des adresses MAC différentes.

Dans ce cas de figure, qui correspond à la plupart des plates-formes d'hébergement, il n'est donc pas possible de bridger les machines virtuelles directement sur le réseau physique. Pour contourner le problème, il y a principalement deux solutions :

\begin{itemize}
    \item Faire du NAT pour "cacher" les machines virtuelles derrière la machine hôte. Dans ce cas les machines virtuelles ont une IP privée mais sont accessible depuis l'extérieur avec leur IP publique ;
    \item Mettre en place un proxy ARP pour "cacher" uniquement les adresses MAC des machines virtuelles. Dans ce cas les machines virtuelles utilisent directement leurs IP publiques.
\end{itemize}

La solution la plus souple et la plus élégante est bien entendu la solution du Proxy ARP. Ce paragraphe propose donc de mettre en place un proxy ARP afin de gérer une "DMZ de machines virtuelles".

    \section{Pré requis}

Ce paragraphe suppose bien évidemment qu'une machine hébergée est disponible avec les extensions de virtualisation et plusieurs IP publiques (au moins 2) sont disponibles, toutes routées vers la machine hôte.

Coté logiciel, on suppose que shorewall \cite{shorewall} est déja installé et correctement configuré sur la machine hôte. %Pour plus d'informations à ce sujet, l'installation de shorewall est présentée en détails dans un document dédié à la sécurité : \cite{security}.

    \section{Configuration réseau de l'hôte}

La toute première étape dans le mise en place de cette DMZ virtuelle est la création d'un bridge sur la machine hôte qui sera le point d'entrée de la DMZ. Pour faire le parallèle avec une installation physique, on peut comparer ce bridge à un switch sur lequel serait branché une interface du firewall et une interface de chaque machine virtuelle.

La création de ce bridge doit être faite en même temps que le montage de l'interface réseau publique de la machine hôte. Pour cela, modifier la configuration réseau de l'hôte dans le fichier /etc/network/interfaces comme indiqué ci-dessous :

\vspace{1em}
\begin{lstlisting}
auto eth0
iface eth0 inet static
    address IP_HOTE
    netmask MASK_HOTE
    gateway IP_GATEWAY
    up brctl addbr dmz0
    up brctl setfd dmz0 0
    up brctl stp dmz0 on
    up ifconfig dmz0 up
\end{lstlisting}
\vspace{1em}

La configuration ci-dessus permet donc d'ajouter une interface \textbf{dmz0} de type bridge pour accueillir les interfaces virtuelles des VMs. Notons que libvirt est capable de gérer ce type de bridge de manière automatique mais dans le cas présent il est indispensable de la gérer manuellement pour les raisons suivantes :

\begin{itemize}
    \item Le bridge doit être nommé de la même façon parce que son nom est utiliser dans la configuration de shorewall ;
    \item Le bridge doit impérativement être monté AVANT le démarrage de Shorewall pour que celui-ci démarre correctement et puisse mettre en place le proxy ARP.
\end{itemize}

\textbf{Attention}, il ne faut surtout pas faire l'erreur d'ajouter eth0 dans les interfaces associées au bridge dmz0, dans ce cas les VMs seraient directement bridgées sur le réseau physique et le switch bloquerai le port. Libvirt ajoutera dynamiquement les interfaces virtuelles (vnet*) au bridge lors du démarre des VMs.
    
    \section{Le proxy ARP avec Shorewall}

Shorewall permet de configurer un proxy ARP de manière relativement simple. Pour des besoins plus génériques, la documentation officielle \cite{proxyarp} est très bien faite.

La première étape dans la mise en place de cette DMZ virtuelle est la création d'une nouvelle zone Shorewall de type IPv4. Cette zone permettra de gérer les règles d'accès à toutes les machines virtuelles présentes dans la DMZ. Ceci ce fait en ajoutant la ligne suivante dans /etc/shorewall/zones :

\vspace{1em}
\begin{lstlisting}
# ZONE  TYPE
dmz     ipv4
\end{lstlisting}
\vspace{1em}

Il faut ensuite associer cette nouvelle zone à une interface disponible sur le machine hôte. Dans le cas présent, il s'agit du bridge qui a été créé précédemment dmz0. Pour cela, ajouter la ligen suivante dans le fichier /etc/shorewall/interfaces :

\vspace{1em}
\begin{lstlisting}
# ZONE  INTERFACE
dmz     dmz0
\end{lstlisting}
\vspace{1em}

L'étape suivante est la gestion des règles de filtrage pour cette zone. Si l'on considère que cette zone est réellement une DMZ, on peut ajouter les lignes suivantes dans /etc/shorewall/policy pour autoriser tout le trafic entrant et sortant de la DMZ :

\vspace{1em}
\begin{lstlisting}
# SOURCE    DEST    POLICY
dmz         all     ACCEPT
all         dmz     ACCEPT
\end{lstlisting}
\vspace{1em}

\textbf{Attention}, la configuration décrite ci-dessus autorise tout le trafic vers les machines de la DMZ. Ces machines ne seront donc pas du tout protégées par le firewall de la machine hôte et il est indispensable d'installer un firewall sur chaque machine virtuelle. Néanmoins, il est tout a fait possible, pour des raisons de performance notamment, de modifier ces règles et de gérer toute la sécurité réseau sur la machine hôte.

La zone DMZ étant définie et configurée, il reste à mettre en place le proxy ARP proprement dit, tâche qui est grandement simplifiée par shorewall. Par exemple, si l'on souhaite démarrer deux VM dans la DMZ ayant pour IP publique IP\_DMZ\_1 et IP\_DMZ\_2, il faut ajouter les lignes suivantes dans le fichier /etc/shorewall/proxyarp :

\vspace{1em}
\begin{lstlisting}
# ADDRESS   INTERFACE   EXTERNAL    HAVEROUTE   PERSISTENT
IP_DMZ_1    dmz0        eth0        no          yes
IP_DMZ_2    dmz0        eth0        no          yes
\end{lstlisting}
\vspace{1em}

Notons que dans la configuration ci-dessus, l'option HAVEROUTE a été définie à "no" pour indiqué à shorewall que les routes nécessaires pour joindre les VMs ne sont renseignées sur la machine hôte. Shorewall ajoutera donc les routes automatiquement.

Enfin, pour permettre à shorewall de transmettre les paquets entre eth0 et dmz0, il faut activer le forwarding en modifiant l'option suivante dans /etc/shorewall/shorewall.conf :

\vspace{1em}
\begin{lstlisting}
IP_FORWARDING=On
\end{lstlisting}
\vspace{1em}

Pour que les modifications soient prisent en compte, il faut bien entendu redémarrer shorewall. \textbf{ATTENTION :} pour que shorewall redémarre correctement, le bridge dmz0 doit déja être actif ! Lorsque tout est vérifié, le redémarrage peut se faire avec la commande ci-dessous :

\vspace{1em}
\begin{lstlisting}
# /etc/init.d/shorewall restart
\end{lstlisting}
\vspace{1em}

    \section{Configuration de libvirt}

Lorsque le proxy ARP est correctement configuré, il faut configurer les machines virtuelles pour qu'elles puissent utiliser cette nouvelle configuration. Cette configuration est extrêmement simple puisqu'il suffit de configurer les VM en mode bridge sur l'interface dmz0. Comme les paquets seront directement envoyés par shorewall sur dmz0, les machines pourront recevoir les paquets sur leur interface réseau.

Cette configuration en mode bridge peut être réalisée avec les différentes interfaces de libvirt (virsh, virt-manager, ...) ou tout simplement en éditant le fichier XML associé à la machine dans /etc/libvirt/qemu/nom\_de\_la\_vm.xml :

\vspace{1em}
\begin{lstlisting}
<interface type='bridge'>
    <mac address='xx:xx:xx:xx:xx:xx'/>
    <source bridge='dmz0'/>
</interface>
\end{lstlisting}
\vspace{1em}


    \section{Configuration réseau des clients}

La machine hôte étant maintenant correctement configurée, il reste à configurer les machines virtuelles pour qu'elles puissent utiliser directement leurs IP publiques. Ceci se fait simplement en utilisant la configuration ci-dessous dans /etc/network/interfaces :

\vspace{1em}
\begin{lstlisting}
auto eth0
iface eth0 inet static
    address IP_DMZ
    netmask 255.255.255.255
    post-up /sbin/ip route add IP_HOTE/32 dev eth0
    post-up /sbin/ip route add default via IP_HOTE
\end{lstlisting}
\vspace{1em}

Si tout c'est passé correctement, les machines virtuelles bridgées sur dmz0, donc situées dans la DMZ, doit être accessible directement en utilisant leur IP publique.

\clearpage
\nocite{*}
\bibliographystyle{unsrt}
\bibliography{\jobname}

%\ifpdf
%\else
%\chapter{Vos commentaires}
%\fi
\end{document}
