Artisan Numérique

/système/virtualisation/conteneurs/lxc/ Les Conteneurs Linux

Petite révolution dans la demeure, je vais encore chambouler notre architecture domestique. En effet, puisqu'aujourd'hui je travaille exclusivement dans mon garage (le vieux rêve que voilà -;), je n'ai plus le courage d'éteindre la bête de course qui me sert de poste de travail. Du coup, le petit serveur miniITX a perdu sa raison d'être et l'envie m'a pris de la virtualiser sur la grosse bête. Quoi de plus logique au fond, tant qu'à consommer des watts, autant que cela soit le plus rentable possible.

Malgré tout, virtualiser "à la KVM, XEN ou VirtualBox" ne me plaisait pas des masses. Trop lourd pour le besoin. Quant à chrooter tout cela, pas assez fiable. C'est alors qu'un ami (coucou Daniel) m'a suggéré la solution OpenVZ. Ce n'est finalement pas elle que je vais mettre ici en oeuvre mais c'est ainsi que j'ai mis le doigt sur un concept de super-chroot alliant isolation et performance.

Hôte, conteneur, isolation et performances

La virtualisation est un vaste et complexe sujet (que je ne maîtrise pas !). Mais au fond, cela revient à une chose simple permettre de faire fonctionner un (ou plusieurs) système d'exploitation (et/ou des applications) comme s'il était sur sa propre machine, alors qu'en réalité il tourneau sein d'un système d'exploitation hôte. Et pour arriver à ce résultat, il y a en gros trois approches :

  • La virtualisation par isolation - cela consiste à mettre en place des contextes permettant de redéfinir ce que voit le conteneur. C'est typiquement ce que fait chroot qui cache la racine réelle du serveur pur ne montrer aux processus enfant qu'une partie de l'arborescence. C'est aussi ce que font de manière beaucoup plus complète OpenVZ, VServer ou encore les zones d'OpenSolaris.
  • La machine virtuelle - le principe est d'émuler un ensemble de matériel (carte réseau, carte écran, disque dur, etc) pour faire croire à l'OS du conteneur qu'il évolue sur une véritable machine. Si celui-ci est compilé pour le même processeur que la machine physique, il est possible de faire l'économie de l'émulation du processeur, ce qui apporte un énorme gain de temps. C'est ce que permettent KVM, le couple Qemu/Kquemu ou encore VirtualBox. Si l'OS est prévu pour un autre processeur, alors il faut aussi l'émuler comme le permet Qemu-ARM, Bochs, les émulateurs de console, etc.
  • La paravirtualisation - (ou hyperviseur) qui tente de ménager la chèvre et le choux en modifiant utilisant des OS conteneur qui ont "conscience" d'être hébergé chez un hôte, ce qui permet de jouer tantôt de l'isolation, tantôt de la virtualisation. C'est typiquement ce que permet XEN (et UML il me semble).

Finalement, ce que l'on peut en déduire à notre niveau, c'est que plus l'isolation entre le conteneur et l'hôte est forte, plus la partie logicielle qui va gérer le conteneur est complexe, et de manière incidente, plus les performances au sein du conteneur s'éloignent d'un système natif. Chroot étant d'un côté du spectre avec une très faible isolation mais des performances natives, à qemu-arm, totalement isolé mais avec de bien moindre performances.

Un Chroot totalement isolé

Pour commencer, un point très important à noter, les considérations de sécurité ne sont pas le sujet de ce papier. Et cela tombe bien car un chroot est tout sauf sécurisé. Cela passe très bien pour l'enfermement d'une poignée de services, mais cela deviendrait critique pour le chrootage d'un OS complet. En effet, le conteneur chrooté partage son /proc et son /dev avec l'hôte, et il a donc tout liberté pour s'introduire dans le système. Libertés que nous allons grandement restreindre mais je ne donne aucune garanti sur la résistance à l'intrusion.

