2. Deuxième partie : virtualisation avec les Linux Containers

2.1. Introduction

Nous allons présenter ici l'installation des Linux Containers sur un serveur dédié OVH. La transposition à d'autres hébergeurs ne devrait pas être problématique, mais il est possible que les considérations sur le noyau Linux ne soient pas adaptées.

2.2. Téléchargement du noyau Linux

Si les Linux Containers représentent la solution de virtualisation soutenue par Linux, les noyaux proposés par OVH ne permettent pas de l'utiliser. Il est en effet nécessaire de disposer de toutes les fonctions des Control Groups (ou cgroups). Or ceux-ci ne sont pas compilés dans les versions OVH.
Il est donc nécessaire de compiler un nouveau noyau Linux.
Cette opération commence par le téléchargement des sources, réalisable selon deux méthodes. Et dans la mesure où nous devons recompiler un noyau, autant en prendre un plus récent que celui proposé par défaut par OVH, qui est en version 2.6.34.6 dans mon cas, comme le montre le résultat de la commande uname -r :
moi@ks123456:~$ uname -r
2.6.34.6-grsec-xxxx-grs-ipv6-64
moi@ks123456:~$
Il existe deux possibilités pour télécharger les sources : récupérer une archive (à l'heure où ces lignes sont écrites, la dernière version stable disponible est la 2.6.38.6), ou récupérer toutes les sources via git.

2.2.1 Téléchargement de l'archive complète

Pour connaître la dernière version stable, il suffit de se rendre sur le site http://www.kernel.org, puis de récupérer la version souhaitée avec un wget et de la décompresser :
moi@ks123456:~$ mkdir noyau
moi@ks123456:~$ cd noyau/
moi@ks123456:~/noyau$ mkdir archive
moi@ks123456:~/noyau$ cd archive
moi@ks123456:~/noyau/archive$ wget -c http://www.kernel.org/pub/linux/kernel/v2.6/linux-2.6.38.6.tar.bz2
--2011-05-12 14:16:00--  http://www.kernel.org/pub/linux/kernel/v2.6/linux-2.6.38.6.tar.bz2
Résolution de www.kernel.org... 199.6.1.165, 130.239.17.5
Connexion vers www.kernel.org|199.6.1.165|:80...connecté.
requête HTTP transmise, en attente de la réponse...200 OK
Longueur: 74811959 (71M) [application/x-bzip2]
Sauvegarde en : «linux-2.6.38.6.tar.bz2»

100%[===========================================================================>] 74 811 959  9,39M/s   ds 7,3s

2011-05-12 14:16:08 (9,79 MB/s) - «linux-2.6.38.6.tar.bz2» sauvegardé [74811959/74811959]

moi@ks123456:~/noyau/archive$ tar jxvf linux-2.6.38.6.tar.bz2
moi@ks123456:~/noyau/archive$ 
Nous allons travailler avec cette version, et nous baser sur le fichier de configuration standard d'OVH. C'est en effet le plus simple pour avoir simpement les différents drivers activés.
Le fichier de configuration d'OVH est récupérable sur ftp://ftp.ovh.net/made-in-ovh/bzImage/2.6.38.2/2.6-config-xxxx-std-ipv6-64 :
moi@ks123456:~/noyau/archive/linux-2.6.38.6$ wget -c ftp://ftp.ovh.net/made-in-ovh/bzImage/2.6.38.2/2.6-config-xxxx-std-ipv6-64
--2011-05-13 09:28:23--  ftp://ftp.ovh.net/made-in-ovh/bzImage/2.6.38.2/2.6-config-xxxx-std-ipv6-64
           => «2.6-config-xxxx-std-ipv6-64»
Résolution de ftp.ovh.net... 213.186.33.9
Connexion vers ftp.ovh.net|213.186.33.9|:21...connecté.
Ouverture de session en anonymous...Session établie!
==> SYST ... complété.    ==> PWD ... complété.
==> TYPE I ... complété.  ==> CWD (1) /made-in-ovh/bzImage/2.6.38.2 ... complété.
==> SIZE 2.6-config-xxxx-std-ipv6-64 ... 62396
==> PASV ... complété.    ==> RETR 2.6-config-xxxx-std-ipv6-64 ... complété.
Taille: 62396 (61K) (non certifiée)

100%[===========================================================================>] 62 396      --.-K/s   ds 0,007s  

2011-05-13 09:28:24 (8,37 MB/s) - «2.6-config-xxxx-std-ipv6-64» sauvegardé [62396]
moi@ks123456:~/noyau/archive/linux-2.6.38.6$
Nous devons recopier ou renommer ce fichier en .config car le processus de sélection des options se basera sur ce fichier s'il existe :
moi@ks123456:~/noyau/archive/linux-2.6.38.6$ cp 2.6-config-xxxx-std-ipv6-64 .config
moi@ks123456:~/noyau/archive/linux-2.6.38.6$ 
Avant de passer à la configuration des options du noyau, nous allons présenter à titre informatif une autre méthode pour récupérer le code source, via git.

2.2.2. Téléchargement via git

Il est également possible de récupérer la dernière version en cours de développement du noyau linux, via git, outil de gestion des versions, créé par Linus Torvalds :
moi@ks123456:~/noyau$ mkdir git
moi@ks123456:~/noyau$ cd git
moi@ks123456:~/noyau/git$ git clone git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux-2.6.git
Cloning into linux-2.6...
remote: Counting objects: 1986739, done.
remote: Compressing objects: 100% (317384/317384), done.
Receiving objects: 100% (1986739/1986739), 412.39 MiB | 9.74 MiB/s, done.
remote: Total 1986739 (delta 1653272), reused 1984340 (delta 1651173)
Resolving deltas: 100% (1653272/1653272), done.272)
moi@ks123456:~/noyau/git$ ls
linux-2.6
moi@ks123456:~/noyau/git$ cd linux-2.6/
moi@ks123456:~/noyau/git/linux-2.6$
moi@ks123456:~/noyau/git/linux-2.6$ git pull
Already up-to-date.
moi@ks123456:~/noyau/git/linux-2.6$
Néanmoins, la dernière version stable devrait nous suffire. Nous n'utiliserons donc pas celle-ci.

2.3. Compilation du noyau

2.3.1 Sélection des options

Une fois le code source récupéré par l'une des deux méthodes précédentes, il faut sélectionner les options à compiler en plus de celles prévues par défaut dans la configuration d'OVH.
En prévision de l'installation des LXC, nous allons sélectionner les options liées aux Control Groups, ou cgroups, ainsi que celles liées aux périphériques réseau.
Mais tout d'abord, il nous faut installer de nouveaux paquets pour pouvoir compiler le code source :
moi@ks123456:~/noyau/archive/linux-2.6.38.6$ sudo aptitude install make
Les NOUVEAUX paquets suivants vont être installés : 
  make 
0 paquets mis à jour, 1 nouvellement installés, 0 à enlever et 0 non mis à jour.
Il est nécessaire de télécharger 398 ko d'archives. Après dépaquetage, 1 249 ko seront utilisés.
Prendre : 1 http://mirror.ovh.net/debian/ squeeze/main make amd64 3.81-8 [398 kB]
 398 ko téléchargés en 3s (121 ko/s)
Sélection du paquet make précédemment désélectionné.
(Lecture de la base de données... 23419 fichiers et répertoires déjà installés.)
Dépaquetage de make (à partir de .../archives/make_3.81-8_amd64.deb) ...
Traitement des actions différées (« triggers ») pour « man-db »...
Paramétrage de make (3.81-8) ...
                                              
moi@ks123456:~/noyau/archive/linux-2.6.38.6$ 
moi@ks123456:~/noyau/archive/linux-2.6.38.6$ sudo aptitude install gcc
Les NOUVEAUX paquets suivants vont être installés : 
  binutils{a} cpp{a} cpp-4.4{a} gcc gcc-4.4{a} libc-dev-bin{a} libc6-dev{a} libgmp3c2{a} libgomp1{a} libmpfr4{a} linux-libc-dev{a} 
  manpages-dev{a} 
0 paquets mis à jour, 12 nouvellement installés, 0 à enlever et 0 non mis à jour.
Il est nécessaire de télécharger 16,6 Mo d'archives. Après dépaquetage, 47,2 Mo seront utilisés.
Voulez-vous continuer ? [Y/n/?] Y
Prendre : 1 http://mirror.ovh.net/debian/ squeeze/main binutils amd64 2.20.1-16 [3 993 kB]
[...]
[...]
moi@ks123456:~/noyau/archive/linux-2.6.38.6$ 
moi@ks123456:~/noyau/archive/linux-2.6.38.6$ sudo aptitude install ncurses-dev
Note : sélection de « libncurses5-dev » à la place du
       paquet virtuel « ncurses-dev »
Les NOUVEAUX paquets suivants vont être installés : 
  libncurses5-dev 
0 paquets mis à jour, 1 nouvellement installés, 0 à enlever et 0 non mis à jour.
[...]
[...]
moi@ks123456:~/noyau/archive/linux-2.6.38.6$ 
moi@ks123456:~/noyau/archive/linux-2.6.38.6$ sudo aptitude install lzma
Les NOUVEAUX paquets suivants vont être installés : 
  lzma 
