virli/tutorial/4/howto.md

6.0 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és 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_CGROUP | 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.

::::: {.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 fil 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
*namespace*s dont on souhaite se dissocier.

::::: {.warning}

**Le comportement de `unshare(2)` (ou `unshare(3)`) avec les *namespace*s *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.