| name | cron-job-designer |
| description | Conception de tâches planifiées — cron expressions, scheduling patterns, monitoring et idempotence. Se déclenche avec "cron", "tâche planifiée", "scheduled task", "cron expression", "job scheduler", "crontab". |
Cron Job Designer
Workflow
1. Qualifier le besoin
Avant d'écrire une expression cron, réponds à ces questions :
| Question | Impact |
|---|
| Fenêtre d'exécution stricte (ex. avant 6h) ? | Timeout max à fixer |
| Idempotent par nature ou à construire ? | Stratégie de verrou |
| Exécution multi-instances (k8s, cloud) ? | Leader election |
| Dépendance externe (API, DB, SFTP) ? | Circuit breaker + retry |
| Fréquence < 1 min ? | Cron inadapté → message queue |
2. Concevoir l'expression cron
Syntaxe standard (5 champs) :
┌── minute (0-59)
│ ┌── heure (0-23)
│ │ ┌── jour du mois (1-31)
│ │ │ ┌── mois (1-12)
│ │ │ │ ┌── jour de la semaine (0-6, 0=dim)
│ │ │ │ │
* * * * *
Exemples copiables :
7 2 * * *
*/15 8-18 * * 1-5
30 3 * * 1 [ $(date +\%d) -le 7 ] && /path/to/script.sh
55 23 28-31 * * [ $(date +\%d) -eq $(cal | awk 'NF{last=$NF} END{print last}') ]
17 * * * *
Valideur en ligne : crontab.guru — toujours vérifier avant déploiement.
Syntaxes étendues (systemd, cloud) :
OnCalendar=Mon *-*-* 03:17:00
rate(1 hour)
cron(17 2 * * ? *)
schedule: "17 2 * * *"
3. Choisir l'outil adapté
| Contexte | Outil recommandé | Remarque |
|---|
| Linux standalone | cron / systemd timer | systemd > cron : logs journald, retry natif |
| .NET / ASP.NET Core | Hangfire ou Quartz.NET | Hangfire = dashboard UI intégré |
| Java / Spring | Quartz ou @Scheduled | Quartz pour clustering |
| Node.js | node-cron ou Bull | Bull si queue nécessaire |
| Python | Celery Beat ou APScheduler | APScheduler pour scripts simples |
| Kubernetes | CronJob k8s | concurrencyPolicy: Forbid par défaut |
| Azure | Azure Functions Timer ou Logic Apps | Timer Trigger = NCRONTAB (6 champs) |
| AWS | EventBridge Scheduler | Remplace CloudWatch Events |
| GCP | Cloud Scheduler | HTTP target ou Pub/Sub |
4. Garantir l'idempotence
Patterns selon le cas :
Marqueur en base (le plus simple) :
INSERT INTO job_executions (job_name, run_date, status)
VALUES ('daily-report', CAST(GETDATE() AS DATE), 'running')
UPDATE job_executions SET status = 'done', ended_at = GETDATE()
WHERE job_name = 'daily-report' AND run_date = CAST(GETDATE() AS DATE)
Verrou Redis (multi-instances) :
import redis
r = redis.Redis()
lock = r.set("job:daily-report", "1", nx=True, ex=300)
if not lock:
print("Job already running, skip")
exit(0)
try:
run_job()
finally:
r.delete("job:daily-report")
Hangfire (.NET) — DisableConcurrentExecution :
[DisableConcurrentExecution(timeoutInSeconds: 600)]
public void DailyReport() { ... }
5. Gérer les erreurs et les retries
Stratégie par défaut :
- 3 tentatives avec backoff exponentiel : 1 min → 5 min → 15 min
- Alerte dès le 1er échec (ne pas attendre le 3e)
- Dead letter : log détaillé + ticket automatique après épuisement des retries
@app.task(bind=True, max_retries=3, default_retry_delay=60)
def daily_sync(self):
try:
sync()
except TemporaryError as exc:
raise self.retry(exc=exc, countdown=60 * (2 ** self.request.retries))
Décision circuit breaker : si le job dépend d'une API externe, ne pas reessayer
indéfiniment — ouvrir le circuit après 3 échecs consécutifs et attendre 10 min.
6. Configurer le monitoring
Métriques minimales à collecter :
job_started_at, job_ended_at, durée d'exécution
- Statut :
success / failed / skipped
- Nombre d'enregistrements traités
{"event":"job_start","job":"daily-report","ts":"2026-06-24T02:17:00Z"}
{"event":"job_end","job":"daily-report","ts":"2026-06-24T02:19:43Z","status":"success","records":1542,"duration_s":163}
Healthchecks.io / Cronitor : ping HTTP à la fin de chaque exécution réussie.
Si le ping n'arrive pas dans la fenêtre attendue → alerte automatique.
curl -fsS --retry 3 https://hc-ping.com/UUID > /dev/null
Alerte sur exécution manquée : configurer un timeout = période × 1.5 (ex. job
quotidien → alerte si pas de ping en 36h).
7. Mécanisme de déclenchement manuel
Chaque job doit pouvoir s'exécuter hors du scheduler :
./scripts/daily-report.sh --force
BackgroundJob.Enqueue(() => new DailyReportJob().Run());
kubectl create job daily-report-manual --from=cronjob/daily-report
Anti-patterns et pièges
Exécution aux heures rondes — 0 * * * * au lieu de 17 * * * * :
toutes les instances cloud redémarrent souvent en début d'heure → pics de charge.
Pas de timeout — un job bloqué occupe un slot indéfiniment et empêche le
suivant de s'exécuter. Toujours fixer activeDeadlineSeconds (k8s) ou un
CancellationToken avec timeout.
Skip silencieux — si concurrencyPolicy: Forbid et que le job précédent
tourne encore, k8s skip sans alerte. Monitorer kube_cronjob_missed_schedule_time.
Tâche trop fine — des milliers de micro-crons pour traiter chaque ligne
indépendamment. Préférer un batch unique qui parcourt les lignes en boucle.
Dépendance à l'heure locale — cron tourne en UTC sur la plupart des serveurs.
Un job "à 8h du matin" en heure d'été Europe/Paris → 6 * * * * en UTC.
Documenter la timezone et utiliser TZ=Europe/Paris dans crontab ou le scheduler.
Accumulation de jobs en attente — si un job rate et que les retries s'accumulent,
la queue explose. Mettre un max_retries + TTL sur chaque job.
Bonnes pratiques 2026
- Préférer systemd timers à cron sur Linux : logs journald,
OnFailure=, RandomizedDelaySec intégré.
- En environnement cloud-native, ne pas dockeriser un cron — utiliser un CronJob k8s ou le scheduler cloud natif.
- Stocker les expressions cron dans le code (Infrastructure as Code), pas dans une UI manuelle.
- Tester les expressions avec
--dry-run ou en environnement de staging avec une fréquence augmentée temporairement.
- Documenter chaque job : expression cron, timezone, durée attendue, propriétaire, impact si manqué.