2015-10-07 01:45:39 +00:00
|
|
|
\newpage
|
|
|
|
|
2017-11-09 00:30:41 +00:00
|
|
|
Les espaces de noms -- *namespaces* {#namespaces}
|
|
|
|
===================================
|
2016-10-19 03:24:05 +00:00
|
|
|
|
|
|
|
## Introduction
|
|
|
|
|
2017-11-09 00:30:41 +00:00
|
|
|
Les espaces de noms du noyau, les *namespaces*, permettent de
|
|
|
|
dupliquer certaines structures, habituellement considérées uniques
|
|
|
|
pour le noyau, dans le but de les isoler d'un groupe de processus à un
|
|
|
|
autre.
|
2016-10-19 03:24:05 +00:00
|
|
|
|
2017-11-09 00:30:41 +00:00
|
|
|
On en dénombre sept depuis Linux 4.6 : `cgroup`, `IPC`, `network`,
|
|
|
|
`mount`, `PID`, `user` et `UTS`.
|
2016-10-19 03:24:05 +00:00
|
|
|
|
2017-11-09 00:30:41 +00:00
|
|
|
La notion d'espace de noms est relativement nouvelle et a été intégrée
|
|
|
|
progressivement au sein du noyau Linux. Aussi, toutes les structures
|
|
|
|
ne sont pas encore *containerisables* :
|
|
|
|
[le document fondateur](https://www.kernel.org/doc/ols/2006/ols2006v1-pages-101-112.pdf)
|
|
|
|
parle ainsi d'isoler les périphériques, ou encore l'horloge. Pour ce
|
|
|
|
dernier,
|
|
|
|
[un patch a même déjà été proposé](https://lwn.net/Articles/179825/).
|
2016-10-19 03:24:05 +00:00
|
|
|
|
2017-11-09 00:30:41 +00:00
|
|
|
### L'espace de noms `mount` {#mount-ns}
|
2016-10-19 03:24:05 +00:00
|
|
|
|
|
|
|
Depuis Linux 2.4.19.
|
|
|
|
|
2017-11-09 00:30:41 +00:00
|
|
|
Cet espace de noms isole la liste des points de montage.
|
2016-10-19 03:24:05 +00:00
|
|
|
|
2017-11-09 00:30:41 +00:00
|
|
|
Chaque processus appartenant à un *namespace* différent peut monter, démonter
|
|
|
|
et réorganiser à sa guise les points de montage, sans que cela n'ait d'impact
|
|
|
|
sur les processus hors de cet espace de noms. Une partition ne sera donc pas
|
|
|
|
nécessairement démontée après un appel à `umount(2)`, elle le sera lorsqu'elle
|
2016-10-20 01:17:42 +00:00
|
|
|
aura effectivement été démontée de chaque *namespace* dans lequel elle était
|
|
|
|
montée.
|
2016-10-19 03:24:05 +00:00
|
|
|
|
2017-11-09 00:30:41 +00:00
|
|
|
Attention il convient cependant de prendre garde aux types de liaison existant
|
|
|
|
entre vos points de montage (voir la partie sur
|
|
|
|
[les particularités des points de montage](#mount)), car les montages et
|
|
|
|
démontages pourraient alors être répercutés dans l'espace de noms parent.
|
2016-10-19 03:24:05 +00:00
|
|
|
|
2017-11-09 00:30:41 +00:00
|
|
|
Une manière rapide pour s'assurer que nos modifications ne sortiront pas de
|
|
|
|
notre *namespace* est d'appliquer le type esclave à l'ensemble de nos points de
|
|
|
|
montage, récursivement, dès que l'on est entré dans notre nouvel espace de
|
|
|
|
noms.
|
|
|
|
|
|
|
|
```shell
|
|
|
|
mount --make-rslave /
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### L'espace de noms `UTS` {#uts-ns}
|
2016-10-19 03:24:05 +00:00
|
|
|
|
|
|
|
Depuis Linux 2.6.19.
|
|
|
|
|
2017-11-09 00:30:41 +00:00
|
|
|
Cet espace de noms isole le nom de machine et son domaine NIS.
|
2016-10-19 03:24:05 +00:00
|
|
|
|
|
|
|
|
2017-11-09 00:30:41 +00:00
|
|
|
### L'espace de noms `IPC` {#ipc-ns}
|
2016-10-19 03:24:05 +00:00
|
|
|
|
|
|
|
Depuis Linux 2.6.19.
|
|
|
|
|
2017-11-09 00:30:41 +00:00
|
|
|
Cet espace de noms isole les objets IPC et les files de messages POSIX.
|
2016-10-19 03:24:05 +00:00
|
|
|
|
|
|
|
Une fois le *namespace* attaché à un processus, il ne peut alors plus parler
|
2017-11-09 00:30:41 +00:00
|
|
|
qu'avec les autres processus de son espace de noms (lorsque ceux-ci passent par
|
|
|
|
l'API IPC du noyau).
|
2016-10-19 03:24:05 +00:00
|
|
|
|
|
|
|
|
2017-11-09 00:30:41 +00:00
|
|
|
### L'espace de noms `PID`
|
2016-10-19 03:24:05 +00:00
|
|
|
|
|
|
|
Depuis Linux 2.6.24.
|
|
|
|
|
2017-11-09 00:30:41 +00:00
|
|
|
Cet espace de noms isole la liste des processus et virtualise leurs numéros.
|
2016-10-19 03:24:05 +00:00
|
|
|
|
|
|
|
Une fois dans un espace, le processus ne voit que le sous-arbre de processus
|
|
|
|
également attachés à son espace. Il s'agit d'un sous-ensemble de l'arbre global
|
|
|
|
de PID : les processus de tous les PID *namespaces* apparaissent donc dans
|
|
|
|
l'arbre initial.
|
|
|
|
|
|
|
|
Pour chaque nouvel espace de noms de processus, une nouvelle numérotation est
|
2017-11-09 00:30:41 +00:00
|
|
|
initiée. Ainsi, le premier processus de cet espace porte le numéro 1 et aura
|
|
|
|
les mêmes propriétés que le processus `init` usuel ; entre autre, si un
|
2016-10-19 03:24:05 +00:00
|
|
|
processus est rendu orphelin dans ce *namespace*, il devient un fils de ce
|
|
|
|
processus, et non un fils de l'`init` de l'arbre global.
|
|
|
|
|
|
|
|
|
2017-11-09 00:30:41 +00:00
|
|
|
### L'espace de nom `network`
|
2016-10-19 03:24:05 +00:00
|
|
|
|
|
|
|
Depuis Linux 2.6.29.
|
|
|
|
|
2017-11-09 00:30:41 +00:00
|
|
|
Cet espace de noms fournit une isolation pour toutes les ressources associées
|
|
|
|
aux réseaux : les interfaces, les piles protocolaires IPv4 et IPv6, les tables
|
|
|
|
de routage, règles pare-feu, ports numérotés, etc.
|
2016-10-19 03:24:05 +00:00
|
|
|
|
|
|
|
Une interface réseau (`eth0`, `wlan0`, ...) ne peut se trouver que dans un seul
|
2017-11-09 00:30:41 +00:00
|
|
|
espace de noms à la fois. Il est par contre possible de les déplacer.
|
2016-10-19 03:24:05 +00:00
|
|
|
|
|
|
|
Lorsque le *namespace* est libéré (généralement lorsque le dernier processus
|
|
|
|
attaché à cet espace de noms se termine), les interfaces qui le composent sont
|
|
|
|
ramenées dans l'espace initial (et non pas dans l'espace parent, en cas
|
|
|
|
d'imbrication).
|
|
|
|
|
|
|
|
|
2017-11-09 00:30:41 +00:00
|
|
|
### L'espace de noms `user`
|
2016-10-19 03:24:05 +00:00
|
|
|
|
|
|
|
Depuis Linux 3.8.
|
|
|
|
|
2017-11-09 00:30:41 +00:00
|
|
|
Cet espace de noms isole la liste des utilisateurs, des groupes, leurs
|
|
|
|
identifiants, les *capabilities*, la racine et le trousseau de clefs du noyau.
|
2016-10-19 03:24:05 +00:00
|
|
|
|
|
|
|
La principale caractéristique est que les identifiants d'utilisateur et de
|
2017-11-09 00:30:41 +00:00
|
|
|
groupe pour un processus peuvent être différents entre l'intérieur et
|
|
|
|
l'extérieur de l'espace de noms. Il est donc possible, alors que l'on est un
|
|
|
|
simple utilisateur à l'extérieur du *namespace*, d'avoir l'UID 0 dans le
|
|
|
|
conteneur.
|
2016-10-19 03:24:05 +00:00
|
|
|
|
|
|
|
|
2017-11-09 00:30:41 +00:00
|
|
|
### L'espace de noms `cgroup` {#cgroup-ns}
|
2016-10-19 03:24:05 +00:00
|
|
|
|
|
|
|
Depuis Linux 4.6.
|
|
|
|
|
2017-11-09 00:30:41 +00:00
|
|
|
Cet espace de noms filtre l'arborescence des *Control Group* en changeant la
|
|
|
|
racine de l'arborescence des cgroups. Au sein d'un *namespace*, la racine vue
|
|
|
|
correspond en fait à un sous-groupe de l'arborescence globale.
|
2016-10-19 03:24:05 +00:00
|
|
|
|
|
|
|
Ainsi, un processus dans un `CGroup` *namespace* ne peut pas voir le contenu
|
|
|
|
des sous-groupes parents (pouvant laisser fuiter des informations sur le reste
|
|
|
|
du système). Cela peut également permettre de faciliter la migration de
|
2017-11-09 00:30:41 +00:00
|
|
|
processus (d'un système à un autre) : l'arborescence des cgroups n'a alors
|
2016-10-19 03:24:05 +00:00
|
|
|
plus d'importance car le processus ne voit que son groupe.
|
|
|
|
|
|
|
|
|
|
|
|
|
2016-10-20 01:17:42 +00:00
|
|
|
## S'isoler dans un nouveau *namespace*
|
2016-10-19 03:24:05 +00:00
|
|
|
|
2016-10-20 01:17:42 +00:00
|
|
|
### Avec son coquillage
|
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
|
2017-11-09 00:30:41 +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">
|
2016-10-19 03:24:05 +00:00
|
|
|
```shell
|
2017-11-09 00:30:41 +00:00
|
|
|
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
|
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
|
|
|
|
2016-10-20 01:17:42 +00:00
|
|
|
### Les appels systèmes
|
|
|
|
|
|
|
|
L'appel système par excellence pour contrôler l'isolation d'un nouveau
|
|
|
|
processus est `clone(2)`.
|
|
|
|
|
|
|
|
L'isolement ou non du processus est faite en fonction des `flags` qui sont
|
|
|
|
passés à la fonction :
|
|
|
|
|
|
|
|
* `CLONE_NEWNS`,
|
|
|
|
* `CLONE_NEWUTS`,
|
|
|
|
* `CLONE_NEWIPC`,
|
|
|
|
* `CLONE_NEWPID`,
|
|
|
|
* `CLONE_NEWNET`,
|
|
|
|
* `CLONE_NEWUSER`,
|
|
|
|
* `CLONE_NEWCGROUP`.
|
|
|
|
|
|
|
|
On peut bien entendu cumuler un ou plusieurs de ces `flags`, et les combiner
|
|
|
|
avec d'autres `flags` attendu par la fonction.
|
|
|
|
|
|
|
|
Les mêmes `flags` sont utilisés lors des appels à `unshare(2)` ou `setns(2)`.
|
|
|
|
|
2017-11-09 00:30:41 +00:00
|
|
|
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
|
|
|
|
similaire à :
|
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>
|
|
|
|
|
|
|
|
#define STACKSIZE (1024*1024)
|
|
|
|
static char child_stack[STACKSIZE];
|
|
|
|
|
|
|
|
int clone_flags = CLONE_CGROUP | CLONE_NEWNET | SIGCHLD;
|
|
|
|
|
|
|
|
pid_t pid = clone(do_execvp,
|
|
|
|
child_stack + STACKSIZE,
|
|
|
|
clone_flags,
|
|
|
|
&args);
|
|
|
|
```
|
2017-10-17 06:29:07 +00:00
|
|
|
</div>
|
2016-10-20 01:17:42 +00:00
|
|
|
|
|
|
|
Le premier argument est un pointeur sur fonction. Il s'agit de la fonction qui
|
|
|
|
sera appelée par le nouveau processus.
|
|
|
|
|
2015-10-07 01:45:39 +00:00
|
|
|
|
2016-10-19 03:24:05 +00:00
|
|
|
## Rejoindre un *namespace*
|
|
|
|
|
2017-11-09 00:30:41 +00:00
|
|
|
Rejoindre un espace de noms se fait en utilisant l'appel système `setns(2)`,
|
2016-10-19 03:24:05 +00:00
|
|
|
auquel on passe le *file descriptor* d'un des liens du dossier
|
|
|
|
`/proc/<PID>/ns/` :
|
|
|
|
|
2017-10-17 06:29:07 +00:00
|
|
|
<div lang="en-US">
|
2016-10-19 03:24:05 +00:00
|
|
|
```c
|
|
|
|
#define _GNU_SOURCE
|
|
|
|
#include <fcntl.h>
|
|
|
|
#include <sched.h>
|
|
|
|
#include <stdlib.h>
|
|
|
|
|
|
|
|
// ./a.out /proc/PID/ns/FILE cmd args...
|
|
|
|
|
|
|
|
int main(int argc, char *argv[])
|
|
|
|
{
|
|
|
|
int fd = open(argv[1], O_RDONLY);
|
|
|
|
if (fd == -1)
|
|
|
|
{
|
|
|
|
perror("open");
|
|
|
|
return EXIT_FAILURE;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (setns(fd, 0) == -1)
|
|
|
|
{
|
|
|
|
perror("setns");
|
|
|
|
return EXIT_FAILURE;
|
|
|
|
}
|
|
|
|
|
|
|
|
execvp(argv[2], &argv[2]);
|
|
|
|
|
|
|
|
perror("execve");
|
|
|
|
return EXIT_FAILURE;
|
|
|
|
}
|
2015-10-07 01:45:39 +00:00
|
|
|
```
|
2017-10-17 06:29:07 +00:00
|
|
|
</div>
|
2015-10-07 01:45:39 +00:00
|
|
|
|
2016-10-19 03:24:05 +00:00
|
|
|
Dans un shell, on utilisera la commande `nsenter(1)` :
|
2015-10-07 01:45:39 +00:00
|
|
|
|
2017-10-17 06:29:07 +00:00
|
|
|
<div lang="en-US">
|
2016-10-19 03:24:05 +00:00
|
|
|
```shell
|
|
|
|
42sh# nsenter --uts=/proc/42/ns/uts /bin/bash
|
2015-10-07 01:45:39 +00:00
|
|
|
```
|
2017-10-17 06:29:07 +00:00
|
|
|
</div>
|
2015-10-07 01:45:39 +00:00
|
|
|
|
|
|
|
|
2017-11-09 00:30:41 +00:00
|
|
|
## Durée de vie d'un *namespace* {#ns-lifetime}
|
2015-10-07 01:45:39 +00:00
|
|
|
|
2017-11-09 00:30:41 +00:00
|
|
|
Le noyau tient à jour un compteur de références pour chaque *namespace*. Dès
|
|
|
|
qu'une référence tombe à 0, l'espace de noms est automatiquement libéré, les
|
2016-10-19 03:24:05 +00:00
|
|
|
points de montage sont démontés, les interfaces réseaux sont réattribués à
|
|
|
|
l'espace de noms initial, ...
|
2015-10-07 01:45:39 +00:00
|
|
|
|
2016-10-19 03:24:05 +00:00
|
|
|
Ce compteur évolue selon plusieurs critères, et principalement selon le nombre
|
2017-11-09 00:30:41 +00:00
|
|
|
de processus qui l'utilise. C'est-à-dire que, la plupart du temps, le
|
2016-10-19 03:24:05 +00:00
|
|
|
*namespace* est libéré lorsque le dernier processus s'exécutant dedans se
|
|
|
|
termine.
|
2015-10-07 01:45:39 +00:00
|
|
|
|
2016-10-19 03:24:05 +00:00
|
|
|
Lorsque l'on a besoin de référencer un *namespace* (par exemple pour le faire
|
|
|
|
persister après le dernier processus), on peut utiliser un `mount bind` :
|
2015-10-07 01:45:39 +00:00
|
|
|
|
2017-10-17 06:29:07 +00:00
|
|
|
<div lang="en-US">
|
2016-10-19 03:24:05 +00:00
|
|
|
```shell
|
|
|
|
42sh# touch /tmp/ns/myrefns
|
|
|
|
42sh# mount --bind /proc/<PID>/ns/mount /tmp/ns/myrefns
|
|
|
|
```
|
2017-10-17 06:29:07 +00:00
|
|
|
</div>
|
2015-10-07 01:45:39 +00:00
|
|
|
|
2016-10-19 03:24:05 +00:00
|
|
|
De cette manière, même si le lien initial n'existe plus (si le `<PID>` s'est
|
|
|
|
terminé), `/tmp/ns/myrefns` pointera toujours au bon endroit.
|
2015-10-07 01:45:39 +00:00
|
|
|
|
2016-10-19 03:24:05 +00:00
|
|
|
On peut très bien utiliser directement ce fichier pour obtenir un descripteur
|
|
|
|
de fichier valide vers le *namespace* (pour passer à `setns(2)`).
|
2015-10-07 01:45:39 +00:00
|
|
|
|
2016-10-19 03:24:05 +00:00
|
|
|
### Faire persister un *namespace*
|
|
|
|
|
2017-11-09 00:30:41 +00:00
|
|
|
Il n'est pas possible de faire persister un espace de noms d'un reboot à
|
|
|
|
l'autre.
|
2015-10-07 01:45:39 +00:00
|
|
|
|
2016-10-19 03:24:05 +00:00
|
|
|
Même en étant attaché à un fichier du disque, il s'agit d'un pointeur vers une
|
|
|
|
structure du noyau, qui ne persistera pas au redémarrage.
|
2015-10-07 01:45:39 +00:00
|
|
|
|
|
|
|
|
2016-10-19 03:24:05 +00:00
|
|
|
## Aller plus loin
|
2015-10-07 01:45:39 +00:00
|
|
|
|
2016-10-19 03:24:05 +00:00
|
|
|
Je vous recommande la lecture des *man* suivants :
|
2015-10-08 01:48:26 +00:00
|
|
|
|
2017-11-09 00:30:41 +00:00
|
|
|
* `namespaces(7)` : introduisant et énumérant les *namespaces* ;
|
2015-10-08 01:48:26 +00:00
|
|
|
|
2016-10-19 03:24:05 +00:00
|
|
|
Pour tout connaître en détails, [la série d'articles de Michael Kerrisk sur
|
|
|
|
les *namespaces*](https://lwn.net/Articles/531114/) est excellente ! Auquel il
|
2017-11-09 00:30:41 +00:00
|
|
|
faut ajouter [le petit dernier sur le `cgroup`
|
2016-10-19 03:24:05 +00:00
|
|
|
*namespace*](https://lwn.net/Articles/621006/).
|
2016-10-20 01:17:42 +00:00
|
|
|
|
2017-11-09 00:30:41 +00:00
|
|
|
[Cet article de Michael Crosby montrant l'utilisation de clone(2)](http://crosbymichael.com/creating-containers-part-1.html)
|
|
|
|
est également des plus intéressants, pour ce qui concerne la programmation
|
|
|
|
plus bas-niveau.
|