LXC

LXC jpp

La grande mode actuelle est aux containers, après quelques essais avec Docker j'ai été déçu, ce n’est pas facile à installer, ni à utiliser et j’y ai trouvé des limitations fortes.

Au hasard de mes lectures j’ai trouvé des articles sur un logiciel connu depuis longtemps mais peu utilisé : LXC. 
Cette possibilité de créer des « Machines » légères, qui, contrairement à d’autres systèmes, utilisent le noyau de la machine support. L’empreinte disque de ces « machines » est donc bien moindre et il n’y a pas grand-chose à installer sur un système normal car presque tout est déjà disponible. 
Les outils LXC sont très légers et semblent performants, un ensemble de modèles (templates ?) de création de conteneurs est disponible et permet déjà de réaliser des installations faciles. L’isolation entre les containers (ou conteneurs?) semble au moins aussi bonne que celle de Docker et consorts.

J’ai donc décidé de réaliser quelques tests en commençant par l’installation d’une Debian 10 "de base" donc tout à fait standard. 
Afin de ne pas être ennuyé par les problèmes réseau, et pour ne pas « casser » la config de la machine de test j’ai monté une nouvelle carte réseau agencée en mode pont (bridge pour les fanatiques) dédiée à LXC.

Les articles suivants devraient vous permettre de « démarrer » rapidement un conteneur LXC en mode privilégié, on passera ensuite au mode non-privilégié qui est quand même plus sécurisant.

LXC n'ext pas à proprement parler un hyperviseur mais plus un système de conteneurisation qui s'appuie,entre autres choses, sur les Cgroups pour compartimenter les machines. LXC est maintenu par la société Canonical créatrice de la distribution Ubuntu.

LXC : quelques commandes

LXC : quelques commandes jpp

D’abord quelques commandes « lxc » (les principales pour commencer) :

La plupart des commandes sont constituées du préfixe « lxc- » suivi d’un suffixe qui précise l’action à exécuter, le tout est assez simple à mémoriser. Pour les autres commandes voir dans /usr/bin.

Quelques paramètres généraux utilisés par la plupart des commandes :

  • -n suivi du nom de l’instance
  • -o nom d’un fichier pour stocker le log de l’opération
  • -P chemin vers le répertoire de stockage, si absent lxc utilise par défaut /var/lib/lxc

Les commandes principales :

  • lxc-checkconfig 
    Avant toute choses s'assurer que votre kernel dispose bien des options nécessaires à LXC. 
     
  • lxc-create :
    Création d’une nouvelle instance, les paramètres précisent le template à utiliser ( t nom_du_template). Exemple : 
    lxc-create -name debian-a -t debian  
    Le nom à indiquer est le nom du template (/usr/share/lxc/templates) sans le préfixe « lxc- ». 
    L’exemple indiqué crée une machine nommée « debian-a » avec le template « lxc-debian ».
  • lxc-start : Démarre l’instance dont le nom est donné en paramètre.
  • lxc-stop : Stoppe la machine dont le nom est donné en paramètre.
  • lxc-attach  nom de l’instance
    Vous «attache » une console avec un shell sur la machine donnée en paramètre.
  • lxc-copy -n machine_modèle -N nouvelle machine

Avec plein d’autres options pour faire des copies éphémères, changer le répertoire de stockage de l’image ….

  • lxc-freeze  nom de l'instance
    Permet de geler une instance dont le nom est donné en paramètre, l’instance est bloquée mais peut être débloquée par lxc-unfreeze
  • lxc-snapshot 
    Réalise un snapshot de la machine dont le nom est donné en paramètre. Cela peut être très utile en phase de création d’une nouvelle instance. 
    Le paramètres « -L » permet de lister les snapshots existants d’une instance. 
    Le paramètre « -r » permet de restaurer un snapshot.
  • lxc-destroy -n nom de la machine
    Détruit l’instance donnée en paramètre, options « -f » pour force et « -s » pour détruire aussi les snapshots.
  • lxc-ls 
    Liste les machines existantes et leur état, l’option « -f » rend le tout plus joli et lisible. 
    J'ai même créé un script remplaçant qui appelle une copie de lxc-ls avec l'option "-f".
  • lxc-info 
    Affiche les informations principales de l'instance donnée en paramètre.


Pour les détails de paramétrage voir les pages « man » qui sont bien faites.

LXC : le réseau

LXC : le réseau jpp

LXC  le réseau.

La partie réseau est gérée par « lxc-net » qui est un démon que se lance au démarrage du système. 
Sa configuration n’est pas complexe mais, pour être fonctionnelle rapidement, nécessite la création d’une interface « pont » dédiée et donc la présence des "br-utils".

