Le *namespace* `cgroup` {#cgroupns}
-----------------------
### 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* :
```bash
42sh$ cat /proc/self/cgroup
0::/user.slice/user-1000.slice/session-3.scope
```
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 :
```bash
42sh$ ls /sys/fs/cgroup/
cgroup.controllers cpuset.cpus.effective memory.pressure
cgroup.max.depth cpuset.mems.effective sys-fs-fuse-connections.mount
cgroup.max.descendants dev-hugepages.mount sys-kernel-config.mount
cgroup.procs dev-mqueue.mount sys-kernel-debug.mount
cgroup.stat init.scope sys-kernel-tracing.mount
cgroup.subtree_control io.pressure system.slice
cgroup.threads memory.numa_stat user.slice
cpu.pressure memory.stat
```
#### S'isoler dans un nouveau *namespace* `cgroup`\
Créons maintenant un nouveau *namespace* `cgroup` :
```bash
42sh$ unshare --cgroup /bin/bash
incgroupns$ cat /proc/self/cgroup
0::/
```
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/` :
```bash
incgroupns$ ls /sys/fs/cgroup/
cgroup.controllers cgroup.threads io.pressure
cgroup.events cpu.pressure memory.numa_stat
cgroup.freeze cpu.stat memory.pressure
cgroup.max.depth cpuset.cpus memory.stat
cgroup.max.descendants cpuset.cpus.effective
cgroup.procs cpuset.mems
cgroup.stat cpuset.mems.effective
cgroup.subtree_control io.stat
```
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 :
```bash
incgroupns$ mkdir /sys/fs/cgroup/mysubgroup
incgroupns$ ls /sys/fs/cgroup/
cgroup.controllers cgroup.threads mysubgroup
cgroup.events cpu.pressure ...
...
```
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 :
```bash
42sh$ docker run --rm -it debian /bin/bash
indocker# cat /proc/self/cgroup
0::/
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 ...
```
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 :
```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` :
```bash
42sh# unshare --cgroup --mount /bin/bash
incgroupns# mount -t cgroup2 none /sys/fs/cgroup
incgroupns# ls /sys/fs/cgroup/
# Vue limitée à notre sous-arbre
```
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 :
```c
#define _GNU_SOURCE
#include
#include
#include
#include
#include
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;
}
```
::::: {.code}
Pour compiler et exécuter :
```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](https://www.kernel.org/doc/html/latest/admin-guide/cgroup-v2.html) : Documentation du noyau sur cgroup v2