0 paquets mis à jour, 1 nouvellement installés, 0 à enlever et 0 non mis à jour.
[...]
[...]
moi@ks123456:~/noyau/archive/linux-2.6.38.6$ 
Pour cela, nous allons utiliser la commande make menuconfig. Dans General setup, il faudra sélectionner toutes les options liées au Control Group support ainsi que tous les Namespaces support :
General setup  --->
	[*] Control Group support  --->
		[*]   Example debug cgroup subsystem
		[*]   Namespace cgroup subsystem   
		[*]   Freezer cgroup subsystem  
		[*]   Device controller for cgroups   
		[*]   Cpuset support   
		[*]     Include legacy /proc/<pid>/cpuset file 
		[*]   Simple CPU accounting cgroup subsystem   
		[*]   Resource counters  
		[*]     Memory Resource Controller for Control Groups  
		[*]       Memory Resource Controller Swap Extension
		[*]         Memory Resource Controller Swap Extension enabled by default (NEW)   
		[*]   Enable perf_event per-cpu per-container group (cgroup) monitoring   
		[*]   Group CPU scheduler  --->
			[*]   Group scheduling for SCHED_OTHER
			[*]   Group scheduling for SCHED_RR/FIFO
		<*>   Block IO controller   
		[*]     Enable Block IO controller debugging
	-*- Namespaces support  --->
    	[*]   UTS namespace
		[*]   IPC namespace
		[*]   User namespace (EXPERIMENTAL)
		[*]   PID Namespaces
		[*]   Network namespace
Ensuite, il faudra sélectionner dans Networking support les options suivantes :
[*] Networking support  --->
	Networking options  --->
		<*> 802.1d Ethernet Bridging
		[*]   IGMP/MLD snooping
		[ ] Distributed Switch Architecture support  --->
		<*> 802.1Q VLAN Support
		[*]   GVRP (GARP VLAN Registration Protocol) support
Puis, dans Device drivers :
Device Drivers  ---> 
	[*] Network device support  --->
		--- Network device support
		< >   Intermediate Functional Block support (NEW)
		< >   Dummy net driver support (NEW)
		< >   Bonding driver support (NEW)
		<*>   MAC-VLAN support (EXPERIMENTAL)
		<*>     MAC-VLAN based tap driver (EXPERIMENTAL)
		< >   EQL (serial line load balancing) support (NEW)
		< >   Universal TUN/TAP device driver support (NEW)
		<*>   Virtual ethernet pair device
		< >   General Instruments Surfboard 1000 (NEW)

	Character devices  --->    
		-*- Unix98 PTY support
		[*]   Support multiple instances of devpts
Si nous n'avions pas utilisé le fichier de configuration standard d'OVH, il nous aurait fallu activer à la main les différents pilotes logiciels liés à notre serveur.
Pour déterminer le matériel installé, le plus simple est d'utiliser la commande lshw (qu'il vous faudra installer avec un aptitude install lshw si ce n'est pas déjà fait). L'option -class network permet par exemple d'obtenir des informations sur la carte réseau :
moi@ks123456:~$ sudo lshw -class network
  *-network               
       description: Ethernet interface
       product: SiS900 PCI Fast Ethernet
       vendor: Silicon Integrated Systems [SiS]
       physical id: 4
       bus info: pci@0000:00:04.0
[...]
[...]
       resources: irq:19 ioport:2000(size=256) memory:8a100000-8a100fff memory:8a120000-8a13ffff
  *-network DISABLED
       description: Ethernet interface
       physical id: 1
       logical name: dummy0
       serial: ea:4d:13:d4:55:fa
       capabilities: ethernet physical
       configuration: broadcast=yes
moi@ks123456:~$
Il est également possible d'avoir une vision synthétique du bus PCI avec la commande lspci :
moi@ks123456:~$ sudo lspci
00:00.0 Host bridge: Silicon Integrated Systems [SiS] 662 Host (rev 01)
00:01.0 PCI bridge: Silicon Integrated Systems [SiS] SiS AGP Port (virtual PCI-to-PCI bridge)
00:02.0 ISA bridge: Silicon Integrated Systems [SiS] SiS964 [MuTIOL Media IO] (rev 36)
00:02.5 IDE interface: Silicon Integrated Systems [SiS] 5513 [IDE] (rev 01)
00:03.0 USB Controller: Silicon Integrated Systems [SiS] USB 1.1 Controller (rev 0f)
00:03.1 USB Controller: Silicon Integrated Systems [SiS] USB 1.1 Controller (rev 0f)
00:03.2 USB Controller: Silicon Integrated Systems [SiS] USB 1.1 Controller (rev 0f)
00:03.3 USB Controller: Silicon Integrated Systems [SiS] USB 2.0 Controller
00:04.0 Ethernet controller: Silicon Integrated Systems [SiS] SiS900 PCI Fast Ethernet (rev 91)
00:05.0 IDE interface: Silicon Integrated Systems [SiS] SATA (rev 01)
00:1f.0 PCI bridge: Silicon Integrated Systems [SiS] PCI-to-PCI bridge
01:00.0 VGA compatible controller: Silicon Integrated Systems [SiS] 661/741/760 PCI/AGP or 662/761Gx PCIE VGA Display Adapter (rev 04)
moi@ks123456:~$
Mais afin d'être sûr de ne rien oublier, nous n'utiliserons aucune de ces informations et ne changerons aucun des paramètres standard OVH !
La sélection des différentes options de cgroups et autres se fait au travers de la commande make menuconfig, qui créera un fichier .config, ou plus exactement qui mettra à jour le fichier .config dans notre cas.
moi@ks123456:~/noyau/archive/linux-2.6.38.6$ make menuconfig
  HOSTCC  scripts/basic/fixdep
  HOSTCC  scripts/basic/docproc
  HOSTCC  scripts/kconfig/conf.o
[...]
[...]
# configuration written to .config
#


*** End of the configuration.
*** Execute 'make' to start the build or try 'make help'.

moi@ks123456:~/noyau/archive/linux-2.6.38.6$
moi@ks123456:~/noyau/archive/linux-2.6.38.6$ ll .c*
-rw-r--r-- 1 moi moi 75113 14 mai   15:16 .config
moi@ks123456:~/noyau/archive/linux-2.6.38.6$
Vous pouvez éventuellement conserver ce fichier .config pour les futures compilations, si vous ne voulez pas repasser par les étapes de make menuconfig et que la liste des modules dont vous avez besoin n'a pas évolué.
Si LXC est déjà installé, on peut également vérifier que cette nouvelle configuration est bien correcte en positionnant la variable CONFIG avant de lancer lxc-checkconfig :
moi@ks123456:~/noyau/archive/linux-2.6.38.6$ CONFIG=/home/moi/noyau/archive/linux-2.6.38.6/.config lxc-checkconfig
--- Namespaces ---
Namespaces: enabled
Utsname namespace: enabled
Ipc namespace: enabled
Pid namespace: enabled
User namespace: enabled
Network namespace: enabled
Multiple /dev/pts instances: enabled 

--- Control groups ---
Cgroup: enabled
Cgroup namespace: enabled
Cgroup device: enabled
Cgroup sched: enabled
Cgroup cpu account: enabled
Cgroup memory controller: enabled
Cgroup cpuset: enabled

--- Misc ---
Veth pair device: enabled
Macvlan: enabled
Vlan: enabled
File capabilities: enabled

Note : Before booting a new kernel, you can check its configuration
usage : CONFIG=/path/to/config /usr/bin/lxc-checkconfig

moi@ks123456:~/noyau/archive/linux-2.6.38.6$
Théoriquement, à ce stade, vous n'avez pas encore installé LXC sur votre serveur, mais sait-on jamais !

2.3.2 Compilation et installation

Il est maintenant possible de compiler le noyau. Attention, en fonction de la puissance de votre machine, le processus peut être assez long (près de 45 minutes sur mon serveur Kimsufi 250G).
Nous avons encore deux possibilités pour la compilation du noyau : la version vanilla (la version Linux pure) et la version Debian. Les deux sont présentées ici, mais nous n'utiliserons que la version Debian.

Noyau vanilla

Pour lancer la compilation du noyau en version vanilla, il suffit de lancer un make :
moi@ks123456:~/noyau/archive/linux-2.6.38.6$ make 
  HOSTLD  scripts/kconfig/conf
scripts/kconfig/conf --silentoldconfig Kconfig
  CHK     include/linux/version.h
  UPD     include/linux/version.h
  CHK     include/generated/utsrelease.h
  UPD     include/generated/utsrelease.h
  CC      kernel/bounds.s
  GEN     include/generated/bounds.h
  CC      arch/x86/kernel/asm-offsets.s
  GEN     include/generated/asm-offsets.h
  CALL    scripts/checksyscalls.sh
[...]
[...]
  OBJCOPY arch/x86/boot/setup.bin
  OBJCOPY arch/x86/boot/vmlinux.bin
  HOSTCC  arch/x86/boot/tools/build
  BUILD   arch/x86/boot/bzImage
