Les CronJobs Kubernetes sont excellents pour les tâches récurrentes telles que les exports de données nocturnes, la rotation des logs ou le nettoyage périodique. Pourtant, de petites erreurs de configuration peuvent provoquer des exécutions chevauchantes, des planifications manquées, des objets d'historique bruyants ou des jobs qui ne démarrent jamais. Ce guide montre des méthodes pratiques et sûres pour configurer, vérifier et dépanner les CronJobs avec des exemples concrets YAML et kubectl. Vous apprendrez à inventorier votre environnement, choisir des valeurs par défaut sûres, déployer les changements sans surprise, valider le comportement, récupérer après des échecs et adopter une liste de vérification qui rend les opérations reproductibles.

## Inventaire des versions et de l'environnement

Avant de modifier quoi que ce soit, capturez une vue minimale mais complète de votre environnement. La version et la topologie déterminent souvent quels champs sont disponibles et comment le contrôleur se comporte.

### Commandes

- Vérifier les versions client et serveur :

kubectl version --short 

- Lister les groupes d'API CronJob pour confirmer la prise en charge :

 kubectl api-resources | grep -i cronjob 

- Inventorier les espaces de noms et les CronJobs existants :

 kubectl get cronjobs.batch -A 

- Inspecter un CronJob spécifique en profondeur :

 kubectl get cronjob my-cron -n myns -o yaml 

### Éléments à capturer (exemples construits ; vérifiez dans votre environnement)

 Élément à capturer Commande exemple Valeur exemple (construite) 
 Version serveur Kubernetes kubectl version --short Server Version: v1.28.3 
 API CronJob kubectl api-resources batch/v1 CronJob 
 Prise en charge du fuseau horaire kubectl explain cronjob.spec.timeZone Champ présent depuis v1.27+ 
 Espace de noms et nom n/a myns/my-cron 
 Santé du contrôleur kubectl get deploy -n kube-system contrôleur cronjob disponible 

### Notes

- batch/v1 CronJob est largement disponible dans les clusters modernes. Si votre cluster est très ancien, confirmez si vous êtes sur batch/v1beta1 et planifiez une migration.

- Le champ timeZone est disponible depuis Kubernetes 1.27+. S'il n'apparaît pas dans la sortie de kubectl explain , ne l'utilisez pas.

## Parcours de configuration sûr

 Cette section couvre les erreurs de configuration courantes et les choix sûrs, avec du YAML pratique que vous pouvez copier et adapter.

## Erreur 1 : Autoriser les exécutions chevauchantes par défaut

Symptôme : Une nouvelle exécution démarre tandis que la précédente est encore active, causant un travail en double ou une contention de ressources.

Pourquoi cela arrive : concurrencyPolicy vaut Allow par défaut.

Choix sûr : Préférez Forbid pour les tâches idempotentes qui ne doivent pas se chevaucher. Utilisez Replace seulement si la nouvelle exécution doit préempter l'ancienne.

Mauvais exemple (chevauchements probables) :

apiVersion: batch/v1
kind: CronJob
metadata:
 name: overlap-prone
spec:
 schedule: "* * * * *" # chaque minute
 jobTemplate:
 spec:
 template:
 spec:
 restartPolicy: OnFailure
 containers:
 - name: worker
 image: busybox:1.36
 command: ["/bin/sh","-c","echo starting; sleep 90; echo done"] 
 Correctif :

spec:
 concurrencyPolicy: Forbid
 schedule: "* * * * *"
 jobTemplate:
 spec:
 backoffLimit: 1
 template:
 spec:
 restartPolicy: OnFailure
 containers:
 - name: worker
 image: busybox:1.36
 command: ["/bin/sh","-c","echo starting; sleep 90; echo done"] 
 Résultat attendu : Lorsqu'une minute s'écoule alors que l'exécution précédente dort encore, la nouvelle exécution est ignorée. Vérifiez avec :

kubectl get jobs -l cronjob-name=overlap-prone 

## Erreur 2 : restartPolicy invalide dans le modèle de Job

 Symptôme : Le Job ne planifie jamais de Pods ; vous voyez des erreurs de validation comme restartPolicy: Always is not allowed for Jobs .

