Are you an LLM? Read llms.txt for a summary of the docs, or llms-full.txt for the full context.
Skip to content

CronJobs Kubernetes

Les CronJobs permettent d'exécuter des tâches planifiées de manière périodique sur un cluster Kubernetes, similairement au cron Unix/Linux. Ils sont idéaux pour les traitements batch, les sauvegardes automatiques, les rapports périodiques ou tout job nécessitant une exécution planifiée.

Introduction

Un CronJob Kubernetes crée des objets Job selon une planification définie au format cron. Chaque Job créé déploie un ou plusieurs Pods pour exécuter la tâche demandée.

Architecture et fonctionnement

CronJob (schedule: "0 2 * * *")
  └─> Job-1 (créé à 02:00)
        └─> Pod-1 (exécution de la tâche)
  └─> Job-2 (créé à 02:00 le lendemain)
        └─> Pod-2 (exécution de la tâche)

Le contrôleur CronJob :

  • Vérifie toutes les 10 secondes la liste des CronJobs du cluster
  • Calcule si un Job doit être créé en fonction du schedule configuré
  • Gère les politiques de concurrence et les délais de démarrage
  • Conserve l'historique des Jobs selon les paramètres de rétention

Exemple de configuration

CronJob basique

cronjob.yaml
apiVersion: batch/v1
kind: CronJob
metadata:
  name: backup-database
  namespace: production
  labels:
    app: backup
    component: database
spec:
  # Exécution tous les jours à 2h du matin
  schedule: "0 2 * * *"
  
  # Politique de concurrence : interdire l'exécution simultanée
  concurrencyPolicy: Forbid
  
  # Nombre de Jobs réussis à conserver dans l'historique
  successfulJobsHistoryLimit: 3
  
  # Nombre de Jobs échoués à conserver dans l'historique
  failedJobsHistoryLimit: 5
  
  # Délai maximum après l'heure planifiée pour démarrer le Job
  startingDeadlineSeconds: 300
  
  # Template du Job à créer
  jobTemplate:
    metadata:
      labels:
        app: backup
        component: database
    spec:
      # Le Job sera conservé 1 jour après son exécution
      ttlSecondsAfterFinished: 86400
      
      # Politique de retry : 3 tentatives maximum
      backoffLimit: 3
      
      # Template du Pod
      template:
        metadata:
          labels:
            app: backup
            component: database
        spec:
          restartPolicy: OnFailure
          
          containers:
          - name: backup
            image: postgres:15-alpine
            command:
            - /bin/sh
            - -c
            - |
              pg_dump -h $DB_HOST -U $DB_USER -d $DB_NAME | \
              gzip > /backup/dump-$(date +%Y%m%d-%H%M%S).sql.gz
            env:
            - name: DB_HOST
              value: "postgresql-service"
            - name: DB_USER
              valueFrom:
                secretKeyRef:
                  name: db-credentials
                  key: username
            - name: PGPASSWORD
              valueFrom:
                secretKeyRef:
                  name: db-credentials
                  key: password
            - name: DB_NAME
              value: "production"
            volumeMounts:
            - name: backup-volume
              mountPath: /backup
            resources:
              requests:
                cpu: 100m
                memory: 256Mi
              limits:
                cpu: 500m
                memory: 512Mi
          
          volumes:
          - name: backup-volume
            persistentVolumeClaim:
              claimName: backup-pvc

Syntaxe du format cron

ChampValeurs possiblesExemple
Minutes0-5930 = à la 30ème minute
Heures0-2314 = à 14h
Jour du mois1-3115 = le 15 du mois
Mois1-126 = juin
Jour de la semaine0-6 (0 = dimanche)1 = lundi
Exemples courants :
# Toutes les heures
schedule: "0 * * * *"
 
# Tous les jours à 3h30 du matin
schedule: "30 3 * * *"
 
# Tous les lundis à 8h
schedule: "0 8 * * 1"
 
