virli/tutorial/4/pidns.md

185 lines
6.4 KiB
Markdown
Raw Normal View History

2017-11-09 00:30:41 +00:00
Le *namespace* `PID` {#pid-ns}
2021-10-31 19:51:17 +00:00
--------------------
2016-10-19 03:24:05 +00:00
2021-10-31 19:51:17 +00:00
### Introduction {#pid-ns-intro}
2016-10-19 03:24:05 +00:00
2017-11-09 00:30:41 +00:00
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é
2016-10-20 02:15:02 +00:00
comme l'`init`.
2016-10-19 03:24:05 +00:00
2016-10-20 02:15:02 +00:00
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,
2018-11-06 13:44:59 +00:00
via `unshare(2)` ou `setns(2)` par exemple, ici, le changement ne sera valable
2017-11-09 00:30:41 +00:00
qu'après le prochain `fork(2)` (ou similaire).
2018-11-06 13:44:59 +00:00
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.
2016-10-20 02:15:02 +00:00
2022-02-24 19:43:43 +00:00
### Isolons !
2016-10-20 02:15:02 +00:00
2022-02-24 19:43:43 +00:00
Première étape s'isoler :
2016-10-20 02:15:02 +00:00
2017-10-17 06:29:07 +00:00
<div lang="en-US">
2022-11-11 09:14:16 +00:00
```
42sh# unshare --pid --fork /bin/bash
2022-11-11 09:14:16 +00:00
inpidns# echo $$
1
2016-10-20 02:15:02 +00:00
```
2017-10-17 06:29:07 +00:00
</div>
2016-10-20 02:15:02 +00:00
2022-11-11 09:14:16 +00:00
::::: {.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](#pid-ns-intro)). Si on
l'omet, voici ce qu'il se passe :
<div lang="en-US">
```
42sh# unshare --pid /bin/sh
inpidns# echo $$
23456
```
</div>
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 :
<div lang="en-US">
```
42sh# unshare --pid /bin/sh
inpidns# echo $$
34567
inpidns# sh
inpidns# echo $$
1
```
</div>
C'est d'ailleurs le bon moment pour regarder le contenu de notre fichier
`pid_for_children` pour le processus `34567` :
<div lang="en-US">
```
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]'
```
</div>
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 :
<div lang="en-US">
```
42sh# unshare --pid /bin/bash
inpidns# echo $$
65432
inpidns# sh
-bash: fork: Cannot allocate memory
```
</div>
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.
:::::
2016-10-20 02:15:02 +00:00
Un coup d'œil à `top` ou `ps aux` devrait nous montrer que l'on est maintenant
2021-10-31 19:51:17 +00:00
le seul processus ... pourtant, il n'en est rien, ces deux commandes continuent
2016-10-20 02:15:02 +00:00
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.
2017-11-09 00:30:41 +00:00
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
2021-09-11 12:41:43 +00:00
notre système initial. Pour s'en sortir, il est nécessaire de s'isoler dans un
*namespace* `mount` séparé.
2016-10-20 02:15:02 +00:00
2022-11-11 09:14:16 +00:00
### Double isolation : ajout du *namespace* `mount`
2016-10-20 02:15:02 +00:00
2022-02-24 19:43:43 +00:00
Voici la nouvelle ligne de commande que l'on va utiliser :
2016-10-20 02:15:02 +00:00
2017-10-17 06:29:07 +00:00
<div lang="en-US">
2022-11-11 09:14:16 +00:00
```
42sh# unshare --pid --mount --fork --mount-proc /bin/bash
2016-10-20 02:15:02 +00:00
```
2017-10-17 06:29:07 +00:00
</div>
2016-10-20 02:15:02 +00:00
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*.
2021-10-31 19:51:17 +00:00
### Arborescence à l'extérieur du *namespace*
2016-10-20 02:15:02 +00:00
2017-11-09 00:30:41 +00:00
Lors de notre première tentative de `top`, lorsque `/proc` était encore monté
2022-02-24 19:43:43 +00:00
sur le `procfs` de l'espace de noms initial : notre processus (au PID 1 dans
2017-11-09 00:30:41 +00:00
son nouveau *namespace*) était présent dans l'arborescence de l'espace initial
2022-02-24 19:43:43 +00:00
avec un PID dans la continuité des autres processus, étonnant !
2016-10-20 02:15:02 +00:00
2022-02-24 19:43:43 +00:00
En fait, l'isolation consiste en une virtualisation des numéros du processus :
2018-11-06 13:44:59 +00:00
la plupart des processus du système initial ne sont pas accessibles, et ceux qui
2017-11-09 00:30:41 +00:00
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.
2016-10-20 02:15:02 +00:00
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.
2021-10-31 19:51:17 +00:00
### Processus orphelins et `nsenter`
2016-10-20 02:15:02 +00:00
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
2022-02-24 19:43:43 +00:00
dans son *namespace*[^PR_SET_CHILD_SUBREAPER] ; il n'est pas sorti de l'espace
2021-10-31 19:51:17 +00:00
de noms.
2019-11-03 17:54:22 +00:00
[^PR_SET_CHILD_SUBREAPER]: en réalité, ce comportement est lié à la propriété
`PR_SET_CHILD_SUBREAPER`, qui peut être définie pour n'importe quel processus
2022-11-11 09:14:16 +00:00
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
2019-11-03 17:54:22 +00:00
propriété définie.
2016-10-20 02:15:02 +00:00
2022-11-11 09:14:16 +00:00
Lorsqu'on lance un processus via `nsenter(1)` ou `setns(2)`, cela crée un
2017-11-09 00:30:41 +00:00
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 à
2016-10-20 02:15:02 +00:00
l'intérieur pour conserver un comportement cohérent.
2016-10-19 03:24:05 +00:00
2021-10-31 19:51:17 +00:00
### Aller plus loin {-}
2016-10-20 02:15:02 +00:00
2021-10-31 19:51:17 +00:00
N'hésitez pas à jeter un œil à la page de manuel consacrée à cet espace de
2022-02-24 19:43:43 +00:00
noms : `pid_namespaces(7)`.