<!-- title: Supervisor, gestion de processus -->
<!-- category: GNU/Linux -->
<!-- tag: planet -->

Quand il s'agit de déployer des programmes de son cru sur un serveur
GNU/Linux, on réalise généralement deux actions :

-    l'écriture d'un script de démarrage et arrêt du programme
-    la *démon-isation* du programme
<!-- more -->
Le premier point n'est pas complexe mais il peut être contraignant. Si  on
envisage de déployer sur Debian, Ubuntu et Fedora, il faut prévoir trois
scripts différents : un pour les scripts à la saucce Sys V, un pour  Upstart
et un autre pour systemd. L'avantage c'est qu'on  peut gérer finement les
dépendances à d'autres services.

Le second point consiste à veiller à ne pas bloquer le script d'init en
lançant le programme. On peut le gérer dans le code de notre programme  en
prévoyant deux modes de lancement de notre programme : *daemon* et
interactif. Python, par exemple, propose [la librairie
daemonize](https://pypi.python.org/pypi/daemonize) pour réaliser cela. JAVA
propose des outils comme JAVA Service Wrapper pour gérer le lancement et
garantir l'arrêt du processus. On peut aussi le gérer de manière externe  au
code, de manière rustique avec un
[nohup](https://en.wikipedia.org/wiki/Nohup), auquel cas il faut gérer
l'arrêt fiable du processus en manipulant son PID.

Voici un exemple de script d'init à la sauce Debian pour un programme
JAVA :

``` shell
### BEGIN INIT INFO
# Provides:          monprog
# Required-Start:    $local_fs $remote_fs $network $syslog
# Required-Stop:     $local_fs $remote_fs $network $syslog
# Default-Start:     2 3 4 5
# Default-Stop:      0 1 6
# X-Interactive:     true
# Short-Description: Start/stop monprog
### END INIT INFO
BOOT_LOG=/var/log/monprog-boot.log
PID_FILE=/opt/monprog/pid
start_java () {
    nohup java -cp "/opt/monprog/lib/*" fr.yax.monprog.Main >$BOOT_LOG 2>&1 &
    echo $! > $PID_FILE
    echo "Monprog started ..."
}
do_start () {
    echo "Starting Monprog ..."
    if [ ! -f $PID_FILE ]; then
        start_java
    else
        PID=$(cat $PID_FILE)
        if [ -d /proc/$PID ]; then
            echo "Monprog is already running ..."
        else
            start_java
        fi
    fi
}
do_stop() {
    echo "Stopping Monprog ..."
    if [ -f $PID_FILE ]; then
        PID =$(cat $PID_FILE);
        kill $PID 2>/dev/null
        echo "Monprog stopped ..."
        rm -f $PID_FILE
    else
        echo "Monprog seems not running ..."
    fi
}
case $1 in
    start)
        do_start
    ;;
    stop)
        do_stop
    ;;
    restart)
        do_stop
        sleep 1
        do_start
    ;;
esac
```

C'est perfectible. Il faudrait tenter l'arrêt avec un signal moins violent
que SIGKILL de façon à l'intercepter dans le code et faire un arrêt  propre.
Si cette méthode ne fonctionne pas au bout de plusieurs secondes, le script d'init pourrait alors opter pour un arrêt radical.

Cette méthode fonctionne bien mais elle nécessite une connaissance  système
pour écrire et maintenir les scripts en fonction des déploiements cible.  Si
on veut donner la possibilité à un utilisateur standard (non *root*) de
démarrer ou arrêter un programme, il faut aussi maîtriser un peu la gestion
des  droits UNIX (avec sudo par exemple).

Une alternative simple pour la plupart des systèmes UNIX (GNU/Linux,  FreeBSD,
Solaris et Mac OS X) est le [programme  Supervisor](http://supervisord.org).
C'est écrit en Python (comme la plupart des  programmes de qualité !  **Troll
inside** ) et de même que MongoDB permet au développeur de reprendre la main
au DBA sur  l'administration de base de donnée, Supervisor permet au
développeur de reprendre un peu  la main à l'admin sys sur le déploiement de
ses applications.

Supervisor est fourni sur la plupart des distributions. On peut l'installer
avec le système de paquet de sa distribution ou bien opter pour une
installation à partir de PIP, l'installeur de programmes Python. Ce dernier
permet d'obtenir la version la plus récente de Supervisor.

Supervisor est composé de deux parties :

-   un service **Supervisord**
-   un client en mode console : **Supervisorctl**

Le client permet d'interagir avec les programmes gérés : démarrer, stopper.
Une interface RPC permet aussi de piloter Supervisord programmatiquement ; je
n'ai pas encore testé cet aspect.

La configuration principale est dans un fichier supervisord.conf qui décrit la
connexion du client supervisorctl (section unix_http_server), la config RPC
(section rpcinterface) et le répertoire des configuration des programmes à
gérer (section includes).

Voici une configuration type :

#### /etc/supervisor/supervisord.conf

    [unix_http_server]
    file=/var/run//supervisor.sock   ; (the path to the socket file)
    chmod=0770                       ; sockef file mode (default 0700)
    chown=root:supervisor

    [supervisord]
    logfile=/var/log/supervisor/supervisord.log
    pidfile=/var/run/supervisord.pid
    childlogdir=/var/log/supervisor

    [rpcinterface:supervisor]
    supervisor.rpcinterface_factory = supervisorrpcinterface:make_main_rpcinterface

    [supervisorctl]
    serverurl=unix:///var/run//supervisor.sock

    [include]
    files = /etc/supervisor/conf.d/*.conf

Les droits sur la socket UNIX sont important. En donnant l'accès à ROOT et au
groupe supervisor, on peut facilement donner l'accès à supervisorctl en
ajoutant un utilisateur dans le groupe supervisor. Attention donner l'accès à
supervisorctl c'est donner le droit de stopper n'importe quel programme géré
par supervisor. C'est un privilège important.

Le reste de la configuration consiste à créer des configurations
additionnelles décrivant des programmes à lancer simplement :

-   par défaut un programme démarre en même temps que le service donc au
démarrage du serveur. C'est configurable.   
-   on peut définir si un programme doit être relancé automatiquement et définir sous quelle condition, par exemple en fonction du code de sortie du programme.   
-   on peut opter pour l'envoi d'un signal au programme afin de demander au programme de s'arrêter proprement plutôt que de forcer un arrêt brutal.   
-   on peut regrouper des programmes pour manipuler des
groupes, faire des démarrages groupés et des arrêts groupés. C'est utile si on a beaucoup de programmes.   
-   au sein d'un groupe on peut définir des
priorités pour ordonner le lancement des programmes du groupe.

Voici un exemple qui définit un groupe mesprogrammes composé de 2 programmes correspondant au même binaire.  

#### /etc/supervisor/conf.d/mesprogrammes.conf

    [group:mesprogrammes]
    programs=monprog1,monprog2

    [program:monprog1]
    directory=/opt/monprogram
    command=/usr/bin/java -DrunDir=/opt/monprog -cp "lib/*" fr.yax.monprog.Main --port 1234
    stopsignal=INT
    priority=500

    [program:monprog2]
    directory=/opt/monprogram
    command=/usr/bin/java -DrunDir=/opt/monprog -cp "lib/*" fr.yax.monprog.Main --port 1235
    stopsignal=INT
    priority=501

Dans cet exemple, on envoie un signal SIGINT à monprog pour lui demander un arrêt propre. Voici un snippet de code JAVA pour intercepter le signal :

#### Interception d'un signal SIGINT en JAVA

``` java
// register a shutdown hook
Runtime.getRuntime().addShutdownHook(new Thread() {
    @Override
    public void run() {
        logger.info("Shutting down has been requested");
        stopCleanly();
    }
});
```

En conclusion, **Supervisor** est un bon outil de gestion de programmes :
fiable, facile à installer et à configurer. En complément d'un outil de
déploiement comme [Fabric](http://www.fabfile.org) un développeur peut
facilement automatiser le  déploiement de son programme sur une ou plusieurs
machines cibles.
