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.