Besoin d’exécuter une commande ou un script de façon récurrente dans votre cluster Kubernetes, comme vous le feriez avec cron sur Linux ? Les CronJobs K8s sont faits pour ça. Dans cet article, on voit comment créer un CronJob robuste, éviter les chevauchements, paramétrer l’historique, gérer les échecs et dépanner. On fera aussi le lien avec cron classique sur Linux pour migrer sereinement.
Dans cet article :
- TL;DR : un CronJob minimal qui fonctionne
- Différences entre CronJob K8s et cron Linux
- Création et options importantes (
concurrencyPolicy,startingDeadlineSeconds,timeZone) - Exemples utiles et patterns courants
- Débogage et observabilité
- Migration depuis cron classique vers CronJob
Pré-requis :
- Un cluster Kubernetes (1.27+ recommandé pour
spec.timeZone) kubectlconfiguré- Si vous débutez avec cron côté Linux, lisez d’abord Linux : Programmer une tâche avec cron
TL;DR : un CronJob minimal
apiVersion: batch/v1
kind: CronJob
metadata:
name: hello
spec:
schedule: "*/5 * * * *" # toutes les 5 minutes
concurrencyPolicy: Forbid # évite les chevauchements
successfulJobsHistoryLimit: 3
failedJobsHistoryLimit: 1
jobTemplate:
spec:
backoffLimit: 2 # réessais si échec (niveau Job)
template:
spec:
restartPolicy: OnFailure
containers:
- name: hello
image: busybox:1.36
args: ["sh", "-c", "date; echo Hello from K8s CronJob"]
- Appliquer :
kubectl apply -f cronjob.yaml - Lister :
kubectl get cronjobspuiskubectl get jobsetkubectl get pods - Logs :
kubectl logs <pod>
CronJob vs cron Linux
Points communs :
- Syntaxe d’horaire type crontab :
* * * * *(minute heure jour mois jour_sem)
Différences clés :
- Exécution dans un Pod (conteneurisé), pas sur l’hôte.
- Gestion des chevauchements via
concurrencyPolicy(Allow / Forbid / Replace). - Historique conservé via
successfulJobsHistoryLimitetfailedJobsHistoryLimit. startingDeadlineSecondsrattrape les exécutions manquées si le contrôleur était indisponible.- Temps et fuseau : Kubernetes 1.27 ou plus supporte
spec.timeZone(IANA, ex:Europe/Paris). Sinon, fuseau du contrôleur.
Pour réviser la syntaxe cron et les pièges, voyez l’article Linux mentionné plus haut.
Créer un CronJob
- Choisir une image conteneur qui contient vos dépendances (ou votre appli). Privilégiez des tags immuables (ex:
:1.36plutôt que:latest). - Définir l’horaire
spec.schedule(style crontab). - Empêcher les doublons avec
spec.concurrencyPolicy: Forbid(ouReplace). - Régler l’historique et les réessais :
successfulJobsHistoryLimit,failedJobsHistoryLimit,jobTemplate.spec.backoffLimit. - Gérer le cas “rattrapage” via
spec.startingDeadlineSeconds. - Définir
resources(limites / requests), variables d’environnement, ConfigMap/Secret, et unserviceAccountNamesi besoin de permissions.
Exemple un peu plus complet :
apiVersion: batch/v1
kind: CronJob
metadata:
name: rapport-quotidien
spec:
schedule: "0 2 * * *" # tous les jours à 02:00
timeZone: Europe/Paris # Kubernetes >= 1.27
concurrencyPolicy: Forbid
startingDeadlineSeconds: 120
successfulJobsHistoryLimit: 3
failedJobsHistoryLimit: 2
suspend: false
jobTemplate:
spec:
backoffLimit: 3
activeDeadlineSeconds: 900 # tue le job au-delà de 15 min
template:
spec:
serviceAccountName: job-reporter
restartPolicy: Never
containers:
- name: app
image: ghcr.io/monorg/rapport:1.0.4
imagePullPolicy: IfNotPresent
env:
- name: APP_ENV
value: production
- name: DB_HOST
valueFrom:
secretKeyRef:
name: app-secrets
key: db_host
resources:
requests: { cpu: "100m", memory: "128Mi" }
limits: { cpu: "500m", memory: "512Mi" }
Options importantes à connaître
schedule: expression cron standard.timeZone(1.27+) : fuseau IANA (ex:Europe/Paris). Facilite les changements d’heure.concurrencyPolicy:Allow(par défaut),Forbid(n’autorise pas un nouveau job si le précédent est encore en cours),Replace(remplace le précédent).startingDeadlineSeconds: délai maximum pour rattraper une exécution manquée.suspend: met le CronJob en pause (stoppe les planifications futures, n’arrête pas un job déjà démarré).successfulJobsHistoryLimit/failedJobsHistoryLimit: nombre de Jobs conservés.jobTemplate.spec.backoffLimit: nombre de réessais si le Pod échoue.jobTemplate.spec.activeDeadlineSeconds: tue le Job au-delà d’une durée.
Exemples utiles
Toutes les 5 minutes, sans chevauchement :
spec:
schedule: "*/5 * * * *"
concurrencyPolicy: Forbid
Le lundi à 09:00, remplacement du job en cours si retard :
spec:
schedule: "0 9 * * 1"
concurrencyPolicy: Replace
startingDeadlineSeconds: 300
Job Python avec venv embarqué dans l’image :
containers:
- name: job
image: ghcr.io/monorg/worker:2.1.0
args: ["python", "-m", "app.jobs.recalcule"]
Export JSON et traitement en CLI (pratique avec jq) :
kubectl get cronjob -o json | jq '.items[] | {name: .metadata.name, schedule: .spec.schedule}'
Débogage et observabilité
État et événements :
kubectl describe cronjob <name>kubectl get jobs --selector=job-name=<prefix>etkubectl describe job/<name>kubectl get events -A | grep -i cronjob
Pods et logs :
kubectl get pods --selector=job-name=<job>kubectl logs <pod>(ou-c <container>si plusieurs conteneurs)
Champs utiles :
.status.lastScheduleTimedu CronJob.spec.startingDeadlineSecondset la politique de concurrence
Problèmes fréquents :
- Image introuvable, Secret ou ConfigMap manquant : événements “Failed to pull image” ou “not found”.
- Job qui n’en finit pas : ajuster
activeDeadlineSecondsetconcurrencyPolicy. - Trop d’objets accumulés : baisser
successfulJobsHistoryLimit/failedJobsHistoryLimitet mettre un TTLController (pour les Jobs si activé viattlSecondsAfterFinished).
Migrer depuis cron (Linux) vers CronJob (K8s)
- Chemins et dépendances : tout doit exister dans l’image (ou via volumes). Pas de
/usr/local/binde l’hôte. - Environnement : variables à définir via
env, ConfigMap / Secret. Pas de$HOMEouPATHimplicite comme dans une session interactive. - Droits : utilisez un
ServiceAccount+ RBAC adaptés. Évitezrootsi possible (SecurityContext). - Fichiers et persistance : écrivez dans un volume (
emptyDir,PersistentVolumeClaim,configMapousecreten lecture seule), pas dans le système de fichiers éphémère du Pod si vous devez conserver les résultats. - Journalisation : stdout / stderr suffisent le plus souvent. Pour du centralisé, branchez Fluent Bit ou Promtail vers Loki ou ELK.
Bonnes pratiques
- Images immuables et petites (Alpine, distroless). Évitez
:latest. concurrencyPolicy: Forbidpour les jobs non idempotents,Replacepour les jobs rapides qu’on préfère relancer.- Limitez mémoire / CPU et fixez des deadlines pour éviter les runaway jobs.
- Externalisez la config (ConfigMap / Secret) et ne logguez jamais des secrets.
- Surveillez l’historique et les événements, alertez sur les pods en
CrashLoopBackOffou les jobs en échec. - Si votre cluster ne tourne pas en continu (edge), utilisez
startingDeadlineSecondspour rattraper les exécutions manquées.
FAQ
Puis-je forcer l’exécution immédiate ?
- Oui : créez un Job “one-shot” à partir du
jobTemplatede votre CronJob ou utilisezkubectl create job --from=cronjob/<name> <job-manuel>.
Comment arrêter temporairement une planification ?
- Passez
spec.suspend: true, remettez àfalsepour reprendre.
Comment éviter les doublons ?
concurrencyPolicy: Forbidet assurez-vous d’avoir des deadlines raisonnables.
Fuseau horaire ?
- Utilisez
spec.timeZonesi votre version de K8s le supporte (1.27+). Sinon, l’horloge du contrôleur fait foi.