# Le 1er de chaque mois à minuit
schedule: "0 0 1 * *"
 
# Toutes les 15 minutes
schedule: "*/15 * * * *"
 
# Du lundi au vendredi à 18h
schedule: "0 18 * * 1-5"

Paramètres de configuration clés

concurrencyPolicy

Définit le comportement lorsqu'un Job est encore en cours d'exécution au moment de la prochaine planification :

ValeurComportementCas d'usage
Allow (défaut)Autorise l'exécution simultanée de plusieurs JobsJobs indépendants, traitement parallèle acceptable
ForbidEmpêche le démarrage si un Job est déjà en coursTraitement exclusif (backup, migration)
ReplaceAnnule le Job en cours et démarre le nouveauLe dernier Job doit toujours s'exécuter
spec:
  concurrencyPolicy: Forbid

startingDeadlineSeconds

Délai maximum (en secondes) après l'heure planifiée pendant lequel le Job peut encore démarrer. Si ce délai est dépassé, le Job ne sera pas créé.

spec:
  # Le Job peut démarrer jusqu'à 5 minutes après l'heure planifiée
  startingDeadlineSeconds: 300
Impact :
  • Si défini : Le contrôleur compte les planifications manquées depuis now - startingDeadlineSeconds
  • Si non défini (nil) : Le contrôleur compte les planifications manquées depuis lastScheduleTime, sans limite de temps
Cas d'usage :
  • Job critique qui perd son utilité après un certain délai (ex: traitement temps réel)
  • Limiter l'accumulation de planifications manquées lors d'une indisponibilité du cluster

successfulJobsHistoryLimit et failedJobsHistoryLimit

Nombre de Jobs terminés (réussis ou échoués) à conserver dans l'historique :

spec:
  successfulJobsHistoryLimit: 3  # Conserver les 3 derniers Jobs réussis
  failedJobsHistoryLimit: 5      # Conserver les 5 derniers Jobs échoués

suspend

Permet de suspendre temporairement l'exécution du CronJob sans le supprimer :

spec:
  suspend: true
Utilisation :
Terminal
# Suspendre un CronJob
kubectl patch cronjob backup-database -n production -p '{"spec":{"suspend":true}}'
 
# Réactiver un CronJob
kubectl patch cronjob backup-database -n production -p '{"spec":{"suspend":false}}'

Commandes kubectl utiles

Consultation et monitoring

Terminal
# Lister tous les CronJobs
kubectl get cronjobs -A
 
# Détails d'un CronJob spécifique
kubectl -n production describe cronjob backup-database
 
# Voir les Jobs créés par un CronJob
kubectl -n production get jobs -l app=backup
 
# Voir l'historique des exécutions (status, démarrage, terminaison)
kubectl -n production get jobs -l app=backup --sort-by=.status.startTime
 
# Voir les Pods d'un Job spécifique
kubectl -n production get pods -l job-name=backup-database-28480640
 
# Consulter les logs du dernier Job exécuté
kubectl -n production logs -l app=backup --tail=100
 
# Événements liés au CronJob
kubectl -n production get events --field-selector involvedObject.kind=CronJob,involvedObject.name=backup-database
 
# Statut détaillé (lastScheduleTime, active Jobs)
kubectl -n production get cronjob backup-database -o yaml | grep -A 5 status:

Opérations courantes

Terminal
# Créer un CronJob depuis un manifeste
kubectl apply -f cronjob-backup.yaml
 
# Déclencher manuellement un Job à partir d'un CronJob
kubectl -n production create job backup-manual-$(date +%Y%m%d-%H%M%S) \
  --from=cronjob/backup-database
 
# Suspendre un CronJob
kubectl -n production patch cronjob backup-database -p '{"spec":{"suspend":true}}'
 
# Réactiver un CronJob
kubectl -n production patch cronjob backup-database -p '{"spec":{"suspend":false}}'
 