Root device is (8, 1)
Setup is 13452 bytes (padded to 13824 bytes).
System is 5461 kB
CRC ad7f4171
Kernel: arch/x86/boot/bzImage is ready  (#1)
moi@ks123456:~/noyau/archive/linux-2.6.38.6$
Deux fichiers sont créés : l'image du système (le fichier arch/x86/boot/bzImage), et la table des symboles utilisés par le noyau (le fichier System.map).
Ces deux fichiers doivent être copiés dans le répertoire /boot. Ils seront par ailleurs renommés afin d'avoir un nom plus explicite.
moi@ks123456:~/noyau/archive/linux-2.6.38.6$ sudo cp arch/x86/boot/bzImage /boot/bzImage-2.6.38.6-xxxx-lxc-ipv6-64
moi@ks123456:~/noyau/archive/linux-2.6.38.6$ sudo cp System.map /boot/System.map-2.6.38.6-xxxx-lxc-ipv6-64
moi@ks123456:~/noyau/archive/linux-2.6.38.6$
Il ne reste plus qu'à lancer update-grub afin de spécifier le nouveau noyau sur lequel démarrer :
moi@ks123456:~/noyau/archive/linux-2.6.38.6$ sudo update-grub
Generating grub.cfg ...
Warning: update-grub_lib is deprecated, use grub-mkconfig_lib instead
Found linux image: /boot/bzImage-2.6.38.6-xxxx-lxc-ipv6-64
  No volume groups found
done
moi@ks123456:~/noyau/archive/linux-2.6.38.6$
puis redémarrer :
moi@ks123456:~/noyau/archive/linux-2.6.38.6$ sudo reboot
The system is going down for reboot NOW!msufi.com (pts/0) (Thu May 14 16:47:5
moi@ks123456:~/noyau/archive/linux-2.6.38.6$
et enfin vérifier que le système a bien démarré sur le nouveau noyau :
moi@ks123456:~$ uname -r
2.6.38.6-xxxx-lxc-ipv6-64
moi@ks123456:~$

Noyau Debian

Nous allons cependant préférer la compilation du noyau en version Debian : en plus de créer un paquet simple à installer, des patchs de sécurité, de corrections de bogues ou d'optimisations propres à Debian seront installés.
Cela se fait en passant l'option deb-pkg à la commande make, avec en paramètre dans la variable KDEB_PKGVERSION la version du paquet créé (si ce n'est déjà fait, il faut au préalable installer le paquet dpkg-dev) :
moi@ks123456:~/noyau/archive/linux-2.6.38.6$ sudo aptitude install dpkg-dev
[...]
moi@ks123456:~/noyau/archive/linux-2.6.38.6$ sudo make KDEB_PKGVERSION=cgroups.1.0 deb-pkg
[...]
moi@ks123456:~/noyau/archive/linux-2.6.38.6$
Les fichiers créés se situent au niveau immédiatement supérieur de l'arborescence :
moi@ks123456:~/noyau/archive/linux-2.6.38.6$ ls -Al ..
drwxr-xr-x 25 moi     moi         4096 16 mai   22:13 linux-2.6.38.6
-rw-r--r--  1 root    root     6423808 16 mai   22:13 linux-headers-2.6.38.6_cgroups.1.0_amd64.deb
-rw-r--r--  1 root    root     6092144 16 mai   22:13 linux-image-2.6.38.6_cgroups.1.0_amd64.deb
-rw-r--r--  1 root    root      810716 16 mai   22:13 linux-libc-dev_cgroups.1.0_amd64.deb
moi@ks123456:~/noyau/archive/linux-2.6.38.6$
La mise à jour de grub et la copie des fichiers aux bons endroits sont réalisées simplement avec la commande dpkg, sur le paquet tout juste créé :
moi@ks123456:~/noyau/archive/linux-2.6.38.6$ sudo dpkg -i ../linux-image-2.6.38.6_cgroups.1.0_amd64.deb
(Lecture de la base de données... 29817 fichiers et répertoires déjà installés.)
Préparation du remplacement de linux-image-2.6.38.6 (en utilisant .../linux-image-2.6.38.6_cgroups.1.0_amd64.deb) ...
Dépaquetage de la mise à jour de linux-image-2.6.38.6 ...
Paramétrage de linux-image-2.6.38.6 (cgroups.1.0) ...
Generating grub.cfg ...
Warning: update-grub_lib is deprecated, use grub-mkconfig_lib instead
Found linux image: /boot/vmlinuz-2.6.38.6
  No volume groups found
done
moi@ks123456:~/noyau/archive/linux-2.6.38.6$
Si c'est le premier noyau que vous compilez, pas de souci, grub ne trouvera qu'une seule image (vmlinuz-2.6.38.6 dans l'exemple ci-dessus) et vous redémarrerez bien dessus. Un reboot et un uname -r vous donneront alors :
moi@ks123456:~$ uname -r
2.6.38.6
moi@ks123456:~$
En revanche, si vous avez plusieurs images, il faut spécifier à la main, dans le fichier /boot/grub/grub.cfg, sur laquelle vous voulez démarrer.
Prenons l'exemple suivant, dans lequel la commande update-grub retourne deux images :
moi@ks123456:~$ sudo update-grub
Generating grub.cfg ...
Warning: update-grub_lib is deprecated, use grub-mkconfig_lib instead
Found linux image: /boot/vmlinuz-2.6.38.6
Found linux image: /boot/vmlinuz-2.6.37+
  No volume groups found
done
moi@ks123456:~$
A cette configuration vont être associés quatre choix pour grub : Pour obtenir ces informations, il suffit de consulter le fichier /boot/grub/grub.cfg, et de savoir que la numérotation des images commence à 0 :
moi@ks123456:~$ sudo cat /boot/grub/grub.cfg
[...]

### BEGIN /etc/grub.d/10_linux ###
menuentry 'Debian GNU/Linux, avec Linux 2.6.38.6' --class debian --class gnu-linux --class gnu --class os {
        insmod part_msdos
        insmod ext2
        set root='(hd0,msdos1)'
        search --no-floppy --fs-uuid --set ae1e27dc-eb6e-418a-ac07-fcc855b9bb62
        echo    'Chargement de Linux 2.6.38.6 ...'
        linux   /boot/vmlinuz-2.6.38.6 root=/dev/sda1 ro  quiet
}
menuentry 'Debian GNU/Linux, avec Linux 2.6.38.6 (mode de dépannage)' --class debian --class gnu-linux --class gnu --class os {
        insmod part_msdos
        insmod ext2
        set root='(hd0,msdos1)'
        search --no-floppy --fs-uuid --set ae1e27dc-eb6e-418a-ac07-fcc855b9bb62
        echo    'Chargement de Linux 2.6.38.6 ...'
        linux   /boot/vmlinuz-2.6.38.6 root=/dev/sda1 ro single
}
menuentry 'Debian GNU/Linux, avec Linux 2.6.37+' --class debian --class gnu-linux --class gnu --class os {
        insmod part_msdos
        insmod ext2
        set root='(hd0,msdos1)'
        search --no-floppy --fs-uuid --set ae1e27dc-eb6e-418a-ac07-fcc855b9bb62
        echo    'Chargement de Linux 2.6.37+ ...'
        linux   /boot/vmlinuz-2.6.37+ root=/dev/sda1 ro  quiet
}
menuentry 'Debian GNU/Linux, avec Linux 2.6.37+ (mode de dépannage)' --class debian --class gnu-linux --class gnu --class os {
        insmod part_msdos
        insmod ext2
        set root='(hd0,msdos1)'
        search --no-floppy --fs-uuid --set ae1e27dc-eb6e-418a-ac07-fcc855b9bb62
        echo    'Chargement de Linux 2.6.37+ ...'
        linux   /boot/vmlinuz-2.6.37+ root=/dev/sda1 ro single
}
### END /etc/grub.d/10_linux ###

[...]
moi@ks123456:~$
Une fois le choix de l'image réalisé, il faut affecter sa valeur à la variable default, toujours dans le fichier /boot/grub/grub.cfg, mais dans les premières lignes :
moi@ks123456:~$ sudo cat /boot/grub/grub.cfg
#
# DO NOT EDIT THIS FILE
#
# It is automatically generated by grub-mkconfig using templates
# from /etc/grub.d and settings from /etc/default/grub
#

### BEGIN /etc/grub.d/00_header ###
if [ -s $prefix/grubenv ]; then
  load_env
fi
set default="0"
if [ "${prev_saved_entry}" ]; then
  set saved_entry="${prev_saved_entry}"

[...]
moi@ks123456:~$
La variable étant positionnée à 0, un reboot se fera sur la première image, à savoir Linux 2.6.38.6. Si nous voulions redémarrer sur l'image Linux 2.6.37+, il faudrait la positionner à 2.

2.4. Mise en œuvre des Linux Containers

2.4.1. Téléchargement des sources et installation

Tout comme pour le noyau, deux possibilités existent pour télécharger les Linux Containers : une archive figée, ou la dernière version en cours d'évolution via git. Les LXC évoluant très rapidement actuellement, nous n'allons nous intéresser qu'à la version git.
Si vous ne l'avez pas déjà fait, il sera nécessaire d'installer le paquet git :
moi@ks123456:~$ sudo aptitude install git
Les NOUVEAUX paquets suivants vont être installés : 
  git libcurl3-gnutls{a} liberror-perl{a} 
0 paquets mis à jour, 3 nouvellement installés, 0 à enlever et 0 non mis à jour.
[...]
moi@ks123456:~$
moi@ks123456:~$ mkdir lxc
moi@ks123456:~$ cd lxc
moi@ks123456:~/lxc$ git clone git://lxc.git.sourceforge.net/gitroot/lxc/lxc
Cloning into lxc...
remote: Counting objects: 4949, done.
remote: Compressing objects: 100% (3516/3516), done.
remote: Total 4949 (delta 3795), reused 1805 (delta 1419)
Receiving objects: 100% (4949/4949), 1006.50 KiB | 908 KiB/s, done.
Resolving deltas: 100% (3795/3795), done.
moi@ks123456:~/lxc$ ls
lxc
moi@ks123456:~/lxc$ cd lxc
moi@ks123456:~/lxc/lxc$ git pull
Already up-to-date.
moi@ks123456:~/lxc/lxc$
Il faut maintenant compiler lxc. Pour cela, nous allons devoir installer les paquets automake et libcap-dev :
moi@ks123456:~/lxc/lxc$ sudo aptitude install automake
Les NOUVEAUX paquets suivants vont être installés : 
  autoconf{a} automake autotools-dev{a} 
0 paquets mis à jour, 3 nouvellement installés, 0 à enlever et 0 non mis à jour.
[...]
moi@ks123456:~/lxc/lxc$
moi@ks123456:~/lxc/lxc$ sudo aptitude install libcap-dev
Les NOUVEAUX paquets suivants vont être installés : 
  libcap-dev 
0 paquets mis à jour, 1 nouvellement installés, 0 à enlever et 0 non mis à jour.
[...]
moi@ks123456:~/lxc/lxc$
Puis il faut lancer autogen.sh, depuis le répertoire lxc/lxc :
moi@ks123456:~/lxc/lxc$ sh ./autogen.sh 
+ test -d autom4te.cache
+ aclocal -I config
+ autoheader
+ autoconf
+ automake --add-missing --copy
configure.ac:11: installing `config/compile'
configure.ac:10: installing `config/config.guess'
configure.ac:10: installing `config/config.sub'
configure.ac:9: installing `config/install-sh'
configure.ac:9: installing `config/missing'
src/lxc/Makefile.am: installing `config/depcomp'
moi@ks123456:~/lxc/lxc$
On peut alors lancer la compilation :
moi@ks123456:~/lxc/lxc$ ./configure --prefix=/usr
checking for a BSD-compatible install... /usr/bin/install -c
checking whether build environment is sane... yes
checking for a thread-safe mkdir -p... /bin/mkdir -p
[...]
moi@ks123456:~/lxc/lxc$ make
[...]
moi@ks123456:~/lxc/lxc$ make check
[...]
moi@ks123456:~/lxc/lxc$ sudo make install
[...]
moi@ks123456:~/lxc/lxc$ make distclean
[...]
moi@ks123456:~/lxc/lxc$
Il est maintenant possible de vérifier que les options nécessaires du noyau ont bien été sélectionnées, avec la commande lxc-checkconfig :
moi@ks123456:~/lxc/lxc$ sudo lxc-checkconfig
--- Namespaces ---
Namespaces: enabled
Utsname namespace: enabled
Ipc namespace: enabled
Pid namespace: enabled
User namespace: enabled
Network namespace: enabled
Multiple /dev/pts instances: enabled

--- Control groups ---
Cgroup: enabled
Cgroup namespace: enabled
Cgroup device: enabled
Cgroup sched: enabled
Cgroup cpu account: enabled
Cgroup memory controller: enabled
Cgroup cpuset: enabled

--- Misc ---
Veth pair device: enabled
Macvlan: enabled
Vlan: enabled
File capabilities: enabled

Note : Before booting a new kernel, you can check its configuration
usage : CONFIG=/path/to/config /usr/bin/lxc-checkconfig

moi@ks123456:~/lxc/lxc$
Il faut ensuite intégrer les cgroups au fichier /etc/ftsab, en y ajoutant la ligne :
none		/cgroup	cgroup	defaults	0	0
moi@ks123456:~/lxc/lxc$ sudo vi /etc/fstab 
# <file system>	<mount point>	<type>	<options>	<dump>	<pass>
/dev/sda1	/	ext4	errors=remount-ro	0	1
/dev/sda2	/home	ext4	defaults	1	2
/dev/sda3	swap	swap	defaults	0	0
none		/cgroup	cgroup	defaults	0	0
Puis les monter :
moi@ks123456:~/lxc/lxc$ sudo mkdir /cgroup
moi@ks123456:~/lxc/lxc$ sudo mount /cgroup
moi@ks123456:~/lxc/lxc$
Il ne nous reste qu'une seule chose à faire avant de commencer à manipuler les containers : effectuer un lien symbolique de /usr/var/lib/lxc vers /home/lxc. Il faut en effet savoir que par défaut, sur les serveurs KIMSUFI, la partition /home est correctement dimensionnée, mais la partition / trop petite pour héberger les données des containers. Or dans cette version des LXC, le chemin /var/lib/lxc (ou /usr/var/lib/lxc dans notre cas) est inscrit en dur dans certains modules.
Nous allons donc créer le répertoire /home/lxc et faire pointer dessus /usr/var/lib/lxc :
moi@ks123456:~/lxc/lxc$ sudo mkdir /home/lxc
moi@ks123456:~/lxc/lxc$ sudo ln -s /home/lxc /usr/var/lib/lxc
moi@ks123456:~/lxc/lxc$

2.4.2. Création des containers

Nous allons tout d'abord installer deux nouveaux paquets : debootstrap et bridge-utils :
moi@ks123456:~$ sudo aptitude install deboostrap
Les NOUVEAUX paquets suivants vont être installés : 
  debootstrap 
0 paquets mis à jour, 1 nouvellement installés, 0 à enlever et 0 non mis à jour.
[...]
moi@ks123456:~$
moi@ks123456:~$ sudo aptitude install bridge-utils
Les NOUVEAUX paquets suivants vont être installés : 
  bridge-utils 
0 paquets mis à jour, 1 nouvellement installés, 0 à enlever et 0 non mis à jour.
[...]
moi@ks123456:~$
Afin de créer des containers utilisant Debian (Squeeze), nous allons recopier le fichier de template dans un répertoire exécutable :
moi@ks123456:~$ sudo cp /usr/lib/lxc/templates/lxc-debian /usr/local/sbin/lxc-debian
moi@ks123456:~$
Il faut ensuite modifier légèrement le fichier standard pour y apporter trois modifications : Vous pouvez soit modifier le ficher à la main, soit créer le fichier lxcdebian.patch suivant pour patch :
--- lxc-debian	2011-05-15 17:34:46.000000000 +0200
+++ lxc-debianV2	2011-05-15 17:38:05.000000000 +0200
@@ -75,16 +75,11 @@
 EOF
 
     # reconfigure some services
-    if [ -z "$LANG" ]; then
-	chroot $rootfs locale-gen en_US.UTF-8 UTF-8
-	chroot $rootfs update-locale LANG=en_US.UTF-8
-    else
-	chroot $rootfs locale-gen $LANG $(echo $LANG | cut -d. -f2)
-	chroot $rootfs update-locale LANG=$LANG
-    fi
+    echo "fr_FR.UTF-8 UTF-8" > $rootfs/etc/locale.gen
+    chroot $rootfs locale-gen fr_FR.UTF-8 UTF-8
+    chroot $rootfs update-locale LANG=fr_FR.UTF-8
 
     # remove pointless services in a container
-    chroot $rootfs /usr/sbin/update-rc.d -f checkroot.sh remove
     chroot $rootfs /usr/sbin/update-rc.d -f umountfs remove
     chroot $rootfs /usr/sbin/update-rc.d -f hwclock.sh remove
     chroot $rootfs /usr/sbin/update-rc.d -f hwclockfirst.sh remove
@@ -106,6 +101,13 @@
 netbase,\
 net-tools,\
 iproute,\
+iputils-ping,\
+iptables,\
+rsyslog,\
+traceroute,\
+wget,\
+vim,\
+aptitude,\
 openssh-server
 
     cache=$1
moi@ks123456:~$ sudo patch /usr/local/sbin/lxc-debian ./lxcdebian.patch
patching file /usr/local/sbin/lxc-debian
moi@ks123456:~$
Il est maintenant possible de procéder à la création de la première VM, que nous appellerons vm2 :
moi@ks123456:~$ sudo mkdir /home/lxc/vm2
moi@ks123456:~$ sudo lxc-debian -p /home/lxc/vm2
debootstrap est /usr/sbin/debootstrap
Checking cache download in /usr/var/cache/lxc/debian/rootfs-squeeze-amd64 ... 
Downloading debian minimal ...
I: Retrieving Release
I: Retrieving Packages
I: Validating Packages
I: Resolving dependencies of required packages...
I: Resolving dependencies of base packages...
I: Found additional required dependencies: insserv libbz2-1.0 libdb4.8 libslang2 
[...]
update-rc.d: using dependency based boot sequencing
Root password is 'root', please change !
moi@ks123456:~$
Notre première machine virtuelle a été correctement créée. Nous allons en créer une seconde, vm3 :
moi@ks123456:~$ sudo mkdir /home/lxc/vm3
moi@ks123456:~$ sudo lxc-debian -p /home/lxc/vm3
debootstrap est /usr/sbin/debootstrap
Checking cache download in /usr/var/cache/lxc/debian/rootfs-squeeze-amd64 ... 
Copying rootfs to /home/lxc/vm3/rootfs...Generating locales (this might take a while)...
[...]
update-rc.d: using dependency based boot sequencing
Root password is 'root', please change !
moi@ks123456:~$
Vous remarquerez que la création est beaucoup plus rapide, les différents paquets à installer ayant été téléchargés et mis en cache lors de la création de la précédente VM.
Nous allons maintenant devoir configurer le réseau, pour le serveur hôte et les VM.

2.4.3. Configuration du réseau

Une des particularités des machines virtuelles déployées sur les KIMUFI est que les adresses IP des hôtes et des VM vont toutes se trouver sur des réseaux différents. La configuration à mettre en œuvre va donc différer de ce que vous pouvez trouver habituellement dans les documents disponibles sur Internet.
Je vais détailler deux méthodes : la première, qui s'apparente à un mode « NATé » traditionnel, est très générale et peut s'appliquer à n'importe quelle configuration utilisant des adresses IP publiques de réseaux différents. La seconde, « bridgée », est spécifique à OVH, qui propose d'associer aux IP failover des adresses Mac virtuelles.
Dans les deux cas, nous considérons que nous avons à notre disposition deux adresses IP Failover : 91.2.2.2 et 91.3.3.3, l'adresse IP publique du serveur hôte étant 91.123.123.133.

2.4.3.1. Mode NATé

Nous allons dans ce mode créer un réseau entre l'hôte et les machines virtuelles, en 192.168.10.0.
Pour cela, nous allons utiliser un « bridge » (un pont), c'est-à-dire une connexion de niveau 2. Je ne vais pas rentrer dans les détails ici, mais il suffit de se représenter ce pont comme un switch Ethernet virtuel, sur lequel nous brancherons les différentes machines.
L'hôte aura deux adresses IP : son adresse publique 91.123.123.123 sur son interface eth0 et son adresse privée 192.168.10.1 sur une interface br0 (i.e. la connexion au switch Ethernet virtuel), et les deux machines virtuelles précédemment créées, uniquement des adresses privées sur leurs interfaces eth0 : 192.168.10.2 et 192.168.10.3 (ces interfaces seront également connectées au switch Ethernet virtuel).

Configuration de l'hôte

Il faut éditer le fichier /etc/network/interfaces pour ajouter une interface bridge :
auto br0
iface br0 inet static
       address 192.168.10.1
       netmask 255.255.255.0
       bridge_ports none
       bridge_stp off
       bridge_fd 0
       bridge_maxwait 5
et également spécifier (au niveau de l'interface eth0) que le transfert des paquets en IPv4 doit être actif :
       post-up echo 1 > /proc/sys/net/ipv4/ip_forward
Le fichier de configuration du réseau pour l'hôte doit donc ressembler à ça :
moi@ks123456:~$ sudo cat /etc/network/interfaces
# This file describes the network interfaces available on your system
# and how to activate them. For more information, see interfaces(5).

# The loopback network interface
auto lo
iface lo inet loopback

auto eth0
iface eth0 inet static
       address 91.123.123.123
       netmask 255.255.255.0
       network 91.123.123.0
       broadcast 91.123.123.255
       gateway 91.123.123.254
       post-up echo 1 > /proc/sys/net/ipv4/ip_forward

auto br0
iface br0 inet static
       address 192.168.10.1
       netmask 255.255.255.0
       bridge_ports none
       bridge_stp off
       bridge_fd 0
       bridge_maxwait 5
moi@ks123456:~$
Vous remarquerez que nous n'attachons pas l'interface bridgée br0 à l'interface Ethernet physique de la machine, eth0. À ce stade, ces deux interfaces ne se « connaissent » pas.
Il ne reste plus qu'à redémarrer les interfaces réseau :
moi@ks123456:~$ sudo /etc/init.d/networking restart
Running /etc/init.d/networking restart is deprecated because it may not enable again some interfaces ... (warning).
Reconfiguring network interfaces...
Waiting for br0 to get ready (MAXWAIT is 5 seconds).
done.
moi@ks123456:~$
Nous avons donc maintenant un serveur hôte avec deux interfaces réseau (en plus du loopback) : l'une, physique, eth0, reliée au réseau de notre hébergeur et disposant d'une adresse IP publique, et l'autre, br0, connectée à un switch Ethernet virtuel, diposant d'une adresse IP privée.

Configuration des machines virtuelles

Nous allons maintenant nous attaquer à la configuration réseau des machines virtuelles, à savoir la connexion de leurs interfaces Ethernet virtuelles au switch Ethernet virtuel br0 du serveur hôte.
Il nous faut pour cela tout d'abord éditer, depuis le serveur hôte, les fichiers de configuration (/home/lxc/vm2/config pour la vm2 et /home/lxc/vm3/config pour la vm3), en ajoutant à la fin les éléments réseau, soit pour la vm2 :
# Config réseau
lxc.utsname = vm2
lxc.network.type = veth
lxc.network.flags = up
lxc.network.link = br0
lxc.network.name = eth0
lxc.network.ipv4 = 192.168.10.2/24
lxc.network.veth.pair = vethVM2
et pour la vm3 :
# Config réseau
lxc.utsname = vm3
lxc.network.type = veth
lxc.network.flags = up
lxc.network.link = br0
lxc.network.name = eth0
lxc.network.ipv4 = 192.168.10.3/24
lxc.network.veth.pair = vethVM3
Vous constatez que nous créons des interfaces de type Ethernet (veth) que nous attachons au switch virtuel (br0) du serveur hôte, via un lien intitulé vethVM2 (resp. vethVM3) du côté du serveur hôte.

Le fichier de configuration complet pour la VM2 devient donc :
moi@ks123456:~$ sudo cat /home/lxc/vm2/config 
lxc.tty = 4
lxc.pts = 1024
lxc.rootfs = /home/lxc/vm2/rootfs
lxc.cgroup.devices.deny = a
# /dev/null and zero
lxc.cgroup.devices.allow = c 1:3 rwm
lxc.cgroup.devices.allow = c 1:5 rwm
# consoles
lxc.cgroup.devices.allow = c 5:1 rwm
lxc.cgroup.devices.allow = c 5:0 rwm
lxc.cgroup.devices.allow = c 4:0 rwm
lxc.cgroup.devices.allow = c 4:1 rwm
# /dev/{,u}random
lxc.cgroup.devices.allow = c 1:9 rwm
lxc.cgroup.devices.allow = c 1:8 rwm
lxc.cgroup.devices.allow = c 136:* rwm
lxc.cgroup.devices.allow = c 5:2 rwm
# rtc
lxc.cgroup.devices.allow = c 254:0 rwm

# mounts point
lxc.mount.entry=proc /home/lxc/vm2/rootfs/proc proc nodev,noexec,nosuid 0 0
lxc.mount.entry=sysfs /home/lxc/vm2/rootfs/sys sysfs defaults  0 0
lxc.tty = 4
lxc.pts = 1024
lxc.rootfs = /home/lxc/vm2/rootfs
lxc.cgroup.devices.deny = a
# /dev/null and zero
lxc.cgroup.devices.allow = c 1:3 rwm
lxc.cgroup.devices.allow = c 1:5 rwm
# consoles
lxc.cgroup.devices.allow = c 5:1 rwm
lxc.cgroup.devices.allow = c 5:0 rwm
lxc.cgroup.devices.allow = c 4:0 rwm
lxc.cgroup.devices.allow = c 4:1 rwm
# /dev/{,u}random
lxc.cgroup.devices.allow = c 1:9 rwm
lxc.cgroup.devices.allow = c 1:8 rwm
lxc.cgroup.devices.allow = c 136:* rwm
lxc.cgroup.devices.allow = c 5:2 rwm
# rtc
lxc.cgroup.devices.allow = c 254:0 rwm

# mounts point
lxc.mount.entry=proc /home/lxc/vm2/rootfs/proc proc nodev,noexec,nosuid 0 0
lxc.mount.entry=sysfs /home/lxc/vm2/rootfs/sys sysfs defaults  0 0

# Config réseau
lxc.utsname = vm2
lxc.network.type = veth
lxc.network.flags = up
lxc.network.link = br0
lxc.network.name = eth0
lxc.network.ipv4 = 192.168.10.2/24
lxc.network.veth.pair = vethVM2
moi@ks123456:~$
Il faut ensuite configurer le fichier /etc/network/interfaces de chacune des VM, pour l'interface eth0.
Il est possible d'accéder directement à ce fichier depuis le serveur hôte en passant par le chemin /home/lxc/vm2/rootfs/ (ou /home/lxc/vm3/rootfs/ pour la VM3).
Le fichier /etc/network/interfaces de la VM2 est très simple, puisque nous n'avons qu'à configurer l'interface eth0 de façon très standard :
moi@ks123456:~$ sudo vi /home/lxc/vm2/rootfs/etc/network/interfaces 
auto lo
iface lo inet loopback

auto eth0
iface eth0 inet static
	address 192.168.10.2
	netmask 255.255.255.0
	broadcast 192.168.10.255
	gateway 192.168.10.1
	post-up route add 192.168.10.1 dev eth0
	post-up route add default gw 192.168.10.1
	post-down route del 192.168.10.1 dev eth0
	post-down route del default gw 192.168.10.1
moi@ks123456:~$
Nous ne faisons plus référence ici au bridge et à l'interface br0 du serveur hôte, car toutes les informations figurent dans le fichier de configuration.
Pour la VM3, seule l'adresse IP de l'interface eth0 doit être changée, pour passer de 192.168.10.2 à 192.168.10.3.

Démarrage des VM

Nous pouvons maintenant démarrer les deux VM :
moi@ks123456:~$ sudo lxc-start -n vm2 -f /home/lxc/vm2/config -d
moi@ks123456:~$ sudo lxc-start -n vm3 -f /home/lxc/vm3/config -d
moi@ks123456:~$
et vérifier qu'elles sont bien vues depuis le serveur hôte :
moi@ks123456:~$ ping 192.168.10.2
PING 192.168.10.2 (192.168.10.2) 56(84) bytes of data.
64 bytes from 192.168.10.2: icmp_req=1 ttl=64 time=0.126 ms
64 bytes from 192.168.10.2: icmp_req=2 ttl=64 time=0.073 ms
64 bytes from 192.168.10.2: icmp_req=3 ttl=64 time=0.089 ms
^C
--- 192.168.10.2 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 1998ms
rtt min/avg/max/mdev = 0.073/0.096/0.126/0.022 ms
moi@ks123456:~$ ping 192.168.10.3
PING 192.168.10.3 (192.168.10.3) 56(84) bytes of data.
64 bytes from 192.168.10.3: icmp_req=1 ttl=64 time=1.84 ms
64 bytes from 192.168.10.3: icmp_req=2 ttl=64 time=0.086 ms
64 bytes from 192.168.10.3: icmp_req=3 ttl=64 time=0.075 ms
^C
--- 192.168.10.3 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2000ms
rtt min/avg/max/mdev = 0.075/0.667/1.840/0.829 ms
moi@ks123456:~$
Nous allons maintenant rentrer dans les VM, en se loguant avec le couple root/root :
moi@ks123456:~$ sudo  lxc-console -n vm2

Type <Ctrl+a q> to exit the console

Debian GNU/Linux 6.0 vm2 tty1

vm2 login: root
Password: 
Linux vm2 2.6.38.6-xxxx-std_cgroups-ipv6-64 #1 SMP Sat May 14 17:50:45 CEST 2011 x86_64

The programs included with the Debian GNU/Linux system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.

Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent
permitted by applicable law.
root@vm2:~# 
On vérifie que la VM2 voit bien la VM3
root@vm2:~# ping 192.168.10.3
PING 192.168.10.3 (192.168.10.3) 56(84) bytes of data.
64 bytes from 192.168.10.3: icmp_req=1 ttl=64 time=2.13 ms
64 bytes from 192.168.10.3: icmp_req=2 ttl=64 time=0.110 ms
^C
--- 192.168.10.3 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1001ms
rtt min/avg/max/mdev = 0.110/1.123/2.136/1.013 ms
root@vm2:~# 
Nous avons donc maintenant un réseau en 192.168.10.0 sur lequel sont connectés le serveur hôte et nos deux machines virtuelles. Mais l'intérêt est assez limité, puisque les VM ne voient pas le réseau extérieur (ce qui n'a en soit rien de surprenant puisque l'interface physique eth0 du serveur hôte est complètement séparée de l'interface br0 du switch Ethernet virtuel).
Pour remédier à cette limitation, nous allons ajouter des règles iptables au niveau du serveur hôte, en commençant par une simple règle de MASQUERADE, soit pour tout le réseau :
iptables -t nat -A POSTROUTING -s 192.168.10.0/24 -o eth0 -j MASQUERADE
soit juste pour une VM donnée, par exemple la VM2 :
iptables -t nat -A POSTROUTING -s 192.168.10.2 -o eth0 -j MASQUERADE
Cette règle va nous permettre, depuis la VM2, de sortir sur le réseau Internet.
Pour l'ajouter, nous saisissons la commande suivante sur le serveur hôte :
moi@ks123456:~$ sudo iptables -t nat -A POSTROUTING -s 192.168.10.2 -o eth0 -j MASQUERADE
moi@ks123456:~$
Nous pouvons vérifier dans la foulée qu'elle a bien été prise en compte :
moi@ks123456:~$ sudo iptables --list -n -t nat -v
Chain PREROUTING (policy ACCEPT 0 packets, 0 bytes)
 pkts bytes target     prot opt in     out     source               destination         

Chain INPUT (policy ACCEPT 0 packets, 0 bytes)
 pkts bytes target     prot opt in     out     source               destination         

Chain OUTPUT (policy ACCEPT 0 packets, 0 bytes)
 pkts bytes target     prot opt in     out     source               destination         

Chain POSTROUTING (policy ACCEPT 0 packets, 0 bytes)
 pkts bytes target     prot opt in     out     source               destination         
    0     0 MASQUERADE  all  --  *      eth0    192.168.10.2         0.0.0.0/0           
moi@ks123456:~$
En se connectant sur la VM2, nous pouvons maintenant constater qu'elle voit bien l'extérieur :
root@vm2:~# ping www.yahoo.fr
PING any-rc.a01.yahoodns.net (206.190.60.37) 56(84) bytes of data.
64 bytes from w2.rc.vip.re4.yahoo.com (206.190.60.37): icmp_req=1 ttl=54 time=80.4 ms
64 bytes from w2.rc.vip.re4.yahoo.com (206.190.60.37): icmp_req=2 ttl=54 time=80.9 ms
^C
--- any-rc.a01.yahoodns.net ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1001ms
rtt min/avg/max/mdev = 80.432/80.715/80.999/0.401 ms
root@vm2:~# 
Si notre objectif est juste de pouvoir accéder à Internet depuis la VM, nous pouvons en rester là. Mais si nous souhaitons pouvoir nous y connecter depuis le réseau extérieur, il nous faut apporter quelques modifications.
Nous allons, depuis le serveur hôte, attacher à chacune de nos machines virtuelles les adresses IP failover que nous avons à notre disposition. Et cet attachement se fera dans les deux sens, en entrée comme en sortie.
Nous allons d'abord configurer une sous-interface eth0:0 sur le serveur hôte, à laquelle nous allons attribuer l'IP Failover 91.2.2.2. Il nous faut pour cela ajouter les lignes suivantes au fichier /etc/network/interfaces, au niveau de l'interface eth0 :
# Création d'une sous-interface, associée à une IP Failover
post-up /sbin/ifconfig eth0:0 91.2.2.2 netmask 255.255.255.255 broadcast 91.2.2.2
post-down /sbin/ifconfig eth0:0 down
Le fichier /etc/network/interfaces devient alors :
moi@ks123456:~$ sudo cat /etc/network/interfaces 
# This file describes the network interfaces available on your system
# and how to activate them. For more information, see interfaces(5).

# The loopback network interface
auto lo
iface lo inet loopback

auto eth0
iface eth0 inet static
	address 91.123.123.123
	netmask 255.255.255.0
	network 91.123.123.0
	broadcast 91.123.123.255
	gateway 91.123.123.254
	post-up echo 1 > /proc/sys/net/ipv4/ip_forward

	# Création d'une sous-interface, associée à une IP Failover
	post-up /sbin/ifconfig eth0:0 91.2.2.2 netmask 255.255.255.255 broadcast 91.2.2.2
	post-down /sbin/ifconfig eth0:0 down

auto br0
iface br0 inet static
       address 192.168.10.1
       netmask 255.255.255.0
       bridge_ports none
       bridge_stp off
       bridge_fd 0
       bridge_maxwait 5
moi@ks123456:~$
Nous allons ensuite modifier nos règles iptables en ajoutant une règle de DNAT indiquant que les paquets entrants à destination de l'adresse 91.2.2.2 doivent être transférés à l'adresse 192.168.10.2 :
iptables -t nat -A PREROUTING -d 91.2.2.2 -j DNAT --to-destination 192.168.10.2
Et afin que les paquets sortants de la machine virtuelles soient vus comme provenant de l'adresse IP 91.2.2.2 et non pas 91.123.123.123, nous allons remplacer la règle de MASQUERADE par une règle de SNAT :
iptables -t nat -D POSTROUTING -s 192.168.10.2 -o eth0 -j MASQUERADE
iptables -t nat -A POSTROUTING -s 192.168.10.2 -o eth0 -j SNAT --to-source 91.2.2.2
Nous pouvons vérifier que tous les paquets sortant de la VM sont bien identifiés comme provenant de l'adresse IP 91.2.2.2, et non plus de 91.123.123.123, avec la commande wget -O - -q myip.dk | grep 'IP Address' (si ce n'est déjà fait, il vous faudra installer la commande wget avec aptitude install wget) :
root@vm2:~# wget -O - -q myip.dk | grep 'IP Address'
                <b>IP Address:</b> <span class="ha4">91.2.2.2</span><br /><br />
                <b>IP Address:</b> <span class="ha6"></span><br /><br />
root@vm2:~# 
Nous avons donc maintenant une VM visible depuis l'extérieur, avec sa propre adresse IP.
Il ne nous reste plus qu'à inscrire en dur les règles iptables en les intégrant au fichier /etc/network/interfaces du serveur hôte, pour nos deux machines virtuelles, VM2 et VM3 :
moi@ks123456:~$ sudo cat /etc/network/interfaces
# This file describes the network interfaces available on your system
# and how to activate them. For more information, see interfaces(5).

# The loopback network interface
auto lo
iface lo inet loopback

auto eth0
iface eth0 inet static
       address 91.123.123.123
       netmask 255.255.255.0
       network 91.123.123.0
       broadcast 91.123.123.255
       gateway 91.123.123.254
       post-up echo 1 > /proc/sys/net/ipv4/ip_forward
       # Pour créer les sous-interfaces
       post-up /sbin/ifconfig eth0:0 91.2.2.2 netmask 255.255.255.255 broadcast 91.2.2.2
       post-up /sbin/ifconfig eth0:1 91.3.3.3 netmask 255.255.255.255 broadcast 91.3.3.3

       # Pour faire croire en sortie que 192.168.10.2 est 91.2.2.2
       post-up /sbin/iptables -t nat -A POSTROUTING -s '192.168.10.2/32' -o eth0 -j SNAT --to-source 91.2.2.2
       # Pour faire croire en sortie que 192.168.10.3 est 91.3.3.3
       post-up /sbin/iptables -t nat -A POSTROUTING -s '192.168.10.3/32' -o eth0 -j SNAT --to-source 91.3.3.3

       # Pour router en entrée les paquets à destination de 91.2.2.2 vers 192.168.10.2
       post-up /sbin/iptables -t nat -A PREROUTING -d 91.2.2.2 -j DNAT --to-destination 192.168.10.2
       # Pour router en entrée les paquets à destination de 91.3.3.3 vers 192.168.10.3
       post-up /sbin/iptables -t nat -A PREROUTING -d 91.3.3.3 -j DNAT --to-destination 192.168.10.3

       post-down /sbin/iptables -t nat -D PREROUTING -d 91.2.2.2 -j DNAT --to-destination 192.168.10.2
       post-down /sbin/iptables -t nat -D PREROUTING -d 91.3.3.3 -j DNAT --to-destination 192.168.10.3
       post-down /sbin/iptables -t nat -D POSTROUTING -s '192.168.10.2/32' -o eth0 -j SNAT --to-source 91.2.2.2
       post-down /sbin/iptables -t nat -D POSTROUTING -s '192.168.10.3/32' -o eth0 -j SNAT --to-source 91.3.3.3
       post-down /sbin/ifconfig eth0:0 down
       post-down /sbin/ifconfig eth0:1 down

auto br0
iface br0 inet static
       address 192.168.10.1
       netmask 255.255.255.0
       bridge_ports none
       bridge_stp off
       bridge_fd 0
       bridge_maxwait 5
moi@ks123456:~$
Vous voilà donc avec un système complètement fonctionnel, dans lequel les paramètres réseau « publics » sont gérés depuis le serveur hôte, les machines virtuelles ne connaissant que la partie réseau LAN privé.
L'intérêt d'une telle configuration est de pouvoir très simplement associer une adresse IP publique à une VM, et donc à un service, sans aucune modification des paramètres réseau de ladite VM. Il est également possible de développer des environnements de test, véritables « bacs à sable » (sand-box), avant mise en production. Un retour-arrière est tout aussi simple : il suffit de changer quelques règles iptables au niveau du serveur hôte.

2.4.3.2. Mode bridgé

Ce mode est rendu possible grâce à OVH, qui propose un service d'adresses Mac virtuelles.
La philosophie est totalement différente du mode NATé, puisqu'ici nous créons un bridge virtuel connecté directement au réseau OVH, et non plus un switch Ethernet virtuel invisible depuis le réseau extérieur. Ce bridge est d'ailleurs un peu particulier, puisque nous allons y raccorder des adresses IP de différents sous-réseaux.
Tout le travail d'acheminement des paquets jusqu'à nos machines virtuelles sera donc géré par OVH, sur la base des adresses Mac virtuelles associées aux adresses IP failover.
Pour que le mode bridgé puisse fonctionner, il faut tout d'abord associer dans son Manager OVH une adresse Mac virtuelle (j'ai choisi le type VMware, mais il est possible que le type OVH fonctionne, la différence ici ne résidant probablement que dans les plages de Mac adresses allouées) à chacune de ses IP Failover.
Considérons les associations suivantes :

