7.9 KiB
Le namespace cgroup
Introduction
L'espace de noms cgroup, introduit dans Linux 4.6, permet de virtualiser la
vue d'un processus sur l'arborescence des Control Groups. Au sein d'un
namespace cgroup, la racine vue par le processus correspond en fait à un
sous-groupe de l'arborescence globale.
Pourquoi virtualiser les cgroups ?
Sans le namespace cgroup, un processus dans un conteneur pourrait :
- Voir l'arborescence complète des cgroups du système hôte
- Obtenir des informations sur d'autres conteneurs
- Comprendre la structure organisationnelle du système
Le namespace cgroup permet de :
- Masquer l'arborescence globale des cgroups
- Faciliter la migration de conteneurs (l'arborescence reste cohérente)
- Renforcer l'isolation entre conteneurs
Utilisation pratique
Observer les cgroups sans isolation\
Commençons par observer notre position dans l'arborescence des cgroups :
Cette ligne nous indique que nous nous trouvons dans le cgroup unifié
/user.slice/user-1000.slice/session-3.scope.
Nous pouvons également explorer l'arborescence complète :
S'isoler dans un nouveau namespace cgroup\
Créons maintenant un nouveau namespace cgroup :
Notre processus se voit maintenant à la racine de l'arborescence des cgroups ! En réalité, il se trouve toujours au même endroit dans l'arborescence globale, mais sa vue a été virtualisée.
Si nous regardons le contenu de /sys/fs/cgroup/ :
Nous voyons seulement notre sous-arbre, pas l'arborescence complète du système.
Créer des sous-cgroups\
Dans notre nouveau namespace, nous pouvons créer des sous-groupes :
Ces sous-groupes sont en réalité créés dans notre branche de l'arborescence globale. Depuis l'extérieur du namespace, ils apparaîtraient sous un chemin différent.
Exemple avec Docker
Docker utilise le namespace cgroup pour isoler la vue des cgroups dans
les conteneurs. Observons cela :
indocker# ls /sys/fs/cgroup/ cgroup.controllers cpu.max memory.events cgroup.events cpu.pressure memory.high cgroup.freeze cpu.stat memory.low cgroup.kill cpu.weight memory.max cgroup.max.depth io.max memory.min cgroup.max.descendants io.pressure memory.numa_stat cgroup.procs io.stat memory.oom.group cgroup.stat io.weight memory.pressure cgroup.subtree_control memory.current memory.stat cgroup.threads memory.events.local memory.swap.current cgroup.type ...
</div>
Le conteneur se voit à la racine, bien qu'il soit en réalité placé dans un
sous-groupe dédié de l'arborescence globale.
### Vérifier la position réelle dans l'arborescence
Depuis l'hôte, nous pouvons voir la véritable position d'un conteneur :
<div lang="en-US">
```bash
42sh$ docker inspect mycontainer | grep -A5 CgroupParent
"CgroupParent": "",
"CgroupDriver": "systemd",
42sh$ ps aux | grep containerd-shim
root 12345 /usr/bin/containerd-shim-runc-v2 ...
42sh$ cat /proc/12345/cgroup
0::/system.slice/docker-abc123def456.scope
Le processus du conteneur se trouve dans un sous-groupe bien spécifique, mais
grâce au namespace cgroup, il se perçoit à la racine.
Combinaison avec le namespace mount
Pour que l'isolation soit complète, il faut généralement combiner le namespace
cgroup avec le namespace mount afin de remonter /sys/fs/cgroup :
Sans remonter le cgroup, nous verrions toujours l'arborescence complète via l'ancien point de montage.
Cas d'usage
Migration de conteneurs\
Lors de la migration d'un conteneur avec des outils comme CRIU (Checkpoint/Restore
In Userspace), le namespace cgroup garantit que l'arborescence des cgroups
reste cohérente du point de vue du processus, même si la structure de l'hôte
cible est différente.
Isolation de sécurité\
Sans namespace cgroup, un processus malveillant dans un conteneur pourrait :
- Lire
/sys/fs/cgroup/pour découvrir d'autres conteneurs - Analyser les métriques d'autres processus
- Déduire des informations sur l'architecture du système
Délégation de contrôle\
Le namespace cgroup permet de donner à un utilisateur non-privilégié le
contrôle d'une sous-arborescence de cgroups, sans lui donner accès à
l'arborescence globale.
Exemple en C
Voici un exemple de création d'un namespace cgroup en C :
static int child_func(void *arg) { FILE *fp; char line[256];
printf("=== Dans le namespace cgroup ===\n");
fp = fopen("/proc/self/cgroup", "r");
if (fp == NULL) {
perror("fopen");
return EXIT_FAILURE;
}
while (fgets(line, sizeof(line), fp) != NULL) {
printf(" %s", line);
}
fclose(fp);
return 0;
}
#define STACK_SIZE (1024 * 1024) static char child_stack[STACK_SIZE];
int main(void) { pid_t pid; FILE *fp; char line[256];
printf("=== Avant le namespace cgroup ===\n");
fp = fopen("/proc/self/cgroup", "r");
if (fp != NULL) {
while (fgets(line, sizeof(line), fp) != NULL) {
printf(" %s", line);
}
fclose(fp);
}
printf("\n");
pid = clone(child_func, child_stack + STACK_SIZE,
CLONE_NEWCGROUP | SIGCHLD, NULL);
if (pid == -1) {
perror("clone");
exit(EXIT_FAILURE);
}
waitpid(pid, NULL, 0);
return 0;
}
</div>
::::: {.code}
Pour compiler et exécuter :
<div lang="en-US">
```bash
42sh$ gcc -o cgroup_ns_demo cgroup_ns_demo.c
42sh$ ./cgroup_ns_demo
=== Avant le namespace cgroup ===
0::/user.slice/user-1000.slice/session-3.scope
=== Dans le namespace cgroup ===
0::/
:::::
Aller plus loin {-}
Pour plus d'informations :
cgroup_namespaces(7)- page de manuel- Cgroup Namespace : https://lwn.net/Articles/621006/
- Control Group v2 : Documentation du noyau sur cgroup v2