Choix sûr : Réglez restartPolicy sur OnFailure (le plus courant) ou Never .

Extrait de correctif :

spec:
 jobTemplate:
 spec:
 template:
 spec:
 restartPolicy: OnFailure # pas Always 

## Erreur 3 : Planifications manquées à cause d'un startingDeadlineSeconds trop petit

 Symptôme : Le CronJob ne crée pas de Job après un incident temporaire du control-plane, une lenteur du registre d'images ou une dérive d'horloge.

Pourquoi cela arrive : startingDeadlineSeconds limite la fenêtre dans laquelle les exécutions manquées sont démarrées. Trop petit signifie que le CronJob abandonne vite. Par exemple, si un job quotidien à 02h00 rate sa fenêtre parce que l'API server était injoignable pendant 15 minutes, un startingDeadlineSeconds de 600 (10 minutes) fera que l'exécution de ce jour sera définitivement ignorée.

Choix sûr : Définissez une fenêtre raisonnable. Pour des jobs horaires, utilisez 3600 secondes (1 heure). Pour des jobs quotidiens, utilisez 86400 secondes (24 heures) ou au moins 3600.

Exemple de correctif pour un job horaire :

spec:
 schedule: "0 * * * *"
 startingDeadlineSeconds: 3600 # autoriser le rattrapage dans l'heure 

## Erreur 4 : Encoder le fuseau horaire dans l'expression cron ou dépendre de l'heure du nœud

 Symptôme : Les jobs se déclenchent à la mauvaise heure locale ou dérivent lors des transitions d'heure d'été/hiver. Par exemple, un job planifié à 0 2 * pour 2h HNE s'exécutera à 2h UTC (21h HNE la veille) en hiver et 3h UTC (22h HNE) en été si le cluster utilise UTC.

Choix sûr :

- Si votre cluster le prend en charge (Kubernetes 1.27+), utilisez spec.timeZone pour définir explicitement le fuseau horaire du job.

- Sinon, planifiez en UTC et convertissez les heures métier en UTC dans votre planification.

Exemple avec timeZone :

spec:
 schedule: "0 2 * * *"
 timeZone: "America/New_York" # utiliser seulement si supporté dans votre version de cluster 

## Erreur 5 : Sur-rétention ou sous-rétention de l'historique des jobs

 Symptôme : Bruit API dû à des centaines d'anciens objets Job ou, inversement, pas assez d'historique pour dépanner un échec de la semaine dernière.

Choix sûr : Ajustez les limites par CronJob selon la fréquence de planification. Pour un job quotidien, gardez 7 réussis et 10 échoués. Pour un job horaire, gardez 24 réussis et 48 échoués.

Exemple :

spec:
 successfulJobsHistoryLimit: 7
 failedJobsHistoryLimit: 10
 jobTemplate:
 spec:
 ttlSecondsAfterFinished: 86400 # optionnel dans le spec du Job quand le contrôleur TTL est activé 

## Erreur 6 : Relances non bornées sur Pods en échec

 Symptôme : Les Pods relancent indéfiniment et chevauchent les planifications suivantes, surtout quand restartPolicy: OnFailure se combine sans backoffLimit .

Choix sûr : Limitez les relances et la durée d'exécution. Définissez backoffLimit pour plafonner les redémarrages de Pod, et activeDeadlineSeconds pour plafonner la durée totale du Job.

Exemple :

spec:
 jobTemplate:
 spec:
 backoffLimit: 2
 activeDeadlineSeconds: 1200 # 20 minutes de plafond dur pour chaque job
 template:
 spec:
 restartPolicy: OnFailure 
 Avec backoffLimit: 2 , un Pod en échec redémarre au maximum deux fois (3 tentatives au total). Ensuite, le Job est marqué comme échoué et le contrôleur CronJob ne relancera pas avant la prochaine planification.

## Erreur 7 : Service account ou permissions manquantes

Symptôme : Les Pods démarrent mais échouent avec des erreurs RBAC quand ils doivent appeler l'API ou accéder à des secrets.

Choix sûr : Assignez un ServiceAccount minimal et les Role/RoleBinding requis dans le même espace de noms.