Serveur hôte

La configuration du réseau du serveur hôte est beaucoup plus simple que dans le cas précédent, puisqu'il suffit de définir l'interfaces br0 à la place de eth0 :
moi@ks123456:~$ sudo vi /etc/network/interfaces
# This file describes the network interfaces available on your system
# and how to activate them. For more information, see interfaces(5).

# The loopback network interface
auto lo
iface lo inet loopback

auto br0
iface br0 inet static
   address 91.123.123.123
   netmask 255.255.255.0
   gateway 91.123.123.254
   network 91.123.123.0
   broadcast 91.123.123.255
   bridge_ports eth0
   bridge_stp off
   bridge_maxwait 5
   bridge_fd 0       
moi@ks123456:~$
Attention à bien configurer bridge_ports eth0 et non plus bridge_ports none comme dans le mode NATé, sous peine d'un passage par le Manager OVH pour récupérer sa machine :-)

Machines virtuelles

Du côté des machines virtuelles, la configuration ne change pas fondamentalement : il faut tout d'abord éditer, depuis le serveur hôte, les fichiers de configuration (/home/lxc/vm2/config pour la vm2 et /home/lxc/vm3/config pour la vm3), en ajoutant à la fin les éléments réseau avec cette fois-ci les IP Failover et les adresses Mac virtuelles, soit pour la vm2 :
# Config réseau
lxc.utsname = vm2
lxc.network.type = veth
lxc.network.flags = up
lxc.network.link = br0
lxc.network.name = eth0
lxc.network.ipv4 = 91.2.2.2
lxc.network.veth.pair = vethVM2
lxc.network.hwaddr = 00:50:56:0a:2a:aa
et pour la vm3 :
# Config réseau
lxc.utsname = vm3
lxc.network.type = veth
lxc.network.flags = up
lxc.network.link = br0
lxc.network.name = eth0
lxc.network.ipv4 = 91.3.3.3
lxc.network.veth.pair = vethVM3
lxc.network.hwaddr = 00:50:56:0b:3b:bb
Pour le fichier /etc/network/interfaces de chaque VM, il faut bien faire attention aux adresses IP utilisées dans les commandes route add et route del : il s'agit de l'adresse de la passerelle du serveur hôte, à savoir l'adresse de votre serveur KIMSUFI, mais dont le dernier octet vaut 254 (plus de détails ici). Voici le fichier de la machine VM2 :
moi@ks123456:~$ sudo cat /home/lxc/vm2/rootfs/etc/network/interfaces 
auto lo
iface lo inet loopback

