**Ceci est une ancienne révision du document !**
Gérer proprement son script d'entrypoint
Une pratique commune est d’utiliser un script shell en tant qu’ENTRYPOINT
, qui va effectuer des opérations quelconques (injection de secrets, initialisation de base de données…), puis démarrer le service, sous forme d’un ou de plusieurs processus.
Entrypoint qui lance un unique processus
Situation
Dans ce cas, le Dockerfile
spécifie :
- Une instruction
ENTRYPOINT
associée à un script shell - Une instruction
CMD
, passée en arguments de l’entrypoint
L’entrypoint est exécuté par un shell comme /bin/sh
ou /bin/bash
. C’est le premier processus exécuté dans le conteneur, il a donc le PID 1.
Traditionnellement, ce script lance le service désigné par CMD
.
Question:
Quel est le risque ?
Problématique
Les scripts ne transmettent pas les signaux à leurs enfants (voir explications ici).
Important:
Le processus désigné par CMD
ne reçoit donc jamais les signaux qui sont envoyés au conteneur. Or, ces signaux peuvent être importants : signal de terminaison, signal de rechargement de configuration… De plus, un processus tué brutalement peut créer des situations indésirables, comme la corruption d’une base de données. Il est donc très important qu’il reçoive les signaux qui lui sont destinés.
Supposons un Dockerfile du style :
FROM debian:buster-slim ... ENTRYPOINT [ "/entrypoint.sh" ] # Notez qu'on lance nginx en premier plan CMD [ "nginx", "-g", "daemon-off;" ]
Avec un entrypoint du style :
- snippet.bash
#!/bin/sh # Injection de secrets sed [...] # Démarrage de nginx $@
Note:
$@
désigne l’ensemble des arguments passés à l’entrypoint, c’est-à-dire le contenu du CMD
du Dockerfile
.
Dans ce cas, la situation ressemble à ceci :
NOM PID entrypoint.sh 1 |__nginx 2
Comme nous venons de le voir, tout signal envoyé au conteneur arrivera à l’entrypoint, qui n’en fera rien, ce qui conduira Docker à envoyer un SIGKILL
au bout d’un moment.
Question:
Comment faire ?
Utiliser exec
Le comportement désiré serait d’exécuter les instruction de l’entrypoint, puis de remplacer l’entrypoint par son processus enfant. C’est exactement le but de la commande exec.
On remplace l’entrypoint par :
- snippet.bash
#!/bin/sh # Injection de secrets sed [...] # Démarrage de nginx exec $@
Après exécution de l’entrypoint, les processus sont comme suit :
NOM PID nginx 1
L’entrypoint a été remplacé par nginx. Il reçoit correctement les signaux.
Question:
Est-ce-que c’est suffisant ?
Ça ne règle pas tous les problèmes, car les processus qui ont un PID 1 sont traités spécialement. Voir la documentation sur l'utilisation d'un système d'initialisation pour parfaire la configuration.
Entrypoint qui lance plusieurs services
Attention:
Attention, la philosophie Docker se résume généralement en : un conteneur, un processus. En réalité, c’est plutôt un conteneur, un service. Il y a donc des cas où il est légitime que plusieurs processus tournent simultanément, mais s’il s’agit de parties bien cloisonnées d’une application (e.g. serveur web et serveur applicatif), il est préférable de les faire tourner dans deux conteneurs séparés.
Un entrypoint qui lance plusieurs services pourrait ressembler à ceci :
- snippet.bash
#!/bin/sh # Injection de secrets sed [...] # Lancement de PHP en arrière plan php-fpm & # Lancement de nginx en premier plan exec nginx -g 'daemon off;'
Après lancement, la situation est la suivante :