Skip to content

#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=2

Configuration

Variables d'environnement

VariableExempleDescription
CELERY_BROKER_URLredis://:pass@rgz-redis:6379/2Redis broker (DB 2)
CELERY_RESULT_BACKENDredis://:pass@rgz-redis:6379/2Redis pour résultats des tâches
DATABASE_URLpostgresql+asyncpg://...@rgz-db:5432/rgzDB pour les tâches ayant besoin de la DB
CELERY_TASK_ALWAYS_EAGERfalsetrue uniquement en tests unitaires
CELERY_TIMEZONEUTCTimezone pour le planificateur Beat
CELERY_WORKER_MAX_TASKS_PER_CHILD100Restart 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 5min

celery_app.py (structure complète)

python
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 CeleryScheduleQueueOutil
rgz.sla.probeevery 5minrgz.monitoring#43 SLA probe engine
rgz.snmp.pollevery 5minrgz.monitoring#39 SNMP poller
rgz.canary.runevery 5minrgz.monitoring#80 Canary test
rgz.dba.recalculateevery 5minrgz.qos#26 DBA daemon
rgz.billing.reconciledaily 00:15rgz.billing#21 Réconciliation
rgz.backup.pg_dumpdaily 03:00rgz.maintenance#75 Backup
rgz.backup.configsdaily 04:00rgz.maintenance#77 Config backup
rgz.apdp.purge_expireddaily 05:00rgz.compliance#78 APDP
rgz.reports.sla_monthlymonthly J+5rgz.reports#70 Rapport SLA
rgz.reports.arcepquarterlyrgz.reports#71 ARCEP
rgz.ssl.renewdaily 02:00rgz.maintenance#50 SSL cert
rgz.firmware.updatedaily 02:00-05:00rgz.maintenance#76 Firmware

Healthcheck

bash
# 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 stats

Sécurité

RègleImplémentation
LL#42 Healthcheckgrep -q celery /proc/1/cmdline — pas de dépendance à ps/pgrep
LL#43 127.0.0.1Si le healthcheck fait une requête HTTP, utiliser 127.0.0.1 pas localhost
LL#33 restartrestart: unless-stopped dans docker-compose
task_acks_lateLes tâches critiques (billing) sont ACK seulement après succès — pas de perte
Redis ACLLe broker Celery utilise un compte Redis dédié rgz-beat (SEC-07)
SEC-09 RessourcesLimits CPU/RAM définies dans docker-compose pour éviter le starvation

Commandes Utiles

bash
# 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

PROJET MOSAÏQUE — 81 outils, 22 conteneurs, 500+ revendeurs WiFi Zone