virli/tutorial/4/pidns.md

6.4 KiB

Le namespace PID

Introduction

L'espace de noms PID est celui qui va nous permettre d'isoler un sous-arbre de processus en créant un nouvel arbre, qui aura son propre processus considéré comme l'init.

Contrairement aux autres namespaces où l'on peut demander à se séparer du namespace en question à n'importe quel moment de l'exécution du processus, via unshare(2) ou setns(2) par exemple, ici, le changement ne sera valable qu'après le prochain fork(2) (ou similaire). En effet, l'espace de noms n'est pas changé, afin que le processus ne change pas de PID en cours de route, puisqu'il dépend du namespace dans lequel il se trouve.

Isolons !

Première étape s'isoler :

``` 42sh# unshare --pid --fork /bin/bash inpidns# echo $$ 1 ```

::::: {.question}

Qu'est-ce qu'il se passe sans l'option --fork ? {-}

\

Nous utilisons ici l'option --fork, pour que le passage dans le nouvel espace de noms des PID soit effectif (cf. Introduction). Si on l'omet, voici ce qu'il se passe :

``` 42sh# unshare --pid /bin/sh inpidns# echo $$ 23456 ```

Ce n'est apparemment pas ce que l'on souhaitait : on est toujours dans l'ancien namespace, puisqu'on a juste unshare(2), sans fork(2). Si l'on va plus loin et que l'on fork en demandant à sh d'exécuter un nouveau processus :

``` 42sh# unshare --pid /bin/sh inpidns# echo $$ 34567 inpidns# sh inpidns# echo $$ 1 ```

C'est d'ailleurs le bon moment pour regarder le contenu de notre fichier pid_for_children pour le processus 34567 :

``` 42sh# ls -l /proc/34567/ns/pid* lrwxrwxrwx 1 nemunaire 1 oct. 23:42 pid -> 'pid:[4026531836]' lrwxrwxrwx 1 nemunaire 1 oct. 23:42 pid_for_children -> 'pid:[4026532993]' ```

On remarque bien que l'on se trouve dans un entre-deux : pid pointe toujours sur l'inode de l'arbre de PID initial, tandis que pid_for_children est prêt à transmettre le nouvel inode aux nouveaux processus. \

Pourquoi ça ne fonctionne pas du tout avec bash ? {-}

\

Si on utilise bash à la place de sh dans les exemples précédents, toujours sans l'option --fork, on obtient une erreur plutôt étrange :

``` 42sh# unshare --pid /bin/bash inpidns# echo $$ 65432 inpidns# sh -bash: fork: Cannot allocate memory ```

Il se trouve que bash commence par fork(2) lui-même afin de réaliser un certain nombre d'opérations. Notre PID 1, le premier PID de notre conteneur, a donc été alloué à un processus d'initialisation de bash, qui s'est terminé depuis.\

Le comportement du noyau, lorsque le PID 1 se termine, est de lancer un kernel panic (car c'est un processus indispensable, notamment de part son rôle de parent pour tous les processus orphelin). Au sein d'un namespace PID qui n'est pas le namespace racine, le noyau appelle la fonction disable_pid_allocation qui retire le flag PIDNS_HASH_ADDING de l'espace de nom. Le fait de ne pas avoir PIDNS_HASH_ADDING fait retourner ENOMEM à la fonction alloc_pid appelée par fork(2) et clone(2). On n'a donc pas d'autre choix que de quitter ce namespace pour en recréer un nouveau.

:::::

Un coup d'œil à top ou ps aux devrait nous montrer que l'on est maintenant le seul processus ... pourtant, il n'en est rien, ces deux commandes continuent d'afficher la liste complète des processus de notre système.

Cela est dû au fait que ces deux programmes, sous Linux, se basent sur le contenu de /proc. D'ailleurs, si l'on affiche le PID du processus courant echo $$, on obtient bien 1.

En l'état, beaucoup d'informations sont divulguées. Mais il n'est pas possible de monter le bon /proc car il serait également monté pour les processus de notre système initial. Pour s'en sortir, il est nécessaire de s'isoler dans un namespace mount séparé.

Double isolation : ajout du namespace mount

Voici la nouvelle ligne de commande que l'on va utiliser :

``` 42sh# unshare --pid --mount --fork --mount-proc /bin/bash ```

Avec l'option --mount-proc, unshare va s'occuper de monter le nouveau /proc.

Cette fois, top et ps nous rapportent bien que l'on est seul dans notre namespace.

Arborescence à l'extérieur du namespace

Lors de notre première tentative de top, lorsque /proc était encore monté sur le procfs de l'espace de noms initial : notre processus (au PID 1 dans son nouveau namespace) était présent dans l'arborescence de l'espace initial avec un PID dans la continuité des autres processus, étonnant !

En fait, l'isolation consiste en une virtualisation des numéros du processus : la plupart des processus du système initial ne sont pas accessibles, et ceux qui font partie de l'espace de noms créé disposent d'une nouvelle numérotation. Et c'est cette nouvelle numérotation qui est montrée au processus.

Si l'on veut interagir avec ce processus depuis un de ses espaces de noms parent, il faut le faire avec son identifiant de processus du même namespace que le processus appelant.

Processus orphelins et nsenter

Au sein d'un namespace, le processus au PID 1 est considéré comme le programme init, les mêmes propriétés s'appliquent donc.

Si un processus est orphelin, il est donc affiché comme étant fils du PID 1 dans son namespace1 ; il n'est pas sorti de l'espace de noms.

Lorsqu'on lance un processus via nsenter(1) ou setns(2), cela crée un processus qui n'est sans doute pas un fils direct du processus d'init de notre conteneur. Malgré tout, même s'il est affiché comme n'étant pas un fils à l'extérieur du conteneur, les propriétés d'init sont biens appliquées à l'intérieur pour conserver un comportement cohérent.

Aller plus loin {-}

N'hésitez pas à jeter un œil à la page de manuel consacrée à cet espace de noms : pid_namespaces(7).


  1. en réalité, ce comportement est lié à la propriété PR_SET_CHILD_SUBREAPER, qui peut être définie pour n'importe quel processus de l'arborescence. Le processus au PID 1 hérite forcément de cette propriété ; il va donc récupérer tous les orphelins, si aucun de leurs parents n'a la propriété définie. ↩︎