\newpage

De l'importance d'avoir un système d'init dans son conteneur
=================

Dans la dure vie d'un processus, créé par son processus parent (`ppid`),
celui-ci gardera son identifiant `pid` durant toute la durée de son
exécution. Puis, lorsqu'il aura terminé, celui-ci sera passé dans un statut
« zombie », en attendant que son parent direct récupère son code de retour
(grâce à l'appel système `wait(2)`).


## Le rôle d'`init`

Parmi ses attributions, `init`, le PID 1 de notre système, est le processus qui
récupère les processus orphelins du système. Lorsque le parent direct d'un
processus meurt, ses fils sont reparenté sous le processus `init` et ils
obtiennent alors comme `ppid` 1. Ils ne conservent pas le PID de leur défunt
parent.

:::: {.more}

Depuis les noyaux 3.4, n'importe quel processus peut se déclarer *child
subreaper* et récupérer tous les orphelins dans sa descendance de processus.

Pour se déclarer *child subreaper*, un processus va utiliser `prctl(2)` avec
l'argument `PR_SET_CHILD_SUBREAPER`.

:::::

Docker procure une isolation, notamment au travers du *namespace* PID : les
processus faisant parti du même *namespace* ne voient seulement qu'une partie
de l'arbre de processus de l'hôte, et notamment, un PID 1 est recréé, il s'agit
du premier processus à s'exécuter dans le *namespace*.

Ceci n'est pas anodin car ce PID 1 de notre conteneur hérite des mêmes
responsabilités qu'`init` : il doit `wait(2)` les zombies qui se créent autour
de lui.
\

Dans un conteneur, on a tendance à vouloir directement lancer une application,
qui ne s'attend sans doute pas à devoir gérer les processus orphelins. Dans
certaines circonstances, il peut donc arriver qu'un conteneur en particulier
génère beaucoup de zombies, qui vont finir par rendre la machine instable, car
le système sera à court de PID à distribuer.

On peut citer notamment la JVM, ou encore les conteneurs de construction de
logiciels (Jenkins, Drone, ...). En effet, si l'on peut blâmer les programmeurs
qui oublient de `wait(2)` leurs fils directs, il peut arriver régulièrement
qu'une erreur de programmation crée des zombies dans les conteneurs de
construction. Sans processus pour les arrêter, chaque *build* risque d'ajouter
de nouveaux zombies.


## `init` dans mes conteneurs

`tini`[^TINI_PROJECT] est un projet qui implémente un `init` tout à fait
minimaliste, en ce sens qu'il se veut être le plus transparent possible, tout
en jouant pleinement son rôle de collecteur et suppresseur de zombies.

[^TINI_PROJECT]: `init` à l'envers : <https://github.com/krallin/tini>

Sans rien ajouter de plus dans nos conteneurs ou nos `Dockerfile`, nous pouvons
l'utiliser en ajoutant simplement `--init` à nos lignes de `docker container
run` :

<div lang="en-US">
```
docker container run --init -d nemunaire/youp0m
```
</div>

Dans cet exemple, avant de lancer notre `ENTRYPOINT`, comme cela devrait être
le cas sans l'option `--init`, ici c'est `tini` qui sera appelé. Il lancera
ensuite l'`ENTRYPOINT` tel qu'il avait été prévu et commencera à jouer son rôle
d'`init` le plus transparent possible, en attendant la venue des zombies.
\

Mais ce n'est pas tout, `tini` aide également à transmettre les signaux
d'extinction du conteneur (généralement `SIGTERM`), lorsque l'on fait un
`docker stop`.

::::: {.question}

#### Ne pourrait-on pas simplement ajouter un *thread* dans Jenkins/DroneCI/... qui s'occupe de gérer les zombies ? {-}
\

Alors ce n'est pas vraiment une solution idéale... si Jenkins s'exécute en tant
que PID 1, il paraît difficile de différencier les processus qui ont été
re-parentés à Jenkins (et sur lesquels il faut `wait(2)` directement) et ceux
qui ont été créés par Jenkins et pour lequel le code de retour est attendu par
d'autres portions du code.

:::::


## Transmission de signaux

En fait, il se trouve que `bash(1)` est capable de réceptionner les zombies et
de `wait(2)`, comme le fait `init(1)`, ce qui évite aussi l'invasion que l'on
cherchait à éviter.

Mais `bash` pose un problème : il ne transmet pas les signaux à ses fils. S'il
reçoit un `SIGTERM`, celui-ci ne sera tout simplement pas traité et encore
moins transmis à ses fils, à moins d'avoir programmé un tel comportement en
shell.

Voici donc une raison supplémentaire de préférer `tini` à `bash` (ou à rien du
tout). D'autant plus qu'à moins d'avoir préparé la fin d'exécution, `bash` ne
retournera pas le code d'erreur de la commande que l'on a lancé, mais plutôt 0.


## Intégration dans les `Dockerfile`

`tini` n'est pas nécessaire dans tous les conteneurs. On considère qu'une
application qui ne `fork(2)` pas n'a pas besoin de processus `init`. De même,
une image contenant un binaire qui gère correctement ses fils et qui n'a pas de
petit enfant n'aura pas besoin de `tini`. Par contre dans les autres cas, il
semble particulièrement indiqué.

L'utilisation par le paramètre `--init` du `run` n'est pas recommandée et
devrait se limiter aux cas où l'image a été construite par quelqu'un qui
n'avait pas en tête ces contraintes. Lorsque l'on sait que des zombies ne vont
pas être géré par leurs parents, le mainteneur se doit d'ajouter `tini` dans
son `Dockerfile`. La méthode recommandée est de l'installer par les paquets de
la distribution (`apt-get install tini`, `apk add tini`, ...). Néanmoins, dans
le cas d'une distribution qui ne possèderait pas le paquet, il convient
d'ajouter ces quelques lignes :

<div lang="en-US">
```
# Install tini for signal processing and zombie killing
ENV TINI_VERSION v0.18.0
RUN wget -O /usr/local/bin/tini "https://github.com/krallin/tini/releases/download/${TINI_VERSION}/tini" && \
  chmod +x /usr/local/bin/tini && \
  tini --version
ENTRYPOINT ["/usr/local/bin/tini", ...]
```
</div>
