9.7 KiB
\newpage
Les espaces de noms -- namespaces
Introduction
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.
On en dénombre sept depuis Linux 4.6 : cgroup
, IPC
, network
,
mount
, PID
, user
et UTS
.
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 parle ainsi d'isoler les périphériques, ou encore l'horloge. Pour ce dernier, un patch a même déjà été proposé.
L'espace de noms mount
Depuis Linux 2.4.19.
Cet espace de noms isole la liste des points de montage.
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
aura effectivement été démontée de chaque namespace dans lequel elle était
montée.
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), car les montages et démontages pourraient alors être répercutés dans l'espace de noms parent.
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.
mount --make-rslave /
L'espace de noms UTS
Depuis Linux 2.6.19.
Cet espace de noms isole le nom de machine et son domaine NIS.
L'espace de noms IPC
Depuis Linux 2.6.19.
Cet espace de noms isole les objets IPC et les files de messages POSIX.
Une fois le namespace attaché à un processus, il ne peut alors plus parler qu'avec les autres processus de son espace de noms (lorsque ceux-ci passent par l'API IPC du noyau).
L'espace de noms PID
Depuis Linux 2.6.24.
Cet espace de noms isole la liste des processus et virtualise leurs numéros.
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
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
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.
L'espace de nom network
Depuis Linux 2.6.29.
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.
Une interface réseau (eth0
, wlan0
, ...) ne peut se trouver que dans un seul
espace de noms à la fois. Il est par contre possible de les déplacer.
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).
L'espace de noms user
Depuis Linux 3.8.
Cet espace de noms isole la liste des utilisateurs, des groupes, leurs identifiants, les capabilities, la racine et le trousseau de clefs du noyau.
La principale caractéristique est que les identifiants d'utilisateur et de 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.
L'espace de noms cgroup
Depuis Linux 4.6.
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.
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
processus (d'un système à un autre) : l'arborescence des cgroups n'a alors
plus d'importance car le processus ne voit que son groupe.
S'isoler dans un nouveau namespace
Avec son coquillage
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
:
Nous avons pu ici modifier le nom de la machine, sans que cela n'affecte notre machine hôte.
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)
.
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 à :
#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);
</div>
Le premier argument est un pointeur sur fonction. Il s'agit de la fonction qui
sera appelée par le nouveau processus.
## Rejoindre un *namespace*
Rejoindre un espace de noms se fait en utilisant l'appel système `setns(2)`,
auquel on passe le *file descriptor* d'un des liens du dossier
`/proc/<PID>/ns/` :
<div lang="en-US">
```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;
}
Dans un shell, on utilisera la commande nsenter(1)
:
Durée de vie d'un namespace
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 points de montage sont démontés, les interfaces réseaux sont réattribués à l'espace de noms initial, ...
Ce compteur évolue selon plusieurs critères, et principalement selon le nombre de processus qui l'utilise. C'est-à-dire que, la plupart du temps, le namespace est libéré lorsque le dernier processus s'exécutant dedans se termine.
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
:
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.
On peut très bien utiliser directement ce fichier pour obtenir un descripteur
de fichier valide vers le namespace (pour passer à setns(2)
).
Faire persister un namespace
Il n'est pas possible de faire persister un espace de noms d'un reboot à l'autre.
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.
Aller plus loin
Je vous recommande la lecture des man suivants :
namespaces(7)
: introduisant et énumérant les namespaces ;
Pour tout connaître en détails, la série d'articles de Michael Kerrisk sur
les namespaces est excellente ! Auquel il
faut ajouter le petit dernier sur le cgroup
namespace.
Cet article de Michael Crosby montrant l'utilisation de clone(2) est également des plus intéressants, pour ce qui concerne la programmation plus bas-niveau.