Ici et pour ne pas modifier une configuration réseau « qui marche » déjà avec deux ponts j’ai préféré rajouter une carte réseau dédiée dans ma machine de développement. Le seul problème que j’ai rencontré est le fait que l’interface supplémentaire s’est « intercalée » entre les deux interfaces existantes et qu’il a fallu que je modifie la config d'un des deux ponts déjà existants … "ensp3s0" est devenu "ensp4s0" dans ma config. 
J’ai ajouté dans /etc/network/interfaces :

allow-hotplug enp3s0 iface br2 inet static address 192.168.3.1 netmask 255.255.255.0 network 192.168.3.0 broadcast 192.168.3.255 bridge_ports enp3s0 bridge_maxwait 1

Contenu du fichier de config de « lxc-net «  dans / etc / default / lxc-net :

USE_LXC_BRIDGE="true" 
LXC_BRIDGE="br2" 
LXC_ADDR="192.168.3.1" 
LXC_NETMASK="255.255.255.0" 
LXC_NETWORK="192.168.3.0/24" 
LXC_DHCP_RANGE="192.168.3.0,192.168.3.255" 
LXC_DHCP_MAX="254" 
LXC_DHCP_CONF_FILE="" 
LXC_DOMAIN=""

En ce qui concerne le fichier de config de mon instance « debian-a » déjà créée par « lxc-create -n debian-a -t debian » j’ai ajouté à la fin du fichier /var/lib/lxc/debian-a/config :

# pour le réseau eth0 
lxc.net.0.type = veth 
# lxc.net.0.veth.mode = bridge 
lxc.net.0.link = br2 
lxc.net.0.name = eth0 
lxc.net.0.ipv4.address = 192.168.3.11/24 
lxc.net.0.ipv4.gateway = 192.168.3.1 
lxc.net.0.flags = up

Comme lxc-net utilise dnsmasq il est nécessaire de désactiver un autre serveur DNS, ici j’ai du désactiver bind9.

D’autre part lxc-net utilise dnsmasq mais il lui transfère un paramètre indésirable (-U dnsmasq) qui déclenche chez moi une erreur de dnsmasq, il m’ a donc fallu : 
renommer « dnsmasq » en « dnsmasq_real », faire un script « dnsmasq » qui contient :

#!/bin/bash 
# 
# Faux dnsmasq pour analyser et filtrer les paramètres reçus 
# 
# ------------------------------------------------------------------------------ 
shift 
shift 
/usr/sbin/dnsmasq_real $*

Afin d’éliminer les deux paramètres superflus. Une fois cette « manip » réalisée dnsmasq est bien présent comme le montre un "ps" après un reboot et l’adresse de mon pont « br2 » est bien celle attendue.

On va pouvoir passer à la suite.

LXC : test première instance

LXC : test première instance jpp

Assurons nous d'abord que notre instance est bien configurée :

lxc-ls -f 
NAME STATE AUTOSTART GROUPS IPV4 IPV6 UNPRIVILEGED 
debian-a STOPPED 0 - - - false

L’instance est bien là et stoppée, essayons de la démarrer et de nous y connecter :

lxc-start debian-a 
lxc-attach debian-a 
root@debian-a:/# ls -al 
total 56 
drwxr-xr-x 18 root root 4096 avril 12 14:26 . 
drwxr-xr-x 18 root root 4096 avril 12 14:26 .. 
lrwxrwxrwx 1 root root 7 avril 6 16:18 bin -> usr/bin 
drwxr-xr-x 2 root root 4096 févr. 1 17:09 boot 
drwxr-xr-x 7 root root 520 avril 12 14:26 dev 
drwxr-xr-x 51 root root 4096 avril 10 22:58 etc 
...... 
drwxr-xr-x 2 root root 4096 avril 6 16:18 opt 
dr-xr-xr-x 301 root root 0 avril 12 14:26 proc 
drwx------ 3 root root 4096 avril 10 22:58 root 
drwxr-xr-x 11 root root 340 avril 12 14:27 run 
lrwxrwxrwx 1 root root 8 avril 6 16:18 sbin -> usr/sbin 
drwxr-xr-x 2 root root 4096 avril 6 17:00 selinux 
drwxr-xr-x 2 root root 4096 avril 6 16:18 srv 
dr-xr-xr-x 12 root root 0 avril 12 14:26 sys 
drwxrwxrwt 7 root root 4096 avril 12 14:27 tmp 
drwxr-xr-x 13 root root 4096 avril 6 16:18 usr 
drwxr-xr-x 11 root root 4096 avril 6 16:18 var