Exemple d'extrait :

apiVersion: v1
kind: ServiceAccount
metadata:
 name: cron-sa
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
 name: cron-reader
rules:
- apiGroups: [""]
 resources: ["configmaps"]
 verbs: ["get","list"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
 name: cron-reader-binding
subjects:
- kind: ServiceAccount
 name: cron-sa
roleRef:
 kind: Role
 name: cron-reader
 apiGroup: rbac.authorization.k8s.io
---
apiVersion: batch/v1
kind: CronJob
metadata:
 name: rbac-aware
spec:
 schedule: "*/15 * * * *"
 concurrencyPolicy: Forbid
 jobTemplate:
 spec:
 template:
 spec:
 serviceAccountName: cron-sa
 restartPolicy: OnFailure
 containers:
 - name: worker
 image: busybox:1.36
 command: ["/bin/sh","-c","kubectl get cm || echo no kubectl here"] 

## Erreur 8 : Utiliser Always pour l'image pull sur des images stables

 Symptôme : Tirages d'images inutiles augmentent la latence ou échouent quand le registre est down.

Choix sûr : Si vous pinez sur un digest ou un tag immuable, utilisez IfNotPresent .

containers:
- name: worker
 image: myrepo/tool@sha256:abc123...
 imagePullPolicy: IfNotPresent 

## Habitudes de déploiement plus sûres

- Exécutions de test : Utilisez kubectl create job --from=cronjob/ pour simuler un job planifié immédiatement.

- Validation dry-run : Utilisez kubectl apply --dry-run=server -f cron.yaml pour attraper les erreurs de schéma.

- Changement progressif : Introduisez un nouveau CronJob avec le suffixe -v2 , exécutez-le en parallèle mais suspendez l'ancien ( suspend: true ) pour une seule fenêtre, puis finalisez le basculement.

- Contrôle de suspension : Mettez temporairement un CronJob en pause si nécessaire :

 kubectl patch cronjob my-cron -p '{"spec":{"suspend":true}}' 

## Vérification et diagnostics

 Utilisez ces vérifications pour confirmer le comportement attendu et isoler rapidement les problèmes.

### Vérifications de base

- Confirmer la santé du contrôleur et l'activité de planification :

kubectl get cronjob my-cron -n myns
kubectl describe cronjob my-cron -n myns 

- Lister les Jobs créés par un CronJob :

 kubectl get jobs -n myns -l cronjob-name=my-cron 

- Inspecter les événements et logs des Pods pour le Job le plus récent :

 JOB=$(kubectl get jobs -n myns -l cronjob-name=my-cron -o jsonpath='{.items[-1:].metadata.name}')
kubectl get pods -n myns -l job-name=$JOB
POD=$(kubectl get pods -n myns -l job-name=$JOB -o jsonpath='{.items[0].metadata.name}')
kubectl logs -n myns $POD 

- Valider l'analyse de l'expression cron et les champs :

 kubectl apply --dry-run=server -f cron.yaml
kubectl explain cronjob.spec # confirmer les champs disponibles 

### Erreurs courantes, symptômes, vérifications et correctifs

 Erreur Ce que vous voyez Vérifier avec Correctif recommandé 
 Exécutions chevauchantes (Allow par défaut) Plusieurs Jobs actifs kubectl get jobs -l cronjob-name=NAME Définir concurrencyPolicy: Forbid ou Replace 
 restartPolicy invalide Job a 0 Pods ; erreur de validation kubectl apply --dry-run=server -f Définir restartPolicy: OnFailure ou Never 
 startingDeadlineSeconds trop petit Planifications manquées non rattrapées kubectl describe cronjob Augmenter startingDeadlineSeconds 
 Support timeZone manquant Champ ignoré ou apply échoue kubectl explain cronjob.spec.timeZone Utiliser planifications UTC ou mettre à jour le cluster 
 Rétention d'historique excessive Centaines d'objets Job kubectl get jobs -l cronjob-name=NAME Baisser les limites successful/failed history 
 Relances non bornées Échecs longs ou chevauchants kubectl describe job ; regarder backoff Définir backoffLimit et activeDeadlineSeconds 
 RBAC manquant Logs Pod montrent erreurs Forbidden kubectl logs ; kubectl auth can-i Attacher ServiceAccount minimal et Role 

### Résultats attendus pour des CronJobs sains

- kubectl get cronjob affiche LAST SCHEDULE avec un timestamp récent qui correspond à votre planification et stratégie de fuseau horaire.

- kubectl get jobs montre soit un job réussi pour chaque période attendue, soit un job en cours d'exécution. Les statuts passent de 0/1 à 1/1 completions.

- kubectl logs du Pod le plus récent contiennent la sortie attendue sans boucles de crash répétées.

## Déclenchement manuel et backfill

 Quand vous devez tester la logique sans attendre la planification :

- Créer un job one-off à partir du modèle CronJob :

kubectl create job --from=cronjob/my-cron my-cron-adhoctest -n myns 

- Étiqueter et suivre le job ad-hoc distinctement :

 kubectl label job my-cron-adhoctest purpose=adhoc -n myns 

- Supprimer le job ad-hoc après validation :

 kubectl delete job my-cron-adhoctest -n myns 

## Modes d'échec et récupération

 Comprenez ce qui échoue, comment cela se présente et comment récupérer en toute sécurité.

### Indisponibilité du contrôleur ou de l'API

- Symptôme : Aucun nouveau Job créé aux heures attendues. LAST SCHEDULE cesse d'avancer.

- Cause : Perturbation du control-plane ou contrôleur CronJob non en cours d'exécution.

- Récupération : Après la récupération du control-plane, assurez-vous que startingDeadlineSeconds est assez grand pour rattraper les exécutions manquées. Confirmez que le déploiement du contrôleur est sain.

### Dérive d'horloge et confusion de fuseau horaire

- Symptôme : Les planifications dérivent par rapport à l'heure métier ou à l'heure d'été/hiver.

- Récupération : Préférez les planifications UTC ou utilisez spec.timeZone si votre version le supporte. Validez en comparant LAST SCHEDULE à une horloge UTC connue.

### Erreurs de tirage d'image

- Symptôme : ImagePullBackOff sur les Pods.

- Récupération : Vérifiez imagePullSecrets , la disponibilité du registre, et envisagez IfNotPresent pour les images immuables. Relancez avec un job ad-hoc pour confirmer.

### Échecs RBAC

- Symptôme : Erreurs Forbidden dans les logs lors de l'accès aux ressources API.

- Récupération : Attachez un ServiceAccount minimal, Role et RoleBinding. Testez les permissions avec :

kubectl auth can-i get configmaps --as=system:serviceaccount:myns:cron-sa -n myns 

### Boucles de crash de Pod ou timeouts

- Symptôme : Les Pods redémarrent répétitivement ou dépassent les fenêtres d'exécution.

- Récupération : Définissez backoffLimit à un nombre raisonnable, ajoutez activeDeadlineSeconds , et assurez-vous que le job est idempotent. Étudiez les logs du conteneur.

### Encombrement d'historique

- Symptôme : Grand nombre de Jobs terminés ralentissent les opérations de listage.

- Récupération : Baissez successfulJobsHistoryLimit et failedJobsHistoryLimit ; activez ttlSecondsAfterFinished dans le modèle de Job si le contrôleur TTL est disponible.

### Arrêt d'urgence et reprise

- Suspendre temporairement la planification :

 kubectl patch cronjob my-cron -n myns -p '{"spec":{"suspend":true}}' 

- Reprendre après atténuation :

 kubectl patch cronjob my-cron -n myns -p '{"spec":{"suspend":false}}' 

## Stratégie de rollback

- Conservez un manifeste connu-bon pour chaque CronJob. Pour faire un rollback, réappliquez le fichier précédent explicitement :

 kubectl apply -f cronjob-prev.yaml 

- Si un changement de champ nécessite une recréation (rare pour les CronJobs), suspendez le job actuel, créez le remplacement avec un nom distinct, validez une exécution de test, puis supprimez l'ancien.

## Vérification après récupération

- Confirmez que LAST SCHEDULE avance à nouveau.

- Assurez-vous qu'un seul Job s'exécute par période quand concurrencyPolicy est Forbid .

- Vérifiez que les limites d'historique conservent assez de contexte pour la revue post-incident sans encombrement.

## Liste de vérification des opérations

 Utilisez cette liste concise lors des revues et avant/après les changements.

### Inventaire et prérequis

- [ ] kubectl version --short capturé (client et serveur)

- [ ] Groupe d'API CronJob confirmé ( batch/v1 )

- [ ] Présence du champ timeZone vérifiée avec kubectl explain

- [ ] Espace de noms et noms enregistrés

### Sécurité de la configuration

- [ ] concurrencyPolicy défini sur Forbid (ou Replace avec justification)

- [ ] restartPolicy défini sur OnFailure ou Never

- [ ] startingDeadlineSeconds dimensionné à la fenêtre de tolérance attendue

- [ ] successfulJobsHistoryLimit et failedJobsHistoryLimit ajustés

- [ ] backoffLimit et activeDeadlineSeconds raisonnables

- [ ] serviceAccountName défini si accès API/secret nécessaire, RBAC minimal

- [ ] Référence d'image immuable, imagePullPolicy approprié

- [ ] suspend défini appropriément pendant le déploiement ou les fenêtres de pause

### Étapes de validation

- [ ] kubectl apply --dry-run=server passe

- [ ] kubectl describe cronjob montre l'heure de prochaine planification attendue

- [ ] Job ad-hoc manuel créé et complété pour les changements nouveaux ou risqués

### Observation et diagnostics

- [ ] LAST SCHEDULE se met à jour comme attendu après le changement

- [ ] Seul le nombre attendu de Jobs actifs par période

- [ ] Logs des Pods inspectés pour signal de succès

### Récupération et rollback

- [ ] Manifeste connu-bon sous la main pour rollback immédiat

- [ ] Capacité à suspendre/reprendre vérifiée

- [ ] Nettoyage des jobs ad-hoc ou échoués effectué

### Signaux de référence et actions

<div class="my-stack-md overflow-x-auto">
<table class="w-full min-w-[42rem] border-collapse text-left">
<thead><tr><th scope="col" class="border border-outline-variant bg-surface-container-low px-4 py-3 text-left font-label-md font-semibold text-on-surface">Signal</th><th scope="col" class="border border-outline-variant bg-surface-container-low px-4 py-3 text-left font-label-md font-semibold text-on-surface">Ce qu&#39;il suggère</th><th scope="col" class="border border-outline-variant bg-surface-container-low px-4 py-3 text-left font-label-md font-semibold text-on-surface">Action suivante</th></tr></thead>
<tbody><tr><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">LAST SCHEDULE n&#39;avance pas</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Problème contrôleur ou planification</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Vérifier santé contrôleur ; revoir startingDeadlineSeconds</td></tr>
<tr><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Plusieurs Jobs concurrents</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Chevauchement autorisé</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Définir concurrencyPolicy: Forbid ou Replace</td></tr>
<tr><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">ImagePullBackOff</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Problème registre ou pull policy</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Vérifier secrets ; envisager IfNotPresent pour tags immuables</td></tr>
<tr><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Forbidden dans logs</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Lacune RBAC</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Attacher ServiceAccount ; ajouter Role/Binding minimal</td></tr>
<tr><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Jobs ne finissent jamais</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Relances non bornées ou tâche longue</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Définir backoffLimit et activeDeadlineSeconds</td></tr></tbody>
</table>
</div>

## Conclusion

Les CronJobs sont fiables quand ils sont configurés délibérément. Le parcours le plus sûr consiste à inventorier les versions et champs, adopter l'exécution non chevauchante par défaut, dimensionner les limites de relance et de délai, et définir une rétention qui garde l'historique utile sans causer d'encombrement. Validez les changements avec un dry-run côté serveur et un job one-off avant d'activer les planifications, et gardez un plan de rollback simple en conservant les manifestes connus-bons. Avec ces pratiques et la liste de vérification ci-dessus, vous pouvez réduire les surprises opérationnelles et maintenir les tâches récurrentes prévisibles.