Celery Beat est le planificateur qui transforme « envoyer le rapport quotidien à 06h00 » en « mettre cette tâche dans le broker toutes les 24 heures ». C’est un processus séparé, à instance unique — jamais deux — et il lui faut sa propre unité systemd, sa propre politique de redémarrage et des garde-fous explicites contre l’exécution de copies multiples. Cet article câble Beat avec django-celery-beat pour que les plannings vivent en base et soient modifiables sans déploiement, et ajoute un garde-fou d’instance unique pour les configurations multi-hôtes.
Comment vérifier
Confirmer que Beat est le seul planificateur en cours et que les dernières expéditions sont bien arrivées :
systemctl status celery-beat.service --no-pager | head
ps -eo pid,etime,cmd | grep '[c]elery.*beat' | head
redis-cli -h 127.0.0.1 -n 0 LLEN celery
journalctl -u celery-beat -n 100 --no-pager | grep -E 'Scheduler|Sending due task'
ls -la /var/lib/celery-beat/celerybeat-schedule*
Deux processus Beat sur des hôtes différents est la source la plus fréquente des bugs d’exécution dupliquée. La ligne ps doit retourner un PID, pas deux.
Ce qui se passe
Beat est un planificateur mono-processus qui se réveille au tic de planification, regarde la définition du planning, et expédie les tâches dues au broker. La planification elle-même se définit de trois façons : en code via app.conf.beat_schedule, dans un fichier celerybeat-schedule adossé à SQLite/fichier, ou en base via django-celery-beat. La troisième option est celle qui passe à l’échelle opérationnellement — les admins peuvent modifier les expressions cron dans l’admin Django sans déploiement, et il y a une source unique de vérité.
Beat ne doit pas tourner deux fois. Deux processus Beat expédieront la même tâche au même moment et vous enverrez deux copies du rapport quotidien. Il n’y a pas de coordination intégrée — la conception suppose qu’on en exécute exactement un. Dans un déploiement multi-hôtes, cela veut dire choisir un hôte et faire tourner Beat seulement sur cet hôte ; ou utiliser un outil comme redis-lock ou une chaîne systemd OnFailure= pour garantir un basculement mutuellement exclusif.
L’état que Beat garde entre les tics (horodatages de dernière exécution) vit soit dans un fichier soit dans une table de base. Avec django-celery-beat, la table django_celery_beat_periodictask détient le planning et django_celery_beat_periodictasks a une seule ligne dont last_update sert de drapeau « table sale » — Beat la sonde pour savoir quand recharger.
La procédure
-
Installer
django-celery-beatet appliquer les migrations :/opt/venvs/app/bin/pip install 'django-celery-beat==2.6.*' /opt/venvs/app/bin/python manage.py migrate django_celery_beat -
L’ajouter à
INSTALLED_APPSet configurer Celery pour utiliser le scheduler base dansapp/settings/prod.py:INSTALLED_APPS = [ # ... "django_celery_beat", ] CELERY_BEAT_SCHEDULER = "django_celery_beat.schedulers:DatabaseScheduler" -
Créer
/etc/systemd/system/celery-beat.service:[Unit] Description=Planificateur Celery Beat After=network.target redis-server.service postgresql.service Requires=redis-server.service [Service] Type=simple User=deploy Group=deploy WorkingDirectory=/var/www/app EnvironmentFile=/etc/celery-app/env StateDirectory=celery-beat StateDirectoryMode=0750 ExecStart=/opt/venvs/app/bin/celery -A app.celery beat \ --schedule=/var/lib/celery-beat/celerybeat-schedule \ --pidfile=/run/celery-beat.pid \ --loglevel=INFO Restart=on-failure RestartSec=5 [Install] WantedBy=multi-user.targetStateDirectory=est la façon dont systemd crée/var/lib/celery-beat/avec le bon propriétaire — pas de dansemkdirpluschownmanuel. -
Activer et démarrer :
sudo systemctl daemon-reload sudo systemctl enable --now celery-beat.service journalctl -u celery-beat -n 40 --no-pager -
Ajouter un planning via l’admin Django ou par programmation :
from django_celery_beat.models import CrontabSchedule, PeriodicTask sched, _ = CrontabSchedule.objects.get_or_create( minute="0", hour="6", day_of_week="*", day_of_month="*", month_of_year="*", timezone="UTC", ) PeriodicTask.objects.get_or_create( crontab=sched, name="daily-report", task="app.tasks.send_daily_report", ) -
Dans une configuration multi-hôtes, faire tourner Beat sur exactement un hôte et le garder avec un script de basculement pair. Le motif minimal est une coordination de style
BindsTo=via un verrou Redis partagé acquis surExecStartPre=:ExecStartPre=/usr/local/bin/celery-beat-lock acquire ExecStopPost=/usr/local/bin/celery-beat-lock releasecelery-beat-lockest un petit script quiSETNXune clé avec un TTL et sort en non-zéro si elle existe déjà.
Pièges courants
- Deux processus Beat sur des hôtes différents : chaque tâche se déclenche deux fois. Il n’y a pas d’« élection de leader » dans Beat — vous devez imposer l’instance unique via systemd, un outil HA ou un verrou pair.
- Mélanger
beat_schedule=en code etdjango-celery-beaten même temps : Beat ne lit que le scheduler configuré ; les entrées de l’autre source sont silencieusement ignorées, et les opérateurs perdent un après-midi à se demander pourquoi une édition de planning n’a pas pris. - Un décalage de fuseau entre
TIME_ZONEde Django,timezonede Celery et le planning crontab — symptôme : des tâches qui se déclenchent N heures à côté. Tout figer en UTC et convertir à l’affichage. - Beat saute un tic en surcharge : si une tâche avec
last_run_at=now()dure plus que son intervalle, le tic suivant arrive avant la fin du premier. Soit élargir l’intervalle, soit réglerevery_minute_run=Falsesur une tâche qui peut être saisonnée. - Oublier
StateDirectory=: au redémarrage, Beat lit un fichier de planning vide et décide que chaque tâche est due « tout de suite », déclenchant le lot quotidien complet en une seconde. Utiliser la directive systemd pour que le fichier de planning survive à un redémarrage avec la bonne propriété.
Pour le côté worker de la pile, voir Celery avec un backend Redis. La pratique d’opérations infogérées de Stack Harbor fait tourner Beat comme service systemd dédié sur un hôte désigné par environnement, avec un script de basculement pair et des alertes qui se déclenchent si Beat est silencieux plus de 90 secondes — la panne Celery la plus courante pour laquelle nous sommes paginés.