Introduction
Les CronJobs sont le planificateur de Kubernetes. Ils exécutent des Jobs selon un calendrier défini, ce qui les rend indispensables pour le traitement par lots, la génération de rapports, les sauvegardes de bases de données et toute autre tâche récurrente. Mais cette commodité s'accompagne d'une frustration fréquente : un CronJob échoue silencieusement. Aucune alarme ne se déclenche, aucun utilisateur ne se plaint, et le seul symptôme est que le rapport d'hier est manquant. Le dépannage de ces échecs silencieux exige une approche systématique et méthodique.
Ce guide s'adresse aux développeurs, ingénieurs DevOps et équipes techniques de startups qui doivent diagnostiquer et corriger les problèmes de CronJob sans perdre des heures à deviner. Vous apprendrez à inspecter l'état actuel, à décoder les événements Kubernetes, à extraire les bons journaux, à traiter les modes de défaillance courants et à récupérer en toute confiance. Chaque technique est illustrée par des commandes réelles et leurs sorties attendues. L'objectif est la sécurité opérationnelle : observer avant de modifier, limiter le rayon d'impact, protéger les valeurs sensibles, vérifier le résultat et documenter le chemin de récupération.
Avant de plonger dans une défaillance spécifique, vous devez avoir une vision claire de votre environnement. Quelle version de Kubernetes exécutez-vous ? Comment le CronJob est-il défini ? Quelles sont ses dépendances ? La section suivante présente un inventaire de version et d'environnement qui vous fera gagner du temps par la suite.
Inventaire des versions et de l'environnement
Une défaillance de CronJob peut souvent être attribuée à une incompatibilité entre la version du contrôleur et les fonctionnalités de l'API que vous utilisez. Commencez par vérifier la version du serveur Kubernetes et la version de l'API CronJob dans vos manifestes. L'API batch/v1 est stable depuis Kubernetes 1.21 et prend en charge le champ timeZone ; les clusters plus anciens peuvent ne prendre en charge que batch/v1beta1. Exemple :
kubectl version --short
Sortie attendue (tronquée) :
Client Version: v1.28.2
Kustomize Version: v5.0.4-0.20230601165947-6ce0bf390ce3
Server Version: v1.27.6
Si le serveur est antérieur à la version 1.21, examinez le manifeste du CronJob pour détecter les champs obsolètes comme startingDeadlineSeconds qui seraient ignorés ou les fuseaux horaires non respectés. Vérifiez également que la ressource API batch/v1 est disponible :
kubectl api-resources | grep cronjobs
La sortie attendue montre cronjobs avec la version de l'API et le nom du groupe :
cronjobs cj batch/v1 true CronJob
Ensuite, obtenez l'état actuel de tous les CronJobs dans l'espace de noms qui vous intéresse. Cette observation en lecture seule indique la planification, si le job est suspendu et l'heure de la dernière planification.
kubectl get cronjobs -n production
Exemple de sortie :
NAME SCHEDULE SUSPEND ACTIVE LAST SCHEDULE AGE
nightly-backup 0 2 * * * False 0 4h 12d
report-generator */15 * * * * False 0 14m 3d
Recherchez les anomalies : une valeur SUSPEND à True signifie que quelqu'un a mis le job en pause. Un nombre ACTIVE supérieur à la concurrence attendue peut indiquer des exécutions qui se chevauchent. La valeur LAST SCHEDULE doit être récente par rapport à la planification ; si elle est ancienne, le contrôleur ne planifie peut-être plus du tout.
Inspectez également le CronJob lui-même avec kubectl describe. Cela affiche les événements, les étiquettes et tous les détails de configuration. Par exemple :
kubectl describe cronjob nightly-backup -n production
La section des événements en bas est souvent le premier endroit où un problème de planification apparaît, par exemple :
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Warning FailedNeedsStart 5m cronjob-controller Cannot determine if job needs to be started: too many missed start times (> 100). Set or decrease .spec.startingDeadlineSeconds or check clock skew.
Cet avertissement spécifique indique que le contrôleur CronJob n'a pas pu démarrer un job parce que l'horloge du cluster est désynchronisée ou que startingDeadlineSeconds est réglé trop bas pour la planification. La solution consiste soit à ajuster le délai, soit à s'assurer que l'heure du plan de contrôle est synchronisée.
Enregistrez toujours les horodatages et l'état actuel avant d'apporter des modifications. Cela vous donne une base de référence à comparer après toute intervention. Cela vous aide également à répondre à la question « Qu'est-ce qui a changé ? » lorsqu'un CronJob qui fonctionnait auparavant commence à échouer.
Chemin de configuration sûr
Une fois que vous comprenez l'environnement, vous pouvez apporter des modifications en toute sécurité. Le mantra est : une modification à la fois, avec une étape de vérification claire. Évitez de modifier plusieurs manifestes ou d'appliquer un grand diff sans possibilité de revenir en arrière.
Commencez par vérifier la configuration du CronJob pour détecter les erreurs courantes. La syntaxe de planification est un coupable fréquent. Kubernetes utilise le format cron standard avec cinq champs : minute, heure, jour du mois, mois, jour de la semaine. Une planification comme 0 0 1 1 ne s'exécute que le 1er janvier à minuit, pas quotidiennement. Les gens confondent souvent le champ jour-du-mois et jour-de-la-semaine, en particulier avec les plages et les pas. Par exemple, /15 s'exécute toutes les 15 minutes, mais 0 0/2 * s'exécute toutes les deux heures à la minute 0.
Utilisez un outil comme crontab.guru pour valider votre planification, ou écrivez un petit test. Par exemple, cet extrait utilise la bibliothèque go pour imprimer les cinq prochaines heures d'exécution à partir d'une planification :
go install github.com/robfig/cron/v3/cmd/crontab@latest
crontab "*/15 * * * *" --count 5
La sortie attendue montre les cinq prochaines heures d'activation dans votre fuseau horaire local. Cela peut rapidement révéler qu'une planification prévue pour 3 h du matin se déclenche en réalité à 15 h en raison d'un problème de fuseau horaire.
Les fuseaux horaires sont une source courante de douleur. Dans Kubernetes 1.24+, vous pouvez définir spec.timeZone dans la spécification du CronJob. Par exemple :
apiVersion: batch/v1
kind: CronJob
metadata:
name: report-generator
spec:
schedule: "0 3 * * *"
timeZone: "America/New_York"
jobTemplate:
spec:
template:
spec:
containers:
- name: report
image: myapp/report:latest
command: ["/bin/sh", "-c", "python generate_report.py"]
restartPolicy: OnFailure
Sans ce champ, la planification est interprétée dans le fuseau horaire du gestionnaire de contrôleur Kubernetes, souvent UTC. Cette différence de six heures peut entraîner l'exécution d'un job au mauvais moment ou pas du tout.
Un autre piège de configuration est la politique de concurrence et les limites d'historique. Les valeurs par défaut sont concurrencyPolicy: Allow et successfulJobsHistoryLimit: 3, failedJobsHistoryLimit: 1. Si votre job peut se chevaucher, définissez concurrencyPolicy: Forbid pour empêcher deux instances de s'exécuter simultanément. Si vous avez besoin de conserver plus d'historique pour le débogage, augmentez les limites.
Lors de l'application de modifications, utilisez d'abord kubectl apply avec --dry-run=client pour valider la syntaxe :
kubectl apply -f cronjob.yaml --dry-run=client
Appliquez ensuite la modification réelle et vérifiez immédiatement l'état :
kubectl apply -f cronjob.yaml
kubectl get cronjob report-generator -o yaml
Recherchez les champs status.lastScheduleTime et status.active pour confirmer que le contrôleur a accepté la modification. Si vous devez revenir en arrière, avoir le manifeste précédent dans un contrôle de version est inestimable. Utilisez toujours un dépôt Git pour les manifestes Kubernetes et étiquetez les versions afin de pouvoir restaurer un état connu et fonctionnel.
Vérification et diagnostic
Lorsqu'un CronJob crée un Job, ce Job crée des Pods. La piste de diagnostic suit toujours cette chaîne : CronJob -> Job -> Pod -> Journaux du conteneur. La première étape consiste à voir quels Jobs ont été créés et leur état.
kubectl get jobs -n production
Exemple de sortie :
NAME COMPLETIONS DURATION AGE
nightly-backup-27891280 1/1 45s 4h
report-generator-27891290 0/1 3m 14m
report-generator-27891291 0/1 2m 14m
Un nombre élevé de jobs échoués ou incomplets est un signal d'alarme. Dans cet exemple, report-generator a deux jobs en cours qui ne sont pas terminés. La colonne COMPLETIONS affiche 0/1, ce qui signifie que le nombre attendu de complétions n'est pas atteint.
Ensuite, inspectez le Job qui correspond à l'exécution en cours. Pour trouver le job exact pour une planification donnée, listez les jobs triés par heure de création :
kubectl get jobs --sort-by=.metadata.creationTimestamp -l job-name
Ou utilisez les ownerReferences dans le job pour remonter au CronJob. Une fois que vous avez le nom du job, décrivez-le :
kubectl describe job report-generator-27891290 -n production
Les événements du job montreront les échecs de création de pod, les erreurs d'extraction d'image ou les problèmes de planification. Par exemple :
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Warning BackOff 4m job-controller Back-off restarting failed container
Normal Scheduled 5m default-scheduler Successfully assigned production/report-generator-27891290-sd7k8 to node-2
Normal Pulling 5m kubelet Pulling image "myapp/report:latest"
Normal Pulled 4m kubelet Successfully pulled image "myapp/report:latest"
Normal Created 4m kubelet Created container report
Normal Started 4m kubelet Started container report
Warning Failed 4m kubelet Error: container "report" terminated with exit code 1
Cela vous indique que le conteneur s'est exécuté mais s'est terminé avec un statut non nul. Le message d'erreur réel se trouvera dans les journaux du conteneur. Pour obtenir les journaux du pod :
kubectl logs report-generator-27891290-sd7k8 -n production
Remplacez le nom du pod par celui de la commande kubectl get pods. Si le pod a déjà été terminé, vous aurez peut-être besoin du drapeau --previous pour obtenir les journaux de la dernière tentative :
kubectl logs report-generator-27891290-sd7k8 -n production --previous
Si le pod est toujours en cours d'exécution, vous pouvez suivre les journaux avec -f. Si le pod a plusieurs conteneurs, spécifiez le nom du conteneur avec -c report.
Pour des diagnostics plus avancés, vous pouvez utiliser kubectl exec pour exécuter des commandes à l'intérieur d'un pod en cours d'exécution, mais cela est souvent inutile pour les CronJobs. Assurez-vous plutôt que votre job écrit suffisamment de journaux sur stdout/stderr pour diagnostiquer les problèmes. S'il utilise un framework de journalisation, configurez-le pour qu'il produise un niveau de débogage lors du dépannage.
Enfin, vérifiez que le job se termine avec succès. Le contrôleur CronJob créera un nouveau job à la prochaine planification. Pour tester sans attendre, vous pouvez créer un job manuellement à partir du modèle du CronJob :
kubectl create job manual-test --from=cronjob/report-generator -n production
Cela crée un Job avec la même spécification que le CronJob. Attendez qu'il se termine et vérifiez son état :
kubectl wait --for=condition=complete job/manual-test --timeout=60s -n production
Si cela réussit, la configuration est probablement correcte et le problème venait de la planification ou d'un problème d'environnement transitoire.
Modes de défaillance et récupération
Les CronJobs peuvent échouer de nombreuses façons. Voici les modes de défaillance les plus courants, comment les identifier et comment récupérer.
Mode de défaillance 1 : erreurs d'extraction d'image
Symptômes : Le job est créé mais les pods échouent avec ErrImagePull ou ImagePullBackOff.
Commande de diagnostic :
kubectl describe pod <nom-du-pod> -n production | grep -A 5 Events
Vous verrez des événements comme :
Warning Failed 10m (x3 over 12m) kubelet Failed to pull image "myapp/report:latest": rpc error: code = NotFound desc = failed to pull and unpack image "docker.io/myapp/report:latest": failed to resolve reference "docker.io/myapp/report:latest": pull access denied, repository does not exist or may require authorization
Récupération : Assurez-vous que le nom et l'étiquette de l'image sont corrects. Si vous utilisez un registre privé, vérifiez que les imagePullSecrets sont correctement configurés dans la spécification du pod et que le secret existe dans l'espace de noms. Vérifiez également que le nœud a un accès réseau au registre. Mettez à jour le manifeste et réappliquez.
Mode de défaillance 2 : CrashLoopBackOff
Symptômes : Le pod démarre, mais le conteneur se termine avec une erreur et redémarre à plusieurs reprises. Le statut du pod affiche CrashLoopBackOff.
Commande de diagnostic :
kubectl get pods -n production | grep report
Exemple de sortie :
report-generator-27891290-sd7k8 0/1 CrashLoopBackOff 6 (2m ago) 14m
Consultez les journaux avec kubectl logs pour voir l'erreur d'application, qui peut être un fichier manquant, une mauvaise connexion à la base de données ou une exception non gérée. Vérifiez également le code de sortie : kubectl get pod <nom> -o jsonpath='{.status.containerStatuses[*].state.terminated.exitCode}'.
Récupération : Déboguez l'application localement en utilisant la même image et les mêmes variables d'environnement. Si le problème est intermittent, ajoutez une logique de nouvelle tentative ou augmentez le backoffLimit dans la spécification du job (la valeur par défaut est 6) pour permettre plus de redémarrages avant de marquer le job comme échoué. Notez que la limite de backoff est par job, pas par pod.
Mode de défaillance 3 : mauvaise configuration de la planification
Symptômes : Le job s'exécute à des moments inattendus, ou pas du tout.
Diagnostic : Examinez la planification du CronJob et comparez-la aux heures prévues. Consultez les journaux du contrôleur (voir ci-dessous). Recherchez également des événements FailedNeedsStart dans la sortie de kubectl describe cronjob.
Récupération : Corrigez la planification et appliquez. Si vous utilisez startingDeadlineSeconds, assurez-vous qu'il est suffisamment grand pour tenir compte de la dérive d'horloge et des temps d'arrêt du contrôleur. Envisagez de définir explicitement timeZone.
Mode de défaillance 4 : le job ne se termine jamais
Symptômes : Le job s'exécute plus longtemps que prévu ou se bloque indéfiniment.
Diagnostic : Consultez les journaux du pod pour voir si l'application est bloquée. Utilisez kubectl top pod pour surveiller l'utilisation des ressources. Si le pod est en attente, vérifiez les événements de planification : kubectl describe pod <nom> indiquera une insuffisance de CPU ou de mémoire.
Récupération : Ajoutez activeDeadlineSeconds à la spécification du job pour forcer l'arrêt après un certain temps. Assurez-vous que les demandes de ressources sont correctement définies et que le cluster a de la capacité. Si le job est idempotent, vous pouvez le supprimer et laisser le CronJob le replanifier.
Mode de défaillance 5 : des modifications de déploiement ou de ConfigMap cassent le job
Symptômes : Le job fonctionnait, puis après un déploiement ou une mise à jour de configuration, il échoue.
Diagnostic : Comparez la configuration actuelle avec la version précédente. Utilisez kubectl rollout history pour les déploiements ou vérifiez le diff du ConfigMap. Vérifiez si les variables d'environnement ou les fichiers montés ont changé.
Récupération : Revenez à la version précédente connue comme fonctionnelle : kubectl rollout undo deployment/myapp. Si un ConfigMap a été modifié, restaurez-le. Ensuite, réappliquez la modification de manière incrémentale avec des tests appropriés.
Meilleures pratiques de récupération
Après avoir corrigé une défaillance, exécutez toujours un job manuel pour vérifier. Documentez l'incident : ce qui s'est passé, pourquoi et ce qui a été changé. Mettez à jour le manifeste du CronJob et ajoutez des commentaires si nécessaire. Envisagez d'ajouter une surveillance et des alertes pour les échecs de CronJob, par exemple en utilisant Prometheus ou un outil comme Kubernetes Event Exporter pour surveiller les événements Failed des jobs.
Pièges courants et comment les éviter
De nombreux problèmes de CronJob proviennent d'erreurs évitables. Voici les pièges les plus fréquents et comment les contourner.
Piège 1 : supposer l'UTC pour les planifications
Par défaut, le contrôleur CronJob utilise le fuseau horaire du plan de contrôle, souvent UTC. Si votre équipe travaille dans un fuseau différent, une planification comme 0 3 * peut s'exécuter à 3 h UTC, ce qui correspond à 23 h à New York (ou 20 h à Los Angeles). Cela peut entraîner l'exécution des jobs à des heures incongrues ou le non-respect des délais.
Comment éviter : Définissez toujours spec.timeZone dans le manifeste du CronJob sur le fuseau souhaité (par exemple, America/New_York). Ce champ est disponible dans Kubernetes 1.24+ (bêta depuis 1.25, stable en 1.27). Pour les clusters plus anciens, vous devez convertir manuellement la planification ou utiliser une solution de contournement.
Piège 2 : ignorer concurrencyPolicy
Si votre job prend plus de temps que l'intervalle entre les planifications, la politique par défaut Allow démarrera une autre instance alors que la première est toujours en cours d'exécution. Cela peut entraîner une contention des ressources ou une corruption des données si le job n'est pas idempotent.
Comment éviter : Définissez concurrencyPolicy: Forbid pour ignorer les nouvelles exécutions si la précédente est encore active, ou Replace pour terminer l'ancien job et en démarrer un nouveau. Testez le comportement en allongeant artificiellement la durée du job.
Piège 3 : ne pas définir de limites de ressources
Un pod CronJob sans demandes ni limites de ressources peut être expulsé sous pression ou affamer d'autres charges de travail. Il peut également consommer un excès de CPU/mémoire, ralentissant le nœud.
Comment éviter : Définissez resources.requests et resources.limits dans la spécification du conteneur. Utilisez l'utilisation historique pour définir des valeurs appropriées. Par exemple :
resources:
requests:
memory: "64Mi"
cpu: "250m"
limits:
memory: "128Mi"
cpu: "500m"
Surveillez avec kubectl top pod et ajustez au besoin.
Piège 4 : ne pas surveiller les échecs de CronJob
Les CronJobs peuvent échouer silencieusement pendant des jours. Sans alertes, vous risquez de ne pas le remarquer jusqu'à ce qu'un rapport critique manque ou qu'une sauvegarde soit nécessaire.
Comment éviter : Mettez en place une surveillance de l'état des CronJobs. Utilisez un outil comme Prometheus et l'exportateur kube-state-metrics, qui fournit des métriques comme kube_cronjob_status_last_schedule_time et kube_job_status_failed. Créez une règle d'alerte qui se déclenche lorsqu'un job échoue ou lorsque la dernière planification est trop ancienne. Utilisez également les événements Kubernetes pour notifier via des outils comme Robusta ou Kubewatch.
Piège 5 : négliger restartPolicy
La restartPolicy dans le modèle de pod pour un Job doit être OnFailure ou Never. Si vous la définissez sur Always (la valeur par défaut typique d'un déploiement), le contrôleur Job rejettera le manifeste avec une erreur de validation. De même, si vous l'omettez, la valeur par défaut est Always, ce qui entraînera l'échec du CronJob.
Comment éviter : Définissez toujours explicitement restartPolicy: OnFailure dans le modèle de job. Cela garantit des nouvelles tentatives en cas d'échec sans interférer avec la sémantique du job.
Piège 6 : utiliser des étiquettes d'image latest
L'utilisation de image: myapp/report:latest dans un CronJob peut entraîner un comportement imprévisible lorsque l'image est mise à jour sans modification correspondante du manifeste. Le CronJob peut extraire une image cassée lors de la prochaine exécution.
Comment éviter : Épinglez vos images de conteneur à une version ou un condensé spécifique. Par exemple, image: myapp/report:v1.2.3. Dans un pipeline CI/CD, mettez à jour le manifeste avec la nouvelle étiquette avant de déployer.
Liste de contrôle opérationnelle
Utilisez cette liste de contrôle pour traiter systématiquement un problème de CronJob. Pour chaque étape, notez ce que vous vérifiez et à quoi ressemble un résultat sain. Remplacez les valeurs d'exemple par vos propres détails.
| Étape | Quoi vérifier | Commande | Résultat sain attendu | Action en cas d'échec | Responsable | Fréquence de révision |
|---|---|---|---|---|---|---|
| 1 | La version du cluster prend en charge les fonctionnalités de l'API CronJob | kubectl version --short | Serveur >= v1.21 (pour batch/v1) | Mettre à niveau le cluster ou ajuster le manifeste | Responsable DevOps (Priya Shah) | Trimestrielle |
| 2 | Le CronJob n'est pas suspendu | kubectl get cronjob <nom> -o jsonpath='{.spec.suspend}' | false | Définir suspend: false | Propriétaire du service (Alex Chen) | Hebdomadaire |
| 3 | La planification est correcte et le fuseau horaire défini | Inspecter le manifeste ou kubectl get cronjob <nom> -o yaml | La planification correspond aux heures prévues ; timeZone défini si nécessaire | Corriger la planification | Propriétaire du service | À chaque modification |
| 4 | L'heure de la dernière planification est récente | kubectl get cronjob <nom> -o jsonpath='{.status.lastScheduleTime}' | Dans l'intervalle attendu | Enquêter sur le contrôleur ou la dérive d'horloge | Responsable DevOps | Quotidienne |
| 5 | Le dernier Job s'est terminé avec succès | kubectl get jobs -l job-name= --sort-by=.metadata.creationTimestamp | tail -1 | COMPLETIONS = 1/1, aucune erreur | Vérifier les journaux et événements du job | Développeur (Jordan Lee) | Après chaque exécution |
| 6 | Les journaux du pod affichent la sortie attendue | kubectl logs | Aucune trace ou message d'erreur | Corriger le code de l'application | Développeur | Pendant le dépannage |
| 7 | L'utilisation des ressources est dans les limites | kubectl top pod | CPU/mémoire en dessous des limites | Augmenter les demandes/limites | Responsable DevOps | Mensuelle |
| 8 | Les alertes de surveillance sont configurées | Vérifier les règles Prometheus ou équivalent | L'alerte se déclenche en cas d'échec du job | Configurer les alertes | Ingénieur fiabilité du site (Maya Patel) | Trimestrielle |
Cette liste de contrôle vous assure de ne pas sauter d'étapes de diagnostic cruciales. La colonne « Responsable » fait référence à l'individu responsable de cette vérification, pas à une équipe. Il est chargé de s'assurer que la vérification est effectuée et que le résultat est pris en compte. La fréquence de révision varie : certaines vérifications sont continues (après chaque exécution), tandis que d'autres sont périodiques.
Conclusion
Le dépannage des CronJobs Kubernetes est une compétence qui rapporte. En suivant un processus systématique — commençant par l'inventaire de l'environnement, passant par des modifications de configuration sûres, diagnostiquant les échecs avec les journaux et les événements, et récupérant en toute confiance — vous pouvez résoudre rapidement les problèmes et éviter qu'ils ne se reproduisent.
Les principaux points à retenir sont :
- Inspectez toujours l'état actuel avant d'apporter des modifications.
- Validez les planifications, en particulier avec les fuseaux horaires.
- Suivez la chaîne CronJob -> Job -> Pod pour trouver la défaillance.
- Vérifiez les journaux, les événements et les codes de sortie.
- Corrigez une chose à la fois et vérifiez avec un job manuel.
- Mettez en place une surveillance pour que les échecs soient remarqués rapidement.
- Documentez les incidents et partagez les connaissances avec votre équipe.
Comme prochaine étape, choisissez un CronJob à faible risque dans votre environnement. Enregistrez sa planification actuelle, l'heure de sa dernière exécution et l'état du job. Ensuite, suivez les étapes de vérification : décrivez le CronJob, vérifiez le dernier job et consultez les journaux de son pod. Si tout est sain, parfait — vous avez maintenant une base de référence. Sinon, vous avez un chemin clair pour diagnostiquer et récupérer.
Un flux de travail opérationnel fiable rend les échecs visibles, protège les données sensibles, limite les modifications à la ressource prévue et définit la vérification de récupération avant qu'un incident ne force la décision. Avec les exemples pratiques et les commandes de ce guide, vous êtes bien équipé pour assurer le bon fonctionnement de vos CronJobs Kubernetes.