auto eth0
iface eth0 inet static
	address 91.2.2.2
	netmask 255.255.255.255
	broadcast 91.2.2.2
	post-up route add 91.123.123.254 dev eth0
	post-up route add default gw 91.123.123.254
	post-down route del 91.123.123.254 dev eth0
	post-down route del default gw 91.123.123.254
moi@ks123456:~$
À noter que dans le mode bridgé, les paquets échangés entre une VM et une autre VM installée sur le même serveur hôte, ou même entre une VM et le serveur hôte lui-même, sortent de la machine physique et remontent jusqu'à un routeur du réseau 91.123.123.0 (le 91.123.123.252 ici), ce qui est tout-à-fait normal, puisqu'étant sur des sous-réseaux différents, ils sont envoyés vers la passerelle par défaut, qui va renvoyer les paquets vers la machine physique hôte.
Vous remarquerez par ailleurs que la variable /proc/sys/net/ipv4/ip_forward n'a pas besoin d'être positionnée à 1.
Dernier point d'attention : vous disposez maintenant de machines virtuelles accessibles depuis Internet, dont le mot de passe root est root... Je vous invite donc fortement à le changer immédiatement.

2.4.3.3. Mode bridgé avec réseau privé interne

Si vous avez retenu le mode bridgé mais que vous souhaitez également avoir des containers qui ne soient pas accessibles depuis l'extérieur, il vous faut alors configurer en plus un réseau privé entre vos différentes VM.
Nous allons donc monter un deuxième bridge, br1, sur lequel sera connecté notre réseau privé, en 192.168.10.0.
Pour le serveur hôte, cela se traduit par une mise à jour du fichier /etc/network/interfaces :
moi@ks123456:~$ sudo cat /etc/network/interfaces
# This file describes the network interfaces available on your system
# and how to activate them. For more information, see interfaces(5).

