cgroups v2 limites de ressources avec systemd

11 min de lecture - 3 juin 2026

hero section cover
Table des matières
  • Limites de ressources cgroups v2 avec systemd
  • Activation de cgroups v2
  • Comment systemd organise les cgroups
  • Limites du processeur
  • Limites de mémoire avec cgroups v2
  • Limites d'E/S
  • Isolation multi-locataires avec des tranches
  • Surveillance avec systemd-cgtop et PSI
Partager

Fixer des limites de CPU, de mémoire et d'E/S avec cgroups v2 et systemd. Configuration pratique pour les hôtes Linux multi-locataires, avec la surveillance PSI et l'isolation des tranches.

Limites de ressources cgroups v2 avec systemd

cgroups v2 est le cadre de contrôle unifié des ressources du noyau Linux. Il remplace la hiérarchie fragmentée de la version 1 par une arborescence unique qui gère de manière cohérente le CPU, la mémoire et les E/S, et sous-tend l'isolation des conteneurs dans Docker, Kubernetes et systemd. Cet article explique comment activer cgroups v2, définir des limites via systemd et l'appliquer à des scénarios réels d'hébergement multi-locataires.

Activation de cgroups v2

Les distributions modernes sont livrées avec cgroups v2 activé par défaut : Ubuntu 21.10+, Debian 11+, Fedora 31+ et RHEL/Rocky 9+. Les systèmes plus anciens peuvent utiliser une hiérarchie hybride ou encore utiliser v1 par défaut. Vérifiez avec :

stat -fc %T /sys/fs/cgroup/

La sortie de cgroup2fs confirme que la version 2 est active. tmpfs signifie généralement v1.

Pour faire passer un système hybride en v2 pure, modifiez /etc/default/grub et ajoutez ce qui suit à GRUB_CMDLINE_LINUX_DEFAULT:

systemd.unified_cgroup_hierarchy=1 cgroup_no_v1=all

Puis régénérez GRUB et redémarrez :

sudo update-grub
sudo reboot

En production, utilisez le noyau 5.2 ou une version plus récente afin de bénéficier du cgroup freezer pour la v2, et systemd 244+ pour une cpuset . Sur Rocky Linux 8 et RHEL 8, vous devrez peut-être également activer explicitement la comptabilité en ajoutant ces lignes à /etc/systemd/system.conf:

DefaultCPUAccounting=yes
DefaultMemoryAccounting=yes
DefaultIOAccounting=yes

Rechargez avec sudo systemctl daemon-reexec. Après le redémarrage, vérifiez quels contrôleurs sont disponibles :

cat /sys/fs/cgroup/cgroup.controllers

Vous devriez voir cpu, memory, io, et pids répertoriés. Ces contrôleurs ne sont pas activés par défaut pour les cgroups enfants. Pour les activer, écrivez dans le fichier de contrôle de la sous-arborescence racine :

echo "+cpu +memory +io" | sudo tee /sys/fs/cgroup/cgroup.subtree_control

Pour une présentation détaillée des différences internes entre la v2 et la v1, la conférence de Michael Kerrisk à NDC TechTown est la meilleure ressource disponible :

Comment systemd organise les cgroups

systemd crée un cgroup pour chaque service qu'il démarre, nommé d'après l'unité. nginx.service obtient /sys/fs/cgroup/system.slice/nginx.service/, et chaque processus qu'il génère réside au sein de ce cgroup. Trois types d'unités correspondent directement à la hiérarchie :

Type d'unitéRôleDescription
.sliceNœud interneRegroupe les services associés et définit les limites partagées
.serviceNœud terminalGère les processus lancés par systemd
.scopeNœud feuilleSuivi des processus lancés en externe (charges utiles des conteneurs, sessions de connexion)

Quatre tranches par défaut sont fournies prêtes à l'emploi : -.slice (root), system.slice, user.sliceet machine.slice. Toute limite appliquée à une tranche s'applique automatiquement à tous les services qu'elle contient.

Une règle de la v2 à retenir : les processus ne peuvent exister que dans des nœuds feuilles. Un cgroup contenant des cgroups enfants ne peut pas héberger directement de processus, c'est pourquoi systemd ne place jamais de services dans le tronc d'une tranche.

