Les Conteneurs Linux
Le 6 octobre, 2009 - 21:52 | Ulhume

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.

root#mkdir /cgroup
root#echo "none /cgroup cgroup rw 0 0" >> /etc/fstab
root#mount /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
root#brctl addbr br0
 
# Paramétrage du bridge, ici nous réglons à 0 le délai de transfert (Forward Delay)
root#brctl setfd br0 0
 
# Ajout de l'interface physique au bridge
root#brctl addif br0 eth0
 
# On vide l'adresse IP de l'interface physique (remplacez eth0 par votre propre interface) ...
root#ifconfig eth0 0.0.0.0
 
# ... pour l'assigner au bridge (changer l'IP pour l'IP de votre host)
root#ifconfig br0 10.0.0.1
 
# Ajout d'une route pour que le bridge communique avec votre routeur (adresse à change pour vos besoins)
root#route 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 :

root#ifconfig
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
root#route
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 :

root#lxc-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 :

root#Création de la racine
# mkdir /var/lib/lxc/mon_serveur/rootfs
 
root#population 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'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.

Vos remarques et commentaires...

tuxce, le 6 octobre, 2009 - 23:59

Je connaissais pas du tout, merci pour la découverte (et surtout pour l'explication détaillée), faut que je teste ça sur archlinux...

Ulhume, le 7 octobre, 2009 - 01:07

Je serais bien content que cela serve, j'ai bien galéré pour arriver à comprendre, surtout le coup d'udev qui m'a rendu chèvre pendant quelques heures :)
Tu me diras ce que cela donne et surtout si toi tu arrives à t'accrocher à un tty (vous utilisez getty ou mingetty sous arch ?).

tuxce, le 8 octobre, 2009 - 16:32

eh bien c'est pas simple... :)
j'ai du compiler le noyau avec une option supplémentaire que je n'ai trouvé ni dans le manpage de lxc ni dans leur pdf:

CONFIG_DEVPTS_MULTIPLE_INSTANCES=y

je te passe les détails de l'install sous archlinux avec une mini archlinux pour tester pour en venir à la console, alors sous archlinux, on utilise agetty:

mknod /dev/tty c 5 0

c'est le périphérique qui correspond à la sortie de la commande "lxc-start", un:

agetty -8 38400 tty linux

me permet d'avoir une console, par contre, je sais pas pour mandriva, mais sous arch, /dev/tty n'est pas considérée sûre, donc pas de login root, il suffit de modifier /etc/securetty

pour la commande lxc-console, là j'ai encore eu plus de mal, en fait, j'ai fait toute une démarche (plus empirique y a pas :)) pour trouver que c'est les pseudo-terminaux à partir de 4:

mknod /dev/tty1 c 136 4
agetty -8 38400 tty1 linux

mais qui n'avait pas grand intérêt car lxc modifie les fichiers automatiquement pour autant qu'ils existent, par exemple:

touch path_vers_rootfs/dev/tty{1,2,3}

voilà, c'est le résumé, j'espère que ça répond à ta question.

remarque: le code de la section "Préparation de /dev" contient du code html et je sais pas si c'est volontaire, mais le ctrl-t est associé à une action de la page alors qu'il est utile sous firefox :)

Ulhume, le 8 octobre, 2009 - 18:59

Merci pour les infos, j'avais pas capté le coup du (136) en revanche j'avais bien tenté celui du "multiple instance" à l'époque ou je croyais qu'il fallait que le monte devpts "à la main".

Ceci dit, en mettant un agetty sur le tty1, tu arrives à te connecter sur la ve en passant par lxc-console toi ? Moi j'obtiens invariablement un "lxc-console: failed to connect to the tty service".

Ma procédure de test est que j'insère un /bin/sh en traver de /sbin/init de sorte à obtenir un shell au moment du lxc-start et là je lance le agetty.

PS: Je commence à me demande si je ne me casse pas les pieds pour rien, car finalement mon coup du .running, il marche plutôt pas mal. :)

tuxce, le 8 octobre, 2009 - 19:28

oui pour agetty, sinon ça peut être une piste, j'ai jamais eu le message "lxc-console: failed to connect to the tty service", il s'est toujours lancé avec le message ctrl+a+q pour quitter mais si je lance pas le agetty il m'affiche rien.

