Comprendre les Cgroups Linux
Les groupes de contrôle (Cgroups) sont l'une des fonctionnalités du noyau Linux qui rendent les conteneurs possibles. Dans cet article, nous allons découvrir ce que sont les groupes de contrôle Linux et leur fonctionnement à travers des explications simples, des illustrations et des exemples.
Qu'est-ce qu'un cgroup
Le groupe de contrôle ou groupe de contrôle Linux est un mécanisme qui peut être utilisé pour organiser hiérarchiquement les processus et contrôler la quantité de ressources système utilisées dans cette hiérarchie de processus (processeur, RAM, vitesse de lecture/écriture sur les périphériques, etc.).

Les contrôles des ressources sont réalisés au moyen de mesures spécifiques le Edge Controllers Cela devrait être activé. Voici un aperçu de certains contrôleurs, associés aux ressources système qu'ils contrôlent :

Implémentation de Cgroup
L'implémentation de Cgroup dans le noyau Linux peut être divisée en deux parties :
-
code de base: regroupement hiérarchique des processus et autres éléments non implémentés dans les contrôleurs
-
le Edge Controllers: des sous-systèmes distincts pour chaque type de ressource (CPU, RAM, etc.), mettant en œuvre le suivi des ressources et les limites le long de la hiérarchie
Versions de Cgroup
Il existe actuellement deux versions de cgroup : la version 1 et la version 2. La version 1 a été initialement publiée sous Linux 2.6.24 et au fil du temps, après des problèmes liés aux incohérences entre les contrôleurs et à la gestion complexe de la hiérarchie cgroup, la version 2 a été créée et officiellement publiée sous Linux 4.5 pour résoudre ce problème.
La version 2 de Cgroup est destinée à remplacer la version 1, mais pour l'instant, cette dernière existe toujours et ne sera probablement pas supprimée pour des raisons de compatibilité. Les contrôleurs disponibles dans la version 1 sont progressivement portés vers la version 1. Les contrôleurs manquants dans la version 2 peuvent toujours être utilisés jusqu'à la version 2, tandis que les autres contrôleurs de la version 1 sont toujours utilisés.
Pseudo-système de fichiers Cgroup (cgroupfs)
Les fonctionnalités de cgroup sont accessibles aux utilisateurs via le pseudo-système de fichiers cgroupfs, monté par défaut dans « /sys/fs/cgroup », mais il peut être monté ailleurs. La version de cgroup utilisée par défaut dans les distributions Linux récentes est la version 2.
$ mount | grep cgroup
cgroup2 on /sys/fs/cgroup type cgroup2 (rw,nosuid,nodev,noexec,relatime,nsdelegate,memory_recursiveprot)
Si vous vous demandez comment monter le cgroupfs quelque part, voici la commande :
mount -t cgroup2 none $MOUNT_POINT
Grâce au système de fichiers cgroupfs, nous pouvons activer/désactiver les contrôleurs de cgroups, créer/supprimer des cgroups, et ajouter des processus à ces derniers pour contrôler l'utilisation de ressources spécifiques. Nous verrons cela en détail dans la section Exemples de manipulation de Cgroup .
Hiérarchie des groupes C
Les cgroups sont organisés selon une hiérarchie parent-enfant. Il y a d'abord le cgroup racine, parent de tout cgroup présent sur le système. Il est fourni nativement par le cgroupfs, monté par défaut dans « /sys/fs/cgroup ».
$ ls /sys/fs/cgroup/
cgroup.controllers cgroup.stat cpu.stat dev-mqueue.mount io.pressure memory.pressure sys-fs-fuse-connections.mount system.slice
cgroup.max.depth cgroup.subtree_control cpuset.cpus.effective init.scope io.prio.class memory.stat sys-kernel-config.mount user.slice
cgroup.max.descendants cgroup.threads cpuset.mems.effective io.cost.model io.stat misc.capacity sys-kernel-debug.mount
cgroup.procs cpu.pressure dev-hugepages.mount io.cost.qos memory.numa_stat proc-sys-fs-binfmt_misc.mount sys-kernel-tracing.mount
La création d'un répertoire dans le groupe de contrôle racine crée un groupe de contrôle enfant, qui peut à son tour avoir des enfants, etc. La distribution des ressources au sein d'une hiérarchie de groupes de contrôle s'effectue de haut en bas (des parents aux enfants).
Seuls les contrôleurs activés dans un groupe de contrôle parent peuvent être configurés dans un groupe de contrôle enfant (activés, désactivés, utilisés pour limiter les ressources, etc.). Cela permet la distribution des ressources aux enfants. Pour en savoir plus sur les schémas de distribution des ressources parent-enfant des groupes de contrôle, consultez la section modèles de distribution des ressources cgroup
Fichiers d'interface Cgroup
Les fichiers du cgroupfs sont des fichiers d'interface de cgroup. Ce sont des fichiers en lecture seule et en écriture qui permettent de configurer le cgroup ou d'obtenir des informations de configuration et des statistiques sur le cgroup et ses contrôleurs.
Par exemple, pour un cgroup donné, nous pouvons obtenir le nombre de cgroups descendants (enfants, petits-enfants, etc.) via son fichier en lecture seule « cgroup.stat ». De même, le fichier d'interface « cgroup.max.descendants » permet de définir le nombre maximal de cgroups descendants.
Les fichiers « cgroup.* » sont fichiers d'interface de base cgroup et les autres sont fichiers d'interface spécifiques aux contrôleursLes fichiers « cpu.* », par exemple, sont des fichiers d'interface pour le contrôleur CPU. Les répertoires représentent les groupes de contrôle enfants.
Utilitaires Cgroup
Outre la manipulation directe des cgroups dans le répertoire cgroupfs, des outils en ligne de commande permettent de configurer les cgroups et le comportement des contrôleurs associés. Ces outils sont fournis par le paquet « cgroup-tools ». En voici quelques exemples. Suivez les liens pour accéder au manuel complet des commandes et à des exemples d'utilisation :
-
cgcréer - Créez un ou plusieurs cgroups en définissant un ou plusieurs couples « Contrôleurs:Chemin » pour chacun d'eux, via l'option « -g ». « Chemin » représente le chemin d'accès au répertoire du cgroupfs. « Contrôleurs » représente la liste des contrôleurs disponibles dans les hiérarchies montées (les cgroupfs disponibles sur le système) où le cgroup sera créé, séparés par une virgule. Un caractère générique peut être utilisé pour indiquer tous les contrôleurs disponibles.
-
cgexec - exécuter un programme dans un cgroup spécifique au sein de contrôleurs choisis
-
cgset - définir des paramètres pour des cgroups spécifiques
-
cgget - afficher les paramètres de cgroups spécifiques
-
cgdelete - supprimer les cgroups
Exemples de manipulation de Cgroup
Comment créer un cgroup
Créez simplement un répertoire à l'intérieur du cgroupfs :
$ cd /sys/fs/cgroup/
$ sudo mkdir mycgroup
ou utilisez l'utilitaire « cgcreate » comme suit :
# Syntax
# cgcreate -g Controllers:Path
# Create a cgroup called mycgroup in hierarchies
# where the cpu and memory controllers are available
$ cgcreate -g cpu,memory:mycgroup
La routine shell fichiers d'interface cgroup sont automatiquement créés pour le nouveau cgroup. Les fichiers d'interface des contrôleurs actifs de ce cgroup sont également visibles :
- fichiers cpu.* - peut être utilisé pour suivre et contrôler la consommation des ressources CPU des processus appartenant au cgroup et à ses descendants
- fichiers io.* - peut être utilisé pour suivre et contrôler la vitesse d'E/S de lecture et d'écriture sur des périphériques de bloc spécifiques, pour les processus appartenant au cgroup et à ses descendants
- fichiers memory.* - peut être utilisé pour suivre et contrôler la consommation des ressources mémoire des processus appartenant au cgroup et à ses descendants
- fichiers pids.* - peut être utilisé pour suivre et contrôler le nombre de tâches que les processus appartenant au cgroup et à ses descendants peuvent créer
- fichiers cpuset.* - peut être utilisé pour suivre et contrôler l'utilisation d'un ensemble spécifique de placement de nœuds de CPU et de mémoire pour les processus appartenant au cgroup et à ses descendants
La liste des contrôleurs qui peuvent être utilisés par un groupe c donné est contrôlée par le groupe c parent cgroup.subtree_control fichier. Voici le contenu du répertoire cgroup nouvellement créé :
$ ls /sys/fs/cgroup/mycgroup/
cgroup.controllers cgroup.max.descendants cgroup.type cpu.stat cpuset.cpus io.max memory.current memory.max memory.stat pids.current
cgroup.events cgroup.procs cpu.idle cpu.uclamp.max cpuset.cpus.effective io.pressure memory.events memory.min memory.swap.current pids.events
cgroup.freeze cgroup.stat cpu.max cpu.uclamp.min cpuset.cpus.partition io.prio.class memory.events.local memory.numa_stat memory.swap.events pids.max
cgroup.kill cgroup.subtree_control cpu.max.burst cpu.weight cpuset.mems io.stat memory.high memory.oom.group memory.swap.high
cgroup.max.depth cgroup.threads cpu.pressure cpu.weight.nice cpuset.mems.effective io.weight memory.low memory.pressure memory.swap.max
Les paramètres de contrôle des ressources d'un groupe de contrôle non racine sont ceux de son ancêtre de groupe de contrôle le plus proche s'il n'a pas de configurations spécifiques.
Comment supprimer un cgroup
Assurez-vous que le cgroup ne possède pas de cgroups enfants ni de processus actifs (pas de processus zombies). Ensuite, supprimez simplement le répertoire cgroup du cgroupfs :
$ cd /sys/fs/cgroup
$ sudo rmdir mycgroup
ou utilisez l'utilitaire « cgdelete » comme suit :
# Syntax
# cgdelete -g Controllers:Path
# Delete the cgroup called mycgroup in hierarchies
# where the cpu and memory controllers are available
$ cgdelete -g cpu,memory:mycgroup
Comment répertorier les contrôleurs de groupe de contrôle disponibles
Jetez simplement un œil au fichier « cgroups.controllers » du cgroup :
$ cat /sys/fs/cgroup/mycgroup/cgroup.controllers
cpuset cpu io memory pids
Ce fichier contient les noms des contrôleurs accessibles au groupe de contrôle. Cela signifie que ce dernier peut contrôler les ressources système associées à ces contrôleurs pour les processus qu'il gère.
Cette liste de contrôleurs disponibles pour le cgroup est contrôlée par le fichier « cgroup.subtree_control » du cgroup parent :
$ cat /sys/fs/cgroup/cgroup.subtree_control
cpuset cpu io memory pids
Ajoutons le contrôleur « misc » à ce fichier et voyons l’impact :
# See the available controllers for the parent cgroup
$ cat /sys/fs/cgroup/cgroup.controllers
cpuset cpu io memory hugetlb pids rdma misc
# Add the misc controller into the parent
# cgroup's cgroup.subtree_control file
$ echo "+misc" > /sys/fs/cgroup/cgroup.subtree_control
# That misc controller is now available
# to the child cgroup called mycgroup
$ cat /sys/fs/cgroup/mycgroup/cgroup.controllers
cpuset cpu io memory pids misc
Un fichier « cgroup.subtree_control » vide dans le répertoire d'un groupe de contrôle parent signifie simplement qu'aucun contrôleur n'est disponible pour l'enfant. Dans ce cas, les processus du groupe de contrôle parent, ainsi que ceux de l'enfant et de ses éventuels frères, partageront les ressources système conformément aux paramètres de contrôle des ressources du groupe de contrôle parent.
Comment ajouter des processus dans un cgroup
Pour ajouter un processus existant dans un cgroup, ajoutez simplement son PID au fichier « cgroup.procs » du cgroup :
$ echo 'PID' > /sys/fs/cgroup/mycgroup/cgroup.procs
ou utilisez l'utilitaire « cgexec » pour exécuter un programme à l'intérieur d'un cgroup :
# Syntax
# cgexec -g Controllers:Path Command
# Run bash inside the cgroup called mycgroup (from /sys/fs/cgroup)
# where the cpu and memory controllers are available
$ cgexec -g cpu,memory:mycgroup bash
Comment lister les cgroups d'un processus spécifique
Jetez simplement un œil au fichier « /proc/PID/cgroup » pour ce processus :
# List the cgroups of the process with PID 16461
$ cat /proc/16461/cgroup
0::/mycgroup
Comment afficher et modifier les paramètres des contrôleurs cgroup
Affichez et modifiez directement les fichiers d'interface cgroups (fichiers « cgroups.* » du répertoire cgroup) ou utilisez les utilitaires « cgget » et « cgset » comme suit :
# Syntax
# cgget [-r Param1 -r Param2 ...] CgroupName1 [CgroupName2 ...]
# cgset [-r Param1=Value1 -r Param2=Value2 ...] CgroupName1 [CgroupName2 ...]
# Show all parameters values for the cgroup called mycgroup
$ cgget cgroup
mycgroup:
cpuset.cpus.partition: member
cpuset.cpus.effective: 0-1
cpuset.mems:
cpuset.mems.effective: 0
cpuset.cpus:
cpu.weight: 100
cpu.stat: usage_usec 21205578248
user_usec 10674775420
system_usec 10530802828
nr_periods 0
nr_throttled 0
throttled_usec 0
(...)
# Show the io.max parameter value for the cgroup called mycgroup
$ cgget -r io.max mycgroup
mycgroup:
io.max:
# Set the value of the io.max parameter for the cgroup called mycgroup
$ cgset -r io.max="8:0 rbps=max wbps=100000000 riops=max wiops=max" mycgroup
# Verify
$ cgget -r io.max mycgroup
mycgroup:
io.max: 8:0 rbps=max wbps=100000000 riops=max wiops=max
Exemple pratique : limiter l'utilisation de la mémoire par les processus
Voyons maintenant un exemple pratique de limitation de l'utilisation de la mémoire par les processus avec un cgroup. Voici le script « memory_allocator.sh » qui alloue 10 Mo de mémoire par seconde jusqu'à 200 Mo :
#!/bin/bash
# Directory for memory allocation
ALLOC_DIR="/dev/shm/mem_alloc_$$"
mkdir -p "$ALLOC_DIR"
# Clean up on exit
trap 'echo "Cleaning up..."; rm -rf "$ALLOC_DIR"' EXIT
# Allocate 10MB every second until 200MB is reached
for i in {1..20}; do
dd if=/dev/zero of="$ALLOC_DIR/block_$i" bs=1M count=10 &>/dev/null
allocated_mb=$((i * 10))
echo "Allocated ${allocated_mb}MB"
sleep 1
done
echo "Total 200MB allocated. Holding for 60 seconds before cleanup."
sleep 60
Nous allons créer un cgroup qui limite l'utilisation de la mémoire à 100 Mo, puis exécuter le script « memory_allocator.sh » via ce cgroup et voir ce qui se passe.
Créons un nouveau cgroup appelé « mycgroup » dans les hiérarchies où le contrôleur de mémoire est disponible.
# Create the cgroup called mycgroup.
# We only have one cgroup hierarchy in this case
# which is mounted at /sys/fs/cgroup
$ cgcreate -g memory:mycgroup
Listons les paramètres actuels du contrôleur de mémoire :
# Listing memory settings of the newly
# created cgroup called mycgroup
$ cgget mycgroup | grep memory
memory.events: low 0
memory.events.local: low 0
memory.swap.current: 0
memory.swap.max: max
memory.swap.events: high 0
memory.pressure: some avg10=0.00 avg60=0.00 avg300=0.00 total=0
memory.current: 0
memory.stat: anon 0
memory.low: 0
memory.swap.high: max
memory.numa_stat: anon N0=0
memory.min: 0
memory.oom.group: 0
memory.max: max
memory.high: max
Définissons maintenant la limite d’utilisation de la mémoire pour ce groupe de contrôle à 100 Mo :
$ cgset -r memory.max=100000000 mycgroup
# Verify
$ cgget -r memory.max mycgroup
mycgroup:
memory.max: 99999744
Très bien. Exécutons maintenant le script « memory_allocator.sh » sur ce groupe de contrôle et voyons ce qui se passe :
$ cgexec -g memory:mycgroup ./memory_allocator.sh
Allocated 10MB
Allocated 20MB
Allocated 30MB
Allocated 40MB
Allocated 50MB
Allocated 60MB
Allocated 70MB
Allocated 80MB
Allocated 90MB
Killed
Ah ! Il semble que le script ait été arrêté juste après l'allocation de 100 Mo de RAM, car le groupe de contrôle limite son utilisation de la mémoire à 100 Mo.
Voici le journal du noyau qui indique que le processus « memory_allocator.sh » a été tué par le Kernel Out-Of-Memory (OOM) Killer car le groupe de mémoire du processus était à court de mémoire :
$ dmesg
(...)
[43362.458500] Tasks state (memory values in pages):
[43362.458505] [ pid ] uid tgid total_vm rss pgtables_bytes swapents oom_score_adj name
[43362.458514] [ 2484] 0 2484 1816 701 57344 0 0 dd
[43362.458541] oom-kill:constraint=CONSTRAINT_MEMCG,nodemask=(null),cpuset=mycgroup,mems_allowed=0,oom_memcg=/mycgroup,task_memcg=/mycgroup,task=dd,pid=2484,uid=0
[43362.458611] Memory cgroup out of memory: Killed process 2484 (dd) total-vm:7264kB, anon-rss:1028kB, file-rss:1776kB, shmem-rss:0kB, UID:0 pgtables:56kB oom_score_adj:0
Voilà. J'espère que vous comprenez mieux les groupes de contrôle Linux maintenant.
Vous souhaitez signaler une erreur ou poser une question ? N'hésitez pas à m'envoyer un e-mail à gmkziz@hackerstack.org. Je serai ravi de répondre.
Si vous aimez mes articles, pensez à vous inscrire à ma newsletter afin de recevoir les derniers articles dès qu'ils sont disponibles.
Prenez soin de vous, continuez à apprendre et à bientôt pour le prochain post 🚀