Project Oxygen & Ideo-LabIDEO LAB Dashboard 2026

⚙️ Circus – Gestion de Processus & Sockets (Python/ZMQ)

Guide complet IDEO-Lab sur le gestionnaire de processus de Mozilla (circusd, circusctl).

1.1

Concept : Gestionnaire Dynamique

Process manager (Python), Keep-Alive, par Mozilla.

Circus Python Mozilla
1.2

Architecture (ZMQ)

circusd (Daemon), circusctl (CLI), Pub/Sub (ZeroMQ).

circusd circusctl ZMQ
1.3

vs. Supervisor

Dynamique (Circus) vs Statique (Supervisor).

Supervisor Comparatif
2.1

Installation (pip)

pip install circus. Dépendances (pyzmq, tornado...).

pip pyzmq
2.2

Config : circus.ini (Structure)

[circus] (Global), [watcher:x], [socket:x], [plugin:x].

circus.ini [circus]
2.3

Config : Endpoints (ZMQ)

endpoint, pubsub_endpoint, stats_endpoint.

endpoint pubsub
3.1

Config : [watcher:x]

Le "programme" (cmd, user, working_dir).

[watcher] cmd
3.2

Watcher : Scaling (numprocesses)

numprocesses = N. (Gère N copies).

numprocesses Scaling
3.3

Watcher : HA (Restart)

autorestart, max_age (Restart par temps).

autorestart max_age
4.1

Config : [socket:x] (Concept)

Définition du socket (host, port, path).

[socket] Socket
4.2

Socket Activation (Héritage FD)

Binder (root), passer le FD, lancer le worker (non-root).

Socket Activation File Descriptor
4.3

Cas d'Usage : Gunicorn + Socket

cmd = gunicorn --fd $(CIRCUS_FD_web).

Gunicorn $(CIRCUS_FD)
5.1

CLI : circusctl (Shell)

status, list, stats. (Connexion ZMQ).

circusctl status
5.2

CLI : Gestion (start/stop/restart)

start [watcher], stop [watcher], restart.

start stop
5.3

CLI : Commandes Dynamiques

addwatcher, rmwatcher, incr/decr (Scaling).

addwatcher Dynamique incr
6.1

Outil : circus-web (UI)

Activer (pip install), Config ([plugin:httpd]).

circus-web Web UI
6.2

Hooks (Callbacks Python)

before_start, after_stop. (Intégration avancée).

Hooks Callbacks
6.3

API Python (Client)

circus.Client (Contrôler circusd depuis Python).

API circus.Client
7.1

Gestion des Logs

stdout_stream, stderr_stream (FileStream).

Logs FileStream
7.2

vs. PM2

Python/Généraliste (Circus) vs Node.js/Cluster (PM2).

PM2 Comparatif
7.3

Cheat-sheet

Résumé circusctl & circus.ini.

Cheatsheet CLI
1.1 Concept : Gestionnaire de Processus Dynamique
Qu'est-ce que Circus ?