# Supprimer tous les Jobs terminés d'un CronJob
kubectl -n production delete jobs -l app=backup --field-selector status.successful=1
 
# Supprimer un CronJob et ses Jobs associés
kubectl -n production delete cronjob backup-database --cascade=foreground

Debugging

Terminal
# Vérifier pourquoi un Job n'a pas démarré
kubectl describe cronjob backup-database -n production | grep -A 10 Events
 
# Voir les Pods échoués d'un Job
kubectl get pods -n production -l job-name=backup-database-28480640 \
  --field-selector status.phase=Failed
 
# Logs d'un Pod échoué
kubectl logs -n production <pod-name> --previous
 
# Diagnostiquer les erreurs d'un Job
kubectl describe job backup-database-28480640 -n production
 
# Voir la configuration effective d'un CronJob
kubectl get cronjob backup-database -n production -o yaml

Troubleshooting

Erreur FailedNeedsStart : Trop de planifications manquées

Symptôme

Le CronJob n'exécute plus de Jobs et affiche l'événement suivant :

"FailedNeedsStart", "Cannot determine if job needs to be started. Too many missed start time (> 100). Set or decrease .spec.startingDeadlineSeconds or check clock skew."

Analyse du fonctionnement

Après analyse de la base de code de Kubernetes, voici le mécanisme exact du contrôleur CronJob :

Étape 1. Vérification périodique (toutes les 10 secondes)

Le contrôleur vérifie toutes les 10 secondes l'ensemble des CronJobs du cluster pour déterminer si un Job doit être créé.

Étape 2. Calcul des planifications manquées

Pour chaque CronJob, le contrôleur calcule le nombre de planifications manquées entre lastScheduleTime et l'instant présent.

Comportement selon startingDeadlineSeconds :
  • Si défini (valeur numérique) : Le calcul des planifications manquées se fait sur la période [now - startingDeadlineSeconds, now]

    Exemple : Si startingDeadlineSeconds: 200, le contrôleur compte combien de planifications ont été manquées dans les 200 dernières secondes uniquement.

  • Si non défini (nil) : Le calcul se fait depuis lastScheduleTime jusqu'à maintenant, sans limite de temps.

L'implémentation exacte du comptage est disponible ici.

Étape 3. Limite des 100 planifications manquées

Si le nombre de planifications manquées dépasse 100, le CronJob ne créera aucun Job et émettra l'événement FailedNeedsStart.

Étape 4. Vérification du délai de démarrage

Si le nombre de planifications manquées est inférieur à 100, le contrôleur vérifie si l'heure actuelle dépasse scheduleTime + startingDeadlineSeconds.

  • Dans les délais : Le Job est créé normalement
  • Hors délai : Le Job n'est pas créé et l'événement suivant est enregistré :
"Missed starting window for {cronjob-name}. Missed scheduled time to start a job {scheduledTime}"

Causes fréquentes

CauseExplication
Cluster suspendu/indisponibleLe contrôleur n'a pas pu traiter les CronJobs pendant une longue période
concurrencyPolicy: ForbidLes exécutions successives bloquées s'accumulent comme planifications manquées
Job de longue duréeUn Job qui dure plusieurs cycles de planification empêche les suivants de démarrer
Schedule trop fréquentPlanification toutes les minutes avec Jobs durant plus d'une minute
startingDeadlineSeconds non définiAccumulation illimitée des planifications manquées lors d'indisponibilités

Solutions

Étape 1. Définir ou réduire startingDeadlineSeconds

Limiter la fenêtre de calcul des planifications manquées :

spec:
  # Autoriser un démarrage jusqu'à 5 minutes après l'heure planifiée
  startingDeadlineSeconds: 300
  schedule: "0 * * * *"  # Toutes les heures

Avec cette configuration, même après une indisponibilité prolongée du cluster, seules les planifications des 5 dernières minutes seront prises en compte.

