LXC des conteneurs légers

LXC des conteneurs légers 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 : 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 ... 
 

Non privilégié avec réseau

Non privilégié avec réseau jpp

Connecter le conteneur au réseau. 
Cette action doit être réalisée conteneur stoppé. 
Il faut compléter le fichier "config" de notre conteneur avec les éléments liés au  réseau, on utilise ici le même pont que pour le premier conteneur (accessible en "br2") : 
lxc.net.0.type = veth 
lxc.net.0.link = br2 
lxc.net.0.name = eth0 
lxc.net.0.ipv4.address = 192.168.3.12/24 
lxc.net.0.ipv4.gateway = 192.168.3.1 
lxc.net.0.flags = up 
On relance le tout et on regarde : 
~$ lxc-start debian-d 
~$ lxc-attach debian-d 
root@debian-d:/# netstat -rn 
Kernel IP routing table 
Destination     Gateway         Genmask         Flags   MSS Window  irtt Iface 
0.0.0.0         192.168.3.1     0.0.0.0         UG        0 0          0 eth0 
192.168.3.0     0.0.0.0         255.255.255.0   U         0 0          0 eth0 
La commande "ping" ne fait pas partie de l'image (bizarre, vous avez dit bizarre?), comme c'est une commande "simple" il suffit de la copier depuis la machine "maître" qui est aussi une Debian Buster (en "root") : 
cp /bin/ping /home/testlxc/VM/LXC/debian-d/rootfs/bin 
Et dans le conteneur ça marche : 
ping -c1 google.fr 
PING google.fr (216.58.213.131) 56(84) bytes of data. 
64 bytes from par21s03-in-f131.1e100.net (216.58.213.131): icmp_seq=1 ttl=52 time=5.31 ms 
Il y a bien du réseau dans ce conteneur, on va y installer un serveur ssh comme proposé lors de la création du conteneur : 
apt-get update    --> fonctionne très bien 
apt-get install openssh-server 
..... 
Creating config file /etc/ssh/sshd_config with new version 
Creating SSH2 RSA key; this may take some time ... 
2048 SHA256:jd2wgb/fFjgQZL1bXr9KHu2aK1eA8+6r0G0dx+Rro4c root@debian-d (RSA) 
Creating SSH2 ECDSA key; this may take some time ... 
256 SHA256:Lr1fp1Ov0PBVG+q0oOs8v5PMaBOh105kXA7rp3Ri9Wg root@debian-d (ECDSA) 
Creating SSH2 ED25519 key; this may take some time ... 
256 SHA256:+74AKYdcsTI+0Nz/V3YEdo1CBnPC89JY74rKhkkcGE8 root@debian-d (ED25519) 
Created symlink /etc/systemd/system/sshd.service → /lib/systemd/system/ssh.service. 
Created symlink /etc/systemd/system/multi-user.target.wants/ssh.service → /lib/systemd/system/ssh.service. 
rescue-ssh.target is a disabled or a static unit, not starting it. 
Processing triggers for systemd (241-7~deb10u3) ... 
Processing triggers for libc-bin (2.28-10) ... 
Tout est donc pour le mieux. 
Après une petite modification dans /etc/ssh/sshd_config pour autoriser la connexion de "root" et un redémarrage du service sshd il est possible de se connecter en ssh dans notre conteneur : 
ssh root@192.168.3.12 
root@192.168.3.12's password:  
Linux debian-d 5.6.4 #3 SMP Sat Apr 18 15:53:49 CEST 2020 x86_64 
... 
Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent 
permitted by applicable law. 
Last login: Mon Apr 20 15:57:58 2020 from 192.168.3.1 
root@debian-d:~#  
Comme vous pourrez le constater ce conteneur n'a pas de limites, il peut "bouffer" toute la mémoire et occuper autant de CPU qu'il lui plait, truster toutes les IO  ...  
Il va falloir lui donner des limites à ne pas franchir afin de laisser des possibilités pour d'autres conteneurs ...

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.

Limiter la mémoire

Limiter la mémoire jpp

