LXC des conteneurs légers
LXC des conteneurs légers jppLa 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 jppD’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 jppLXC 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 jppAssurons 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 lo: flags=73<UP,LOOPBACK,RUNNING> mtu 65536 |
Là aussi tout semble correct, faisons un tout petit test (ssh sur une autre machine physique du réseau) :
ssh root@xxxxxxxxx Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent permitted by applicable law. |
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 The programs included with the Debian GNU/Linux system are free software; Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent |
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é jppInitialisation 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 jppConnecter 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é jppLXC : 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 jppLimiter 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 drupadminSi 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 jppSi 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 jppLa 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 jppRien 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 jppUtiliser 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.
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%.
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.