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.

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 ?

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 :

Dockerfile 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 ?

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.

  • technique/docker/good_practices/multi.1602594760.txt.gz
  • de qduchemi