⚙️ Supervisor – Gestion de Processus (supervisord & supervisorctl)
Guide complet IDEO-Lab sur le gestionnaire de processus (Client/Serveur) en Python.
Concept : Gestionnaire de Processus
Garder les processus "vivants" (Keep Alive), Monitoring.
Supervisor Process PythonArchitecture Client/Serveur
supervisord (Serveur) vs supervisorctl (Client).
vs. systemd / init.d
Niveau OS (systemd) vs Niveau Application (supervisor).
systemd init.d ComparatifConfig : [supervisord]
Le bloc principal (nodaemon=true, pidfile, logfile).
Config : [program:x]
Définition d'un programme (command, directory).
Config : Auto-Restart
autostart, autorestart (true, unexpected).
Config : Logs (stdout/stderr)
stdout_logfile, stderr_logfile, logrotate.
Config : Sécurité (User)
user (Exécuter en tant que non-root).
Config : [include]
Gestion (files = /etc/supervisor/conf.d/*.conf).
Outil : supervisorctl
Le client CLI (Shell interactif).
supervisorctl CLIsupervisorctl : status
Afficher l'état (RUNNING, STOPPED, FATAL).
status RUNNINGsupervisorctl : start/stop/restart
Gérer les processus (start myapp, restart all).
supervisorctl : reread/update
Appliquer les changements de config (.conf).
Interface Web (inet_http_server)
Activer l'interface web ([inet_http_server]).
Events (Événements)
[eventlistener]. Réagir (ex: PROCESS_STATE_FATAL).
Cas d'Usage : Gunicorn / uWSGI
Gérer les workers d'applications (Python/Django/Flask).
Gunicorn uWSGICas d'Usage : Queue Workers
Gérer les workers (Celery, RabbitMQ, Redis).
Celery QueueCas d'Usage : Docker
Le "mauvais" usage (CMD ["supervisord"]).
Qu'est-ce que supervisord ?
Supervisor (ou supervisord) est un système de contrôle de processus (Process Control System) écrit en Python.
Son unique objectif est de contrôler et de surveiller d'autres processus (ex: vos applications, vos scripts, vos workers). Il s'assure qu'ils sont "vivants" (running) et les redémarre s'ils crashent.
Caractéristiques Clés
- Garder "Vivants" (Keep Alive) : (Rôle n°1) Redémarre automatiquement les processus qui se terminent (
autorestart). - Gestion (Control) : Offre un client (
supervisorctl) pour démarrer, arrêter, redémarrer les processus (sanskillmanuel). - Centralisation (Logs) : Capture
stdoutetstderrde tous les processus enfants et les gère (rotation, taille). - Web UI : (Optionnel) Fournit une interface web simple pour voir/gérer les processus.
- Événements (Events) : Peut émettre des événements (ex: "Processus X est entré en état FATAL") pour déclencher d'autres scripts (ex: une alerte Slack).
Supervisor utilise une architecture Client/Serveur (sur la même machine).
+--------------------------------+
| [ Serveur : supervisord ] | (Daemon/Service Principal)
| (Écrit en Python) |
| |
| - Lit /etc/supervisord.conf |
| - Lance les [program:x] |
| - Surveille les PIDs |
| - Capture stdout/stderr |
| - Écoute (Socket ou TCP) |
+--------------------------------+
▲
│ (Socket UNIX: /tmp/supervisor.sock)
│ (ou TCP: localhost:9001)
│
+--------------------------------+
| [ Client : supervisorctl ] | (Outil CLI)
| |
| (Envoie des commandes :) |
| - "status" |
| - "restart myapp" |
| - "reread" |
+--------------------------------+
supervisord: Le serveur (daemon) qui tourne en arrière-plan. C'est lui qui gère réellement les processus enfants.supervisorctl: Le client (interface de commande) qui se connecte (via un socket UNIX ou un port TCP) àsupervisordpour lui donner des ordres.
Problème : "systemd (le standard Linux moderne) peut aussi redémarrer (Restart=on-failure) des services. Pourquoi utiliser Supervisor ?"
Réponse : Ils n'opèrent pas au même niveau. systemd est pour les services système (OS), supervisor est pour les processus applicatifs (User).
| Critère | systemd (Init System) | supervisor (Process Manager) |
|---|---|---|
| Niveau | Niveau OS (PID 1) | Niveau Application (User-space) |
| Usage | Gérer les services "système" (ex: sshd, nginx, docker, supervisord lui-même). | Gérer les processus "applicatifs" (ex: 8 workers gunicorn, 4 workers celery). |
| Configuration | Fichiers .service (/etc/systemd/system/). Nécessite sudo. | Fichiers .conf (/etc/supervisor/conf.d/). Peut tourner en non-root. |
| Gestion | systemctl (Lent, lourd, root). | supervisorctl (Rapide, léger, non-root, Web UI). |
| Portabilité | Linux (Spécifique systemd) | Portable (Python) (Linux, macOS, *BSD). |
| Cas d'Usage | Démarrer supervisord au boot. | Démarrer/Gérer 10 copies de mon_app.py (scaling). |
Bonne Pratique : Utiliser systemd pour démarrer (et garder "vivant") supervisord. Puis utiliser supervisor pour gérer (plus finement) l'application (ex: Gunicorn).
[supervisord] (Global)La configuration principale (ex: /etc/supervisord.conf) est un fichier .ini.
Le bloc [supervisord] définit le comportement du daemon (serveur) lui-même.
[supervisord] ; (Chemin du fichier de log principal du serveur) logfile=/var/log/supervisord/supervisord.log ; (Taille max avant rotation) logfile_maxbytes=50MB ; (Nombre de backups à garder) logfile_backups=10 ; (Niveau de log) loglevel=info ; (Chemin du fichier PID) pidfile=/var/run/supervisord.pid ; (Si 'true', tourne en "foreground". ; Indispensable pour Docker !) nodaemon=false ; (Utilisateur (si 'nodaemon=false')) user=root ; (Dossier pour les fichiers de config 'include') childlogdir=/var/log/supervisord/
[program:x] (Le Programme)C'est la définition du processus (Job) à superviser. (Généralement dans un fichier séparé, ex: /etc/supervisor/conf.d/myapp.conf).
[program:myapp_web] ; --- 1. L'Identité --- ; (Le nom (ID) utilisé dans supervisorctl) ; (Doit être unique) ; program_name=myapp_web (auto-déduit) ; --- 2. La Commande (Le Quoi) --- ; (La commande à exécuter. ; Chemins absolus recommandés.) command=/var/www/myenv/bin/gunicorn --workers 4 myapp:app ; (Optionnel : Démarrer dans ce dossier) directory=/var/www/myapp/ ; (Optionnel : S'exécuter en tant que cet utilisateur) user=www-data ; --- 3. La Gestion (Le Comment) --- ; (Démarrer automatiquement au lancement de supervisord) autostart=true ; (Redémarrer automatiquement si le process crash) autorestart=true ; (Priorité (999 = défaut)) priority=999 ; (Temps d'attente (sec) après le lancement ; avant de le considérer comme "RUNNING") startsecs=5 ; (Nombre de tentatives de redémarrage) startretries=3 ; (Codes de sortie "attendus" (OK). ; Ne pas redémarrer si exit code = 0 ou 2) exitcodes=0,2 ; (Logs - Voir 3.1) stdout_logfile=/var/log/myapp/app.log stderr_logfile=/var/log/myapp/app.err.log
autostart & autorestartCes deux directives (dans [program:x]) sont cruciales pour la HA (Haute Disponibilité) du processus.
autostart
Type : Booléen (true / false).
Défaut : true.
Action : Si true, supervisord (le serveur) démarre ce programme automatiquement lors de son propre lancement (ex: au boot du serveur).
autorestart
Type : true, false, ou unexpected.
Défaut : unexpected.
Action : Redémarrer le processus s'il s'arrête ?
false: Ne jamais redémarrer (si le processus est un "one-shot" script).true: Toujours redémarrer (même si le processus s'arrête "proprement" avec un exit code 0).unexpected(Recommandé) : Redémarrer uniquement si le processus s'arrête avec un code de sortie (exit code) non-attendu.- (Par défaut, les codes "attendus" (
exitcodes) sont 0 et 2). - (Si le script crash (exit 1), il sera redémarré. S'il se termine (exit 0), il ne le sera pas).
- (Par défaut, les codes "attendus" (
Une fonction clé de Supervisor est de capturer les sorties "standard" (stdout) et "erreur" (stderr) de vos processus (qui, sinon, seraient perdues).
[program:myapp] command=python myapp.py ; --- Logs --- ; (Recommandé) Envoyer la sortie standard (echo) ; dans ce fichier stdout_logfile=/var/log/myapp/app.log ; (Recommandé) Envoyer la sortie erreur (exceptions) ; dans ce fichier stderr_logfile=/var/log/myapp/app.err.log ; --- Rotation (Logrotate intégré) --- ; (Taille max du fichier avant rotation) stdout_logfile_maxbytes=10MB stderr_logfile_maxbytes=10MB ; (Nombre de backups à garder) stdout_logfile_backups=5
Alternative (stdout) : Si vous préférez que vos logs aillent dans le syslog principal (ou soient gérés par journald/systemd), vous pouvez utiliser :
; (Ne pas capturer, laisser hériter du parent) stdout_logfile=NONE stderr_logfile=NONE ; (Ou (legacy) rediriger vers le syslog) ; stdout_logfile=syslog ; stderr_logfile=syslog
user)Par défaut, supervisord est souvent lancé en tant que root (via systemd). Si user n'est pas spécifié, les programmes ([program:x]) seront aussi lancés en tant que root.
Risque : C'est une faille de sécurité majeure (Violation du Moindre Privilège). Si votre application (ex: Gunicorn) est compromise, l'attaquant obtient un shell root.
Bonne Pratique
Toujours spécifier un utilisateur non-privilégié (ex: www-data, myuser) pour exécuter le processus.
[program:my_gunicorn_app] command=/var/www/myenv/bin/gunicorn myapp:app ; (Sécurité) Lancer le processus Gunicorn ; en tant que l'utilisateur 'www-data' user=www-data ; (Le dossier doit appartenir à www-data) directory=/var/www/myapp
[include] (Modularité)Ne mettez pas vos définitions [program:x] dans le fichier principal /etc/supervisord.conf.
La bonne pratique (similaire à Nginx/Apache) est d'utiliser le bloc [include] pour charger des configurations modulaires (ex: 1 fichier .conf par application).
/etc/supervisord.conf (Principal)
[supervisord] ... (Configuration globale...) ... [inet_http_server] ... [supervisorctl] ... ; --- Inclure tous les fichiers .conf --- [include] files = /etc/supervisor/conf.d/*.conf
/etc/supervisor/conf.d/app1.conf
[program:my_app_1] command=/usr/bin/python /srv/app1/run.py user=app1 autostart=true autorestart=true ...
/etc/supervisor/conf.d/app2.conf
[program:my_app_2] command=/usr/bin/python /srv/app2/run.py user=app2 ...
supervisorctlsupervisorctl est l'interface client (CLI) pour contrôler le daemon supervisord.
Mode Interactif (Shell)
Lancer supervisorctl sans argument ouvre un shell interactif (le plus courant).
$ sudo supervisorctl myapp:worker_01 RUNNING pid 1234, uptime 1:10:05 myapp:worker_02 RUNNING pid 1235, uptime 1:10:05 my_other_app FATAL Exited too quickly supervisor> status (Affiche la même chose) supervisor> restart myapp:worker_01 myapp:worker_01: stopped myapp:worker_01: started supervisor> help (Affiche toutes les commandes) supervisor> exit
Mode Commande (Direct)
Permet de passer une commande directement (utile pour les scripts).
$ sudo supervisorctl status $ sudo supervisorctl restart all
supervisorctl : statusLa commande status est la commande de base. Elle affiche l'état de tous les processus gérés.
supervisor> status app:worker-01 RUNNING pid 1120, uptime 0:02:15 app:worker-02 RUNNING pid 1121, uptime 0:02:15 celery STARTING pid 1122, uptime 0:00:03 db_backup STOPPED Jan 05 10:00 AM gunicorn_web FATAL Exited too quickly nginx EXITED Jan 05 10:15 AM (exit status 0)
| État (State) | Description |
|---|---|
RUNNING | Nominal. (Tourne depuis plus longtemps que startsecs). |
STARTING | Nominal. (En cours de démarrage, moins que startsecs). |
STOPPED | Arrêté (volontairement via stop, ou autostart=false). |
EXITED | Arrêté (proprement). (autorestart=false ou autorestart=unexpected et exit code 0). |
FATAL | Panne. Le processus a (crashé) trop de fois (startretries) trop vite (startsecs). (autorestart a échoué). |
BACKOFF | En attente avant de retenter un démarrage (après un FATAL). |
supervisorctl : start / stop / restartCommandes pour gérer le cycle de vie d'un programme ([program:x]).
# Syntaxe (Shell) supervisor> [commande] [nom_programme] supervisor> [commande] [groupe]:* supervisor> [commande] all
start [nom]: Démarrer un processus (ex:start myapp).stop [nom]: Arrêter un processus (envoieSIGTERM, puisSIGKILLaprèsstopwaitsecs).restart [nom]: Équivaut àstoppuisstart.
Gestion de Groupe
Si vous utilisez des groupes (ex: [group:myworkers]) ou des processus numérotés (ex: process_name=%(program_name)s_%(process_num)02d), vous pouvez utiliser le : (ou :*).
# Redémarrer un seul worker supervisor> restart myapp:worker_01 # Redémarrer TOUS les workers de l'app "myapp" supervisor> restart myapp:* # Redémarrer TOUS les programmes supervisor> restart all
supervisorctl : reread & update (Appliquer Config)Le Piège : Si vous modifiez un fichier .conf, supervisord ne le détecte pas automatiquement. Un restart ne suffit pas.
Vous devez forcer le rechargement de la configuration (en 2 étapes).
Workflow (Mise à jour de .conf)
(Vous venez de modifier /etc/supervisor/conf.d/myapp.conf) $ sudo supervisorctl # 1. REREAD # (Demande à supervisord de scanner les fichiers .conf # et de voir les *différences* (nouveaux/modifiés)) supervisor> reread myapp: changed my_new_app: available # 2. UPDATE # (Applique les changements : démarre les "available", # redémarre les "changed") supervisor> update myapp: stopped myapp: started my_new_app: added process group # (Alternative) # supervisor> reload # (Fait reread + update, mais redémarre TOUT)
Supervisor inclut une interface web (très basique, mais fonctionnelle) pour voir/gérer les processus (Start/Stop/Restart).
Configuration (supervisord.conf)
Elle est désactivée par défaut. Pour l'activer, ajoutez ce bloc :
[inet_http_server] ; (Écoute sur TCP 9001, accessible à tous) port=*:9001 ; (BONNE PRATIQUE : Sécuriser l'accès) ;port=127.0.0.1:9001 ;username=admin ;password=mon_pass_hash
Après un supervisorctl reload, l'interface est accessible sur http://[IP_Serveur]:9001.
Supervisor peut émettre des événements (Events) sur son "Event Bus" interne lorsque l'état d'un processus change. Un [eventlistener] est un [program] spécial qui s'abonne à ces événements.
Usage (Ex: Alerte Slack si crash)
Étape 1 : Créer un script (ex: /usr/local/bin/notify_slack.py) qui lit (stdin) et envoie à Slack.
Étape 2 : Configurer le Listener (.conf).
[eventlistener:slack_alerter] ; (Le script à lancer) command=/usr/bin/python /usr/local/bin/notify_slack.py ; (Les événements à écouter) ; (PROCESS_STATE_FATAL = État 'FATAL') ; (PROCESS_STATE_EXITED = Si 'autorestart=unexpected') events=PROCESS_STATE_FATAL,PROCESS_STATE_EXITED
Flux :
[program:myapp]passe en étatFATAL(voir 4.2).supervisordémet l'événementPROCESS_STATE_FATAL.[eventlistener:slack_alerter](qui écoute) reçoit l'événement.supervisordenvoie les infos (Payload) austdindu scriptnotify_slack.py.
C'est le cas d'usage le plus courant pour Supervisor. Gérer les "workers" (processus) d'une application web Python (Django, Flask) servie par Gunicorn ou uWSGI (derrière Nginx).
Supervisor ne remplace pas Gunicorn. Supervisor gère Gunicorn.
# Fichier: /etc/supervisor/conf.d/my_django_app.conf
[program:django_gunicorn]
; Commande (ex: 4 workers, bind sur un socket UNIX)
command=/path/to/myenv/bin/gunicorn \
--workers 4 \
--bind unix:/tmp/gunicorn.sock \
myproject.wsgi:application
; S'exécuter dans le bon dossier
directory=/path/to/myproject/
; S'exécuter en tant que 'www-data'
user=www-data
; (Gestion des logs stdout/stderr)
stdout_logfile=/var/log/gunicorn/access.log
stderr_logfile=/var/log/gunicorn/error.log
; (Keep Alive)
autostart=true
autorestart=true
startsecs=10
startretries=3
Autre cas d'usage majeur : gérer les workers (consommateurs) de tâches asynchrones (Queues).
Ex: Celery (Python), RabbitMQ Consumers, Laravel Horizon (PHP).
# Fichier: /etc/supervisor/conf.d/celery_workers.conf ; (On utilise le "numprocs" pour lancer 4 copies) [program:celery_worker] command=/path/to/myenv/bin/celery -A myapp worker --loglevel=INFO directory=/path/to/myapp user=www-data autostart=true autorestart=true ; --- Scaling --- ; (Lancer 4 instances de ce 'command') numprocs=4 ; (Donner des noms uniques : worker_00, worker_01...) process_name=%(program_name)s_%(process_num)02d ; (Arrêter (SIGTERM) gracieusement (laisser finir la tâche)) stopwaitsecs=600
Un "anti-pattern" (mauvaise pratique) courant est d'utiliser Supervisor à l'intérieur d'un conteneur Docker pour gérer plusieurs services (ex: Nginx + Gunicorn + Redis) dans 1 seul conteneur.
Le Problème
La philosophie de Docker est : "1 Conteneur = 1 Processus (service)". Docker (le moteur) est *déjà* un gestionnaire de processus (il gère le CMD/ENTRYPOINT).
# Mauvais (Anti-Pattern) # (Le conteneur gère 3 services) CMD ["/usr/bin/supervisord", "-n"]
Pourquoi c'est mauvais :
- Signaux :
docker stopenvoieSIGTERMàsupervisord(PID 1).supervisorddoit alors (correctement) relayer leSIGTERMà ses enfants (Nginx, Gunicorn...). (Compliqué). - Logs : Docker est conçu pour lire
stdout/stderr(du PID 1). Supervisor *capture* (détourne)stdout/stderret les écrit dans des *fichiers* (.log) *à l'intérieur* du conteneur (docker logsest vide). - Santé (Healthcheck) : Si Gunicorn (enfant) crash,
supervisord(PID 1) continue de tourner. Docker pense que le conteneur est "sain" (HEALTHY).
La "Bonne" Façon (Docker Compose)
Utiliser 3 conteneurs séparés (Nginx, Gunicorn, Redis) et les orchestrer avec docker-compose.yml.
