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