sinon +1 pour le script, surtout que pour que l'affichage de démarrage fonctionne chez moi, j'ai quand même du modifier les scripts de démarrage d'arch, donc au final, ton script fonctionne et j'ai un ssh, pas trop besoin de console.

Ulhume, le 8 octobre, 2009 - 20:27

Merci de me conforter pour éviter la procrastination ;-) DOnc pour l'instant, ça réponde 100% à mon besoin. J'ai pour l'heure 3 VE qui tournent côte à côte, un serveur avec Postfix, Imap et tout le tintouin, une autre pour le dev web avec apache & co, et une troisième en 32bits. Chacune avec leur IP, et ça semble tourner sans soucis. Peut-être qu'à terme faudra que je m'intéresse aux restrictions avec cgroup mais pour l'instant, aucune ne se bouffe le nez.

Tiens puisqu'on en est au tests, l'option -d de lxc-start fonctionne chez toi ? Pas que ce soit d'une utilité flagrante non plus ceci dit.

Allez maintenant je me fixe un autre chalenge, arriver à faire tourner un serveur funambol :)

tuxce, le 8 octobre, 2009 - 21:27

ça fonctionne oui
au cas où, la distribution et les versions sont certes différentes, mais vu que tu as un comportement différent des commandes lxc-*, c'est peut être la version git de lxc, j'utilise la révision 6a6ad7af58ba541ea27300fdd05715ef9454ce8e datant de "Wed Oct 7 16:06:09 2009 +0200" et d'après ce que je vois dans le log, il y a eu pas mal de changement en une journée.
(Le dernier log avant le 7/10 date du 18/08 )

feilong74, le 7 octobre, 2009 - 09:28

Très intéressant.
Ce qui me gène (a priori) par rapport à la virtualisation c'est qu'ici on ne peut pas alloué de fréquence processeur (Max) ou de taille mémoire (Max) à nos conteneurs. Avons nous une notion de priorité entre les différents conteneurs ? Qu'empêche un conteneur de venir saturer la CPU de l'hôte ?

Ulhume, le 7 octobre, 2009 - 09:49

La réponse est dans cgroup en fait. C'est là que va pouvoir être limité les différentes ressources associées à un conteneur. J'avoue que je n'ai pas encore bien explorer cette possibilité mais ceci te donnera peut-être des idées :

