virli/tutorial/4/howto.md

172 lines
6.0 KiB
Markdown
Raw Normal View History

2021-10-31 19:51:17 +00:00
Utiliser les *namespace*s
-------------------------
2016-10-19 03:24:05 +00:00
2021-10-31 19:51:17 +00:00
### S'isoler dans un nouveau *namespace*
2016-10-19 03:24:05 +00:00
2022-11-11 09:14:16 +00:00
Si l'on voit l'isolation procurée par les *namespace*s 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.
2016-10-19 03:24:05 +00:00
2021-10-31 19:51:17 +00:00
L'intérêt principal de cette approche, exploitée bien après la mise en avant du
concept, est que l'utilisation des *namespace*s ne se limite pas seulement à
2022-11-11 09:14:16 +00:00
des machines virtuelles légères. On retrouve ainsi dans Google Chrome et
Firefox de nombreuses utilisations des *namespace*s 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.
2016-10-19 03:24:05 +00:00
2021-10-31 19:51:17 +00:00
Nous allons voir dans cette partie plusieurs méthodes pour utiliser ces espaces
de noms.
2016-10-19 03:24:05 +00:00
2022-11-11 09:14:16 +00:00
#### Dans son shell\
2016-10-19 03:24:05 +00:00
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
2017-11-09 00:30:41 +00:00
nécessaire pour lancer l'appel système `unshare(2)`, puis, tout comme
2016-10-19 03:24:05 +00:00
`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
2022-02-24 19:43:43 +00:00
nous sommes passés dans un autre *namespace* `UTS` :
2016-10-19 03:24:05 +00:00
2017-10-17 06:29:07 +00:00
<div lang="en-US">
```
42sh# hostname --fqdn
koala.zoo.paris
42sh# sudo unshare -u /bin/bash
2019-11-03 17:54:22 +00:00
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
2016-10-19 03:24:05 +00:00
```
2017-10-17 06:29:07 +00:00
</div>
2016-10-19 03:24:05 +00:00
2017-11-09 00:30:41 +00:00
Nous avons pu ici modifier le nom de la machine, sans que cela n'affecte notre
2016-10-19 03:24:05 +00:00
machine hôte.
2015-10-07 01:45:39 +00:00
2022-11-11 09:14:16 +00:00
#### Les appels système\
2016-10-20 01:17:42 +00:00
L'appel système par excellence pour contrôler l'isolation d'un nouveau
processus est `clone(2)`.
2022-11-11 09:14:16 +00:00
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 :
2016-10-20 01:17:42 +00:00
* `CLONE_NEWNS`,
* `CLONE_NEWUTS`,
* `CLONE_NEWIPC`,
* `CLONE_NEWPID`,
* `CLONE_NEWNET`,
* `CLONE_NEWUSER`,
2021-10-31 19:51:17 +00:00
* `CLONE_NEWCGROUP`,
* `CLONE_NEWTIME`.
2016-10-20 01:17:42 +00:00
2022-11-11 09:14:16 +00:00
::::: {.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 *namespace*s 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.
:::::
2021-10-31 19:51:17 +00:00
On peut bien entendu cumuler un ou plusieurs de ces *flags*, et les combiner
avec d'autres attendus par `clone(2)`.
2016-10-20 01:17:42 +00:00
2021-10-31 19:51:17 +00:00
Ces mêmes *flags* sont utilisés lors des appels à `unshare(2)` ou `setns(2)`,
que nous verrons plus tard.
2016-10-20 01:17:42 +00:00
2022-11-11 09:14:16 +00:00
::::: {.code}
2017-11-09 00:30:41 +00:00
Pour créer un nouveau processus qui sera à la fois dans un nouvel espace de
2021-10-31 19:51:17 +00:00
noms réseau et dans un nouveau *namespace* `cgroup`, on écrirait un code C
2022-02-24 19:43:43 +00:00
semblable à :
2016-10-20 01:17:42 +00:00
2017-10-17 06:29:07 +00:00
<div lang="en-US">
2016-10-20 01:17:42 +00:00
```c
#include <sched.h>
2016-10-20 01:17:42 +00:00
2021-09-11 12:41:43 +00:00
#define STACKSIZE (1024 * 1024)
static char child_stack[STACKSIZE];
2016-10-20 01:17:42 +00:00
int clone_flags = CLONE_CGROUP | CLONE_NEWNET | SIGCHLD;
2016-10-20 01:17:42 +00:00
2022-05-04 09:18:16 +00:00
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
2016-10-20 01:17:42 +00:00
```
2017-10-17 06:29:07 +00:00
</div>
2016-10-20 01:17:42 +00:00
2021-09-11 12:41:43 +00:00
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.
2022-11-11 09:14:16 +00:00
::::: {.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.
2016-10-20 01:17:42 +00:00
2015-10-07 01:45:39 +00:00
2021-10-31 19:51:17 +00:00
##### `unshare`\
2015-10-07 01:45:39 +00:00
2022-11-11 09:14:16 +00:00
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
2021-10-31 19:51:17 +00:00
`unshare(2)`. Celui-ci prend uniquement en argument une liste de *flags* des
*namespace*s dont on souhaite se dissocier.
2015-10-07 01:45:39 +00:00
2021-10-31 19:51:17 +00:00
::::: {.warning}
2022-11-11 09:14:16 +00:00
**Le comportement de `unshare(2)` (ou `unshare(3)`) avec les *namespace*s *PID*
et *Time* n'est pas celui que l'on peut attendre !**\
2015-10-08 01:48:26 +00:00
2021-10-31 19:51:17 +00:00
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*.
2015-10-08 01:48:26 +00:00
2021-10-31 19:51:17 +00:00
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`.
2016-10-20 01:17:42 +00:00
2021-10-31 19:51:17 +00:00
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`.
2022-11-11 09:14:16 +00:00
2021-10-31 19:51:17 +00:00
:::::
2022-11-11 09:14:16 +00:00
Nous avons vu comment créer et nous dissocier d'un espace de nom. Maintenant
voyons comment en rejoindre un déjà existant.