Ce billet va tenter de résumer la manière dont j'utilise LXC pour créer et utiliser des maquettes de grande taille pour tester des choses (protocoles, logiciels, ...) ayant trait aux réseaux informatiques.
Table des matières
Pourquoi maquetter ?
Cela peut être utile de simuler la présence et les interactions d'un nouvel élément avant sa mise en production effective sur le réseau physique voire de maquetter un nouveau réseau complet pour voir s'il répond aux objectifs fixés avant d'acheter le matériel.
Maquetter permet aussi de découvrir et d'apprendre : mettre en œuvre des techniques, des protocoles, ... dans une maquette permet de les découvrir plus efficacement que d'étudier simplement la théorie.
Maquetter est aussi utile dans la recherche, pour faire des expériences et les rendre reproductibles, savoir quels facteurs sont aggravants (en les isolant les uns après les autres), trouver des solutions et les tester.
Les avantages de LXC pour maquetter
Bien que LXC puisse être utilisé pour isoler des applications, notamment pour un usage serveur, il présente également des avantages pour maquetter les machines d'un réseau.
LXC reste plus léger que de la virtualisation de type KVM : un ordinateur portable relativement modeste avec 3G de RAM fait tourner une maquette de 40 routeurs qui font de l'OSPF, du BGP et s'échangent un peu de trafic.
Dans un milieu plus "apprentissage/découverte", LXC est meilleur que Netkit (et ses frontend graphiques comme Marionnet), à mon avis, car LXC est maintenu activement et il offre la possibilité d'utiliser un noyau plus récent (2.26 avec Netkit, 2.19 avec Marionnet), ce qui n'est pas négligage quand on veut tester des fonctionnalités nouvelles ou quand certaines applications (notamment des démons) ont trop évoluées et ne sont plus capables de se binder sur un vieux noyau (il me semble avoir vu un cas avec Apache httpd). Sans compter la facilité de modification du système de base dans le cas de LXC (alors que c'est un véritable calvaire avec Netkit). Je ne sais pas ce qu'apporte (ou non) LXC comparé à User Mode Linux utilisé directement, sans passer par Netkit. ÉDIT du 19/09/2013 à 19h05 : Comme le mentionne Kartoch dans les commentaires, on peut utiliser Netkit sans avoir les droits root (modulo que /dev/shm ne doit pas être en noexec), ce qui a son intérêt dans une salle de TP à la fac/IUT/autre. Fin de l'édit
Néanmoins, autant dans un usage d'isolation des applications sur un serveur, je peux entrevoir les limites de LXC, la sécurité (un conteneur compromis qui permettrait de remonter au noyau et de mettre le zouk dans le reste du système/des autres conteneurs, cela ne me surprendrait pas), autant j'avoue ne pas savoir dans quels cas le fait que ce soit le même noyau dans l'hôte et dans tous les conteneurs est nuisible à un maquettage. Je peux simplement dire que cela me semble être utile dans les expériences aux timings serrés ou pour faire des mesures car la vision du temps est identique dans tous les conteneurs : l'instant T est le même dans tous les conteneurs.
Mettre des conteneurs LXC en réseau
Pour relier les conteneurs entre eux, on peut utiliser un ou plusieurs bridges. C'est intégré de base dans LXC et cela permet de voir tout le trafic de la maquette en dumpant simplement le trafic qui passe sur le bridge avec tcpdump/wireshark. Très pratique pour débugger, faire des stats, ... De plus, comme un bridge mémorise les MAC, comme un switch physique, le trafic unicast n'est pas transféré aux conteneurs qui n'en sont pas les destinataires.
Si l'on a besoin de fonctionnalités plus avancées (VLAN, agrégation de liens, ...), on peut utiliser Open vSwitch. J'avais testé pour découvrir : la communication entre conteneurs LXC juste marche. J'ai entendu dire que VDE ne fonctionnait pas avec les interfaces TAP de LXC. N'ayant jamais eu besoin de plus qu'un bridge, je n'ai jamais vérifié.
J'ignore s'il est possible d'interfacer LXC avec des routeurs Cisco/Juniper émulés. Dynamips semble pouvoir utiliser VDE mais comme LXC semble ne pas pouvoir ...
Adressage
Pour adresser/nommer votre maquette, je vous conseille fortement d'utiliser les ressources réservées à l'IANA : IPv4/v6, ASN, TLD, ... Cela fait sérieux et évite les collisions avec le réseau local (quand vous aurez eu à dépatouiller un tel cas, vous vous mettrez à utiliser les ressources réservées, je vous le garantit !).
Rendre la maquette plus réaliste
Au niveau réseau, Netem (network emulator) permet de rajouter de la latence, de la perte, de la corruption, ... sur les liens entre les différents conteneurs LXC. Pour que cela ne fasse pas trop artificiel, il est possible de faire varier la latence, la perte, ... en utilisant une distribution à la normale. On trouve plein d'exemples sur le web : Netem | The Linux Foundation, Simuler un lien WAN sous Linux chez NicoLargo, ... Netem est juste indispensable dès lors que l'on essaye de reproduire des réseaux réels ou, au moins, des conditions réelles.
Au niveau de chaque conteneur, les cgroups (control groups) permettent d'affecter des priorités d'accès aux ressources (CPU, réseau, ...) différentes pour chaque conteneur. Cela permet de favoriser un conteneur plutôt qu'un autre. Dans le cadre de maquettes réseau, cela permet de tenir compte des disparités que l'on trouve sur un réseau physique : on n'a des modèles de routeurs différents avec des capacités de traitement différentes ... Cela permet aussi de mesurer le comportement d'algorithmes de routage dans des situations défavorables. LXC lui-même est basé sur les cgroups. Je n'ai pas encore utilisé les cgroups dans une maquette donc je n'ai pas de ressources à conseiller plus que d'autres.
Automatiser le tout
Je vais vous montrer comment j'automatise la création et la gestion d'une grosse maquette avec des conteneurs LXC. L'ennui, c'est qu'il y a du besoin spécifique dedans (routing, forward, ...). Je vais essayer de tout bien vous expliquer pour que vous puissiez adapter à vos projets.
Preseed-file
Mes conteneurs sont tous basés sur Debian puisque Debian, c'est la vie. Pour créer des dizaines de conteneurs LXC à la chaîne, il est nécessaire d'avoir un preseed-file bien foutu qui configurera une bonne partie du conteneur (login, logiciels supplémentaires à installer, ...) lors de la création dudit conteneur sans poser aucune question dans une interface semi-graphique (ça ne passe pas à l'échelle ce genre de chose). Or, un preseed-file bien foutu, qui juste marche, bah ça ne court pas le web.
Voici celui que j'utilise, issu de quelques trouvailles personnelles minoritaires et de la fusion de ces deux modèles : Creating a Debian sliver template sur wiki.confine-project.eu et Debian wheezy preseed file for lxc-create -t debian lxc version 0.9.0.alpha2.
Pour le résumer :
- Les conteneurs auront l'architecture amd64 et seront créés à partir du dépôt sid ;
- Packages supplémentaires : less iputils-ping iptables telnet traceroute wget nano rsyslog bash-completion quagga et openssh-server ;
- Utilisation d'un bridge nommé br0 ;
- MAC fixe (on la remplacera plus loin vu que LXC ne sait pas faire tout seul) ;
- On ne supprime pas la capabilitie « sys_admin » car elle est nécessaire à Zebra ;
- On ne crée pas un compte utilisateur normal ;
- Le mot de passe root est « toor » ;
- On utilise le fuseau horaire « Europe/Paris » ;
# # Debian preseed file for CONFINE sliver template # Tested on lxc 0.9.0~alpha3-2 and live-debconfig 4.0~a17. # ## Distribution and packages lxc-debconfig lxc-debconfig/distribution string sid lxc-debconfig lxc-debconfig/architecture string amd64 lxc-debconfig lxc-debconfig/archives multiselect none lxc-debconfig lxc-debconfig/mirror string ftp://ftp2.fr.debian.org/debian/ lxc-debconfig lxc-debconfig/archive-areas multiselect main lxc-debconfig lxc-debconfig/packages string less iputils-ping iptables telnet traceroute wget nano rsyslog bash-completion quagga # ## Network # Please adjust to the name of the bridge used in your host. lxc-debconfig lxc-debconfig/eth0-bridge string br0 # Private MAC address, to be replaced on sliver creation. lxc-debconfig lxc-debconfig/eth0-mac string 52:C0:A1:AB:BA:1A # Private veth interface name, to be replaced on sliver creation. #lxc-debconfig lxc-debconfig/eth0-veth string veth-sliver # ## Other container options lxc-debconfig lxc-debconfig/auto boolean false # Use live-debconfig to further configure the container. lxc-debconfig lxc-debconfig/lxc-debconfig-with-live-debconfig boolean true lxc-debconfig lxc-debconfig/apt-recommends boolean false # Avoid debconf questions. lxc-debconfig lxc-debconfig/debconf-frontend select noninteractive ## (default value) ##lxc-debconfig lxc-debconfig/debconf-priority string medium # For running commands in the container and host at the end. #lxc-debconfig lxc-debconfig/late-command string #lxc-debconfig lxc-debconfig/late-host-command string # Capabilities to be dropped from the container. lxc-debconfig lxc-debconfig/capabilities string \ audit_control audit_write ipc_lock mac_admin mac_override \ sys_module sys_pacct sys_rawio sys_resource sys_time \ syslog wake_alarm # For mounting filesystems in container. #lxc-debconfig lxc-debconfig/mount0/entry string ##lxc-debconfig lxc-debconfig/mount0/comment string \ ## Bind mount host path in container # ## Live-debconfig scripts configuration # (For some reason live-debconfig options must be on a single line # or the following options are not interpreted correctly.) live-debconfig live-debconfig/components multiselect openssh-server, passwd # ### LXC (sysvinit) # Perform LXC tweaks in the container. live-debconfig live-debconfig/sysvinit/lxc-enable boolean true ## (default values) ##live-debconfig live-debconfig/sysvinit/lxc-consoles string 6 ##live-debconfig live-debconfig/sysvinit/lxc-disable-services string checkroot.sh hwclockfirst.sh hwclock.sh kmod module-init-tools mountall.sh mountkernfs.sh umountfs umountroot ### Hardware clock access (util-linux) live-debconfig live-debconfig/util-linux/hwclockaccess boolean false # ### Host name (hostname) # Host name, to be replaced on sliver creation. live-debconfig live-debconfig/hostname/hostname string sliver # ### Network configuration (ifupdown) live-debconfig live-debconfig/ifupdown/lo-comment string The loopback interface live-debconfig live-debconfig/ifupdown/lo-enable boolean true # Private interface method, to be replaced on sliver creation. live-debconfig live-debconfig/ifupdown/eth0-ipv4-comment string The private interface live-debconfig live-debconfig/ifupdown/eth0-ipv4-method select dhcp # For static configuration of network interfaces. ##live-debconfig live-debconfig/ifupdown/eth0-ipv4-method select static ##live-debconfig live-debconfig/ifupdown/eth0-ipv4-address string 1.2.3.4 ##live-debconfig live-debconfig/ifupdown/eth0-ipv4-netmask string 255.255.255.0 ##live-debconfig live-debconfig/ifupdown/eth0-ipv4-gateway string 1.2.3.1 ##live-debconfig live-debconfig/ifupdown/eth0-ipv4-network string 1.2.3.0 ##live-debconfig live-debconfig/ifupdown/eth0-ipv4-broadcast string 1.2.3.255 ##live-debconfig live-debconfig/ifupdown/eth0-ipv4-mtu string 1500 ##live-debconfig live-debconfig/ifupdown/eth0-ipv4-post-up string post-command # For static configuration of DNS. ##live-debconfig live-debconfig/ifupdown/nameserver-addresses string 5.6.7.8 9.10.11.12 ##live-debconfig live-debconfig/ifupdown/nameserver-domain string example.com ##live-debconfig live-debconfig/ifupdown/nameserver-search string lan example.com ##live-debconfig live-debconfig/ifupdown/nameserver-options string debug # ### Users (passwd) live-debconfig live-debconfig/passwd/shadow boolean true live-debconfig live-debconfig/passwd/root-login boolean true live-debconfig live-debconfig/passwd/make-user boolean false live-debconfig live-debconfig/passwd/root-password string toor live-debconfig live-debconfig/passwd/root-password-again string toor # FUSEAU HORAIRE tzdata tzdata/Areas select Europe tzdata tzdata/Zones/Etc select UTC tzdata tzdata/Zones/Europe select Paris |
Depuis peu, il n'est plus possible d'utiliser un preseed-file avec le template Debian sauf à utiliser le package « lxc » qui se trouve dans le dépôt « experimental » de Debian. Voir : 0.9.0~alpha3-2+deb8u1 : “lxc” package : Debian .
Scripts
Ensuite, il faut des scripts pour automatiser la création, le démarrage, l'arrêt et l'éventuelle destruction des conteneurs. Ci-dessus, vous trouverez les scripts que j'utilise. Ce n'est pas du grand art mais ça fait ce qui doit être fait.
Commun
Commun est un fichier qui sert à positionner les variables et les tests utiles à plusieurs scripts comme le nom de tous les conteneurs de la maquette LXC ou vérifier les droits.
MACHINES="R1 R2 R3 R4" if [ $USER != "root" ] then echo "Vous devez exécuter ce script en étant root." exit 1 fi |
Créer
Pour que vous puissiez comprendre ce script, je dois vous expliquer comment je hiérarchise les différents éléments qui permettent de créer la maquette.
Racine de la maquette
|-- network
|-- routing
|-- scripts
|-- tests
Network contient des fichiers nommés « config.$NOM_DU_ROUTER ». Chacun d'entre eux contient les instructions qui permettent de mettre en place le réseau sous LXC (« lxc.network.type », « lxc.network.ipv4 », « lxc.network.hwaddr », ...). Elles seront ajoutées au fichier « config » du conteneur correspondant lors de la création de la maquette. C'est aussi dans ce dossier que je stocke un fichier « hosts » qui fait la correspondance entre le nom de mes machines (voir des interfaces quand j'ai besoin d'une telle granularité (exemple : « 198.18.0.1 eth0.r1.local. »)) et leur adresse IP "principale" (une loopback, par exemple).
Routing contient, quand j'en ai besoin, les fichiers de configuration de Quagga. Exemple : bgpd.conf, ospfd.conf, zebra.conf, ... Tous sont suffixés avec le nom du conteneur LXC sur lequel devra être mis le fichier. Exemple : « zebra.conf.R1 ». Ils seront copiés dans le dossier /etc/quagga lors de la création de la maquette.
Scripts contient tous les scripts shell de gestion de la maquette. Typiquement ceux que je suis en train de vous présenter. 😛
Tests contient des scripts ou des binaires qui permettent d'effectuer des tests sur la maquette. Le script bash « pingall » (voir plus bas) est typiquement un de ces moyens de test. Ils seront copiés dans /root.
Ceci étant dit, voici le script le plus complet que j'ai écrit pour construire une maquette avec LXC :
#!/bin/bash source commun HERE=`pwd` PARENT=$HERE/.. for i in $MACHINES do # Création du conteneur lxc-create -n $i -t debian -- --preseed-file=$HERE/preseed-file # Proc doit être en RW pour permettre l'activation de l'ip_forward sed -i 's/proc proc proc ro/proc proc proc rw/' /var/lib/lxc/$i/config # Pour que SSH accepte les connexions sed -i 's/UsePAM yes/UsePAM no/' /var/lib/lxc/$i/rootfs/etc/ssh/sshd_config # Mise en place du réseau head -n-6 /var/lib/lxc/$i/config > /var/lib/lxc/$i/config.tmp mv /var/lib/lxc/$i/config.tmp /var/lib/lxc/$i/config cat $PARENT/network/config.$i >> /var/lib/lxc/$i/config # Toutes les interfaces sont sur le même bridge donc les réponses ARP ne venant # pas de la bonne interface peuvent être gênantes. Pour eviter ça, on active arp_filter echo "net.ipv4.conf.all.arp_filter = 1" >> /var/lib/lxc/$i/rootfs/etc/sysctl.conf echo "net.ipv4.conf.default.arp_filter = 1" >> /var/lib/lxc/$i/rootfs/etc/sysctl.conf intSurCeRouteur=`grep -Eo "eth[0-9]" /var/lib/lxc/$i/config` for j in $intSurCeRouteur do echo "net.ipv4.conf.$j.arp_filter = 1" >> /var/lib/lxc/$i/rootfs/etc/sysctl.conf done # Associations IP<->nom cat $PARENT/network/hosts >> /var/lib/lxc/$i/rootfs/etc/hosts # Routing (on veut zebra + ospf + bgp) cp $PARENT/routing/zebra.conf.$i /var/lib/lxc/$i/rootfs/etc/quagga/zebra.conf cp $PARENT/routing/ospfd.conf.$i /var/lib/lxc/$i/rootfs/etc/quagga/ospfd.conf cp $PARENT/routing/bgpd.conf.$i /var/lib/lxc/$i/rootfs/etc/quagga/bgpd.conf sed -i 's/^zebra=no/zebra=yes/' /var/lib/lxc/$i/rootfs/etc/quagga/daemons sed -i 's/^ospfd=no/ospfd=yes/' /var/lib/lxc/$i/rootfs/etc/quagga/daemons sed -i 's/^bgpd=no/bgpd=yes/' /var/lib/lxc/$i/rootfs/etc/quagga/daemons # Forward sed -i 's/#net.ipv4.ip_forward=1/net.ipv4.ip_forward=1/' /var/lib/lxc/$i/rootfs/etc/sysctl.conf # Tests cp $PARENT/tests/pingall.sh /var/lib/lxc/$i/rootfs/root/ chmod u+x /var/lib/lxc/$i/rootfs/root/pingall.sh done |
Lancer
On met en place ce qu'il faut (cgroups, création du bridge, on ne drop pas les paquets qui passent d'un conteneur à l'autre, ..) puis on lance les conteneurs et on vérifie qu'ils sont bien lancés.
#!/bin/bash source commun mount -t cgroup none /sys/fs/cgroup brctl addbr br0 ip link set up dev br0 iptables -P FORWARD ACCEPT ip6tables -P FORWARD ACCEPT for i in $MACHINES do lxc-start -d -n $i done sleep 10 for i in $MACHINES do echo "$i :" lxc-info -n $i echo -e "\n" done |
Alors oui, ce script causera l'affichage d'erreurs lors d'utilisations successives car il n'y a pas de contrôles et que le bridge existe déjà et que les cgroups sont déjà montés. Rien de grave donc, juste trois lignes de textes sur la console. 😛
Stopper
On arrête les conteneurs et on vérifie qu'ils ont bien été arrêtés.
#!/bin/bash source commun for i in $MACHINES do lxc-stop -n $i done sleep 10 for i in $MACHINES do echo "$i :" lxc-info -n $i echo -e "\n" done |
Je sens que je vais me faire troller sur l'utilisation de « lxc-stop » qui fait un arrêt violent des conteneurs en lieu et place de « lxc-halt » qui laisse les conteneurs s'éteindre par eux-mêmes. L'explication est simple : sur chacune de mes maquettes, lxc-halt fonctionne pour quelques conteneurs très minoritaires même en attendant et je dois lancer lxc-stop ensuite ... Et comme je n'ai remarqué aucun dégât en utilisant uniquement lxc-stop ... bah j'utilise uniquement lxc-stop.
Détruire
Permet de détruire tous les conteneurs LXC de la maquette.
#!/bin/bash source commun for i in $MACHINES do lxc-destroy -n $i done |
Netem
Pour ajouter une latence calculée et différente entre chaque lien sans utiliser de distribution à la normale :
#!/bin/bash # À lancer sur l'hôte, après que toutes les interfaces du lab # aient été créées ... soit environ start.sh + 10 secondes modprobe sch_netem # Un groupe de lignes = un routeur. Ici R1 tc qdisc add dev lxc_R1_R2 root netem delay 1.180890283ms tc qdisc add dev lxc_R1_R3 root netem delay 2.3495148631ms # R2 tc qdisc add dev lxc_R2_R1 root netem delay 1.180890283ms # R3 tc qdisc add dev lxc_R3_R1 root netem delay 2.3495148631ms tc qdisc add dev lxc_R3_R4 root netem delay 0.8994545122ms # R4 tc qdisc add dev lxc_R4_R3 root netem delay 0.8994545122ms |
Pour appliquer d'autres caractéristiques (perte, corruption) sur un ou plusieurs liens, il suffit d'ajouter des lignes à ce script.
Pingall
Pour vérifier que le réseau de mes maquettes est opérationnel, j'utilise, en premier lieu, un bête script qui ping toutes les adresses IPs allouées (en utilisant les noms, comme ça, on teste aussi la validé du fichier /etc/hosts 😉 ) et qui s'arrête à la première erreur rencontrée.
J'utilise souvent une loopback adressée sur chaque routeur. Cela permet de considérer les routeurs comme des destinations, ce qui est toujours utile (qui à dit MPLS ? 🙂 ). Cela permet aussi "d'accrocher" les protocoles de routage dynamiques (exemple : BGP et « update-source ») dessus.
#!/bin/bash INTERFACES_PHY="eth0.r1.local eth1.r1.local eth0.r2.local eth0.r3.local eth1.r3.local eth0.r4.local " INTERFACES_LO="lo.r1.local lo.r2.local lo.r3.local lo.r4.local" echo -e "\033[0;32m\n\n---------- Testons d'abord chaque lien ----------\n\033[0;39;49m" j=0 for i in $INTERFACES_PHY do ping -c 2 -w 2 $i if [ $? -ne 0 ] then echo -e "\033[0;31m\n\n---------- ÉCHEC ----------\n\033[0;39;49m" exit 5 fi echo -e "\n" done echo -e "\033[0;32m\n\n---------- Testons ensuite chaque loopback ----------\n\033[0;39;49m" j=0 for i in $INTERFACES_LO do ping -c 2 -w 2 $i if [ $? -ne 0 ] then echo -e "\033[0;31m\n\n---------- ÉCHEC ----------\n\033[0;39;49m" exit 5 fi echo -e "\n" done echo -e "\033[0;32m\n\n---------- SUCCÈS ----------\n\033[0;39;49m" exit 0 |