root@antinea [webdev] # tree /cgroup/nehia/
/cgroup/nehia/
|-- cpu.rt_period_us
|-- cpu.rt_runtime_us
|-- cpu.shares
|-- cpuacct.usage
|-- cpuacct.usage_percpu
|-- cpuset.cpu_exclusive
|-- cpuset.cpus
|-- cpuset.mem_exclusive
|-- cpuset.mem_hardwall
|-- cpuset.memory_migrate
|-- cpuset.memory_pressure
|-- cpuset.memory_spread_page
|-- cpuset.memory_spread_slab
|-- cpuset.mems
|-- cpuset.sched_load_balance
|-- cpuset.sched_relax_domain_level
|-- devices.allow
|-- devices.deny
|-- devices.list
|-- freezer.state
|-- net_cls.classid
|-- notify_on_release
`-- tasks

feilong74, le 7 octobre, 2009 - 10:17

Merci pour la rapidité. Vraiment très intéressant. Je sens que je ne vais pas pouvoir m'empêcher de tester tout ça !

Ulhume, le 7 octobre, 2009 - 13:31

N'hésites pas à faire des retours. Avec ma méthode j'ai deux VPS qui tournent comme des coucous (d'ailleurs, ce qui me surprend c'est que ça tourne plus vite dans le VPS que sur l'hôte...). En revanche, j'aimerais arriver à utiliser /sbin/init de manière propre, quitte à nettoyer sysinit. Si tu as des idées là dessus (cf le pb des tty et du halt)...

feilong74, le 9 octobre, 2009 - 09:05

Je me lance ce matin sur LXC, je suis entrain de compiler un noyau 2.6.31 sur une VM debian.
A première vu pour ton problème de tty, il te manquerait un module dans ton noyau :
... -> charactere devices -> UNIX98 Pty Support -> "Support Multiple instances of devpts".

Je reviendrai ici pour un retour, mais à première vu je vais partir sur les sources de dev de LXC qui permettraient de lancer les containers sous forme de daemon :
lxc-start -n serveur -d -o /tmp/startlxcvmserveur

feilong74, le 9 octobre, 2009 - 09:12

oups, j'avais pas vu l'échange avec tuxce plus haut...
Personnellement, Je suis parti sur une prise de note détaillée ici :
http://blogpmenier.dynalias.net/docext/lxc/

Ulhume, le 9 octobre, 2009 - 09:28

Nan, on critiquera comme on voudra la mandriva, mais ces toutes les options pour LXC (y compris celle-ci) sont activées en standard dans le kernel "server".

Maintenant, comme on se le disais avec Tuxce, le système de fichier sémaphore est pas si mal au fond, au moins on charge le strict nécessaire et le déchargement est sans encombre.

Dab, le 9 octobre, 2009 - 00:37

Très instructif ton papier, je ne connaissais pas. Merci
... c'est quand même presuqe "aussi lourd" à mettre en oeuvre qu'un bon vieux xe
n ;)

Ah j'ai retrouvé ce comparatif très intéressant qemu/kvm/vserver/openvz et xen:
http://bearstech.com/files/LB-virtualisationEntrepriseBearstech.pdf

Ulhume, le 9 octobre, 2009 - 09:30

Oui sans aucun doute sauf que XEN ne me permet pas (à moins que j'ai loupé un truc) de travailler sur le même FS de l'hôte. Et pour moi ce point est très important, surtout pour les machines de développement pour lesquelles Eclipse et Apache/PHP bossent finalement dans le même dossier.

Merci pour la doc, je vais mater cela.

PPmarcel, le 26 janvier, 2010 - 15:59

Dans l'idée de migrer éventuellement de OpenVZ vers Lxc, j'aurais quelques questions à te poser.

Est-ce que à l'usage Lxc est aussi confortable à l'utilisation que OpenVz ou Vserver?

Par exemple, je n'ai pas trouvé le paramétrage des quantités de RAM ou de disque utilisé pour un conteneur. Sous OpenVZ cela se définis avec une commande. Est-ce que sous Lxc tout ça se paramètre dans des fichiers de conf?

Et toi, comment est-ce que tu qualifierais l'avancement de ce projet? Est-il mature pour être utilisé en production assez facilement, ou bien a-t-il besoin de plus de temps pour devenir vraiment efficace?

Ludovic LANGE, le 27 décembre, 2009 - 19:11

Bonjour,

Bravo pour l'article très intéressant. J'aurais souhaité en savoir plus sur les scripts de lancement / arrêt mentionnés dans l'article, malheureusement ils me semblent avoir disparus du svn. Est-il possible de les y remettre ?
En particulier, je cherche à industrialiser des environnements LXC, et pouvoir fermer (proprement) un conteneur qui tourne - en gros comme par exemple dans la suggestion suivante : http://www.mail-archive.com/devel@openvz.org/msg19543.html , histoire de bien gérer l'arrêt / relance de l'hôte. C'est un sujet qui n'est pas effleuré ni par LXC ni par les distributions, donc j'aurais aimé voir une autre approche que le lxc-stop un peu brutal...

PPmarcel, le 26 janvier, 2010 - 15:30

C'est un billet très intéressant que tu nous as livré ici!
Je pense que je testerais un de ces jours.

Par contre, ton plugin de syntaxe n'interprète pas les mises en formes du premier bloc de "Préparation de /dev". C'est très illisible, est-ce que tu pourrais y faire quelque chose?

Ulhume, le 26 janvier, 2010 - 15:39

C'est réglé, merci :)

Yanick, le 7 mai, 2010 - 11:16

Salut tt le monde
Quelqu'un aurait-il depuis des infos concernant la limitation des ressources avec les VM via les cgroup ou autres?

Publier un nouveau commentaire

Le contenu de ce champ sera maintenu privé et ne sera pas affiché publiquement. If you have a Gravatar account associated with the e-mail address you provide, it will be used to display your avatar.
  • To highlight piece of code, just surround them with <code type="language"> Your code &tl;/code>>. Language can be java,c++,bash,etc... Everything Geshi support.
  • Tags HTML autorisés : <a> <em> <strong> <cite> <code> <ul> <ol> <li> <dl> <dt> <dd> <blockquote> <div> <p> <br>
  • Les lignes et les paragraphes vont à la ligne automatiquement.
  • Les adresses de pages web et de courriels sont transformées en liens automatiquement.

Plus d'informations sur les options de formatage

CAPTCHA
Cette question est là pour déterminer si vous êtes humain ou pas...