Cette instance ressemble bel et bien à une machine standard, voyons pour la partie réseau :

root@debian-a:/# ifconfig 
eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500 
inet 192.168.3.11 netmask 255.255.255.0 broadcast 192.168.3.255 
inet6 fe80::54de:e3ff:fe2b:e5fb prefixlen 64 scopeid 0x20<link> 
ether 56:de:e3:2b:e5:fb txqueuelen 1000 (Ethernet) 
RX packets 30 bytes 5576 (5.4 KiB) 
RX errors 0 dropped 0 overruns 0 frame 0 
TX packets 17 bytes 3190 (3.1 KiB) 
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0

lo: flags=73<UP,LOOPBACK,RUNNING> mtu 65536 
inet 127.0.0.1 netmask 255.0.0.0 
inet6 ::1 prefixlen 128 scopeid 0x10<host> 
loop txqueuelen 1000 (Local Loopback) 
RX packets 0 bytes 0 (0.0 B) 
RX errors 0 dropped 0 overruns 0 frame 0 
TX packets 0 bytes 0 (0.0 B) 
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0

Là aussi tout semble correct, faisons un tout petit test (ssh sur une autre machine physique du réseau) :

ssh root@xxxxxxxxx 
The authenticity of host 'xxxxxxx (192.168.X.Y)' can't be established. 
ECDSA key fingerprint is SHA256:4flIBNtrJjrrVMT3NLBvKkF4XrpODBKBvP/fwZlK9pE. 
Are you sure you want to continue connecting (yes/no)? yes 
Warning: Permanently added 'xxxxxxx,192.168.X.Y' (ECDSA) to the list of known hosts. 
root@xxxxxxxx's password: 
Linux xxxxxxxx 5.4.0-0.bpo.3-amd64 #1 SMP Debian 5.4.13-1~bpo10+1 (2020-02-07) 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. 
Last login: Sun Apr 12 12:40:21 2020 from 192.168.2.8 
root@xxxxxxxx:~#

C’est OK, le réseau marche … essayons de faire la mise à jour du système puisque /etc/apt/sources/list a l’air correct : 

root@debian-a# apt-get update 
Get:1 http://security.debian.org stable/updates InRelease [65,4 kB] 
Get:2 http://deb.debian.org/debian stable InRelease [122 kB] 
Get:3 http://deb.debian.org/debian stable/main amd64 Packages [7 907 kB] 
Get:4 http://security.debian.org stable/updates/main amd64 Packages [187 kB] 
Get:5 http://security.debian.org stable/updates/main Translation-en [100 kB] 
Get:6 http://deb.debian.org/debian stable/main Translation-en [5 970 kB] 
Get:7 http://deb.debian.org/debian stable/main Translation-fr [2 478 kB] 
Fetched 16,8 MB in 3s (6 103 kB/s) 
Reading package lists... Done

Tout est donc OK, cette instance semble parfaitement fonctionnelle et occupe #686Mo sur disque. 
On peut s'y connecter en ssh (il faut fixer un nouveau mot se passe "root", lxc-attach vous connecte par ssh en "root) comme sur n'importe quelle autre machine :

ssh root@192.168.3.11 
root@192.168.3.11's password:  
Linux debian-a 5.5.9 #1 SMP Fri Mar 13 19:22:24 CET 2020 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. 
Last login: Fri Apr 10 22:58:45 2020 from 192.168.3.1 
root@debian-a:~# 

Mais elle n'est pas très sécurisée car elle fonctionne avec les droits "root" ce qui n'est pas forcément le plus judicieux et que d'autre part rien ne l'empêche de monopoliser l'ensemble de la mémoire, du CPU des IO ... 
Si on utilise la commande "top" la mémoire disponible est celle de la machine pĥysique sous-jacente, "cat /proc/meminfo" donne bien l'intégralité de la mémoire physique. 
De même "df" montre l'intégralité du disque contenant le répertoire /var/lib/lxc. 
Il reste donc encore des choses à faire pour rendre les choses plus sûres et maîtrisables.

Un conteneur non privilégié

Un conteneur non privilégié jpp

