Utiliser un système d'initialisation

Attention:

Si vous utilisez un script shell comme entrypoint, la documentation sur les entrypoints et les processus multiples sera utile en premier lieu.

Parfois, le Dockerfile spécifie une commande (CMD) qui s’exécute en arrière-plan (comme un démon). Or Docker a besoin que le premier processus qui s’exécute dans le conteneur s’exécute en premier plan, sans quoi il s’éteint.

Autre problème, même si la commande s’exécute en premier plan, elle porte le PID 1.

Note:

Le premier processus d’un conteneur (de PID 1) est souvent référencé comme init, en référence aux systèmes Unix.

Ce processus est le parent de tous les autres, et doit transmettre les signaux qu’il reçoit à ses enfants (par exemple, un signal de terminaison).

Note:

Un peu d’explications : un signal, au sens Linux, est une sorte de notification asynchrone que l’on peut envoyer à un processus. Il existe de nombreux signaux, par exemple :

  • SIGTERM, pour demander à un processus de se terminer
  • SIGINT, envoyé avec un Ctrl+C, par exemple
  • SIGKILL, pour tuer un processus
  • SIGHUP, souvent utilisé pour recharger la configuration d’un service

Ces signaux peuvent être envoyés avec la commande kill.

Les processus peuvent enregistrer des handlers, qui sont exécutés lorsqu’un signal d’un certain type est reçu. Si jamais un processus n’enregistre pas de handler pour un signal particulier, le noyau passe au comportement par défaut : tuer le processus.

Sauf… pour le processus de PID 1. Dans ce cas, il n’y a pas de comportement par défaut.

Important:

Concrètement, lorsque l’on fait un docker stop, un SIGTERM est envoyé au processus de PID 1. Si celui-ci n’a pas de handler pour SIGTERM, il ne se passe rien car il n’y a pas de comportement par défaut, et au bout d’un timeout assez long (~30 secondes), Docker envoie un SIGKILL.

Or, la plupart des applications lancées avec le PID 1 (par exemple Python) n’ont pas de handlers.

Compose, depuis la version 3.7, adresse ce problème avec une directive très simple :

snippet.yaml
services:
  exemple:
    init: true

Cette directive a pour effet d’exécuter tini en PID 1, puis la commande ou l’entrypoint en tant qu’enfant.

Tini va :

  • S’exécuter en premier plan.
  • S’assurer qu’il n’y a pas de processus zombie, qui peuvent à forcer remplir la table des processus sur l’hôte.
  • Transmettre les signaux aux enfants : comme ils n’ont pas le PID 1, alors le fonctionnement par défaut (à savoir tuer le processus s’il n’a pas installé de handler) fonctionne.
  • Attendre la terminaison de son enfant pour terminer, même si celui-ci s’exécute en arrière plan.
  • Terminer avec le code de retour de son enfant, ce qui permet de savoir s’il y a eu une erreur ou non.

Note:

Un rôle subsidiaire du processus init, sur les systèmes Linux, est justement de faire le ménage dans les processus zombie.

  • technique/docker/good_practices/init.txt
  • de qduchemi