# The loopback network interface
auto lo
iface lo inet loopback

auto br0
iface br0 inet static
        address 91.123.123.123
        netmask 255.255.255.0
        network 91.123.123.0
        broadcast 91.123.123.255
        gateway 91.123.123.254
        bridge_ports eth0
        bridge_stp off
        bridge_fd 0
        bridge_maxwait 5

auto br1
iface br1 inet static
        address 192.168.10.1
        netmask 255.255.255.0
        network 192.168.10.0
        broadcast 192.168.10.255
        bridge_ports none
        bridge_stp off
        bridge_fd 0
        bridge_maxwait 5
moi@ks123456:~$
Vous remarquerez que le bridge br1 n'est associé à aucune interface physique (bridge_ports none).
Il faut également activer le transfert des paquets IPv4 au niveau du serveur hôte :
echo 1 > /proc/sys/net/ipv4/ip_forward
Pour les VM disposant d'une adresse IP publique, nous allons créer une deuxième interface Ethernet, eth1, associée au bridge br1. Nous allons pour cela d'abord ajouter les informations sur la deuxième carte dans le fichier de configuration, avec l'adresse IP privée choisie, soit pour la VM2 :
# Deuxième interface
lxc.network.type = veth
lxc.network.flags = up
lxc.network.link = br1
lxc.network.name = eth1
lxc.network.ipv4 = 192.168.10.2
lxc.network.veth.pair = vethVM2priv
Le fichier /home/lxc/vm2/config devient donc :
moi@ks123456:~$ sudo cat /home/lxc/vm2/config
lxc.tty = 4
lxc.pts = 1024
lxc.rootfs = /home/lxc/vm2/rootfs
[...]
# Config réseau
lxc.utsname = vm2
lxc.network.type = veth
lxc.network.flags = up
lxc.network.link = br0
lxc.network.name = eth0
lxc.network.ipv4 = 91.2.2.2
lxc.network.hwaddr = 00:50:56:0a:2a:aa
lxc.network.veth.pair = vethVM2pub
# Deuxième interface
lxc.network.type = veth
lxc.network.flags = up
lxc.network.link = br1
lxc.network.name = eth1
lxc.network.ipv4 = 192.168.10.2
lxc.network.veth.pair = vethVM2priv