Étape 2. Supprimer temporairement le CronJob et le recréer
Terminal
# Sauvegarder la configuration
kubectl get cronjob backup-database -n production -o yaml > cronjob-backup.yaml
 
# Supprimer le CronJob
kubectl delete cronjob backup-database -n production
 
# Attendre quelques secondes puis recréer
kubectl apply -f cronjob-backup.yaml

Cette action réinitialise le lastScheduleTime et efface l'historique des planifications manquées.

Étape 3. Ajuster concurrencyPolicy

Si les Jobs peuvent s'exécuter en parallèle sans conflit :

spec:
  concurrencyPolicy: Allow  # Au lieu de Forbid
Étape 4. Vérifier la synchronisation horaire du cluster

L'erreur mentionne également check clock skew. Vérifiez que les nœuds du cluster et le Control Plane sont synchronisés (NTP configuré correctement).

Terminal
# Sur chaque nœud du cluster
timedatectl status
Étape 5. Optimiser la durée d'exécution des Jobs

Si les Jobs sont trop longs :

  • Optimiser le traitement
  • Réduire les ressources allouées si elles sont excessives
  • Envisager de découper le traitement en sous-tâches parallèles

Questions fréquentes

Q1 : Si startingDeadlineSeconds: 10 et que le CronJob ne peut pas démarrer à l'heure planifiée, peut-il démarrer dans les 10 secondes suivantes ?

Réponse : Oui. Le contrôleur tentera de créer le Job tant que le délai de 10 secondes après l'heure planifiée n'est pas écoulé. Si ce délai est dépassé, cette exécution ne sera pas faite et sera comptabilisée comme planification manquée pour les vérifications ultérieures.

Q2 : Avec concurrencyPolicy: Forbid, si un CronJob essaie d'être planifié alors qu'un Job est déjà en cours, est-ce comptabilisé comme un échec ?

Réponse : Oui, cela est considéré comme une planification manquée. Le contrôleur enregistre que l'exécution prévue n'a pas eu lieu en raison de la politique de concurrence. Si ce scénario se répète et que startingDeadlineSeconds n'est pas défini, cela peut conduire à l'accumulation de plus de 100 planifications manquées et déclencher l'erreur FailedNeedsStart.

Job qui ne démarre jamais

Symptôme

Le CronJob est actif, mais aucun Job n'est créé aux heures planifiées.

Diagnostic

Terminal
# Vérifier le statut du CronJob
kubectl get cronjob backup-database -n production -o yaml | grep -A 5 status
 
# Consulter les événements
kubectl describe cronjob backup-database -n production | grep -A 10 Events
 
# Vérifier si le CronJob est suspendu
kubectl get cronjob backup-database -n production -o jsonpath='{.spec.suspend}'

Causes et solutions

CauseSolution
CronJob suspenduRéactiver : kubectl patch cronjob <name> -p '{"spec":{"suspend":false}}'
Expression cron invalideValider sur crontab.guru et corriger
Problème de fuseau horaireVérifier le fuseau du Control Plane
Quota de ressources atteintVérifier les ResourceQuotas du namespace

Job échoue systématiquement

Symptôme

Le CronJob crée des Jobs, mais ils échouent à chaque exécution.

Diagnostic

Terminal
# Lister les Jobs échoués
kubectl get jobs -n production -l app=backup --field-selector status.successful=0
 
# Détails d'un Job échoué
kubectl describe job <job-name> -n production
 
# Logs du Pod échoué
kubectl logs -n production <pod-name>
 
# Voir les tentatives de restart
kubectl get pods -n production -l job-name=<job-name> -o wide

Causes courantes

CauseSymptômeSolution
Image inexistanteErrImagePull, ImagePullBackOffVérifier le nom et le tag de l'image
Secrets manquantsErreur au démarrage du conteneurVérifier que les Secrets existent dans le bon namespace
Volumes non disponiblesPod en état PendingVérifier les PVC et la disponibilité du stockage
Ressources insuffisantesPod en état PendingAjuster les requests/limits ou scaler le cluster
Erreur applicativeExit code non-zeroAnalyser les logs et corriger le script/application
TimeoutPod tué après activeDeadlineSecondsAugmenter la limite ou optimiser le traitement

