virli/tutorial/4/cgroupns.md

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 :

```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 ...

</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 :

```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 <sys/wait.h> #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;

}

</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 :