Limiter la mémoire utilisable par une instance LXC. 
Note : la machine physique de test dispose de 16Go de RAM et de 8Go de Swap, elle fonctionne avec un kernel très récent (à l'époque) : 5.6.7, quasiment le dernier sorti à ce jour. 
Sans aucune limitation l'instance LXC dispose de l'intégralité de la mémoire de la machine physique, "free mem" montre : 
              total        used        free      shared  buff/cache   available 
Mem:       16363688       17616    16281500        8184       64572    16346072 
Swap:       8811516           0     8811516 
On voit bien 16Go de mémoire et un swap de 8Go. 
Pour limiter la mémoire utilisable par notre container on met en place les paramètres suivants :

lxc.cgroup.memory.limit_in_bytes = 128M 
lxc.cgroup.memory.memsw.limit_in_bytes = 192M 
Après redémarrage du conteneur "free mem" donne alors : 
              total        used        free      shared  buff/cache   available 
Mem:         131072       12332      105408        8184       13332      118740 
Swap:        196608           8      196600 
Ce qui correspond bien aux paramètres fixés, la commande "lxc-info" est plus bavarde et donne plus de renseignements : 
lxc-info debian-d 
Name:           debian-d 
State:          RUNNING 
PID:            8280 
IP:             192.168.3.12 
Memory use:     36.44 MiB 
KMem use:       7.50 MiB 
Link:           veth9SRIEJ 
 TX bytes:      7.89 KiB 
 RX bytes:      12.70 KiB 
 Total bytes:   20.59 KiB 
Au passage une petite singularité : la variable "$HOME" de root n'est pas initialisée dans le conteneur mais se trouve ici égale à "/home/testlxc" (HOME de l'utilisateur qui a lancé l'instance). J'ai du installer dans .profile "export HOME=/root" et il faut en plus utiliser un "switch" lors du lancement de lxc-attach en ajoutant : 
--clear-env. Comme par miracle on se retrouve alors avec une variable HOME correcte. 
 

Ajuster la mémoire

Ajuster la mémoire drupadmin

Si l'on veut s'intéresser aux limitations concernant le CPU (en % et en nombre de CPU) il va falloir donner à notre utilisateur quelques droits supplémentaires sur les CGROUPS. 
En effet, par défaut (sur une Debian Buster), seuls les droits sur "freezer" et "memory" sont attribués aux utilisateurs il faut donc ajouter les droits sur "cpu","cpuset" et "cpuacct" pour pouvoir "jouer" avec les paramètres "cpu". 
Il faut corriger un fichier de PAM qui permet d'attribuer ces droits : /etc/pam.d/common-session : 
Ligne originale : 
session    optional    pam_cgfs.so -c freezer,memory,name=systemd 
Ligne corrigée : 
session    optional    pam_cgfs.so -c freezer,memory,cpu,cpuset,cpuacct,blkio,name=systemd 
Après reconnection de notre utilisateur "testlxc", vérifier quels cgroups sont "autorisés" avec :  
cat /proc/self/cgroup | grep testlxc 
10:cpuset:/user/testlxc/0 
9:blkio:/user/testlxc/0 
8:freezer:/user/testlxc/0 
4:cpu,cpuacct:/user/testlxc/0 
Le "grep testlxc" est à adapter en fonction de votre utilisateur et cela évite des lignes inutiles ... 
Note j'ai ajouté "blkio" qui sera utilisable plus tard ... 
Autre "bizarrerie" découverte lors de ce test : 
La commande "df" renvoie la taille du disque physique porteur du "rootfs" et non la taille du "rootfs" lui même, on n'est pas dans une machine virtuelle ! 
J'ai installé Apache/Php/Opcache dans ce conteneur et j'ai quelque peu bataillé avec la mémoire partagée : il faut mettre de "bons" paramètres (kernel.shm...) :

  • Dans notre conteneur 

local.conf:kernel.shmmax = 34359738368 
local.conf:kernel.shmall = 131072 
local.conf:kernel.shmmni = 65536

  • . Mais aussi dans la machine physique 

local.conf:kernel.shmmax = 34359738368 
local.conf:kernel.shmall = 536870912 
local.conf:kernel.shmmni = 65536 
afin de permettre à Opcache d'initialiser la mémoire nécessaire. 
Paramètres ajustés dans le fichier "config": 
# limiter mémoire 
lxc.cgroup.memory.limit_in_bytes = 384M 
lxc.cgroup.memory.memsw.limit_in_bytes = 640M

Les valeurs données ci-dessus sont indicatives .. mais elles ont fonctionné pour moi et Apache a pu démarrer avec Opcache. 
Après ces quelques ajustements le site est accessible normalement de l'extérieur ... et les performances sont aussi bonnes que sur la machine physique.

Ajuster la mémoire

Ajuster la mémoire jpp

Si l'on veut s'intéresser aux limitations concernant le CPU (en % et en nombre de CPU) il va falloir donner à notre utilisateur quelques droits supplémentaires sur les CGROUPS. 
En effet, par défaut (sur une Debian Buster), seuls les droits sur "freezer" et "memory" sont attribués aux utilisateurs il faut donc ajouter les droits sur "cpu","cpuset" et "cpuacct" pour pouvoir "jouer" avec les paramètres "cpu". 
Il faut corriger un fichier de PAM qui permet d'attribuer ces droits : /etc/pam.d/common-session : 
Ligne originale : 
session    optional    pam_cgfs.so -c freezer,memory,name=systemd 
Ligne corrigée : 
session    optional    pam_cgfs.so -c freezer,memory,cpu,cpuset,cpuacct,blkio,name=systemd 
Après reconnection de notre utilisateur "testlxc", vérifier quels cgroups sont "autorisés" avec :  
cat /proc/self/cgroup | grep testlxc 
10:cpuset:/user/testlxc/0 
9:blkio:/user/testlxc/0 
8:freezer:/user/testlxc/0 
4:cpu,cpuacct:/user/testlxc/0 
Le "grep testlxc" est à adapter en fonction de votre utilisateur et cela évite des lignes inutiles ... 
Note j'ai ajouté "blkio" qui sera utilisable plus tard ... 
Autre "bizarrerie" découverte lors de ce test : 
La commande "df" renvoie la taille du disque physique porteur du "rootfs" et non la taille du "rootfs" lui même, on n'est pas dans une machine virtuelle ! 
J'ai installé Apache/Php/Opcache dans ce conteneur et j'ai quelque peu bataillé avec la mémoire partagée : il faut mettre de "bons" paramètres (kernel.shm...) :

  • Dans notre conteneur 

local.conf:kernel.shmmax = 34359738368 
local.conf:kernel.shmall = 131072 
local.conf:kernel.shmmni = 65536

  • . Mais aussi dans la machine physique 

local.conf:kernel.shmmax = 34359738368 
local.conf:kernel.shmall = 536870912 
local.conf:kernel.shmmni = 65536 
afin de permettre à Opcache d'initialiser la mémoire nécessaire. 
Paramètres ajustés dans le fichier "config": 
# limiter mémoire 
lxc.cgroup.memory.limit_in_bytes = 384M 
lxc.cgroup.memory.memsw.limit_in_bytes = 640M

Les valeurs données ci-dessus sont indicatives .. mais elles ont fonctionné pour moi et Apache a pu démarrer avec Opcache. 
Après ces quelques ajustements le site est accessible normalement de l'extérieur ... et les performances sont aussi bonnes que sur la machine physique.

Limiter les IO

Limiter les IO jpp

La limitation des IO pose quelques problèmes pour les conteneurs non privilégiés, mais quelques pistes se dessinent. 
Après pas mal de recherches et tests je me suis décidé à créer un conteneur MariaDB avec une limitation sur l'essentiel des caractéristiques soit les IO. 
Ce container MariaDB/Mysql complétera le serveur Web de l'article précédent. 
Le container est configuré avec 512M de mémoire et 768M (512M + 256M de swap) et un "disque" externe sous forme d'un répertoire contenant deux sous répertoires "data" et "binlog", le "error.log" restera dans le /var/log/mysql du conteneur. Il faut donc quelque peu corriger le fichier de configuration de MariaDB pour ne pas mettre les données dans /var/lib/mysql ou /var/log/mysql. 
J'ai voulu utiliser le paramétrage "blkio.weight" mais celui-ci nécessite de disposer au niveau du système du scheduler d'IO CFQ qui n'est plus disponible dans les noyaux récents ??? 
De toutes les façons aucun fichier contenant "weight" n'est visible dans la hiérarchie des cgroups, vérification : 
cd /sys/fs/cgroup 
find ./ -name '*weight*' 
ne ramène rien, 
donc aucun "Cgroup" avec "weight" n'existe ...  Le paramètre "weight" (10 .. 1000) qui est censé définir la répartition entre les IO de différents conteneurs semble inutilisable. 
Les seuls paramètres sur lesquels on peut agir sont ceux définissant la vitesse max autorisée : 
. blkio.throttle.read_bps_device 
. blkio.throttle.read_iops_device 
. blkio.throttle.write_bps_device 
. blkio.throttle.write_iops_device 
On peut donc limiter les IO en IOPS ou en octets pas seconde, c'est déjà pas mal ... Mais quelle est la syntaxe exacte pour lxc ? 
La syntaxe (évidente, si l'on a déjà pratiqué LXC) ne fonctionne malheureusement que pour les conteneurs privilégiés : 
lxc.cgroup.blkio.throttle.read_bps_device = 8:0 50 
8:0 car il s'agit pour moi du disque /dev/sda, on peut voir les valeurs avec : 
ls -al /dev/sda 
brw-rw---- 1 root disk 8, 0 mai    5 18:07 /dev/sda

J'ai trouvé ceci après un examen des logs de la commande "lxc-start" en mode "DEBUG" qui signale des "denied" depuis le programme "cgfsng.c". Pour autoriser les commandes "blkio.throttle...device" en mode non-privilégié il faut, encore, aller modifier le fichier /etc/pam.d/common-session et ajouter une autorisation. Cette ligne devient alors : 
session optional pam_cgfs.so -c freezer,memory,cpu,cpuset,cpuacct,blkio,cgfsng,name=systemd 
Et après avoir avoir fermé et ré-ouvert la session de mon utilisateur "testlxc" le conteneur démarre avec les limites suivantes : 
# limiter IO 
lxc.cgroup.blkio.throttle.read_bps_device = 8:0 50000 
lxc.cgroup.blkio.throttle.write_iops_device = 8:0 50000 

et les bonnes valeurs sont inscrites dans les fichiers correspondants.  
Contrôle : 
cd /sys/fs/cgroup/blkio/user/testlxc/0/lxc/debian-e 
cat *device 
8:0 50 
8:0 50 
La limite à 50000 est volontairement extrêmement faible pour voir une instance sérieusement ralentie. 
C'est le cas,

  • à 50000 le démarrage et l'arrêt du conteneur sont très ralentis, des accès à la base MariaDB sont très poussifs voire interminables ...
  • Au delà de 500000 les choses s'améliorent très sérieusement, démarrage rapide, accès à la base MariaDB rapides ...

Il semble par ailleurs que la limitation soit effective sur les accès "physiques" au disque mais ne touche pas les accès à des données dans les caches. C'est visible en "droppant les caches" sur le système physique, un simple "ls -al" demande du temps la première fois mais est instantané par la suite.

En résumé la limitation joue au niveau des IO "physiques" mais ne bride pas le conteneur pour les accès depuis le cache ce qui garantit un certain niveau de performance tout en permettant de mieux maîtriser la concurrence entre conteneurs pour les accès physiques. 
 

Tests sur limitation IO

Tests sur limitation IO jpp

Rien ne vaut quelques exemples chiffrés pour mieux voir l'influence d'un paramètre. 
Les comptages sont effectués sur 3 tables de #30800 rangs, la table (1) utilise 24Mo, la table (2) 29MO, la table (3) environ 16Mo et la table (4) de 976000 rangs et 5 partitions de #130Mo. Le chargement des tables a été fait avec une vitesse de 2MO/s  
Le filesystem est installé sur un RAID de disques HDD "classiques". 
Mariadb est réglé avec : 
query_cache_type = 0 
query_cache_limit = 0 
query_cache_size = 0 
innodb_buffer_pool_dump_at_shutdown = 0 
innodb_buffer_pool_load_at_startup  = 0 
Pour chaque valeur de lxc.cgroup.blkio.throttle.read_bps_device  les tests ont été effectués avec des comptages simples (select count(*)) sur les différentes tables. La deuxième série de chaque test a été effectuée après :

  • "echo 3 > /proc/sys/vm/drop_caches" sur la machine physique
  • systemctl restart mariadb

afin de limiter l'influence des caches du système et de MariaDB. 
Les tests ont été effectués pour des valeurs de "blkio.throttle... bps_device" de 100 000 à 100 000 000 et sont résumés dans les tableaux suivants :

100 000 BPS
1 19,384  
2 Infini ...  
3 non testé  
4 non testé  

Avec cette valeur très faible le fonctionnement n'est pas possible, tout est beaucoup trop lent.

200 000 BPS
1 46,618 44,958
2 102,426 102,560
3 41,208 41,288
4 283,057 282,193
500 000 BPS
1 14,614 12,223
2 41,132 42,069
3 16,441 16,605
4 113,378 112,617
     
1 000 000 BPS
1 0,519 0,518
2 20,586 20,586
3 8,209 8,209
4 57,419 56,300
     
2 500 000 BPS
1 0,212 0,211
2 8,196 8,200
3 3,303 3,300
4 22,505 22,505
5 000 000 BPS
1 0,151 0,153
2 6,096 6,099
3 1,999 1,998
4 11,364 11,312
     
10 000 000 BPS
1 0,119 0,116
2 4,091 4,098
3 1,599 1,598
4 11,315 12,512
     
25 000 000 BPS
1 0,043 0,042
2 0,799 0,782
3 0,300 0,299
4 3,006 2,917
     
100 000 000 BPS
1 0,041 0,040
2 0,219 0,215
3 0,095 0,096
4 2,841 2,941

Pour référence même test sur la machine de référence (CoreI5 a 3,5GHz avec 16GO de RAM) avec un réglage de MariaDB beaucoup plus "offensif" (innodb_buffer_cache 4096Mo ... et disques SSD) :

REFERENCE
1 0,020
2 0,160
3 0,060
4 0,250

A partir de 5 millions BPS on trouve un fonctionnement exploitable.

LXC : test site web

LXC : test site web jpp

Utiliser LXC avec Apache + MariaDB. 
Sur mes deux conteneurs LXC 1 et 2 j'ai installé ce qui suit : 
1) Apache2 + Opcache (448Mo RAM/ 640Mo RAM+SWAP), sur le CPU 3. 
Installation d'une copie du site ... 
Configuration du site pour Apache en HTTPS, y compris une installation des clefs SSL du site réel, cela me signalera une erreur lors de l'accès puisque le nom de site est modifié. 
Reconfiguration de l'accès à la base de données vers ma deuxième instance de machine LXC dédiée à MariaDB. 
2) Mariadb (512Mo RAM / 768Mo RAM+SWAP) sur le CPU 2 
Installation d'une copie de la base du site et création d'un user adéquat. 
Après quelques ajustements l'accès au site est correct (sauf erreur SSL signalée comme prévu), mais on peut passer outre et cela n'influence pas le système de test adopté.