Circus est un gestionnaire de processus (Process Manager) et un "watchdog" (chien de garde), écrit en Python (développé à l'origine par Mozilla).

Comme Supervisor (guide précédent), son objectif principal est de lancer, surveiller, et redémarrer (keep-alive) des processus (ex: des serveurs web, des workers).

Il est conçu pour être une alternative "moderne" à Supervisor, en mettant l'accent sur la flexibilité et la gestion dynamique.

Philosophie (Dynamique & Sockets)

Circus a été conçu pour résoudre certaines limitations de Supervisor :

  • Gestion de Sockets (Socket Management) : (Sa force n°1) Peut ouvrir (bind) des sockets (ex: Port 80) au nom des processus (similaire à systemd).
  • Gestion Dynamique : (Sa force n°2) Permet d'ajouter/supprimer des watchers (processus) "à chaud" (via circusctl addwatcher) sans recharger la configuration.
  • Communication (ZMQ) : Utilise ZeroMQ (ZMQ) pour la communication (Pub/Sub) entre le daemon et le client, au lieu de XML-RPC (Supervisor).
  • Web UI : Intègre une interface web (circus-web).
1.2 Architecture (circusd, circusctl, ZMQ)

Circus utilise une architecture Client/Serveur, mais basée sur la messagerie ZeroMQ (ZMQ).

Composants
  • circusd : Le daemon (serveur) principal. Il gère les "Watchers" (processus enfants) et les "Sockets".
  • circusctl : L'outil CLI (client). Il envoie des commandes (messages ZMQ) au daemon circusd.
  • circus-web : (Optionnel) Un plugin (pip install circus-web) qui fournit une interface web (via circushttpd) pour monitorer/contrôler.
Communication (ZMQ Pub/Sub)

C'est le cœur de l'architecture "dynamique" de Circus.

circusd expose (par défaut) deux endpoints (sockets ZMQ) :

  • endpoint (ZMQ REQ/REP) : (Le "Contrôleur") Le socket REP (Reply) où circusd écoute les commandes (REQ - Request) de circusctl.
  • pubsub_endpoint (ZMQ PUB/SUB) : (L'"Event Bus") Le socket PUB (Publish) où circusd publie (broadcast) tous les événements (ex: "Processus 'A' a crashé", "Processus 'B' démarré"). Les clients (circusctl, circus-web) s'abonnent (SUB) à ce flux.

Avantage : Permet un monitoring "temps réel" (basé Push/Events) plutôt que "polling" (basé Pull/Request).

1.3 Comparaison : Circus vs. Supervisor

Les deux sont des gestionnaires de processus en Python, mais avec des philosophies différentes.

CritèreSupervisor (Guide Précédent)Circus (Mozilla)
ObjectifStabilité (Gardien "simple").Dynamisme (Gestion "à chaud").
DépendancesPython (pur).Python + ZMQ (Lib C).
CommunicationXML-RPC (Client/Serveur synchrone).ZeroMQ (Pub/Sub asynchrone).
Gestion ConfigStatique (Nécessite reread + update).Dynamique (Supporte addwatcher/rmwatcher à chaud).
Socket ActivationNon.Oui ([socket:x]). (Peut binder Port 80 en root, et passer le FD au worker non-root).
PopularitéExtrêmement populaire (Standard de facto).Moins populaire (Niche).
UsageGérer gunicorn, celery (simple).Gérer gunicorn (scalé, binding socket), applications distribuées.
2.1 Installation (pip)

Circus s'installe via pip (le gestionnaire de paquets Python). Il est recommandé de l'installer dans un environnement virtuel (venv) si vous l'utilisez pour un projet spécifique, ou globalement si c'est un service système.

Dépendances (ZMQ)

Circus dépend de pyzmq, qui lui-même dépend de libzmq (une librairie C). Vous devez souvent installer les dépendances de compilation avant.

# (Debian/Ubuntu)
$ sudo apt-get update
$ sudo apt-get install -y python3-dev python3-pip libzmq3-dev

# (Installation de Circus)
$ sudo pip3 install circus

# (Optionnel : Installer la Web UI)
$ sudo pip3 install circus-web

# Vérifier
$ circusd --version
2.2 Config : circus.ini (Structure)

La configuration de Circus (.ini) est très similaire à celle de Supervisor.

# Fichier: circus.ini

# --- 1. Section Globale [circus] ---
# (Définit les endpoints de communication ZMQ)
[circus]
endpoint = tcp://127.0.0.1:5555
pubsub_endpoint = tcp://127.0.0.1:5556
stats_endpoint = tcp://127.0.0.1:5557
check_delay = 5

# --- 2. Section Watcher (Processus) ---
# (Voir 3.1)
[watcher:my_app]
cmd = /path/to/gunicorn myapp:app
numprocesses = 4
user = www-data

# --- 3. Section Socket (Gestion) ---
# (Voir 4.1)
[socket:web]
host = 127.0.0.1
port = 8080

# --- 4. Section Plugins (Web UI) ---
# (Voir 6.1)
[plugin:httpd]
use = circusweb.circushttpd
port = 8000
2.3 Config : Endpoints (ZMQ)

La section [circus] (globale) définit les endpoints (points de terminaison) ZMQ que le daemon circusd va ouvrir.

DirectiveSocket ZMQRôle
endpointREQ/REP(Le "Contrôleur"). L'endpoint principal où circusctl envoie ses commandes (start, stop...).
pubsub_endpointPUB/SUB(L'"Event Bus"). L'endpoint où circusd publie (broadcast) tous les événements (start, crash...).
stats_endpointPUB/SUB(Optionnel) Endpoint (séparé) qui publie les statistiques (CPU/RAM). (Utilisé par circus-web).
Format (Socket ZMQ)
  • IPC (Local) : (Socket UNIX) ipc:///tmp/circus.sock
  • TCP (Réseau) : tcp://127.0.0.1:5555
3.1 Config : [watcher:x] (Le Programme)

Un Watcher (Observateur) est l'équivalent du [program:x] de Supervisor. C'est le processus à surveiller.

[watcher:my_gunicorn_app]

# (La commande à lancer)
cmd = /path/to/myenv/bin/gunicorn myapp:app --bind ...

# (Le dossier de travail)
working_dir = /srv/myapp

# (Lancer en tant que cet utilisateur)
user = www-data

# (Scaling) Lancer 4 instances (workers) de cette commande
numprocesses = 4

# (Keep-Alive) Redémarrer si ça crash
autorestart = true

# (Logs stdout/stderr)
# (Par défaut, Circus gère les logs via ZMQ/API.
#  On peut les rediriger vers des fichiers :)
stdout_stream.class = FileStream
stdout_stream.filename = /var/log/myapp.out
stderr_stream.class = FileStream
stderr_stream.filename = /var/log/myapp.err
3.2 Watcher : Scaling (numprocesses)

La directive numprocesses (dans [watcher:x]) contrôle le nombre d'instances (processus) de cette commande à lancer.

[watcher:my-worker]
cmd = python worker.py
# (Lance 8 copies de worker.py)
numprocesses = 8

Différence vs PM2 :
numprocesses est "stupide". Il lance N copies. Il n'inclut pas le "Load Balancer (TCP)" magique du cluster mode de PM2.

Si vous lancez numprocesses = 8 sur un serveur web (Gunicorn) sans [socket] (4.3), les 8 processus essaieront de binder le même Port 8000 et échoueront (sauf si Gunicorn gère lui-même le partage, ce qu'il peut faire).

Usage : Idéal pour les workers (Celery, RabbitMQ) qui n'écoutent pas sur un port (ils "pull" depuis une queue).

3.3 Watcher : HA (Restart) & Contrôle

Directives (dans [watcher:x]) pour le "Keep-Alive".

DirectiveDéfautDescription (Équiv. Supervisor)
autorestartfalseSi true, redémarre le processus s'il s'arrête (proprement ou crash). (Équiv. autorestart=true).
max_retries5Nombre de tentatives (startretries). Si -1 = infini.
warmup_delay0Temps (sec) avant de considérer le démarrage "réussi". (startsecs).
max_age-1 (Infini)(Intéressant) Redémarre (Recycle) le worker s'il a tourné pendant X secondes (ex: pour contrer les fuites mémoire).
graceful_timeout30Temps (sec) (stopwaitsecs) pour attendre (SIGTERM) avant de tuer (SIGKILL).
4.1 Config : [socket:x] (Concept)

C'est la fonctionnalité la plus puissante de Circus (similaire à systemd Socket Activation).

La section [socket:NOM] définit un socket que circusd (le daemon) va créer et gérer.

[socket:web_socket]
# (Nom du socket)

# (Type de socket : 'tcp' ou 'unix')
host = 0.0.0.0
port = 8080
# (ou 'path = /tmp/mysocket.sock')

# (Permissions (si socket UNIX))
; mode = 0770

Les [watcher] (3.1) peuvent ensuite "demander" (via use_sockets) à utiliser ce socket (voir 4.3).

4.2 Socket Activation (Héritage de FD)
Le Problème (Permissions)

Pour binder (écouter) le Port 80 (privilégié), Gunicorn (ou votre app) doit être lancé en root (dangereux). Si on le lance en www-data, il ne peut binder que des ports > 1024 (ex: 8000), et il faut un Nginx (Reverse Proxy) devant.

La Solution (Circus Socket - Héritage de File Descriptor)

Vous laissez circusd (qui tourne en root) créer le socket (Port 80) avant de lancer le worker.

Flux (Simplifié) :
1. [circusd (root)] démarre.
2. [circusd (root)] lit [socket:web] (port 80).
3. [circusd (root)] Ouvre (bind) le Port 80.
   (Le Kernel lui donne le File Descriptor (FD) 3).

4. [circusd (root)] lit [watcher:gunicorn] (user=www-data).
5. [circusd (root)] "droppe" ses privilèges (devient www-data).
6. [circusd (www-data)] lance (fork) 'gunicorn'.
7. [circusd (www-data)] passe (hérite) le FD 3
   au processus 'gunicorn' (via env var).

8. [gunicorn (www-data)] démarre et écoute sur le FD 3.

Résultat : L'application (gunicorn) tourne en
non-root (www-data) mais écoute sur un
port privilégié (80).
4.3 Cas d'Usage : Gunicorn + Socket

Exemple complet de configuration (circus.ini) pour Gunicorn (Python) utilisant la "Socket Activation".

[circus]
# ... (endpoints)

# 1. Définir le socket (nommé "web")
[socket:web]
host = 127.0.0.1
port = 8000
# (On peut mettre port = 80 si circusd est root)

# 2. Définir le watcher (Gunicorn)
[watcher:gunicorn_app]

# 3. La commande Gunicorn
#    (Note: Gunicorn est compatible 'systemd'
#     et lit '--fd [num]' pour l'héritage)
cmd = /path/to/myenv/bin/gunicorn myapp:app \
        --workers 4 \
        --fd $(CIRCUS_FD_web)

# 4. (Le 'FD' (File Descriptor) est passé via une
#     variable d'environnement nommée $(CIRCUS_FD_[nom_socket]))

# 5. Spécifier d'utiliser (hériter) le socket
use_sockets = web

# (Le reste de la config)
user = www-data
working_dir = /srv/myapp
autorestart = true
5.1 CLI : circusctl (Shell)

circusctl est l'équivalent de supervisorctl. C'est le client CLI qui se connecte au daemon circusd (via ZMQ).

Commandes (Mode Interactif ou Direct)
$ circusctl
circusctl 0.17.1

circus> status
circus: active
my_app: active
  ├─ 1: active (pid 1234)
  └─ 2: active (pid 1235)

# (Alias pour 'status')
circus> list

# (Statistiques (CPU/MEM) via ZMQ Stats)
circus> stats
...

# (Quitter)
circus> quit
5.2 CLI : Gestion (start/stop/restart)

Commandes pour gérer le cycle de vie d'un watcher.

# (Syntaxe: circusctl [commande] [nom_watcher])

# Démarrer (si 'stopped')
circus> start my_app

# Arrêter (envoie SIGTERM, puis SIGKILL)
circus> stop my_app

# Redémarrer (Stop + Start)
circus> restart my_app

# (Toutes les commandes peuvent cibler un PID spécifique)
circus> restart my_app:1

# Recharger la configuration (reload)
circus> reloadconfig
5.3 CLI : Commandes Dynamiques (Scaling)

La force de Circus (via ZMQ) est sa capacité à modifier la configuration à chaud (dynamiquement), sans reloadconfig.

addwatcher / rmwatcher

Ajoute/Supprime un nouveau processus (Watcher) au daemon en cours d'exécution.

# (Ajoute un nouveau watcher 'mon_script_temporaire')
$ circusctl addwatcher mon_script_temporaire "python script.py"

# (Le watcher est ajouté et démarré)
$ circusctl status
...
mon_script_temporaire: active
...
incr / decr (Scaling)

Augmente ou diminue (dynamiquement) le numprocesses d'un watcher.

(État: my_app (numprocesses=4))

# (Augmente de 2 -> 6 workers)
$ circusctl incr my_app 2

# (Diminue de 1 -> 5 workers)
$ circusctl decr my_app 1
6.1 Outil : circus-web (Web UI)

circus-web (similaire à l'UI de Supervisor) est un plugin qui fournit une interface web simple (basée sur bottle.py) pour monitorer et gérer Circus.

1. Installation (Plugin)
$ pip install circus-web
2. Configuration (circus.ini)

Vous devez "dire" à circusd de charger ce plugin (via la section [plugin]).

[circus]
# (Le endpoint ZMQ 'stats' doit être activé)
stats_endpoint = tcp://127.0.0.1:5557

# ...

# (Charger le plugin Web UI)
[plugin:httpd]
# (Le 'point d'entrée' python du plugin)
use = circusweb.circushttpd
host = 0.0.0.0
port = 8000
# (Optionnel : Auth)
# username = admin
# password = secret

Après redémarrage de circusd, l'UI est accessible sur http://[IP_Serveur]:8000.

6.2 Hooks (Callbacks Python)

C'est une fonctionnalité avancée de Circus (similaire aux "Events" de Supervisor, mais en Python natif).

Vous pouvez définir des "hooks" (callbacks) Python qui sont exécutés à différents moments du cycle de vie du watcher.

[watcher:my_app]
cmd = ...
# (Définit le hook 'before_start'
#  vers la fonction 'mon_hook'
#  dans le module 'hooks.py')
hooks.before_start = myapp.hooks.mon_hook
# Fichier: myapp/hooks.py
# (Le hook reçoit le watcher et d'autres infos)
def mon_hook(watcher, hook_name, **kwargs):
    print(f"Hook '{hook_name}' déclenché pour {watcher.name}!")
    # (Ex: Envoyer une alerte Slack)
    return True # (True = Continuer, False = Annuler l'action)
Hooks Courants
  • before_start / after_start
  • before_stop / after_stop
  • before_restart / after_restart
6.3 API Python (Client)

Puisque Circus utilise ZMQ, il est facile de le piloter (comme circusctl) depuis un script Python.

# (Nécessite 'pip install circus')
import circus

# 1. Se connecter au endpoint ZMQ (REQ/REP)
client = circus.Client(endpoint="tcp://127.0.0.1:5555")

try:
    # 2. Envoyer une commande (Message)
    # (Ex: 'list' = 'status')
    response = client.call("list")
    
    print(response['statuses'])
    # {'my_app': 'active'}
    
    # 3. Envoyer une commande avec args (ex: restart)
    response = client.call("restart", name="my_app")
    
except Exception as e:
    print(f"Erreur: {e}")

Usage : Intégration dans des outils d'automatisation (Ansible, Fabric) ou des interfaces web personnalisées.

7.1 Gestion des Logs (Streams)

La gestion des logs (stdout/stderr) dans Circus est basée sur des "Streams" (flux).

Par défaut (si non configuré), Circus envoie tout au stdout/stderr du daemon circusd lui-même (qui peut être géré par systemd/journald).

Configuration (circus.ini)

Vous pouvez définir des "streams" (pipelines) pour stdout et stderr.

[watcher:my_app]
cmd = ...

# --- Option 1 : Fichiers (FileStream) ---
stdout_stream.class = FileStream
stdout_stream.filename = /var/log/myapp.out
stdout_stream.max_bytes = 10485760 # 10MB
stdout_stream.backup_count = 5

stderr_stream.class = FileStream
stderr_stream.filename = /var/log/myapp.err

# --- Option 2 : Rediriger stdout vers stderr ---
stdout_stream.class = RedirectStream
stdout_stream.to = stderr

# --- Option 3 : ZMQ (Pub/Sub) ---
# (Envoyer les logs sur un endpoint ZMQ)
stdout_stream.class = ZMQStream
7.2 Comparaison : Circus vs. PM2
CritèreCircus (Python)PM2 (Node.js)
Langage CibleGénéraliste (Python). (Gère Gunicorn, Celery...).Spécialisé Node.js.
Clustering (LB Natif)Non. (Lance N copies, mais sans LB L4).Oui (Mode Cluster). (LB L4 Round-Robin natif).
Socket ActivationOui ([socket], --fd).Non (Nécessite Nginx/systemd).
Gestion DynamiqueOui (addwatcher, incr).Non (Nécessite reload de l'Ecosystem).
Startup (Boot)Non (Manuel, via systemd/cron).Oui (pm2 startup).
Reload (Zero-Downtime)Non (restart brutal).Oui (reload).
VerdictBon pour les workers Python, excellent pour le binding de sockets (Gunicorn).Standard de facto pour les apps web Node.js (grâce au Cluster).
7.3 Cheat-sheet : circusctl & circus.ini
CLI (circusctl)
# --- Démarrage (Daemon) ---
# (Lance le daemon en arrière-plan)
$ circusd circus.ini
# (Lance en foreground)
$ circusd --log-output - circus.ini

# --- Client (circusctl) ---
$ circusctl

# (Statut)
circus> status
circus> list

# (Gestion)
circus> start [watcher]
circus> stop [watcher]
circus> restart [watcher]

# (Scaling Dynamique)
circus> incr [watcher] [nb]
circus> decr [watcher] [nb]

# (Config)
circus> reloadconfig
Config (circus.ini)
[circus]
endpoint = tcp://127.0.0.1:5555
pubsub_endpoint = tcp://127.0.0.1:5556
check_delay = 5
include = /etc/circus/conf.d/*.ini

# (Gérer un socket)
[socket:web]
host = 0.0.0.0
port = 80

# (Gérer un watcher)
[watcher:my_app]
cmd = /path/to/gunicorn --fd $(CIRCUS_FD_web)
user = www-data
numprocesses = 4
autorestart = true
use_sockets = web