Jobs non nettoyés (historique surchargé)

Symptôme

Des dizaines/centaines de Jobs et Pods terminés encombrent le namespace.

Solution

Terminal
# Ajuster les limites d'historique dans le CronJob
kubectl patch cronjob backup-database -n production --type=merge -p '
spec:
  successfulJobsHistoryLimit: 3
  failedJobsHistoryLimit: 5
'
 
# Nettoyer manuellement les Jobs terminés
kubectl delete jobs -n production -l app=backup --field-selector status.successful=1
 
# Nettoyer les Pods terminés
kubectl delete pods -n production --field-selector status.phase=Succeeded

Logs disparus après expiration du Pod

Problème

Les Pods sont supprimés automatiquement et les logs ne sont plus accessibles.

Solutions

Étape 1. Augmenter la durée de rétention
spec:
  successfulJobsHistoryLimit: 5  # Conserver plus de Jobs
  jobTemplate:
    spec:
      ttlSecondsAfterFinished: 86400  # Garder les Jobs 24h
Étape 2. Centraliser les logs

Mettre en place une solution de collecte de logs (Loki, ELK, Datadog, etc.) pour conserver les logs au-delà de la durée de vie des Pods.

Étape 3. Rediriger les logs vers un volume persistant
spec:
  jobTemplate:
    spec:
      template:
        spec:
          containers:
          - name: job
            command:
            - /bin/sh
            - -c
            - |
              # Rediriger stdout et stderr vers un fichier
              /app/script.sh 2>&1 | tee /logs/job-$(date +%Y%m%d-%H%M%S).log
            volumeMounts:
            - name: logs-volume
              mountPath: /logs
          volumes:
          - name: logs-volume
            persistentVolumeClaim:
              claimName: logs-pvc

Bonnes pratiques

Conception et configuration

PratiqueJustification
Définir startingDeadlineSecondsÉvite l'accumulation infinie de planifications manquées lors d'indisponibilités
Utiliser concurrencyPolicy: ForbidPrévient les exécutions simultanées pour les traitements exclusifs (backup, migration)
Limiter l'historique des JobssuccessfulJobsHistoryLimit: 3, failedJobsHistoryLimit: 5 pour éviter la surcharge etcd
Configurer ttlSecondsAfterFinishedNettoyage automatique des Jobs terminés après un délai défini
Définir activeDeadlineSecondsÉvite qu'un Job bloqué consomme des ressources indéfiniment
Spécifier resources (requests/limits)Garantit la planification et évite la famine de ressources
Utiliser restartPolicy: OnFailurePermet les tentatives de retry dans le même Pod au lieu de créer de nouveaux Pods

Exemple de configuration optimale

apiVersion: batch/v1
kind: CronJob
metadata:
  name: optimized-cronjob
  namespace: production
  labels:
    app.kubernetes.io/name: backup
    app.kubernetes.io/component: database
    environment: production
