virli/tutorial/3/cgroups.md

13 KiB

Les cgroups

Les cgroups (pour Control Groups) 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 (en 2008 !), les cgroups 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) : limites et statistiques de bande passante sur les disques ;
  • cpu : cycles CPU minimums garantis ;
  • cpuacct (inclus dans cpu dans la v2) : statistiques du temps CPU utilisé ;
  • cpuset : 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 : règles de contrôle de création (mknod) et d'accès aux périphériques ;
  • freezer : pour suspendre et reprendre l'exécution d'un groupe de tâches ;
  • hugetlb : statistiques et limitation de l'usage de la fonctionnalité HugeTLB (permettant d'obtenir des pages mémoires plus grandes que 4 kB) ;
  • memory : statistiques et limitation d'usage de la mémoire vive et de la swap ;
  • net_cls (v1 seulement) : 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) : surcharge la valeur de l'option de priorité SO_PRIORITY, ordonnant la file d'attente des paquets sortants ;
  • pids : 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 cgroups.

  • 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 cgroups v1.

  • Votre dossier /sys/fs/cgroup est vide ou inexistant, vous pouvez choisir d'utiliser la version de votre choix :

    Pour utiliser la v1 :

    ```bash mkdir /sys/fs/cgroup/freezer/ mount -t cgroup -o freezer none /sys/fs/cgroup/freezer/ ```

    Pour utiliser la v2 :

    ```bash mkdir /sys/fs/cgroup/ mount -t cgroup2 none /sys/fs/cgroup/ ```

Avant d'aller plus loin, notez que les exemples seront donnés pour les deux versions des cgroups à 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 cgroups 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 :

```bash cd /sys/fs/cgroup/freezer/ # v1 cd /sys/fs/cgroup/ # v2 ```

Puis on crée notre groupe :

```bash mkdir virli ls virli/ ```

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 :

```bash echo "+memory" > cgroup.subtree_control ```

::::: {.warning} Si vous obtenez l'erreur No such file or directory, c'est sans doute que vous avez les cgroups 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 :

```bash echo $PID > /sys/fs/cgroup/{,freezer/}virli/cgroup.procs ```

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 cgroups 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 :

```bash 42sh$ cat /sys/fs/cgroup/freezer/virli/freezer.state # v1 42sh$ cat /sys/fs/cgroup/virli/cgroup.freeze # v2 ```

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

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 :

```bash for i in $(seq 9999); do echo -n $i; sleep .1; echo -n " - "; sleep .1; done ```

Maintenant, nous allons donner l'ordre au noyau de ne plus allouer de temps de calcul à notre shell et ses fils :

```bash echo FROZEN > /sys/fs/cgroup/freezer/virli/freezer.state # v1 echo 1 > /sys/fs/cgroup/virli/cgroup.freeze # v2 ```

À cet instant, vous devriez voir votre compteur s'arrêter. Pour reprendre l'exécution :

```bash echo THAWED > /sys/fs/cgroup/freezer/virli/freezer.state # v1 echo 0 > /sys/fs/cgroup/virli/cgroup.freeze # v2 ```

::::: {.exercice}

Script de monitoring {- #script-monitoring}

À nous maintenant de concevoir un script de collecte de statistiques issues des cgroups, 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 memhog1 pour remplir rapidement votre mémoire.

:::::

``` 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 ```

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 :

```bash ./monitor group_name prog [args [...]] ```

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

Au-delà de la simple consultation, les cgroups 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 :

``` # 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 ```
``` # 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 ```

Chaque cgroups 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 :

``` 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%) ```

:::::

Pour aller plus loin {-}

Pour tout connaître en détail, la série d'articles de Neil Brown sur les Control groups2 est excellente ! Plus cet article sur la version 23.


  1. 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. ↩︎

  2. https://lwn.net/Articles/604609/ ↩︎

  3. https://lwn.net/Articles/679786/ ↩︎