399 lines
13 KiB
Markdown
399 lines
13 KiB
Markdown
Les *cgroup*s
|
||
-------------
|
||
|
||
Les *cgroup*s (pour *Control Group*s) permettent de collecter des statistiques
|
||
sur des **groupes de processus** (voire même, des threads !) et de leur
|
||
attribuer des propriétés. Il est par exemple possible de leur imposer des
|
||
limites d'utilisation de ressources ou d'altérer leur comportement : quantité
|
||
de RAM, temps CPU, bande passante, ...
|
||
|
||
Apparue dès [Linux
|
||
2.6.24](https://kernelnewbies.org/Linux_2_6_24#Task_Control_Groups)
|
||
(en 2008 !), les *cgroup*s sont répartis en différents sous-systèmes
|
||
(*subsystem*), chacun étant responsable d'un type de ressources
|
||
spécifique :
|
||
|
||
- [`blkio` (`io` dans la v2)
|
||
:](https://www.kernel.org/doc/html/latest/admin-guide/cgroup-v1/blkio-controller.html)
|
||
limites et statistiques de bande passante sur les disques ;
|
||
- `cpu` : cycles CPU minimums garantis ;
|
||
- [`cpuacct` (inclus dans `cpu` dans la v2)
|
||
:](https://www.kernel.org/doc/html/latest/admin-guide/cgroup-v1/cpuacct.html)
|
||
statistiques du temps CPU utilisé ;
|
||
- [`cpuset`
|
||
:](https://www.kernel.org/doc/html/latest/admin-guide/cgroup-v1/cpusets.html)
|
||
associe des tâches à un/des CPU particuliers (par exemple pour dédier un cœur
|
||
du CPU à un programme, qui ne pourra alors utiliser que ce CPU et pas les
|
||
autres) ;
|
||
- [`devices`
|
||
:](https://www.kernel.org/doc/html/latest/admin-guide/cgroup-v1/devices.html)
|
||
règles de contrôle de création (`mknod`) et d'accès aux périphériques ;
|
||
- [`freezer`
|
||
:](https://www.kernel.org/doc/html/latest/admin-guide/cgroup-v1/freezer-subsystem.html)
|
||
pour suspendre et reprendre l'exécution d'un groupe de tâches ;
|
||
- [`hugetlb` :](https://www.kernel.org/doc/html/latest/admin-guide/cgroup-v1/hugetlb.html) statistiques et limitation de l'usage de la fonctionnalité `HugeTLB` (permettant d'obtenir des pages mémoires plus grandes que 4 kB) ;
|
||
- [`memory` :](https://www.kernel.org/doc/html/latest/admin-guide/cgroup-v1/memory.html) statistiques et limitation d'usage de la mémoire vive et de la *swap* ;
|
||
- [`net_cls` (v1 seulement) :](https://www.kernel.org/doc/html/latest/admin-guide/cgroup-v1/net_cls.html) applique un `classid` à tous les paquets émis par les tâches du *cgroup*, pour filtrage par le pare-feu en sortie ;
|
||
- [`net_prio` (v1 seulement) :](https://www.kernel.org/doc/html/latest/admin-guide/cgroup-v1/net_prio.html) surcharge la valeur de l'option de priorité `SO_PRIORITY`, ordonnant la file d'attente des paquets sortants ;
|
||
- [`pids` :](https://www.kernel.org/doc/html/latest/admin-guide/cgroup-v1/pids.html) statistiques et limitation du nombre de processus ;
|
||
- ...
|
||
|
||
Nous allons commencer par faire quelques tests avec le *cgroup* *freezer*, qui
|
||
permet d'interrompre l'exécution d'un groupe de processus, puis de la reprendre
|
||
lorsqu'on le décide.
|
||
|
||
|
||
### Montage du *freezer*
|
||
|
||
En fonction de la configuration de votre système, vous allez vous trouver dans
|
||
l'une de ces trois situations :
|
||
|
||
- Votre dossier `/sys/fs/cgroup` contient à la fois des fichiers `cgroup.*` et
|
||
éventuellement des dossiers : vous avez une distribution moderne qui utilise
|
||
la nouvelle version des `cgroup`s.
|
||
|
||
- Votre dossier `/sys/fs/cgroup` contient d'autres dossiers au nom des
|
||
sous-systèmes que l'on a listés ci-dessus : il s'agit des `cgroup`s v1.
|
||
|
||
- Votre dossier `/sys/fs/cgroup` est vide ou inexistant, vous pouvez choisir
|
||
d'utiliser la version de votre choix :
|
||
|
||
Pour utiliser la v1 :
|
||
|
||
<div lang="en-US">
|
||
```bash
|
||
mkdir /sys/fs/cgroup/freezer/
|
||
mount -t cgroup -o freezer none /sys/fs/cgroup/freezer/
|
||
```
|
||
</div>
|
||
|
||
|
||
Pour utiliser la v2 :
|
||
|
||
<div lang="en-US">
|
||
```bash
|
||
mkdir /sys/fs/cgroup/
|
||
mount -t cgroup2 none /sys/fs/cgroup/
|
||
```
|
||
</div>
|
||
|
||
Avant d'aller plus loin, notez que les exemples seront donnés pour les deux
|
||
versions des `cgroup`s à chaque fois.
|
||
|
||
::::: {.question}
|
||
|
||
#### Quelles sont les différences entre les deux versions des *cgroups* ? {-}
|
||
|
||
La principale différence entre les deux est la fusion des différents
|
||
sous-systèmes au sein d'une même arborescence. Dans la première version, chaque
|
||
sous-système disposait de sa propre arborescence et il fallait créer les
|
||
groupes et associer les tâches pour chaque sous-système. Avec la seconde
|
||
version, une seule création est nécessaire, quelque soit le nombre de
|
||
sous-systèmes que l'on souhaite utiliser.
|
||
|
||
:::::
|
||
|
||
|
||
### Création d'un nouveau groupe
|
||
|
||
Les *cgroup*s sont organisés autour d'une arborescence de groupe, où chaque
|
||
groupe est représenté par un dossier. Il peut bien évidemment y avoir des
|
||
sous-groupes, en créant des dossiers dans les dossiers existants, etc.\
|
||
|
||
La première étape dans l'utilisation d'un *cgroup* est donc de créer un groupe.
|
||
|
||
Pour ce faire, il suffit de créer un nouveau dossier dans un groupe existant,
|
||
par exemple la racine.
|
||
|
||
On commence par se rendre à la racine :
|
||
|
||
<div lang="en-US">
|
||
```bash
|
||
cd /sys/fs/cgroup/freezer/ # v1
|
||
cd /sys/fs/cgroup/ # v2
|
||
```
|
||
</div>
|
||
|
||
Puis on crée notre groupe :
|
||
|
||
<div lang="en-US">
|
||
```bash
|
||
mkdir virli
|
||
ls virli/
|
||
```
|
||
</div>
|
||
|
||
Nous avons maintenant un nouveau groupe de processus `virli` dans le *cgroup*
|
||
*Freezer*. Comme il s'agit d'une hiérarchie, le groupe `virli` hérite des
|
||
propriétés de son (ses) père(s).
|
||
|
||
|
||
### Sélection de contrôleur (v2 seulement)
|
||
|
||
Du fait de l'unification de tous les sous-systèmes, si vous utilisez la seconde
|
||
version, vous allez devoir activer le ou les contrôleurs dont vous avez besoin
|
||
(tandis que dans la première version, on se rendait dans l'arborescence du
|
||
sous-système que l'on voulait).
|
||
|
||
Pour activer le contrôleur *memory*, nous utilisons la commande suivante à la
|
||
racine :
|
||
|
||
<div lang="en-US">
|
||
```bash
|
||
echo "+memory" > cgroup.subtree_control
|
||
```
|
||
</div>
|
||
|
||
::::: {.warning}
|
||
Si vous obtenez l'erreur `No such file or directory`, c'est sans doute que vous
|
||
avez les `cgroup`s v1 activé quelque part. Vous devriez plutôt utiliser la
|
||
première version, le fait qu'elle soit active empêche l'utilisation de la v2 en
|
||
parallèle.
|
||
:::::
|
||
|
||
On peut voir les contrôleurs actifs en consultant le fichier
|
||
`virli/cgroup.controllers`.
|
||
|
||
Le contrôleur *freezer* est généralement activé par défaut, il n'y a pas besoin
|
||
de l'activer.
|
||
|
||
|
||
### Rattachement de processus
|
||
|
||
Pour le moment, ce nouveau groupe ne contient aucun processus, comme le montre
|
||
le fichier `cgroup.procs` de notre groupe. Ce fichier contient la liste des
|
||
processus rattachés à notre *cgroup*.
|
||
|
||
Ouvrons un nouveau terminal (c'est lui que l'on va geler), et récupérons son
|
||
PID : `echo $$`.
|
||
|
||
Pour ajouter une tâche à ce groupe, cela se passe de cette manière :
|
||
|
||
<div lang="en-US">
|
||
```bash
|
||
echo $PID > /sys/fs/cgroup/{,freezer/}virli/cgroup.procs
|
||
```
|
||
</div>
|
||
|
||
Il faut ici remplacer `$PID` par le PID du shell que l'on a relevé juste avant.
|
||
|
||
::::: {.question}
|
||
|
||
#### Ne devrait-on pas utiliser `>>` pour ajouter le processus au fichier ? {-}
|
||
|
||
Malgré l'utilisation de la redirection `>` (et non `>>`), il s'agit bel et bien
|
||
d'un ajout au fichier et non d'un écrasement. Il faut garder en tête que le
|
||
système de fichier est entièrement simulé et que certains comportements sont
|
||
adaptés.
|
||
|
||
:::::
|
||
|
||
En validant cette commande, nous avons déplacé le processus dans ce groupe, il
|
||
n'est alors plus dans aucun autre groupe (pour ce *cgroup*, il ne bouge pas
|
||
dans les autres *cgroup*s de la v1).
|
||
|
||
::::: {.question}
|
||
|
||
#### Où sont placés les nouveaux processus ? {-}
|
||
|
||
Les nouveaux processus héritent des groupes de leur père.
|
||
|
||
Si vous lancez un `top` dans votre nouveau terminal, son PID sera présent dans
|
||
le fichier `cgroup.procs`.
|
||
|
||
:::::
|
||
|
||
|
||
### Consultation de l'état
|
||
|
||
En affichant le contenu du dossier `virli`, nous pouvions constater que
|
||
celui-ci contenait déjà un certain nombre de fichiers. Certains d'entre eux
|
||
sont en lecture seule et permettent de lire des statistiques instantanées sur
|
||
le groupe ; tandis que d'autres sont des propriétés que nous pouvons modifier.
|
||
|
||
Nous pouvons consulter l'état de gel du groupe en affichant le contenu du
|
||
fichier :
|
||
|
||
<div lang="en-US">
|
||
```bash
|
||
42sh$ cat /sys/fs/cgroup/freezer/virli/freezer.state # v1
|
||
42sh$ cat /sys/fs/cgroup/virli/cgroup.freeze # v2
|
||
```
|
||
</div>
|
||
|
||
Pour plus d'informations sur les différents fichiers présents dans ce *cgroup*,
|
||
consultez
|
||
[la documentation associée](https://www.kernel.org/doc/html/latest/admin-guide/cgroup-v1/freezer-subsystem.html) :\
|
||
<https://www.kernel.org/doc/html/latest/admin-guide/cgroup-v1/freezer-subsystem.html>
|
||
|
||
|
||
### Changement d'état
|
||
|
||
Faisons exécuter à notre interpréteur une commande pour voir effectivement
|
||
l'exécution s'arrêter. Si vous manquez d'inspiration, utilisez :
|
||
|
||
<div lang="en-US">
|
||
```bash
|
||
for i in $(seq 9999); do echo -n $i; sleep .1; echo -n " - "; sleep .1; done
|
||
```
|
||
</div>
|
||
|
||
Maintenant, nous allons donner l'ordre au noyau de ne plus allouer de temps de
|
||
calcul à notre shell et ses fils :
|
||
|
||
<div lang="en-US">
|
||
```bash
|
||
echo FROZEN > /sys/fs/cgroup/freezer/virli/freezer.state # v1
|
||
echo 1 > /sys/fs/cgroup/virli/cgroup.freeze # v2
|
||
```
|
||
</div>
|
||
|
||
À cet instant, vous devriez voir votre compteur s'arrêter. Pour reprendre
|
||
l'exécution :
|
||
|
||
<div lang="en-US">
|
||
```bash
|
||
echo THAWED > /sys/fs/cgroup/freezer/virli/freezer.state # v1
|
||
echo 0 > /sys/fs/cgroup/virli/cgroup.freeze # v2
|
||
```
|
||
</div>
|
||
|
||
|
||
::::: {.exercice}
|
||
|
||
### Script de monitoring {- #script-monitoring}
|
||
|
||
À nous maintenant de concevoir un script de collecte de statistiques issues des
|
||
*cgroup*s, similaire à `telegraf`.
|
||
|
||
Dans un premier temps, commençons par afficher dans la console, la quantité de
|
||
mémoire utilisée par le groupe monitoré.
|
||
|
||
::::: {.code}
|
||
|
||
Vous pouvez utiliser un programme comme `memhog`[^memhog] pour remplir rapidement votre
|
||
mémoire.
|
||
|
||
[^memhog]: Ce programme fait partie du paquet `numactl`, mais vous trouverez une
|
||
version modifiée plus adaptée à nos tests sur
|
||
<https://nemunai.re/post/slow-memhog>.
|
||
|
||
:::::
|
||
|
||
<div lang="en-US">
|
||
```
|
||
42sh# ./monitor group_name memhog 500
|
||
~~~ 13595 ~~~ Current memory usage: 75194368
|
||
~~~ 13595 ~~~ Current memory usage: 150290432
|
||
~~~ 13595 ~~~ Current memory usage: 223690752
|
||
~~~ 13595 ~~~ Current memory usage: 296828928
|
||
~~~ 13595 ~~~ Current memory usage: 368001024
|
||
~~~ 13595 ~~~ Current memory usage: 438517760
|
||
~~~ 13595 ~~~ Current memory usage: 480329728
|
||
~~~ 13595 ~~~ Current memory usage: 155648
|
||
```
|
||
</div>
|
||
|
||
Le modèle de sortie standard de votre script `monitor` n'a pas d'importance, il
|
||
doit cependant être possible d'y trouver des statistiques intéressantes, dont
|
||
la quantité de mémoire utilisée. Ici nous affichons au début le PID du
|
||
processus, ce qui peut simplifier le débogage du script.\
|
||
|
||
Il s'utilise de la manière suivante :
|
||
|
||
<div lang="en-US">
|
||
```bash
|
||
./monitor group_name prog [args [...]]
|
||
```
|
||
</div>
|
||
|
||
Où :
|
||
|
||
- `group_name` correspond au nom du/des *cgroup*(s) à créer/rejoindre.
|
||
- `prog [args [...]]` est la commande que l'on souhaite monitorer, à exécuter
|
||
dans le *cgroup*.
|
||
|
||
::::: {.warning}
|
||
Si vous n'avez pas le *cgroup* *memory*, il est possible qu'il ne soit pas
|
||
activé par défaut par votre système. Si vous êtes dans ce cas, essayez
|
||
d'ajouter `cgroup_enable=memory` à la ligne de commande de votre noyau.
|
||
:::::
|
||
|
||
Gardez ce script dans un coin, nous allons le compléter dans les sections
|
||
suivantes.
|
||
|
||
:::::
|
||
|
||
### Fixer des limites {#Fixer-des-limites}
|
||
|
||
Au-delà de la simple consultation, les *cgroup*s peuvent servir à limiter la
|
||
quantité de ressources mises à disposition à un groupe de processus.
|
||
|
||
Pour définir une limite, nous allons écrire la valeur dans le fichier
|
||
correspondant à une valeur limite, comme par exemple
|
||
`memory.max_usage_in_bytes` (v1) ou `memory.max` (v2), qui limite le nombre
|
||
d'octets que notre groupe de processus va pouvoir allouer au maximum :
|
||
|
||
<div lang="en-US">
|
||
```
|
||
# cgroup v1
|
||
42sh$ cat /sys/fs/cgroup/memory/virli/memory.max_usage_in_bytes
|
||
0
|
||
# 0 = Aucune limite
|
||
42sh$ echo 4M > /sys/fs/cgroup/memory/virli/memory.max_usage_in_bytes
|
||
# Maintenant, la limite est à 4MB, vérifions...
|
||
42sh$ cat /sys/fs/cgroup/memory/virli/memory.max_usage_in_bytes
|
||
4194304
|
||
```
|
||
</div>
|
||
|
||
<div lang="en-US">
|
||
```
|
||
# cgroup v2
|
||
42sh$ cat /sys/fs/cgroup/virli/memory.max
|
||
max
|
||
# max = Aucune limite
|
||
42sh$ echo 4M > /sys/fs/cgroup/virli/memory.max
|
||
# Maintenant, la limite est à 4MB, vérifions...
|
||
42sh$ cat /sys/fs/cgroup/virli/memory.max
|
||
4194304
|
||
```
|
||
</div>
|
||
|
||
Chaque *cgroup*s définit de nombreux indicateurs et possède de nombreux
|
||
limiteurs, n'hésitez pas à consulter la documentation associée à chaque
|
||
*cgroup*.
|
||
|
||
::::: {.exercice}
|
||
|
||
Mettez à jour votre script de monitoring pour prendre en compte les
|
||
limites que vous avez définies :
|
||
|
||
<div lang="en-US">
|
||
```
|
||
42sh# mkdir /sys/fs/cgroup...
|
||
42sh# echo 512M > /sys/fs/cgroup.../memory.max_usage_in_bytes
|
||
42sh# ./monitor group_name memhog 500
|
||
~~~ 13595 ~~~ Current memory usage: 75194368/550502400 (13%)
|
||
~~~ 13595 ~~~ Current memory usage: 150290432/550502400 (27%)
|
||
~~~ 13595 ~~~ Current memory usage: 223690752/550502400 (40%)
|
||
~~~ 13595 ~~~ Current memory usage: 296828928/550502400 (53%)
|
||
~~~ 13595 ~~~ Current memory usage: 368001024/550502400 (66%)
|
||
~~~ 13595 ~~~ Current memory usage: 438517760/550502400 (79%)
|
||
~~~ 13595 ~~~ Current memory usage: 480329728/550502400 (87%)
|
||
~~~ 13595 ~~~ Current memory usage: 155648/550502400 (0%)
|
||
```
|
||
</div>
|
||
|
||
:::::
|
||
|
||
|
||
### Pour aller plus loin {-}
|
||
|
||
Pour tout connaître en détail, [la série d'articles de Neil Brown sur les
|
||
Control groups](https://lwn.net/Articles/604609/)[^lwncgroups] est excellente !
|
||
Plus [cet article sur la version 2](https://lwn.net/Articles/679786/)[^lwncgroupsv2].
|
||
|
||
[^lwncgroups]: <https://lwn.net/Articles/604609/>
|
||
[^lwncgroupsv2]: <https://lwn.net/Articles/679786/>
|