spec:
  schedule: "0 2 * * *"
  concurrencyPolicy: Forbid
  startingDeadlineSeconds: 600
  successfulJobsHistoryLimit: 3
  failedJobsHistoryLimit: 5
  suspend: false
  
  jobTemplate:
    metadata:
      labels:
        app.kubernetes.io/name: backup
        app.kubernetes.io/component: database
    spec:
      # Nettoyage automatique après 24h
      ttlSecondsAfterFinished: 86400
      
      # 3 tentatives maximum en cas d'échec
      backoffLimit: 3
      
      # Timeout global du Job : 30 minutes
      activeDeadlineSeconds: 1800
      
      template:
        metadata:
          labels:
            app.kubernetes.io/name: backup
        spec:
          restartPolicy: OnFailure
          
          # ServiceAccount dédié avec droits minimaux
          serviceAccountName: backup-sa
          
          containers:
          - name: backup
            image: registry.example.com/backup-tool:1.2.3
            imagePullPolicy: IfNotPresent
            
            command: ["/bin/sh", "-c"]
            args:
            - |
              set -euo pipefail
              echo "Starting backup at $(date)"
              /usr/local/bin/backup-script.sh
              echo "Backup completed at $(date)"
            
            env:
            - name: ENV
              value: "production"
            
            envFrom:
            - secretRef:
                name: backup-secrets
            - configMapRef:
                name: backup-config
            
            resources:
              requests:
                cpu: 200m
                memory: 512Mi
              limits:
                cpu: 1000m
                memory: 1Gi
            
            volumeMounts:
            - name: backup-storage
              mountPath: /backups
            
            # Health check pour détecter les blocages
            livenessProbe:
              exec:
                command: ["/bin/sh", "-c", "pgrep -f backup-script"]
              initialDelaySeconds: 30
              periodSeconds: 60
              timeoutSeconds: 5
              failureThreshold: 3
          
          volumes:
          - name: backup-storage
            persistentVolumeClaim:
              claimName: backup-pvc
          
          # Tolérations et affinité si besoin
          tolerations:
          - key: "workload"
            operator: "Equal"
            value: "batch"
            effect: "NoSchedule"

Monitoring et alerting

Métriques à surveiller :
MétriqueAlerte siAction
Nombre de planifications manquées> 5 consécutivesInvestiguer les logs du CronJob
Taux d'échec des Jobs> 50% sur 24hVérifier les logs des Pods échoués
Durée d'exécution> 2x la durée moyenneOptimiser le traitement ou augmenter les ressources
Nombre de Jobs en cours> 1 (si concurrencyPolicy: Forbid)Vérifier pourquoi les Jobs ne se terminent pas
Exemple de règle Prometheus pour alerter :
- alert: CronJobFailing
  expr: |
    kube_cronjob_status_last_schedule_time
    and on (cronjob) (kube_job_status_failed{job=~".*"} > 0)
  for: 1h
  labels:
    severity: warning
  annotations:
    summary: "CronJob {{ $labels.cronjob }} échoue"
    description: "Le CronJob {{ $labels.cronjob }} a des échecs répétés."
 
- alert: CronJobNotScheduled
  expr: |
    time() - kube_cronjob_status_last_schedule_time > 3600 * 2
  for: 10m
  labels:
    severity: critical
  annotations:
    summary: "CronJob {{ $labels.cronjob }} non exécuté"
    description: "Aucune exécution depuis plus de 2h."

Sécurité

  • ServiceAccount dédié : Créez un ServiceAccount spécifique avec les permissions RBAC minimales nécessaires
  • Secrets chiffrés : Utilisez des Secrets pour les credentials, jamais de valeurs en dur
  • Image de confiance : Utilisez des images provenant de registres privés ou validés
  • Limites de ressources : Évitez les abus de ressources avec des limits appropriées
  • Scan de vulnérabilités : Scannez les images des CronJobs régulièrement

Cas d'usage courants

Sauvegarde de base de données

schedule: "0 2 * * *"  # Tous les jours à 2h
concurrencyPolicy: Forbid

Nettoyage de fichiers temporaires

schedule: "0 */6 * * *"  # Toutes les 6 heures
concurrencyPolicy: Replace
ttlSecondsAfterFinished: 3600

Génération de rapports hebdomadaires

schedule: "0 8 * * 1"  # Tous les lundis à 8h
concurrencyPolicy: Allow
successfulJobsHistoryLimit: 10

Synchronisation de données

schedule: "*/30 * * * *"  # Toutes les 30 minutes
concurrencyPolicy: Forbid
startingDeadlineSeconds: 1800

Ressources complémentaires