moi@ks123456:~$
Il faut ensuite ajouter la nouvelle interface eth1 aux fichiers /etc/network/interfaces de nos VM, qui deviennent donc pour la VM2 :
moi@ks123456:~$ sudo cat /home/lxc/vm2/rootfs/etc/network/interfaces
auto lo
iface lo inet loopback

auto eth0
iface eth0 inet static
        address 91.2.2.2
        netmask 255.255.255.255
        broadcast 91.2.2.2
        post-up route add 91.123.123.254 dev eth0
        post-up route add default gw 91.123.123.254
        post-down route del 91.123.123.254 dev eth0
        post-down route del default gw 91.123.123.254


auto eth1
iface eth1 inet static
        address 192.168.10.2
        netmask 255.255.255.0
        broadcast 192.168.10.255
moi@ks123456:~$
et pour la VM3 :
moi@ks123456:~$ sudo cat /home/lxc/vm3/rootfs/etc/network/interfaces
auto lo
iface lo inet loopback

auto eth0
iface eth0 inet static
        address 91.3.3.3
        netmask 255.255.255.255
        broadcast 91.3.3.3
        post-up route add 91.123.123.254 dev eth0
        post-up route add default gw 91.123.123.254
        post-down route del 91.123.123.254 dev eth0
        post-down route del default gw 91.123.123.254