Mais au delà des considérations de sécurité, un conteneur chroot, de par sa faible isolation, n'est pas pratique du tout dés qu'il s'agit de faire tourner plein de services dedans. En effet, si le système de fichier du conteneur est correctement isolé, il n'en est pas de même pour les processus (le conteneur "voit" ceux de l'hôte). Enfin concernant les interfaces réseau, il est bien évidement possible de créer un bridge ou un alias utilisé par l'environnement chrooté mais nombre de services deviennent de véritables empoisonneurs dés qu'il s'agit de les obliger à écouter une seule interface (ex. typiquement les RPC comme NFS ou YP). L'idéal serait donc de pouvoir disposer d'un chroot à géométrie variable, capable de disposer, au minimum, de son propre contexte processus et réseau.

Ce type de chroot est l'équivalent des zones de Solaris ou les Jails de BSD. Dans l'univers Linux il existe plusieurs possibilités allant dans ce sens avec des outils comme OpenVZ ou Linux VServer. Mais ces solutions ont un inconvénient de taille, ils ne fonctionnent qu'avec un patch du kernel et ont toujours un bon nombre de versions de retard. Ce ne serait pas un problème si de nos jours, certains outils aussi idiots qu'indispensables (udev pour ne pas le nommer) n'avaient pas le mauvais goût de fonctionner en user-land (où il n'a rien à foutre à mon sens) et donc imposer des versions récentes de kernel (c'est encore le cas récemment avec la dernière version d'udev qui impose un kernel 2.6.26, ou plus, à cause d'un appel à sa nouvelle fonction signalfs).

Linux Containers

Fort heureusement, une nouvelle solution commence à pointer le bout de son nez sous le nom de Linux Containers (ou LXC). Il s'agit là d'un jeu de nouvelles options dans le kernel qui ont la particularité d'être intégré en mainstream. Cela comprend un ensemble d'isolateurs (ou namespace) et cgroup (pour control group file system) permettant de contrôler de manière unifiée un jeu de processus et leurs enfants. Les isolateurs ajoutés sont nombreux et fonctionnent comme chroot pour la racine :

  • UTS namespace qui permet d'avoir pour chaque conteneur son propre hostname/domainame distinct de celui de l'hôte.
  • IPC Namespace qui permet d'avoir pour chaque conteneur son jeu privé de fonctions sem, shm, msg, etc.
  • User Namespace qui permet d'avoir par conteneur des
  • utilisateurs de l'hôte distinct (y compris root !!), même s'ils partagent le même UID/GID.
  • PID Namespace - un apport énorme sur chroot, permettant au conteneur de disposer de sa propre liste de processus. Les processus host ne sont donc plus visible du conteneur.
  • Network namespace - la aussi une avancée très importante permettant au conteneur de disposer de ses propres interfaces réseaux (y compris localhost!).

L'ensemble de ces fonctionnalités (namespaces et cgroup) permet de créer relativement facilement un super-chroot beaucoup plus isolant que la version d'origine. C'est ce que cherche à obtenir le projet Linux Containers. Ce dernier fournit une batterie de nouvelles commandes qui à partir d'une simple racine (identique à celle utilisée pour un chroot), permettent de créer, démarrer, arrêter ou encore monitorer un conteneur.

Maintenant que nous avons la théorie en tête passons à la pratique.

Préparation de la machine hôte

Le kernel

L'utilisation du nouveau système de serveurs virtuels privés implique l'activation d'un certains nombre d'options dans le noyau. S'agissant de la distribution Mandriva, les paquets appelés kernel-server ont déjà ces options d'activées, il n'y a donc aucun noyau à recompiler si vous les utilisez. Pour les autres, vous devez tout d'abord ajouter le contrôle par groupe.

General setup --->
[*] Group CPU scheduler
[*] Group scheduling for SCHED_OTHER
[_] Group scheduling for SCHED_RR/FIFO
Basis for grouping tasks (Control groups)
(X) Control groups

[*] Control Group support  --->
[ ]   Example debug cgroup subsystem
[*]   Namespace cgroup subsystem
[*]   Freezer cgroup subsystem
[*]   Device controller for cgroups
[*]   Cpuset support
[*]     Include legacy /proc/ /cpuset file
[*]   Simple CPU accounting cgroup subsystem
[*]   Resource counters
[*]     Memory Resource Controller for Control Groups
[*]       Memory Resource Controller Swap Extension(EXPERIMENTAL)
Activation du contrôle par groupe dans le kernel

Ensuite vous devez activer la prise en charge des espaces de nom :

General setup --->
[*] Namespaces support
[*]   UTS namespace
[*]   IPC namespace
[*]   User namespace (EXPERIMENTAL)
[*]   PID Namespaces (EXPERIMENTAL)
[*]   Network namespace
Activation des namespaces dans le kernel

Vous n'avez plus qu'à recompiler le noyau et à démarrer dessus.

Montage de cgroup

cgroup est un psoeudo système de fichier, comme proc ou feu udevfs. Pour l'utiliser, vous devez donc créer un dossier sur lequel ce système sera monté. Pour automatiser cela, nous allons ajouter ce montage à fstab.

rootmkdir /cgroup
rootecho "none    /cgroup cgroup rw 0 0" >> /etc/fstab
rootmount /cgroup
montage du psoeudo filesystem cgroup

Mise en place du réseau

Là c'est la partie un peu "touchy" de l'histoire. En effet, chacun de nos conteneurs va à terme disposer de sa propre adresse IP. Il va donc nous falloir créer autant d'interface ethernet virtuels (veth) que de conteneur. Ensuite nous allons relier ces interfaces à un bridge. Le rôle du bridge va être de faire circuler l'information et peut être vu comme une sorte de switch.

Enfin, lorsque nous avons nos interfaces virtuelles connectés au bridge, il faudra encore y connecter l'interface physique de l'hôte (eth0) et assigner l'ancienne adresse IP ce cet interface au bridge lui-même. A partir de là notre switch virtuel est fonctionnel.

Ça c'est la théorie, et pour une fois la pratique est un peu plus simple. La seule chose que nous allons devoir faire est de créer le bridge et d'y associer l'interface physique. Les interfaces virtuels seront créés, connectés et paramétrés automatiquement lors du démarrage du conteneur correspondant.

Pour mettre en place le bridge, vous devez installer le paquet bridge-tools qui nous fournit, entre autre, la commande brctl. La création manuelle se passerait comme ceci :

Création du bridge nommé br0
rootbrctl addbr br0

Paramétrage du bridge, ici nous réglons à 0 le délai de transfert (Forward Delay)
rootbrctl setfd br0 0

Ajout de l'interface physique au bridge
rootbrctl addif br0 eth0

On vide l'adresse IP de l'interface physique (remplacez eth0 par votre propre interface) ...
rootifconfig eth0 0.0.0.0

... pour l'assigner au bridge (changer l'IP pour l'IP de votre host)
rootifconfig br0 10.0.0.1

Ajout d'une route pour que le bridge communique avec votre routeur (adresse à change pour vos besoins)
rootroute add -net default gw 10.0.0.100 br0
Création manuelle d'un bridge

Voilà, c'est tout. Normalement, vous devez pouvoir pinger le monde extérieur et de l'extérieur pouvoir pinger votre host. Ceci étant dit, ça va être vite galère de faire cela à chaque fois. Chaque distribution permet d'automatiser ce genre de montage et sous Mandriva cela se fait simplement en commençant par arrêter le réseau (service network stop) puis en recopiant /etc/sysconfig/network-scripts/ifcfg-eth0 vers /etc/sysconfig/network-scripts/ifcfg-zbr0. Notez le "z" devant le "br0" qui est une astuce stupide pour que cet interface se monte après eth0. Ceci fait, modifiez ifcfg-eth0 pour qu'il commence comme ceci :

DEVICE=eth0
BRIDGE=br0
BOOTPROTO=static
IPADDR=0.0.0.0
NETMASK=255.255.255.0
GATEWAY=0.0.0.0
ONBOOT=yes
Script de paramétrage de l'interface réseau physique

Puis modifier ifcfg-zbr0 pour qu'il commence comme cela :

DEVICE=br0
TYPE=bridge
DELAY=0
STP=off
BOOTPROTO=static
IPADDR=10.0.0.1
NETMASK=255.255.255.0
GATEWAY=10.0.0.100
DNS1=212.27.40.240
ONBOOT=yes
Script de paramétrage du bridge

Pour que cela fonctionne chez vous, vous devez bien évidement changer l'adresse de la passerelle (GATEWAY) et du serveur de nom (DNS1). Vous n'avez maintenant plus qu'à redémarrer le réseau (service network restart) et à tester comme précédemment pour voir si tout répond correctement. Le mieux est même de redémarrer la machine pour être certain que le montage est reproductible. Vous devriez ainsi obtenir les traces suivantes :

rootifconfig
br0       Link encap:Ethernet  HWaddr 00:1A:4D:5C:9A:04
inet adr:10.0.0.1  Bcast:10.0.0.255  Masque:255.255.255.0
UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
RX packets:11105 errors:0 dropped:0 overruns:0 frame:0
TX packets:11398 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 lg file transmission:0
RX bytes:6019978 (5.7 MiB)  TX bytes:1566064 (1.4 MiB)

eth0      Link encap:Ethernet  HWaddr 00:1A:4D:5C:9A:04
UP BROADCAST RUNNING PROMISC MULTICAST  MTU:1500  Metric:1
RX packets:2683832 errors:0 dropped:0 overruns:0 frame:0
TX packets:2128799 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 lg file transmission:1000
RX bytes:3418220009 (3.1 GiB)  TX bytes:1255231891 (1.1 GiB)
Interruption:28 Adresse de base:0x6000
rootroute
Table de routage IP du noyau
Destination     Passerelle      Genmask         Indic Metric Ref    Use Iface
10.0.0.0        *               255.255.255.0   U     0      0        0 br0
link-local      *               255.255.0.0     U     1262   0        0 br0
default         10.0.0.100      0.0.0.0         UG    10     0        0 br0
Preuve de fonctionnement du bridge

Lutter contre uDev

Dans la majorité des distributions "modernes", udev prend en charge la détection des nouvelles interfaces réseau pour passer la main à un outil de paramétrage. Pour la Mandriva, cet outil va chercher à détecter la présence d'un script de configuration pour cette interface et le cas échéant en créer un. Ensuite il va donner la main à ifplugd pour que ce dernier monitore l'interface et le paramètre à l'aide du script lorsqu'il détectera son activité (link beat).

Ceci est très bien dans la majorité des cas mais bien ennuyeux concernant nos futures conteneurs. En effet, comme nous l'avons vu, LXC va créer automatiquement une interface ethernet virtuelle qu'il va rattacher au bridge. Comme pour l'interface physique, elle ne devra pas avoir d'adresse IP au niveau de l'hôte (elle en aura une au niveau du conteneur). Pour corser le tout, LXC nomme l'interface virtuelle veth_XXXXX, où XXXX est le pid du processus racine du conteneur. Du coup, udev va flanquer tout cela par terre et appelant la procédure de configuration automatique qui va créer un script pour chaque interface créée, c'est à dire à chaque redémarrage du conteneur (normal, le pid est à chaque fois différent). Et comme il n'a aucune information sur cette interface, la procédure va aller au plus simple en lui affectant une recherche d'IP via DHCP. Ifplug va récupérer cela et reconfigurer l'interface virtuelle avec l'IP que lui donnera un DHCP du réseau, ce qui va la déconnecter du bridge.

Pour régler ce problème, il faut paramétrer uDev pour ignorer les interfaces de type veth. Pour cela, il faut aller dans /lib/udev/rules.d/76-net.rules (cela peut varier d'une distro à l'autre) et ajouter la ligne sur veth suivante :

SUBSYSTEM!="net", GOTO="mdv_net_end"
ENV{INTERFACE}=="", GOTO="mdv_net_end"
KERNEL=="veth*", GOTO="mdv_net_end"


ACTION=="add", RUN+="net_create_ifcfg"
ACTION=="add|remove", RUN+="net_action"

LABEL="mdv_net_end"
Paramétrage d'uDev

Ceci fait, un petit coup de pkill udevd, udevd -d pour relancer, et le problème devra être réglé. Nous vérifierons cela un peu plus loin.

Récupération des outils Linux Containers

Comme dit plus haut, le nouveau système Linux Containers dispose d'une série d'outils user-land permettant de gérer les conteneurs. Si vous avez déjà cela en paquet dans votre distribution, installez-le, sinon, il suffit d'aller les chercher et les compiler :

git clone git://lxc.git.sourceforge.net/gitroot/lxc/lxc
...
cd lxc
./autogen
...
./configure --prefix=
...
make
...
make install
...
ls /bin/lxc-*
/bin/lxc-cgroup*       /bin/lxc-console*  /bin/lxc-destroy*  /bin/lxc-freeze*  /bin/lxc-monitor*  /bin/lxc-restart*  /bin/lxc-start*     /bin/lxc-unshare*
/bin/lxc-checkconfig*  /bin/lxc-create*   /bin/lxc-execute*  /bin/lxc-info*    /bin/lxc-netstat*  /bin/lxc-setcap*   /bin/lxc-stop*      /bin/lxc-version*
/bin/lxc-checkpoint*   /bin/lxc-debian*   /bin/lxc-fedora*   /bin/lxc-ls*      /bin/lxc-ps*       /bin/lxc-sshd*     /bin/lxc-unfreeze*  /bin/lxc-wait*
Compilation des outils LXC

Tout est maintenant enfin prêt pour créer notre premier conteneur.

Utilisation de Linux Container

Création d'un conteneur mandriva

Les conteneurs LXC sont stockés dans le dossier /var/lib/lxc (personnellement j'ai fais un symlink vers un dossier plus adéquat). Chaque conteneur aura là dedans un dossier à son nom (ex. /var/lib/lxc/mon_serveur pour le conteneur mon_serveur). Dans chacun de ces dossiers se trouvera un sous-dossier rootfs qui correspond à la racine du système virtualisé (comme pour chroot). La première chose à faire est donc de créer la structure d'accueil. Pour cela nous allons commencer par créer un fichier de configuration pour la nouvelle machine, nommons le mon_serveur.conf à la racine du dossier home.

lxc.utsname = mon_serveur
lxc.network.type = veth
lxc.network.flags = up
lxc.network.link = br0
lxc.network.ipv4 = 10.0.0.10/24
lxc.network.name = eth0

lxc.pts=1023
lxc.tty=12

// Droits sur null et zero
lxc.cgroup.devices.allow = c 1:3 rwm
lxc.cgroup.devices.allow = c 1:5 rwm

// Droits sur random et urandom
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

// Droits sur l'horloge
lxc.cgroup.devices.allow = c 254:0 rwm

// droits sur les 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
~/mon_server.conf

Ce fichier de configuration contient principalement le nom du serveur et le paramétrage réseau de celui-ci. Le gros bloc à la fin correspond aux droits du group de processus représenté par le conteneur. En effet, nous allons grâce à cgroup pouvoir paramétrer finement ce que le conteneur a le droit de faire. Il nous paramétrons cgroup pour autoriser l'accès aux devices important du sous-système (/dev/null, /dev/urandom, etc.).

Nous allons maintenant utiliser cette configuration pour créer le conteneur :

rootlxc-create -n mon_serveur -f ~/mon_serveur.conf
Création du conteneur

La structure est maintenant créée, ce que nous pouvons vérifier facilement :

tree /var/lib/lxc/mon_serveur/
/var/lib/lxc/mon_serveur/
|-- config
|-- network
|   `-- veth0
|       |-- ipv4
|       |   `-- addresses
|       |-- link
|       |-- name
|       `-- up
|-- pts
|-- state
|-- tty
`-- utsname
Structure du conteneur

Bien évidement il s'agit là que d'une configuration initiale qu'il est possible d'étendre par la suite. La configuration que se trouve la racine est un peu comme celle d'un kernel linux dans /boot. Elle est là à titre indicatif et je n'ai constaté aucun impact en la modifiant, sauf pour le paramétrage du cgroup. Pour le reste, si vous désirez changer la configuration, il faut toucher aux éléments suivants :

  • network/vethX - pour les paramètres réseau
  • pts - pour le nombre de /dev/pts.
  • tty - pour le nombre de tty alloué.
  • utsname - pour le nom de la machine.

Création de la racine

Comme vous l'avez déjà constaté, il manque pour l'instant le dossier rootfs qui va contenir la racine de notre système. Pour la créer nous allons utiliser la même technique que pour la création d'un GNU/Linux en chroot :

rootCréation de la racine
mkdir /var/lib/lxc/mon_serveur/rootfs

rootpopulation d'une mandriva (utilisez basesystem-mini pour une version encore plus compact)
urpmi basesystem urpmi locales-fr drakxtools-curses --root /var/lib/lxc/mon_serveur/rootfs
Peuplement de la racine

Notez que vous pouvez installer une version 32bits de GNU/Linux dans un conteneur pour un hôte 64bits sans aucun problème. Vous pouvez aussi installer n'importe quelle distribution linux pour peu qu'elle soit compatible avec le kernel hôte.

Notez enfin que vous pouvez ajouter d'autres paquets à l'installation en relançant la commande urpmi ... --root ....

Préparation de /dev

Il s'agit là d'une étape optionnelle. En effet, urpmi nous a créé un dossier /dev correctement peuplé. Mais certains paranoïaques voudront limiter au maximum le nombre de devices disponibles dans ce dossier. La procédure qui suit permet de vider le dossier /dev, et d'y mettre le stricte nécessaire :

chroot rootfs
cd /dev
rm -rf *
mkdir pts
mkdir shm
mknod -m 777 null c 1 3
mknod -m 777 zero c 1 5
mknod -m 644 random c 1 8
mknod -m 644 urandom c 1 9
chown root:root random urandom
exit
Le minimum dans le dossier /dev

Paramétrage du réseau et des services

Pour que tout soit fonctionnel, il y a encore quelques paramétrages à faire. Pas la peine de lancer le conteneur pour cela, un chroot rootfs devrait suffire.

Tout d'abord nous allons installer les services nécessaires au conteneur (openssh-server est un minimum pour avoir une console, etc) en passant par upmi. Ensuite il suffira d'utiliser chkconfig ou drakxservices pour déterminer les services à lancer.

Nous allons ensuite vider /etc/fstab de son contenu pour éviter les montages intempestifs et y ajouter les montages importants. Au final, votre fichier /etc/fstab devra ressembler à ceci :

/proc /proc proc defaults 0 0
tmpfs /dev/shm tmpfs defaults 0 0
/sys /sys sysfs defaults 0 0
/etc/fstab du conteneur

Après cela, il nous faut créer un fichier /etc/sysconfig/network-scripts/ifcfg-eth0 qui contiendra quelque chose comme cela :

DEVICE=eth0
ONBOOT=yes
IPADDR=10.0.0.10
NETMASK=255.255.255.255
GATEWAY=10.0.0.100
DNS1=DNS1=212.27.40.240

Gestion du démarrage/arrêt

Création d'un faux "init" (à voir plus loin)

# mv /sbin/init /sbin/init.old
# echo "echo 'Bienvenue sur le conteneur'" > /sbin/init
# echo "/bin/sh" >> /sbin/init
# exit

Normalement il ne devrait rien y avoir de plus à faire. Malgré tout, nous allons avoir quelques problèmes avec /sbin/init :

  • Comme nous avons installé dans rootfs une distribution tout ce qu'il y a de plus standard, le processus d'initialisation va tenter, via rc.sysinit, de monter des tas de choses inutiles voir nuisibles au conteneur (ex. udev). Ce serait facile à régler en vidant rc.sysinit de son contenu.
  • Je n'ai pas trouvé le moyen d'éteindre proprement les services lancés par un halt, shutdown ou encore /etc/rc.d/rc.shutdown.
  • Malgré tous mes efforts, je n'ai pas réussi à créer au sein du conteneur un seul tty valide. Ce qui dans le cadre d'un init standard pose problème car le lancement des consoles (mgetty ou mingetty) plante entraînant un plantage de l'init lui-même (too many respawn).

Pour régler tout cela, ma solution (un peu pourrie j'en conviens) consiste à créer notre propre remplacement de /sbin/init qui ressemblera à cela :

#!/bin/sh

# Réseau
. /etc/sysconfig/network
. /etc/sysconfig/network-scripts/ifcfg-eth0
route add -net default gw $GATEWAY eth0
hostname ${HOSTNAME}

# Set the NIS domain name
if [ -n "$NISDOMAIN" ]; then
nisdomainname $NISDOMAIN
fi

# arrêt/démarrage des services
function process() {
for service in $(ls /etc/rc.d/rc$1.d/* | sort); do
case $service in
*network* | *halt* | *kill*) ;;
*)
$service $2
;;
esac
done
}

# Montage des psoeudos systèmes de fichier
mount -t proc /proc /proc
mount -t sysfs /sys /sys
mount -t tmpfs  none /dev/shm
mount -a

# Démarrage des services
process 5 start

# Attente de la mise mort de la machine
rm -rf /.dead
> /.running
while [ -f /.running ] ; do
sleep 1
done

# Arrêt des services
process 0 stop

# Démontage des systèmes de fichier
umount /proc
umount /sys
umount /dev/shm
umount -a
/sbin/init dans le conteneur

Telle que cette initialisation est conçue, elle commence par monter les systèmes de fichiers utiles (sauf /dev/pts est pris en charge par lxc) et ensuite va démarrer les services actifs. La partie bien stupide qui suit permet de palier à l'absence de tty. Il s'agit d'une simple boucle d'attente sur la présence du fichier /.running. Ainsi pour arrêter le conteneur, il suffit de détruire ce fichier. Les services alors proprement sont déchargés et les systèmes de fichier démontés. Le conteneur s'arrêtera alors de lui-même.

Lancement du conteneur

Le conteneur est maintenant prêt à être lancé, cela se passe avec la commande lxc-start :

lxc-start -n mon_serveur
Starting sshd                   [  OK  ]
Lancement du conteneur

Et c'est tout, le conteneur est en route et dispose d'une logiquement adresse IP. Vous pouvez donc la "pinger" de l'extérieur pour vérifier. Vous constaterez au passage sur l'hôte l'apparition d'une interface veth0 qui est l'équivalent d'eth0 pour le conteneur. Vous pouvez éteindre le conteneur en vous connectant en ssh dessus et en détruisant le fichier /.running.

hostssh mon_user@10.0.0.10
mon_user@10.0.0.10&039;s password:
mon_user@mon_serveur [~] $ su -
Password:
root@mon_serveur [~] # rm -rf /.running
root@mon_serveur [~] # Connection to 10.0.0.10 closed by remote host.
Connection to 192.168.154.103 closed.
Connexion au conteneur et extinction

Conclusion

Il est possible de se rendre la vie plus agréable en ajoutant, comme pour OpenVZ, la possibilité que certains conteneurs se lancent au démarrage de l'hôte. C'est le rôle de ce script qui est à placer en /etc/init.d et à activer avec chkconfig ou drakxservices. Pour l'utiliser il suffit de rajouter à la configuration du conteneur un paramètre lxc.onboot=yes qui impliquera son démarrage automatique. Ce script exploite un autre script de mon cru, lxcctl qui simplifie le lancement et l'extinction d'un conteneur. Il fournit ainsi les commandes lxcctl start mon_conteneur, lxcctl stop mon_conteneur et lxcctl info mon_conteneur. En espérant que ceci finisse par être intégré à LXC directement (sous réserve que les tty fonctionnent évidement).

Voilà, c'est la fin de ce "petit" papier sur ce projet très prometteur qu'est LXC. Personnellement j'ai un peu trouvé ce que je cherchais depuis très longtemps et que j'avais effleuré avec chroot. Grâce à LXC, je peux non seulement virtualiser le serveur domestique, mais aussi dupliquer à volonté des conteneurs pour mes projets (un conteneur "type" pour le développement WEB, un autre pour Android, etc.). Et tout cela sans souffrir de la moindre perte de performance.