La machine dédiée au site "KVM" est, elle aussi, limitée aux CPU 2 et 3 (même machine) et possède 1536 Mo de RAM + 2Go de swap.

Premier test : 
L'accès est apparemment aussi rapide que sur la version standard du site qui est installée dans une machine virtuelle KVM (Apache et MariaDB) avec 1536Go de mémoire et deux processeurs.

Tests comparés chiffrés. 
Les tests ont été réalisés avec le programme "siege" paramétré comme suit :

  • 7 users en parallèle
  • Durée de 3 minutes
  • Fichier de l'ensemble des pages du site extrait du fichier "sitemap.xml" 
  • Prise des mesures CPU sur la machine physique avec "sar" et quelques scripts de mise en forme, les graphes sont réalisés avec OpenOffice sur les données "sar" mises en forme par les scripts.

Les tests ont été réalisés deux fois pour chaque "site" :

  • Premier test juste après démarrage
  • Deuxième test peu après

Quelques résultats tirés de ces tests.

Machine « KVM »   Machine « LXC »
             
TEST 1 : après démarrage des machines
Transactions: 38450 hits   67415 hits  
Availability: 100.00 %     100.00 %    
Elapsed time: 179.95   time: 179.69 secs
Data transferred: 537.99   transferred: 941.41 MB
Response time: 0.03   time: 0.02 secs
Transaction rate: 213.67   rate: 375.17 trans/sec
Throughput: 2.99 MB/sec   5.24 MB/sec  
Concurrency: 6.91     6.92    
Successful transactions: 36335   transactions: 63621  
Failed transactions: 0   transactions: 0  
Longest transaction: 1.52   transaction: 2.98  
Shortest transaction: 0.00   transaction: 0.00  
             
             
TEST2 : à la suite du 1
Transactions: 53626 hits   68353 hits  
Availability: 100.00 %   100.00 %  
Elapsed time: 179.21   time: 179.76 secs
Data transferred: 749.06   transferred: 954.27 MB
Response time: 0.02   time: 0.02 secs
Transaction rate: 299.24   rate: 380.25 trans/sec
Throughput: 4.18 MB/sec   5.31 MB/sec  
Concurrency: 6.85     6.92    
Successful transactions: 50610   transactions: 64540  
Failed transactions: 0   transactions: 0  
Longest transaction: 2.19   transaction: 1.20  
Shortest transaction: 0.00   transaction: 0.00  

J'aussi mesuré la consommation CPU lors de ces tests, et voici quelques graphes de consommation.

Graphe consommation CPU site "LXC"

On peut remarquer une consommation du CPU 1 parfaitement parallèle à celle du CPU 2 qui est celui consacré à la base de  données, après les lectures initiales la base fonctionne sur les caches, il s'agit donc probablement de la charge due aux IO initiales de la base de données. 
Le CPU "Apache" est proche de 100%, celui de la base de données stagne à moins de 20% après un plateau à 40%.

Consommation CPU site "KVM"

Les  deux CPU sont proches de 100% (mesure faite sur la machine physique).

On constate un "rendement" meilleur du site "LXC" dont la consommation CPU totale est inférieure et le débit assez nettement supérieur.