auto eth1
iface eth1 inet static
        address 192.168.10.3
        netmask 255.255.255.0
        broadcast 192.168.10.255
moi@ks123456:~$
Considérons maintenant que nous souhaitions ajouter une VM sur ce réseau interne, mais sans adresse IP publique. Le processus de création de cette VM, que nous appellerons VM10, n'est pas différent de ce que nous avons vu jusqu'à présent. Deux points à noter tout de même : l'interface eth0 devra être associée au bridge br1, et l'adresse IP privée, 192.168.10.10, sera attribuée directement sur son interface eth0 :
moi@ks123456:~$ sudo cat /home/lxc/vm10/config
[...]
# Config réseau
lxc.utsname = vm10
lxc.network.type = veth
lxc.network.flags = up
lxc.network.link = br1
lxc.network.name = eth0
lxc.network.ipv4 = 192.168.10.10/24
lxc.network.veth.pair = vethVM10
moi@ks123456:~$
moi@ks123456:~$ sudo cat /home/lxc/vm10/rootfs/etc/network/interfaces
auto lo
iface lo inet loopback

auto eth0
iface eth0 inet static
        address 192.168.10.10
        netmask 255.255.255.0
	broadcast 192.168.10.255
	gateway 192.168.10.1
	post-up route add 192.168.10.1 dev eth0
	post-up route add default gw 192.168.10.1
	post-down route del 192.168.10.1 dev eth0
	post-down route del default gw 192.168.10.1
moi@ks123456:~$

Enfin, nous devons configurer le serveur hôte comme pour le mode NATé, sans quoi la VM10 ne pourra pas se connecter à Internet, ce qui limite beaucoup les possibilités d'ajout ou de mise à jour de paquets.
Nous ajouterons donc une règle iptables de MASQUERADE sur l'interface br0 pour l'adresse IP de la VM10 :
iptables -t nat -A POSTROUTING -s 192.168.10.10 -o br0 -j MASQUERADE
Vous noterez que la règle est ajoutée sur l'interface br0 et non br1, car la traduction d'adresse se fait sur l'interface de sortie vers le réseau externe.
Ces différentes lignes doivent être ajoutées au fichier /etc/network/interfaces de votre serveur hôte. Vous devriez donc avoir un contenu assez proche de :
moi@ks123456:~$ sudo cat /etc/network/interfaces
# This file describes the network interfaces available on your system
# and how to activate them. For more information, see interfaces(5).

# The loopback network interface
auto lo
iface lo inet loopback

auto br0
iface br0 inet static
        address 91.123.123.123
        netmask 255.255.255.0
        network 91.123.123.0
        broadcast 91.123.123.255
        gateway 91.123.123.254
        bridge_ports eth0
        bridge_stp off
        bridge_fd 0
        bridge_maxwait 5

auto br1
iface br1 inet static
        address 192.168.10.1
        netmask 255.255.255.0
        network 192.168.10.0
        broadcast 192.168.10.255
        bridge_ports none
        bridge_stp off
        bridge_fd 0
        bridge_maxwait 5

       	post-up echo 1 > /proc/sys/net/ipv4/ip_forward

	post-up /sbin/iptables -t nat -A POSTROUTING -s 192.168.10.10 -o br0 -j MASQUERADE
	post-down /sbin/iptables -t nat -D POSTROUTING -s 192.168.10.10 -o br0 -j MASQUERADE
moi@ks123456:~$
Voilà, vous avez donc maintenant un environnement avec trois VM, dont deux accessibles depuis Internet et une accessible uniquement depuis le serveur hôte ou les deux autres VM.

2.4.4. Duplication d'un container

L'intérêt des containers réside également dans les possibilités de duplication qu'ils offrent : vous pouvez tout-à-fait créer une image "maître" d'un container contenant tous les services souhaités (serveur Apache, DNS, mail, etc.) et la dupliquer à l'infini pour déployer de nouvelles VM plutôt que d'en recréer à partir de rien.
Dupliquer une VM est très simple : il vous suffit de créer une archive du répertoire de la VM source, de la recopier dans le répertoire adéquat et de mettre ensuite à jour les paramètres réseau.
Supposons que nous voulions créer la VM20 sur la base de la VM10, avec comme adresse IP 192.168.10.20. La procédure commence donc par la création d'une archive du répertoire /home/lxc/vm10 et de sa recopie dans la répertoire (à créer) /home/lxc/vm20/ :
moi@ks123456:~$ cd /home/lxc/vm10
moi@ks123456:/home/lxc/vm10$ sudo tar cvf ImageVM.tar *
[...]
moi@ks123456:/home/lxc/vm10$ sudo mkdir /home/lxc/vm20
moi@ks123456:/home/lxc/vm10$ cd /home/lxc/vm20	
moi@ks123456:/home/lxc/vm20$ sudo tar xvf /home/lxc/vm10/ImageVM.tar
[...]
moi@ks123456:/home/lxc/vm20$
Il faut maintenant modifier le fichier de configuration de la VM20, pour remplacer toutes les occurrences de vm10 par vm20 et mettre à jour l'adresse IP :
moi@ks123456:~$ sudo sed -i "s/vm10/vm20/g" /home/lxc/vm20/config
moi@ks123456:~$ sudo sed -i "s/VM10/VM20/g" /home/lxc/vm20/config
moi@ks123456:~$ sudo sed -i "s/192.168.10.10/192.168.10.20/g" /home/lxc/vm20/config
moi@ks123456:~$ 
Il ne reste alors plus qu'à changer l'adresse IP figurant dans le fichier /etc/network/interfaces de la VM20 :
moi@ks123456:~$ sudo sed -i "s/192.168.10.10/192.168.10.20/g" /home/lxc/vm20/rootfs/etc/network/interfaces
moi@ks123456:~$ 
Et afin qu'il soit possible d'accéder à Internet depuis cette nouvelle VM, nous ajoutons une règle iptables de MASQUERADE :
iptables -t nat -A POSTROUTING -s 192.168.10.20 -o br0 -j MASQUERADE
Si vous souhaitez que cette règle survive à un redémarrage du serveur hôte, vous pouvez par exemple l'ajouter au fichier de configuration des interfaces réseau de votre serveur hôte, qui devrait alors ressembler à ça :
moi@ks123456:~$ sudo cat /etc/network/interfaces
# This file describes the network interfaces available on your system
# and how to activate them. For more information, see interfaces(5).

# The loopback network interface
auto lo
iface lo inet loopback

auto br0
iface br0 inet static
        address 91.123.123.123
        netmask 255.255.255.0
        network 91.123.123.0
        broadcast 91.123.123.255
        gateway 91.123.123.254
        bridge_ports eth0
        bridge_stp off
        bridge_fd 0
        bridge_maxwait 5

auto br1
iface br1 inet static
        address 192.168.10.1
        netmask 255.255.255.0
        network 192.168.10.0
        broadcast 192.168.10.255
        # gateway 192.168.10.1
        bridge_ports none
        bridge_stp off
        bridge_fd 0
        bridge_maxwait 5

        post-up echo 1 > /proc/sys/net/ipv4/ip_forward

	post-up /sbin/iptables -t nat -A POSTROUTING -s 192.168.10.10 -o br0 -j MASQUERADE
	post-up /sbin/iptables -t nat -A POSTROUTING -s 192.168.10.20 -o br0 -j MASQUERADE

	post-down /sbin/iptables -t nat -D POSTROUTING -s 192.168.10.20 -o br0 -j MASQUERADE
	post-down /sbin/iptables -t nat -D POSTROUTING -s 192.168.10.10 -o br0 -j MASQUERADE
moi@ks123456:~$
La duplication ne présentait ici que peu d'intérêt, mais lorsque vous avez une VM sur laquelle ont été déployés un grand nombre de services, le gain de temps est conséquent par rapport à une installation depuis un container vide.
Dernière mise à jour : 30/05/2011
Me contacter : olivier chez delloye point org

Valid HTML 4.01 Transitional