Définissez toujours les limites via systemd plutôt que d'écrire /sys/fs/cgroup/ directement. Les modifications manuelles ne persistent pas après les redémarrages et entrent en conflit avec la propriété exclusive de la hiérarchie par systemd. Utilisez systemctl set-property pour les modifications ponctuelles et les unités temporaires (systemctl edit nginx.service) pour les modifications permanentes.

Limites du processeur

cgroups v2 vous offre deux contrôles du CPU : une limite stricte (cpu.max, accessible sous le nom CPUQuota dans systemd) et un poids proportionnel (cpu.weight / CPUWeight).

CPUQuota est un plafond absolu. CPUQuota=50% autorise la moitié d'un cœur ; CPUQuota=200% autorise l'utilisation de deux cœurs complets. Le service est limité s'il tente d'aller au-delà, quel que soit le niveau d'inactivité du reste du processeur.

CPUWeight n'a d'importance qu'en cas de contention. La plage va de 1 à 10 000, la valeur par défaut étant 100. Trois services avec des poids de 150, 100 et 50 reçoivent respectivement environ 50 %, 33 % et 17 % du temps CPU lorsqu'ils le sollicitent tous en même temps. Lorsque le CPU est inactif, les poids n'imposent aucune contrainte.

Pour les charges de travail sensibles à la latence, affectez les processus à des cœurs spécifiques avec AllowedCPUs=. Cela réduit les changements de contexte et maintient le cache par cœur actif :

[Service]
CPUQuota=200%
CPUWeight=150
AllowedCPUs=0-3

Utilisez un quota fixe lorsque vous avez besoin d'un coût prévisible (facturation multi-locataires, isolation des voisins bruyants). Utilisez des poids lorsque vous souhaitez une utilisation maximale du matériel et que vous avez simplement besoin d'un ordre de priorité pendant les pics.

Limites de mémoire avec cgroups v2

La mémoire comporte deux niveaux : memory.high (logicielle, limitation) et memory.max (dur, OOM). Pour plus d'informations sur la mémoire swap, la récupération de pages et le mécanisme OOM killer du noyau, consultez notre article complémentaire sur la gestion de la mémoire sous Linux.

Réglez memory.high environ 10 à 20 % en dessous memory.max. Le noyau commence à récupérer des pages et à limiter les allocations dès que memory.high est dépassé, ce qui permet généralement à la charge de travail de se rétablir avant que le OOM killer ne se déclenche. Si l'utilisation atteint memory.max, le noyau tue les processus du cgroup.

Une configuration typique :

[Service]
MemoryHigh=400M
MemoryMax=512M
MemorySwapMax=0

MemorySwapMax=0 désactive la page d'échange pour ce cgroup. Cela vaut la peine d'être fait pour les charges de travail sensibles à la latence (bases de données, streaming en temps réel) où les E/S de la page d'échange feraient chuter la latence de queue.

Pour les pools de travailleurs où le fait de laisser des processus orphelins derrière eux corromprait l'état partagé, écrivez 1 dans le fichier memory.oom.group . Lorsqu'un processus est tué pour cause de mémoire insuffisante (OOM), le noyau tue tous les processus du cgroup en même temps.

Consultez memory.events pour voir à quelle fréquence un service a été bridé ou tué par OOM :

cat /sys/fs/cgroup/system.slice/nginx.service/memory.events

Le high et oom_kill vous indiquent si vos limites sont correctement dimensionnées. Des valeurs non nulles persistantes signifient que la charge de travail a besoin de plus de marge.

Limites d'E/S

Le contrôleur d'E/S présente la même conception à deux modes : des limites absolues via io.max et un partage proportionnel via io.weight.

Les limites s'appliquent par périphérique bloc, identifié par des numéros majeur:mineur. Vous pouvez les trouver avec lsblk -o NAME,MAJ:MIN. Une configuration systemd typique :

[Service]
IOReadBandwidthMax=/dev/sda 50M
IOWriteBandwidthMax=/dev/sda 30M
IOReadIOPSMax=/dev/sda 1000
IOWriteIOPSMax=/dev/sda 500

io.weight fonctionne comme cpu.weight: plage de 1 à 10 000, valeur par défaut 100. Attribuer 500 à un service destiné aux clients et 50 à une sauvegarde nocturne empêche la sauvegarde de saturer le disque pendant les heures de pointe, tout en lui permettant d'utiliser toute la bande passante lorsque rien d'autre n'en a besoin.

