Bibliothèque Python : Celery et APScheduler
La bibliothèque Python de SilenceWatch surveille les jobs planifiés de votre application sans que vous déclariez quoi que ce soit : elle lit les jobs de votre planificateur, les déclare avec leur vraie planification, et envoie un signal autour de chaque exécution. Elle est sous licence Apache 2.0, volontairement, pour pouvoir être embarquée dans n’importe quelle application.
| Planificateur | Installation | Ce qu’elle lit |
|---|---|---|
| Celery beat | pip install "silencewatch[celery]" |
le beat_schedule (crontab, timedelta, secondes) |
| APScheduler 3 | pip install "silencewatch[apscheduler]" |
les jobs du planificateur (déclencheurs cron et interval) |
Python 3.9 ou plus récent. Aucune autre dépendance.
Mise en route
Section intitulée « Mise en route »Créez la clé d’API dans Réglages › Clés d’API de votre projet et donnez-la au processus :
export SILENCEWATCH_API_KEY=sw_...# export SILENCEWATCH_URL=https://watch.example.com # seulement si vous hébergez SilenceWatch vous-mêmefrom celery import Celeryfrom celery.schedules import crontabfrom silencewatch.integrations.celery import instrument
app = Celery("shop")app.conf.beat_schedule = { "nightly-backup": {"task": "shop.tasks.backup", "schedule": crontab(hour=2, minute=0)}, "sync-prices": {"task": "shop.tasks.sync_prices", "schedule": 900.0},}instrument(app)Un worker déclare la planification à son démarrage, dans le processus principal avant que son pool ne se divise, si bien que chaque processus enfant a les clés de ping. Les signaux sont envoyés par le worker qui exécute chaque tâche planifiée.
APScheduler
Section intitulée « APScheduler »from apscheduler.schedulers.background import BackgroundSchedulerfrom silencewatch.integrations.apscheduler import instrument
scheduler = BackgroundScheduler()scheduler.add_job(backup, "cron", hour=2, id="nightly-backup")scheduler.add_job(sync_prices, "interval", minutes=15, id="sync-prices")instrument(scheduler)scheduler.start()Ce qu’elle fait
Section intitulée « Ce qu’elle fait »Au démarrage, en un seul appel HTTP, chaque job trouvé est déclaré à POST /api/v1/checks/sync et les clés de ping renvoyées sont gardées en mémoire. Autour de chaque exécution :
POST /p/<clé>/start au début de l’exécutionPOST /p/<clé>/0?duration_ms=1234 quand elle se terminePOST /p/<clé>/fail?duration_ms=1234 quand elle lève une exceptionChaque job reçoit une identité stable qui le rattache à son historique malgré les redémarrages, les redéploiements et les renommages :
| Intégration | Clé |
|---|---|
| Celery | celery:shop.tasks.backup (le nom de la tâche) |
| APScheduler | apscheduler:nightly-backup (l’identifiant du job ; sans lui, apscheduler:app.jobs:backup, la fonction) |
Un job qui disparaît du code est marqué orphelin, jamais supprimé. Si plusieurs intégrations tournent dans un même processus, tout ce qu’elles trouvent est déclaré en une seule requête.
Configuration
Section intitulée « Configuration »Chaque réglage est un argument nommé (instrument(app, api_key="…", environment="staging")) et une variable d’environnement. L’argument l’emporte. Une durée dans l’environnement est un nombre de secondes.
| Argument | Variable d’environnement | Valeur par défaut |
|---|---|---|
api_key |
SILENCEWATCH_API_KEY |
aucune : rien n’est envoyé |
base_url |
SILENCEWATCH_URL |
https://app.silencewatch.com |
environment |
SILENCEWATCH_ENVIRONMENT |
production |
enabled |
SILENCEWATCH_ENABLED |
true ; false retire tout |
default_grace |
SILENCEWATCH_DEFAULT_GRACE |
5 minutes, pour les jobs qui n’en déclarent pas |
auto_register |
SILENCEWATCH_AUTO_REGISTER |
true ; false pour gérer les checks à la main |
report_start |
SILENCEWATCH_REPORT_START |
true ; false pour n’envoyer que le résultat |
timeout |
SILENCEWATCH_TIMEOUT |
2 secondes par appel, volontairement court |
queue_capacity |
SILENCEWATCH_QUEUE_CAPACITY |
1000 ; les plus anciens sont abandonnés quand elle est pleine |
timezone |
SILENCEWATCH_TIMEZONE |
UTC, pour les jobs cron dont la planification n’en porte pas |
prune |
SILENCEWATCH_PRUNE |
true |
per_instance_jobs |
SILENCEWATCH_PER_INSTANCE_JOBS |
aucune ; jobs surveillés sur chaque instance, voir plus bas |
instance_id |
SILENCEWATCH_INSTANCE_ID |
aucune ; le nom stable de l’instance |
prune marque comme orphelins tous les checks du projet et de l’environnement que la déclaration omet. Utilisez un environnement par application ; si plusieurs services partagent un même projet et un même environnement, mettez prune=False, sinon chacun marquerait les checks des autres comme orphelins.
Plusieurs instances
Section intitulée « Plusieurs instances »Quand une application tourne en plusieurs processus ou serveurs, tous déclarent les mêmes jobs dans le même environnement : chaque job est donc un seul check pour tous, en bonne santé tant que l’un d’eux le signale.
- Celery (et django-celery-beat) : beat confie chaque exécution à un seul worker, quel qu’il soit, et le check l’entend. C’est la bonne règle, et la seule.
- APScheduler tourne dans chaque processus. Démarré dans chaque worker Gunicorn ou chaque réplique, un job s’exécute une fois par processus, et le check reste en bonne santé tant que l’un d’eux l’exécute : un processus dont le planificateur est bloqué passe inaperçu pendant qu’un autre travaille. Si le job doit tourner une seule fois, démarrez le planificateur dans un seul processus ; s’il doit tourner dans chacun, lisez la suite.
Les jobs qui doivent tourner dans chaque instance (vider un cache local, supprimer des fichiers temporaires) peuvent être surveillés sur chacune :
instrument(scheduler, per_instance_jobs=["apscheduler:evict-cache", "apscheduler:tmp-*"], instance_id=os.environ["HOSTNAME"])ou SILENCEWATCH_PER_INSTANCE_JOBS=apscheduler:evict-cache,apscheduler:tmp-* et
SILENCEWATCH_INSTANCE_ID. Ces jobs sont déclarés dans un environnement propre à l’instance,
production.web-1 : chaque instance a son check, et celle qui se tait est vue pendant que les
autres signalent. Un * final désigne un préfixe ; les autres jobs restent partagés. Les tâches
Celery ne sont jamais surveillées par instance, puisqu’un seul worker exécute chaque exécution :
la bibliothèque le signale et les garde partagées.
instance_id n’a pas de valeur par défaut, et doit rester le même d’un redémarrage et d’un
déploiement à l’autre (le nom d’un pod de StatefulSet, le nom d’hôte d’un serveur) : un nom qui
change à chaque déploiement laisserait des checks que plus rien ne signale, et qui tomberaient en
panne. Sans lui, les jobs listés sont surveillés comme partagés, avec un avertissement. Quand vous
retirez une instance pour de bon, supprimez ses checks.
Les règles qu’elle ne brisera pas
Section intitulée « Les règles qu’elle ne brisera pas »- Elle ne fait jamais échouer votre job. Toute erreur réseau ou HTTP est avalée et journalisée en
WARNING. Votre tâche s’exécute exactement une fois, et son résultat et son exception sont intacts. - Elle ne bloque jamais votre job. Envoyer un signal est un ajout à une file traité par un thread démon. Un SilenceWatch injoignable ne coûte rien à votre job.
- Elle est bornée. La file contient au plus
queue_capacitysignaux ; au-delà, les plus anciens sont abandonnés, car l’état le plus récent est celui qui compte. Les avertissements sont limités à un par minute. - Elle se dégrade en silence. Sans clé, sans réseau, sans serveur : l’application démarre et tourne exactement comme sans la bibliothèque. Un processus démarré pendant que SilenceWatch était injoignable se déclare à son premier signal, au plus une fois par minute.
- Elle résiste au fork. Le pool prefork de Celery et gunicorn se divisent après le démarrage de la bibliothèque ; chaque enfant démarre sa propre file et son propre thread.
enabled=Falseretire tout le mécanisme : pas de découverte, pas de crochets, pas de threads.
- Celery. Une tâche planifiée par plusieurs entrées beat est déclarée à partir de celle qui se déclenche le plus souvent. Un
.delay()manuel d’une tâche planifiée envoie aussi un signal, car un worker ne peut pas le distinguer d’un déclenchement planifié. Les planifications solaires ne sont pas lues. Les tâches périodiques gardées en base pardjango-celery-beatsont lues, sans rien configurer, et une modification faite pendant que les workers tournent est déclarée à nouveau en moins d’une minute. Un crontab qui restreint à la fois le jour du mois et le jour de la semaine se déclenche quand les deux correspondent dans Celery et quand l’un des deux correspond dans cron ; le journal le signale. - APScheduler. Version 3 uniquement (la version 4 a une autre API). La durée d’un job inclut le temps passé à attendre un exécuteur. Les déclencheurs
dateponctuels, les déclencheurs combinésand/oret les champs cronweek,yearet « troisième vendredi » ne s’expriment pas et sont ignorés avec un avertissement. Les jobs ajoutés ou retirés après le démarrage sont déclarés de nouveau deux secondes plus tard. - Les jobs de moins de 30 secondes sont déclarés avec une période de 30 secondes, la plus courte acceptée, et un avertissement les nomme.
- Les expressions cron que le serveur ne sait pas évaluer sont ignorées avec un avertissement plutôt que de faire échouer toute la déclaration.