Initialisation d'un conteneur non privilégié : 
Comme la plupart des "templates" ne permettent pas de charger des images utilisables en mode non-privilégié il est recommandé d'utiliser le template "download". 
Celui-ci nécessite quelques paramètres supplémentaires pour fonctionner en mode automatique. Si ces paramètres ne sont pas fourni le template vous demande les données en affichant pour chaque choix une liste des possibilités, la liste des distributions disponibles est impressionnante ... 
Mais avant de créer le premier conteneur non privilégié il faut effectuer quelques mises à jour (utilisateur "testlxc" créé pour la circonstance) :

  • Créer un répertoire pour stocker nos conteneurs, sinon ceux-ci seront créés dans .local/share/lxc. Ici j'ai créé un répertoire $HOME/VM/LXC, volontairement en deuxième niveau en dessous de $HOME.
  • Créer le fichier /etc/lxc/lxc-usernet contenant :

# user type_de_réseau pont nombre 
testlxc   veth         br2  10

  • Créer le répertoire $HOME/.config/LXC
  • Créer dans ce répertoire un fichier "lxc.conf" contenant :

lxc.lxcpath = /home/testlxc/VM/LXC 
lxc.default_config = /home/testlxc/.config/lxc/default.conf

  • Créer un fichier "default.conf" contenant :

lxc.apparmor.profile = generated 
lxc.apparmor.allow_nesting = 1 
### 
lxc.idmap = u 0 296608 65536 
lxc.idmap = g 0 296608 65536 
Ces valeurs sont issues de : 
grep testlxc /etc/subuid (pour la ligne "u") 
grep testlxc /etc/subgid (pour la ligne "g") 
Ouf, c'est fini pour la configuration.

Pour les tests j'ai utilisé un script (TC) avec log détaillé dans un fichier, surtout pour les premiers tests : 
LOGPRI=DEBUG 
TEMPLATE=download 
NOM=debian-d 
LOTG=" --logfile=/dev/stdout --logpriority=${LOGPRI} " 
COMPL=' -- --dist debian --release buster --arch amd64 ' 
REP=' -P /home/testlxc/VM/LXC/' 
lxc-create  -n ${NOM} ${REP} -t ${TEMPLATE} ${LOG}  ${COMPL}  2>&1 | tee TC.LOG

Et on exécute notre script : 
./TC 
No such file or directory - Failed to open tty 
No such file or directory - Failed to open tty 
Using image from local cache 
Unpacking the rootfs 
..... 
You just created a Debian buster amd64 (20200419_05:24) container.

To enable SSH, run: apt install openssh-server 
No default root or user password are set by LXC.

Les deux "No such file..." n'ont pas l'air de gêner ...

On va voir de qui se passe dans ce nouveau conteneur : 
lxc-start debian-d 
lxc-attach debian-d 
root@debian-d:/# ls -al 
total 68 
drwxr-xr-x  21 root   root    4096 Apr 19 05:29 . 
drwxr-xr-x  21 root   root    4096 Apr 19 05:29 .. 
drwxr-xr-x   2 root   root    4096 Apr 19 05:27 bin 
drwxr-xr-x   2 root   root    4096 Feb  1 17:09 boot 
drwxr-xr-x   6 root   root     500 Apr 20 11:43 dev 
drwxr-xr-x  40 root   root    4096 Apr 20 11:43 etc 
drwxr-xr-x   2 root   root    4096 Feb  1 17:09 home 
drwxr-xr-x  10 root   root    4096 Apr 19 05:26 lib 
drwxr-xr-x   2 root   root    4096 Apr 19 05:25 lib64 
.... 
drwxr-xr-x   2 root   root    4096 Apr 19 05:25 srv 
dr-xr-xr-x  12 nobody nogroup    0 Apr 20 11:43 sys 
drwxrwxrwt   7 root   root    4096 Apr 20 11:43 tmp 
drwxr-xr-x  10 root   root    4096 Apr 19 05:25 usr 
drwxr-xr-x  11 root   root    4096 Apr 19 05:25 var 
Ceci ressemble bien au contenu d'une machine réelle. 
On personnalise rapidement le mot de passe de "root" afin de sécuriser un peu ce conteneur. 
on en sort par "exit" puis "lxc-stop debian-d" ou par : 
"systemctl poweroff" plus court !

Maintenant il reste à connecter notre machine au réseau ... 
 

LXC : Non privilégie et sécurisé

LXC : Non privilégie et sécurisé jpp

LXC : Il est nécessaire de sécuriser un peu ce conteneur en limitant ses possibilités :

  • Mémoire
  • Cpu
  • IO, pour cela on verra un peu plus tard.

Mais ce n'est pas une mince affaire de gérer les "cgroups" à travers LXC car il existe pas mal de contraintes dont la plupart doivent être levées au niveau du système. 
Les tests sont donc assez longs et j'ai du passer un temps assez long avant de rédiger la suite de cet article ... le temps de consulter pas mal de documents et de faire quelques tests.