#10 — rgz-beat
PLANIFIÉ
Priorité: 🔴 CRITIQUE · Type: TYPE A · Conteneur: rgz-beat · Code: app/celery_app.py (même image que #01)
Dépendances: #01 rgz-api, #05 rgz-redis, #04 rgz-db
Description
rgz-beat est l'orchestrateur de tâches planifiées de la plateforme RGZ. Il utilise Celery Beat, le planificateur de tâches périodiques de la bibliothèque Celery, avec Redis comme broker de messages. Ce service est responsable de déclencher toutes les opérations automatisées qui font fonctionner la plateforme en arrière-plan : les sondes de monitoring SLA toutes les 5 minutes, le recalcul des limites de bande passante (DBA), la réconciliation des paiements chaque nuit, les sauvegardes de base de données, et bien d'autres.
Le point crucial de rgz-beat est qu'il utilise exactement la même image Docker que rgz-api (docker/api/Dockerfile). Seule la commande de démarrage diffère : rgz-api lance uvicorn app.main:app, tandis que rgz-beat lance celery -A app.celery_app beat --loglevel=info. Cette approche garantit que le code des tâches Celery (dans app/tasks/) a accès aux mêmes modèles, services, et dépendances que l'API REST. Il n'y a pas de duplication de code.
Les tâches sont réparties dans plusieurs queues spécialisées pour éviter qu'une tâche lourde (comme un pg_dump) ne retarde les sondes de monitoring. Chaque queue est traitée par des workers dédiés (des instances supplémentaires de rgz-api en mode worker). Les queues sont : rgz.monitoring (SNMP, SLA, canary — haute priorité), rgz.qos (DBA recalcul — priorité moyenne), rgz.billing (réconciliation, facturation — priorité normale), rgz.maintenance (backup, firmware — basse priorité), rgz.compliance et rgz.reports.
La règle LL#42 est particulièrement importante : le Dockerfile Python 3.11-slim ne contient pas ps ni pgrep. Le healthcheck Docker ne peut donc pas utiliser pgrep celery. À la place, il faut utiliser le pseudo-système de fichiers /proc : grep -q celery /proc/1/cmdline fonctionne toujours et ne nécessite aucun outil système supplémentaire.
Architecture Interne
rgz-beat (même image que rgz-api)
│
▼
celery -A app.celery_app beat --loglevel=info
│
├── Planificateur Beat — lit le schedule depuis celery_app.py
│ └── Envoie les tâches aux queues Redis à l'heure prévue
│
▼
rgz-redis (broker)
│
├── Queue: rgz.monitoring ←── rgz-worker-monitoring
│ ├── rgz.sla.probe (every 5min)
│ ├── rgz.snmp.poll (every 5min)
│ └── rgz.canary.run (every 5min)
│
├── Queue: rgz.qos ←── rgz-worker-qos
│ └── rgz.dba.recalculate (every 5min)
│
├── Queue: rgz.billing ←── rgz-worker-billing
│ └── rgz.billing.reconcile (daily 00:15)
│
├── Queue: rgz.maintenance ←── rgz-worker-maintenance
│ ├── rgz.backup.pg_dump (daily 03:00)
│ └── rgz.backup.configs (daily 04:00)
│
├── Queue: rgz.compliance ←── rgz-worker-compliance
│ └── rgz.apdp.purge_expired (daily 05:00)
│
└── Queue: rgz.reports ←── rgz-worker-reports
├── rgz.reports.sla_monthly (monthly J+5)
└── rgz.reports.arcep (quarterly)
Chaque worker = instance du conteneur rgz-api avec:
CMD: celery -A app.celery_app worker -Q rgz.monitoring --concurrency=2Configuration
Variables d'environnement
| Variable | Exemple | Description |
|---|---|---|
CELERY_BROKER_URL | redis://:pass@rgz-redis:6379/2 | Redis broker (DB 2) |
CELERY_RESULT_BACKEND | redis://:pass@rgz-redis:6379/2 | Redis pour résultats des tâches |
DATABASE_URL | postgresql+asyncpg://...@rgz-db:5432/rgz | DB pour les tâches ayant besoin de la DB |
CELERY_TASK_ALWAYS_EAGER | false | true uniquement en tests unitaires |
CELERY_TIMEZONE | UTC | Timezone pour le planificateur Beat |
CELERY_WORKER_MAX_TASKS_PER_CHILD | 100 | Restart worker après 100 tâches (anti-leak) |
Fichiers de configuration
app/
├── celery_app.py # App Celery + beat_schedule complet
└── tasks/
├── billing.py # #21 reconciliation daily 00:15
├── sla.py # #25 crédit SLA auto
├── qos.py # #26 DBA recalcul every 5min
├── monitoring.py # #39 SNMP poll every 5min
├── sla_probe.py # #43 ICMP/TCP probe every 5min
├── compliance.py # #49 compliance check
├── ssl_renew.py # #50 Let's Encrypt
├── reports.py # #70 SLA mensuel + #73 trending
├── arcep.py # #71 ARCEP trimestriel
├── rca.py # #72 post-incident RCA
├── forecast.py # #74 revenue forecast
├── backup.py # #75 pg_dump daily 03:00
├── firmware.py # #76 CPE firmware 02h-05h
├── config_backup.py # #77 git backup daily 04:00
├── apdp.py # #78 APDP suppression 90j
└── canary.py # #80 sondes synthétiques every 5mincelery_app.py (structure complète)
from celery import Celery
from celery.schedules import crontab
from app.config import settings
app = Celery(
"rgz",
broker=settings.CELERY_BROKER_URL,
backend=settings.CELERY_RESULT_BACKEND,
include=[
"app.tasks.billing",
"app.tasks.sla",
"app.tasks.qos",
"app.tasks.monitoring",
"app.tasks.sla_probe",
"app.tasks.compliance",
"app.tasks.ssl_renew",
"app.tasks.reports",
"app.tasks.arcep",
"app.tasks.rca",
"app.tasks.forecast",
"app.tasks.backup",
"app.tasks.firmware",
"app.tasks.config_backup",
"app.tasks.apdp",
"app.tasks.canary",
],
)
app.conf.update(
task_serializer="json",
accept_content=["json"],
result_serializer="json",
timezone="UTC",
enable_utc=True,
worker_max_tasks_per_child=100, # Anti-memory leak
task_acks_late=True, # ACK après exécution réussie
task_reject_on_worker_lost=True, # Re-queue si worker crash
worker_prefetch_multiplier=1, # Une tâche à la fois par worker
)
app.conf.beat_schedule = {
# ── Monitoring (every 5min) ──────────────────────────────────
"sla-probe": {
"task": "rgz.sla.probe",
"schedule": 300.0, # 5 minutes
"options": {"queue": "rgz.monitoring"},
},
"snmp-poll": {
"task": "rgz.snmp.poll",
"schedule": 300.0,
"options": {"queue": "rgz.monitoring"},
},
"canary-run": {
"task": "rgz.canary.run",
"schedule": 300.0,
"options": {"queue": "rgz.monitoring"},
},
# ── QoS (every 5min) ────────────────────────────────────────
"dba-recalculate": {
"task": "rgz.dba.recalculate",
"schedule": 300.0,
"options": {"queue": "rgz.qos"},
},
# ── Billing (daily) ──────────────────────────────────────────
"billing-reconcile": {
"task": "rgz.billing.reconcile",
"schedule": crontab(hour=0, minute=15), # daily 00:15 UTC
"options": {"queue": "rgz.billing"},
},
# ── Maintenance (daily) ──────────────────────────────────────
"backup-pg-dump": {
"task": "rgz.backup.pg_dump",
"schedule": crontab(hour=3, minute=0), # daily 03:00 UTC
"options": {"queue": "rgz.maintenance"},
},
"backup-configs": {
"task": "rgz.backup.configs",
"schedule": crontab(hour=4, minute=0), # daily 04:00 UTC
"options": {"queue": "rgz.maintenance"},
},
# ── Compliance (daily) ───────────────────────────────────────
"apdp-purge-expired": {
"task": "rgz.apdp.purge_expired",
"schedule": crontab(hour=5, minute=0), # daily 05:00 UTC
"options": {"queue": "rgz.compliance"},
},
# ── Reports (monthly/quarterly) ──────────────────────────────
"sla-monthly-report": {
"task": "rgz.reports.sla_monthly",
"schedule": crontab(day_of_month=5, hour=8, minute=0), # J+5 08:00
"options": {"queue": "rgz.reports"},
},
}Celery Tasks Planifiées
| Tâche Celery | Schedule | Queue | Outil |
|---|---|---|---|
rgz.sla.probe | every 5min | rgz.monitoring | #43 SLA probe engine |
rgz.snmp.poll | every 5min | rgz.monitoring | #39 SNMP poller |
rgz.canary.run | every 5min | rgz.monitoring | #80 Canary test |
rgz.dba.recalculate | every 5min | rgz.qos | #26 DBA daemon |
rgz.billing.reconcile | daily 00:15 | rgz.billing | #21 Réconciliation |
rgz.backup.pg_dump | daily 03:00 | rgz.maintenance | #75 Backup |
rgz.backup.configs | daily 04:00 | rgz.maintenance | #77 Config backup |
rgz.apdp.purge_expired | daily 05:00 | rgz.compliance | #78 APDP |
rgz.reports.sla_monthly | monthly J+5 | rgz.reports | #70 Rapport SLA |
rgz.reports.arcep | quarterly | rgz.reports | #71 ARCEP |
rgz.ssl.renew | daily 02:00 | rgz.maintenance | #50 SSL cert |
rgz.firmware.update | daily 02:00-05:00 | rgz.maintenance | #76 Firmware |
Healthcheck
# LL#42 : ps/pgrep absents dans python:3.11-slim
# Utiliser /proc (toujours disponible, pas besoin d'outils système)
grep -q celery /proc/1/cmdline && echo "OK" || echo "FAIL"
# Depuis l'hôte
docker exec rgz-beat grep -q celery /proc/1/cmdline && echo "Celery running"
# Inspecter les tâches planifiées
docker exec rgz-beat celery -A app.celery_app inspect scheduled
# Voir les workers actifs
docker exec rgz-beat celery -A app.celery_app inspect active
# Stats complètes
docker exec rgz-beat celery -A app.celery_app inspect statsSécurité
| Règle | Implémentation |
|---|---|
| LL#42 Healthcheck | grep -q celery /proc/1/cmdline — pas de dépendance à ps/pgrep |
| LL#43 127.0.0.1 | Si le healthcheck fait une requête HTTP, utiliser 127.0.0.1 pas localhost |
| LL#33 restart | restart: unless-stopped dans docker-compose |
| task_acks_late | Les tâches critiques (billing) sont ACK seulement après succès — pas de perte |
| Redis ACL | Le broker Celery utilise un compte Redis dédié rgz-beat (SEC-07) |
| SEC-09 Ressources | Limits CPU/RAM définies dans docker-compose pour éviter le starvation |
Commandes Utiles
# Démarrage avec rebuild
docker compose -f /home/claude-dev/RGZ/docker-compose.core.yml up -d --build rgz-beat
# Logs Beat (planificateur)
docker logs rgz-beat -f --tail=100
# Status conteneur
docker inspect rgz-beat --format='{}'
# Lister toutes les tâches planifiées
docker exec rgz-beat celery -A app.celery_app inspect scheduled
# Déclencher une tâche manuellement (forcer une réconciliation)
docker exec rgz-beat celery -A app.celery_app call rgz.billing.reconcile
# Déclencher un backup immédiat
docker exec rgz-beat celery -A app.celery_app call rgz.backup.pg_dump
# Purger une queue
docker exec rgz-beat celery -A app.celery_app purge -Q rgz.maintenance
# Flower (monitoring UI Celery) — si déployé
# curl http://localhost:5555/api/tasks
# Vérifier l'état des workers
docker exec rgz-beat celery -A app.celery_app inspect active_queues
# Debug : exécuter une tâche en mode eager (synchrone, sans broker)
docker exec rgz-beat python -c "
from app.celery_app import app
app.conf.task_always_eager = True
from app.tasks.sla_probe import run_sla_probe
result = run_sla_probe.apply()
print('Result:', result.get())
"Implémentation TODO
- [x] Service défini dans
docker-compose.core.yml(même image que rgz-api) - [x] Variables d'env dans
.env.example - [ ]
app/celery_app.py— App Celery + beat_schedule complet (12 tâches planifiées) - [ ]
app/tasks/billing.py— #21 réconciliation 00:15 - [ ]
app/tasks/sla.py— #25 crédit SLA auto - [ ]
app/tasks/qos.py— #26 DBA recalcul every 5min - [ ]
app/tasks/monitoring.py— #39 SNMP poll - [ ]
app/tasks/sla_probe.py— #43 ICMP/TCP probe - [ ]
app/tasks/compliance.py— #49 compliance check - [ ]
app/tasks/ssl_renew.py— #50 Let's Encrypt - [ ]
app/tasks/reports.py— #70 SLA mensuel + #73 trending - [ ]
app/tasks/arcep.py— #71 ARCEP trimestriel - [ ]
app/tasks/backup.py— #75 pg_dump - [ ]
app/tasks/firmware.py— #76 CPE firmware - [ ]
app/tasks/config_backup.py— #77 git backup - [ ]
app/tasks/apdp.py— #78 APDP suppression - [ ]
app/tasks/canary.py— #80 sondes synthétiques - [ ] Tests unitaires tasks avec
CELERY_TASK_ALWAYS_EAGER=true - [ ] Dashboard Flower déployé pour monitoring visuel des queues
Dernière mise à jour: 2026-02-21