virli/tutorial/4/howto.md
Pierre-Olivier Mercier 25aef1af17
All checks were successful
continuous-integration/drone/push Build is passing
New spelling fixes
2026-04-10 16:19:05 +07:00

6.5 KiB

Utiliser les namespaces

S'isoler dans un nouveau namespace

Si l'on voit l'isolation procurée par les namespaces comme des machines virtuelles, on peut se dire qu'il suffit d'exécuter un appel système pour arriver dans un conteneur bien isolé. Cependant, le choix fait par les développeurs de Linux a été de laisser le choix des espaces de noms dont on veut se dissocier.

L'intérêt principal de cette approche, exploitée bien après la mise en avant du concept, est que l'utilisation des namespaces ne se limite pas seulement à des machines virtuelles légères. On retrouve ainsi dans Google Chrome et Firefox de nombreuses utilisations des namespaces dans le simple but d'accroître la sécurité de leur navigateur. Ainsi, les threads de rendu n'ont pas accès au réseau et sont cloisonnés de manière transparente pour l'utilisateur.

Nous allons voir dans cette partie plusieurs méthodes pour utiliser ces espaces de noms.

Dans son shell\

De la même manière que l'on peut utiliser l'appel système chroot(2) depuis un shell via la commande chroot(1), la commande unshare(1) permet de faire le nécessaire pour lancer l'appel système unshare(2), puis, tout comme chroot(1), exécuter le programme passé en paramètre.

En fonction des options qui lui sont passées, unshare(1) va créer le/les nouveaux namespaces et placer le processus dedans.

Par exemple, nous pouvons modifier sans crainte le nom de notre machine, si nous sommes passés dans un autre namespace UTS :

``` 42sh# hostname --fqdn koala.zoo.paris 42sh# sudo unshare -u /bin/bash bash# hostname --fqdn koala.zoo.paris bash# hostname lynx.zoo.paris bash# hostname --fqdn lynx.zoo.paris bash# exit 42sh# hostname --fqdn koala.zoo.paris ```

Nous avons pu ici modifier le nom de la machine, sans que cela n'affecte notre machine hôte.

Les appels système\

L'appel système par excellence pour contrôler l'isolation d'un nouveau processus est clone(2).

Ce syscall, propre à Linux, crée habituellement un nouveau processus enfant de notre processus courant (mais il peut aussi créer des threads, on va voir qu'il fait beaucoup de choses), comme fork(2) (qui lui est un appel système POSIX). Mais il prend en plus de nombreux paramètres. L'isolement ou non du processus se fait en fonction des flags qui sont passés. On retrouve donc :

  • CLONE_NEWNS,
  • CLONE_NEWUTS,
  • CLONE_NEWIPC,
  • CLONE_NEWPID,
  • CLONE_NEWNET,
  • CLONE_NEWUSER,
  • CLONE_NEWCGROUP,
  • CLONE_NEWTIME.

::::: {.more}

Le nom du flag CLONE_NEWNS est historique et assez peu explicite contrairement aux autres : il désigne en fait l'espace de nom mount.

Au départ, les namespaces ont été pensés pour former un tout : une couche d'isolation complète pour les processus. Mais lors des développements suivants, il s'est avéré pratique de pouvoir choisir finement de quels aspects on souhaitait se dissocier.

C'est ainsi que pour chaque nouveau namespace, un nouveau flag est introduit.

:::::

On peut bien entendu cumuler un ou plusieurs de ces flags, et les combiner avec d'autres attendus par clone(2).

Ces mêmes flags sont utilisés lors des appels à unshare(2) ou setns(2), que nous verrons plus tard.

::::: {.code}

Pour créer un nouveau processus qui sera à la fois dans un nouvel espace de noms réseau et dans un nouveau namespace cgroup, on écrirait un code C semblable à :

```c #include

#define STACKSIZE (1024 * 1024) static char child_stack[STACKSIZE];

int clone_flags = CLONE_NEWCGROUP | CLONE_NEWNET | SIGCHLD;

pid_t pid = clone(do_execvp, // First function executed by child child_stack + STACKSIZE, // Assume stack grows downward clone_flags, // clone specials flags args); // Arguments to pass to // do_execvp

</div>

Dans cet exemple, le processus fils créé disposera d'un nouvel espace de noms
pour les *CGroups* et disposera d'une nouvelle pile réseau.

::::: {.more}

#### Compilation des exemples C {-}
\

Pour compiler les programmes C utilisant les namespaces, aucune bibliothèque
spéciale n'est requise au-delà de la bibliothèque C standard :

<div lang="en-US">
```bash
42sh$ gcc -o mon_programme mon_programme.c

Notez que certains appels système utilisés avec les namespaces nécessitent la définition de _GNU_SOURCE au début du fichier source pour accéder aux extensions GNU :

```c #define _GNU_SOURCE #include // ... ```

:::::

::::: {.question}

Quel est le rôle du flag SIGCHLD ? {-}

\

Lorsque l'on crée un nouveau processus, on ajoute l'option SIGCHLD afin d'être notifié par signal lorsque notre processus fils a terminé son exécution. Cela permet d'être réveillé de notre wait(2).

:::::

:::::

L'appel système clone(2) va donc créer un nouveau processus, ou un nouveau thread, mais parfois on souhaite juste isoler notre processus actuel.

unshare\

Lorsque l'on souhaite faire entrer notre processus courant ou notre thread dans un nouvel espace de noms, on peut utiliser l'appel système unshare(2). Celui-ci prend uniquement en argument une liste de flags des namespaces dont on souhaite se dissocier.

::::: {.warning}

Le comportement de unshare(2) (ou unshare(3)) avec les namespaces PID et Time n'est pas celui que l'on peut attendre !\

En effet, après avoir appelé unshare, le processus reste tout de même dans son namespace Time ou PID d'origine, seuls ses enfants (après un appel à fork(2) ou clone(2) par exemple) seront dans le nouveau namespace.

Cela poserait trop de problème de faire changer le PID d'un processus en cours d'exécution, de même qu'il serait impensable que la CLOCK_MONOTONIC puisse faire un saut en avant ou en arrière. Alors le choix qui a été fait est que seuls les fils créés après l'appel à unshare(2) seront concrètement dans le nouveau namespace. C'est dans cette situation que pid et pid_for_children peuvent être différents dans le dossier /proc/<PID>/ns.

On ne remarque pas cette bizarrerie avec clone(2), car il crée déjà un nouveau processus, son PID et sa CLOCK_MONOTONIC sont directement à la bonne valeur dès l'exécution de la fonction fn.

:::::

Nous avons vu comment créer et nous dissocier d'un espace de nom. Maintenant voyons comment en rejoindre un déjà existant.