Les limites d'E/S ne s'appliquent que lorsque vous ciblez le bon périphérique. Le noyau suit les E/S par périphérique bloc, donc une limite sur /dev/sda n'a aucun effet sur les E/S destinées à /dev/nvme0n1. Sur les hôtes équipés de plusieurs disques, définissez des limites par périphérique.

Isolation multi-locataires avec des tranches

Pour les environnements partagés, définissez une tranche par locataire. Créez /etc/systemd/system/tenant-a.slice:

[Slice]
CPUQuota=200%
CPUWeight=150
MemoryHigh=3584M
MemoryMax=4096M
MemorySwapMax=0
IOReadBandwidthMax=/dev/sda 200M
TasksMax=512

TasksMax=512 limite le nombre total de processus et de threads, ce qui empêche une bombe à fourche dans un locataire de mettre l'hôte hors service. Placez les services du locataire dans cette tranche (via Slice=tenant-a.slice dans leurs fichiers d'unité) et ils héritent automatiquement de tout.

Ce modèle permet également de séparer les tâches d'arrière-plan bruyantes des services destinés aux utilisateurs. Placez les sauvegardes, la rotation des journaux et les tâches par lots dans une background.slice avec une faible CPUWeight et io.weight . Ils disposent de toutes les ressources lorsque le système est inactif et s’effacent lorsque le trafic de production arrive.

Pour les environnements d'exécution de conteneurs comme Docker et Podman, ajoutez Delegate=yes à leurs fichiers d'unité systemd. Cela leur permet de gérer leurs propres sous-cgroups sans droits root, et les limites définies sur la tranche parente s'appliquent toujours à tout ce qui se trouve en dessous.

Surveillance avec systemd-cgtop et PSI

Pour obtenir une vue en temps réel de type « top » de l'utilisation du CPU, de la mémoire et des E/S par cgroup, exécutez :

systemd-cgtop

Pour la hiérarchie statique et savoir où se trouvent les processus, utilisez systemd-cgls.

La fonctionnalité v2 la plus utile pour la surveillance en production est la Pressure Stall Information (PSI). La PSI indique le pourcentage de temps pendant lequel les tâches d’un cgroup ont été bloquées en attente d’une ressource, présenté dans trois fichiers par cgroup :

cat /sys/fs/cgroup/tenant-a.slice/cpu.pressure
cat /sys/fs/cgroup/tenant-a.slice/memory.pressure
cat /sys/fs/cgroup/tenant-a.slice/io.pressure

Un CPU à 100 % d'utilisation avec 0 % de pression est en bon état. Chaque tâche qui a besoin de CPU l'obtient. Le même CPU à 80 % d'utilisation mais avec 30 % de pression signifie que les tâches font la queue pour obtenir du temps d'exécution. Alertez sur le PSI, pas sur l'utilisation : il détecte les conflits que les métriques d'utilisation manquent complètement.

Ajustez les limites en temps réel sans redémarrer quoi que ce soit :

sudo systemctl set-property tenant-a.slice MemoryMax=6144M

La modification s'applique immédiatement et persiste après les redémarrages. Associé aux alertes basées sur le PSI, cela vous permet de réagir aux variations de charge avant qu'elles ne se transforment en interruptions OOM ou en latence incontrôlable.

Si vous exécutez des charges de travail multi-locataires à haute densité et avez besoin d'un hôte disposant de la marge nécessaire pour appliquer ces politiques de manière optimale, nos serveurs dédiés sont conçus pour cela.

Blog

À l'honneur cette semaine

Plus d'articles
Pourquoi il est important d'avoir un VPS puissant et sans compteur

Pourquoi il est important d'avoir un VPS puissant et sans compteur

Un VPS sans compteur offre une bande passante forfaitaire à une vitesse de port fixe. En quoi il diffère des forfaits avec compteur, quand il est rentable et ce qu'il faut vérifier avant d'acheter.

7 min de lecture - 9 mai 2025

Gestion de la mémoire sous Linux : Swap, OOM Killer & Cgroups

12 min de lecture - 31 mai 2026

Plus d'articles