
Un Job exécute une tâche jusqu'à sa complétion : migration de base de données, traitement batch, génération de rapport. Un CronJob crée des objets Job selon un planning ; ce sont ensuite ces Jobs qui exécutent les Pods nécessaires.
Contrairement aux Pods d'un Deployment, qui tournent en continu, un Job s'arrête une fois son travail terminé. Kubernetes garantit que la tâche ira au bout, même après un échec temporaire.
Une tâche planifiée ou relancée doit pouvoir être rejouée sans effet indésirable : c'est ce qu'on appelle une tâche idempotente. L'exigence devient critique pour les CronJobs et les Jobs à tentatives multiples, qui se rejouent sans que personne ne le décide.
Prérequis : un cluster Kubernetes avec kubectl configuré.
Ce que vous allez apprendre
Section intitulée « Ce que vous allez apprendre »- Créer un Job pour une tâche ponctuelle
- Configurer le comportement en cas d'échec (backoffLimit, activeDeadlineSeconds)
- Paralléliser l'exécution avec completions et parallelism
- Planifier des tâches récurrentes avec CronJob
- Maîtriser les paramètres de production (concurrencyPolicy, suspend, timeZone)
- Appliquer les bonnes pratiques pour le nettoyage automatique
Job ou Deployment ?
Section intitulée « Job ou Deployment ? »Le choix se joue sur une seule question : la charge a-t-elle une fin ? Un Deployment redémarre indéfiniment ses Pods, c'est exactement ce qu'il faut pour un service web mais c'est catastrophique pour un script de migration, qui serait relancé en boucle après chaque succès. Le tableau ci-dessous vous aide à trancher avant d'écrire le moindre YAML.
| Ressource | Comportement | Cas d'usage |
|---|---|---|
| Deployment | Maintient N Pods en continu | Applications web, API, services |
| Job | Exécute jusqu'à complétion, puis s'arrête | Migration, backup, traitement batch |
| CronJob | Crée un Job selon un planning | Tâches planifiées, maintenance |
Créer votre premier Job
Section intitulée « Créer votre premier Job »Un Job crée un Pod, exécute la tâche, et se termine une fois le travail fait :
apiVersion: batch/v1kind: Jobmetadata: name: hello-jobspec: template: spec: containers: - name: hello image: busybox:1.37@sha256:9db7b59979c38555a39def84a31fb98b5296952f9e3afd4f6f11f05b07adfab0 command: ["echo", "Hello from Kubernetes Job"] restartPolicy: Neverkubectl apply -f hello-job.yamlAttendez la fin de l'exécution :
kubectl wait --for=condition=complete job/hello-job --timeout=60sVérifiez le statut :
kubectl get jobsNAME STATUS COMPLETIONS DURATION AGEhello-job Complete 1/1 4s 10sObserver et inspecter un Job
Section intitulée « Observer et inspecter un Job »Un Job terminé ne dit rien de lui-même : c'est le Pod qu'il a créé qui porte la sortie du programme. Trois commandes suffisent pour tout savoir : kubectl logs pour la sortie, kubectl describe pour le compteur de tentatives et les événements, kubectl get pods pour retrouver les Pods même après la fin du Job. Gardez en tête que ces informations disparaissent avec le Job si vous avez activé un nettoyage automatique.
Voir les logs
Section intitulée « Voir les logs »kubectl logs job/<nom> sélectionne automatiquement un Pod du Job, vous n'avez pas besoin de connaître son nom généré.
kubectl logs job/hello-jobHello from Kubernetes JobDétails du Job
Section intitulée « Détails du Job »kubectl describe affiche la configuration effective et surtout la liste des événements, où apparaissent les échecs de planification et les redémarrages.
kubectl describe job hello-jobInformations clés :
- Completions : nombre d'exécutions réussies requises
- Parallelism : nombre de Pods exécutés en parallèle
- Active / Succeeded / Failed : état des Pods
Lister les Pods créés par un Job
Section intitulée « Lister les Pods créés par un Job »Kubernetes appose le label job-name sur chaque Pod créé. C'est lui qui permet de tous les retrouver, y compris ceux qui ont échoué avant une nouvelle tentative.
kubectl get pods -l job-name=hello-jobAnatomie d'un Job
Section intitulée « Anatomie d'un Job »Le manifeste minimal vu plus haut fonctionne, mais il laisse Kubernetes appliquer ses valeurs par défaut : six tentatives, aucune limite de durée, aucun nettoyage. L'exemple ci-dessous montre les quatre champs qui transforment un Job jetable en tâche exploitable en production. Tous se déclarent au niveau du spec du Job, pas dans le template du Pod, une confusion fréquente qui fait rejeter le manifeste par l'API.
apiVersion: batch/v1kind: Jobmetadata: name: backup-databasespec: backoffLimit: 3 # Tentatives max en cas d'échec activeDeadlineSeconds: 300 # Timeout global (5 min) ttlSecondsAfterFinished: 3600 # Nettoyage auto après 1h template: spec: containers: - name: backup image: postgres:16@sha256:f1c3376c26f2609ab9f29f71f824103fe2fcd8ee0346485cb6122a4f93df6f94 command: ["pg_dump", "-h", "db-host", "-U", "admin", "mydb"] restartPolicy: Never| Champ | Description | Valeur par défaut |
|---|---|---|
backoffLimit | Nombre de reprises avant abandon | 6 |
activeDeadlineSeconds | Durée max d'exécution du Job | Illimitée |
ttlSecondsAfterFinished | Nettoyage automatique après N secondes | Non activé |
completions | Nombre d'exécutions réussies requises | 1 |
parallelism | Nombre de Pods simultanés | 1 |
Comportement en cas d'échec
Section intitulée « Comportement en cas d'échec »Un Job qui échoue ne s'arrête pas tout de suite : Kubernetes relance le travail jusqu'à épuisement du quota de tentatives. Trois réglages encadrent ce comportement, et ils répondent à des questions différentes : combien de fois réessayer (backoffLimit), pendant combien de temps au maximum (activeDeadlineSeconds), et comment le Pod lui-même est relancé (restartPolicy). Sans eux, une tâche cassée peut consommer des ressources pendant des heures avant que quelqu'un ne s'en aperçoive.
backoffLimit : limiter les tentatives
Section intitulée « backoffLimit : limiter les tentatives »Si un Pod échoue, Kubernetes le relance avec un délai exponentiel (10s, 20s, 40s...). backoffLimit stoppe les tentatives :
spec: backoffLimit: 3 # 3 reprises, donc 4 tentatives au totalLe commentaire ci-dessus n'est pas un détail : le compteur porte sur les
reprises, pas sur les tentatives. La première exécution n'en est pas une, et
le nombre de Pods réellement créés vaut donc toujours backoffLimit + 1 :
backoffLimit | Pods créés |
|---|---|
0 | 1 |
1 | 2 |
3 | 4 |
Un backoffLimit: 0 ne désactive pas le Job, il interdit la reprise. Dans
les trois cas, le Job finit en Failed avec la raison
BackoffLimitExceeded et le message
Job has reached the specified backoff limit.
backoffLimit vaut 6 par défaut, y compris sur un Job indexé. Ce mode
ouvre en plus backoffLimitPerIndex, qui compte les reprises par partition
plutôt que pour le Job entier, mais ce champ n'est pas activé par défaut.
activeDeadlineSeconds : timeout global
Section intitulée « activeDeadlineSeconds : timeout global »Empêche un Job de tourner indéfiniment :
spec: activeDeadlineSeconds: 300 # 5 minutes maxSi le temps est dépassé, tous les Pods sont arrêtés et le Job passe en Failed avec la raison DeadlineExceeded.
restartPolicy : comportement du Pod
Section intitulée « restartPolicy : comportement du Pod »Ce champ décide de ce qui est recréé après un échec : un Pod entièrement neuf, ou le même conteneur relancé dans le Pod existant. La différence compte dès que la tâche écrit dans un volume emptyDir, car ce volume est perdu avec le Pod.
| Valeur | Comportement |
|---|---|
Never | Kubernetes crée un nouveau Pod à chaque tentative |
OnFailure | Le même Pod redémarre (garde les volumes locaux) |
Jobs parallèles
Section intitulée « Jobs parallèles »Deux champs pilotent le parallélisme : completions fixe le nombre total d'exécutions réussies attendues, parallelism le nombre de Pods qui tournent en même temps. Kubernetes se charge de maintenir le second jusqu'à atteindre le premier. Attention, les Pods d'un même Job reçoivent tous la même commande : sans mécanisme de partage du travail, ils traiteraient les mêmes données.
apiVersion: batch/v1kind: Jobmetadata: name: parallel-jobspec: completions: 10 # 10 exécutions au total parallelism: 3 # 3 Pods en parallèle template: spec: containers: - name: worker image: busybox:1.37@sha256:9db7b59979c38555a39def84a31fb98b5296952f9e3afd4f6f11f05b07adfab0 command: ["sh", "-c", "echo 'Processing...' && sleep 5"] restartPolicy: NeverKubernetes lance 3 Pods en parallèle, puis en lance de nouveaux dès qu'un se termine, jusqu'à atteindre 10 complétions.
Jobs indexés
Section intitulée « Jobs indexés »En mode Indexed, chaque Pod reçoit un index de complétion unique. Kubernetes l'expose par l'annotation batch.kubernetes.io/job-completion-index, et la variable d'environnement JOB_COMPLETION_INDEX est lisible depuis le conteneur :
spec: completions: 5 parallelism: 5 completionMode: Indexed template: spec: containers: - name: worker image: busybox:1.37@sha256:9db7b59979c38555a39def84a31fb98b5296952f9e3afd4f6f11f05b07adfab0 command: ["sh", "-c", "echo Processing chunk $JOB_COMPLETION_INDEX"] restartPolicy: NeverChaque Pod affiche sa propre partition, de Processing chunk 0 à
Processing chunk 4, et l'index se retrouve sur le Pod lui-même, dans
l'annotation batch.kubernetes.io/job-completion-index. C'est le mécanisme à
retenir pour découper un gros volume de données en tranches traitées en
parallèle.
Nettoyage automatique avec TTL
Section intitulée « Nettoyage automatique avec TTL »Sans nettoyage, les Jobs terminés s'accumulent. ttlSecondsAfterFinished les supprime automatiquement :
spec: ttlSecondsAfterFinished: 3600 # Supprimé 1h après complétionActivez ttlSecondsAfterFinished sur tous vos Jobs pour éviter l'accumulation de ressources. Ce délai s'applique aux Jobs réussis comme aux Jobs échoués : tous sont supprimés automatiquement, avec leurs Pods.
CronJobs : planifier des tâches récurrentes
Section intitulée « CronJobs : planifier des tâches récurrentes »Un CronJob crée automatiquement des Jobs selon un planning. Il ne lance jamais de Pod directement : à chaque échéance, il fabrique un nouvel objet Job, qui crée à son tour les Pods. C'est pour cette raison que le manifeste contient un jobTemplate qui enveloppe le template de Pod, avec deux niveaux de spec imbriqués.
apiVersion: batch/v1kind: CronJobmetadata: name: daily-backupspec: schedule: "0 2 * * *" # Tous les jours à 2h du matin timeZone: "Europe/Paris" # Fuseau horaire explicite successfulJobsHistoryLimit: 3 # Garde les 3 derniers Jobs réussis failedJobsHistoryLimit: 1 # Garde le dernier Job échoué jobTemplate: spec: template: spec: containers: - name: backup image: postgres:16@sha256:f1c3376c26f2609ab9f29f71f824103fe2fcd8ee0346485cb6122a4f93df6f94 command: ["pg_dump", "-h", "db-host", "-U", "admin", "mydb"] restartPolicy: NeverSyntaxe cron
Section intitulée « Syntaxe cron »Le champ schedule reprend la syntaxe cron classique à cinq champs, séparés par des espaces, de la minute vers le jour de la semaine. Une expression invalide est rejetée à la création de l'objet, mais une expression valide qui ne correspond pas à votre intention passera sans avertissement.
| Expression | Signification |
|---|---|
0 * * * * | Chaque heure à :00 |
0 2 * * * | Tous les jours à 2h |
0 0 * * 0 | Chaque dimanche à minuit |
*/15 * * * * | Toutes les 15 minutes |
0 9-17 * * 1-5 | Chaque heure de 9h à 17h, lundi à vendredi |
Observer les CronJobs
Section intitulée « Observer les CronJobs »La sortie de kubectl get cronjobs condense l'essentiel : le planning appliqué, le fuseau retenu, l'état de suspension, le nombre de Jobs actifs et la date de la dernière exécution déclenchée.
kubectl get cronjobsNAME SCHEDULE TIMEZONE SUSPEND ACTIVE LAST SCHEDULE AGEdaily-backup 0 2 * * * Europe/Paris False 0 12h 3dParamètres CronJob de production
Section intitulée « Paramètres CronJob de production »Un CronJob qui fonctionne en lab peut poser problème en production dès que la tâche dure plus longtemps que l'intervalle prévu, ou que le cluster était indisponible à l'heure dite. Quatre champs répondent à ces situations : concurrencyPolicy pour les chevauchements, startingDeadlineSeconds pour les exécutions manquées, suspend pour la mise en pause, timeZone pour l'heure locale. Les valeurs par défaut privilégient la souplesse plutôt que la sécurité, à vous de les resserrer.
Les valeurs par défaut, relevées sur un CronJob minimal
Section intitulée « Les valeurs par défaut, relevées sur un CronJob minimal »Avant de régler quoi que ce soit, sachez ce que Kubernetes pose à votre place. Les deux limites d'historique décident de ce que vous retrouverez le jour où il faudra comprendre un échec passé, et un seul Job échoué est conservé par défaut :
| Champ | Défaut |
|---|---|
concurrencyPolicy | Allow |
suspend | false |
successfulJobsHistoryLimit | 3 |
failedJobsHistoryLimit | 1 |
startingDeadlineSeconds | non défini |
timeZone | non défini, le fuseau est celui du contrôleur |
Un Job, de son côté, démarre avec backoffLimit: 6, completions: 1,
parallelism: 1 et aucun TTL.
concurrencyPolicy : gérer les chevauchements
Section intitulée « concurrencyPolicy : gérer les chevauchements »Ce champ répond à une question précise : que faire si l'exécution précédente n'est pas terminée quand la suivante doit démarrer ?
| Valeur | Comportement |
|---|---|
Allow | Autorise plusieurs Jobs simultanés (par défaut) |
Forbid | Skip l'exécution si un Job est en cours |
Replace | Arrête l'ancien Job et en démarre un nouveau |
spec: concurrencyPolicy: Forbid # Recommandé pour les backupsstartingDeadlineSeconds : délai de démarrage
Section intitulée « startingDeadlineSeconds : délai de démarrage »Si le cluster était indisponible à l'heure prévue, combien de temps après peut-on encore démarrer ?
spec: startingDeadlineSeconds: 3600 # 1h de délai maxSi startingDeadlineSeconds est défini, Kubernetes saute une exécution qui a dépassé ce délai. Sans ce champ, les occurrences n'ont aucune date limite de démarrage et seront lancées même très en retard.
suspend : mettre en pause
Section intitulée « suspend : mettre en pause »Arrêter temporairement les exécutions sans supprimer le CronJob :
kubectl patch cronjob daily-backup -p '{"spec":{"suspend":true}}'Réactiver :
kubectl patch cronjob daily-backup -p '{"spec":{"suspend":false}}'timeZone : fuseau horaire explicite
Section intitulée « timeZone : fuseau horaire explicite »Depuis Kubernetes 1.27, vous pouvez spécifier le fuseau horaire :
spec: timeZone: "Europe/Paris"Sans timeZone, le planning utilise le fuseau horaire du kube-controller-manager, souvent UTC dans de nombreux clusters.
Déclencher manuellement un CronJob
Section intitulée « Déclencher manuellement un CronJob »Attendre 2 h du matin pour vérifier qu'une sauvegarde fonctionne n'a aucun sens. L'option --from recopie le jobTemplate du CronJob dans un Job qui démarre tout de suite : le planning n'est pas modifié, et le Job manuel porte le nom que vous lui donnez, ce qui évite de le confondre avec les exécutions automatiques.
kubectl create job --from=cronjob/daily-backup manual-backupSuivez l'exécution :
kubectl wait --for=condition=complete job/manual-backup --timeout=300skubectl logs job/manual-backupCe Job n'est pas pour autant indépendant du CronJob, contrairement à ce que son nom choisi à la main laisse croire. Kubernetes lui pose un lien de propriété :
kubectl get job manual-backup -o jsonpath='{.metadata.ownerReferences}'[{"apiVersion":"batch/v1","controller":true,"kind":"CronJob","name":"daily-backup"}]La conséquence est concrète : supprimez le CronJob et le Job manuel part avec lui, ses journaux compris. Si vous menez une investigation, récupérez les logs avant de toucher au CronJob, ou retirez le lien de propriété du Job avant de supprimer son parent.
Cas réels
Section intitulée « Cas réels »Les trois manifestes qui suivent reprennent les champs déjà vus, mais avec des valeurs choisies pour l'usage visé. Comparez-les : une migration se veut non rejouable et rapide, un backup se veut fiable et historisé, une purge se veut discrète. Le même objet Kubernetes se configure très différemment selon la conséquence d'une exécution en double.
Migration de base de données
Section intitulée « Migration de base de données »Une migration rejouée peut corrompre les données : le backoffLimit est ramené au strict minimum, et l'activeDeadlineSeconds évite qu'un verrou de base laisse le Job en cours indéfiniment. Les identifiants arrivent par envFrom depuis un Secret, jamais en clair dans le manifeste.
apiVersion: batch/v1kind: Jobmetadata: name: db-migrationspec: backoffLimit: 1 # Pas de retry sur migration activeDeadlineSeconds: 600 # 10 min max ttlSecondsAfterFinished: 86400 template: spec: containers: - name: migrate image: myapp-migrations:2.4.0 command: ["./manage.py", "migrate", "--no-input"] envFrom: - secretRef: name: db-credentials restartPolicy: NeverSauvegarde planifiée
Section intitulée « Sauvegarde planifiée »Deux backups qui écrivent en même temps dans le même volume produisent une archive inutilisable : concurrencyPolicy: Forbid interdit ce cas. L'historique conservé (sept Jobs réussis) permet de retrouver la trace d'un échec passé, et le dump est écrit dans un PersistentVolumeClaim, pas dans le conteneur, qui disparaît avec le Pod.
apiVersion: batch/v1kind: CronJobmetadata: name: postgres-backupspec: schedule: "0 3 * * *" timeZone: "Europe/Paris" concurrencyPolicy: Forbid successfulJobsHistoryLimit: 7 # 1 semaine d'historique failedJobsHistoryLimit: 3 jobTemplate: spec: template: spec: containers: - name: backup image: postgres:16@sha256:f1c3376c26f2609ab9f29f71f824103fe2fcd8ee0346485cb6122a4f93df6f94 command: - /bin/sh - -c - pg_dump -h $DB_HOST -U $DB_USER $DB_NAME | gzip > /backup/$(date +%Y%m%d).sql.gz envFrom: - secretRef: name: db-credentials volumeMounts: - name: backup-volume mountPath: /backup volumes: - name: backup-volume persistentVolumeClaim: claimName: backup-pvc restartPolicy: NeverPurge de données anciennes
Section intitulée « Purge de données anciennes »Une purge hebdomadaire tolère un décalage, elle est donc programmée le dimanche à 4 h, hors des créneaux de charge. Notez la position du ttlSecondsAfterFinished : il est placé dans le jobTemplate, car c'est bien le Job créé qui doit être nettoyé, pas le CronJob lui-même.
apiVersion: batch/v1kind: CronJobmetadata: name: data-cleanupspec: schedule: "0 4 * * 0" # Chaque dimanche à 4h timeZone: "Europe/Paris" jobTemplate: spec: ttlSecondsAfterFinished: 86400 template: spec: containers: - name: cleanup image: myapp-migrations:2.4.0 command: ["./cleanup.sh", "--older-than", "90d"] restartPolicy: NeverDebug : problèmes courants
Section intitulée « Debug : problèmes courants »Quatre situations reviennent en permanence avec les Jobs, et elles se diagnostiquent toutes de la même manière : d'abord les logs du Pod, ensuite les événements du Job ou du CronJob. Le réflexe qui fait gagner du temps est de vérifier que le Pod existe encore : si un TTL l'a supprimé, ses logs sont définitivement perdus et il faut relancer la tâche pour observer.
-
Le Job reste en "Running" indéfiniment
Vérifiez les logs du Pod :
Fenêtre de terminal kubectl logs job/my-jobSolution : ajoutez
activeDeadlineSecondspour forcer un timeout. -
Le Job échoue avec BackoffLimitExceeded
Analysez les événements :
Fenêtre de terminal kubectl describe job my-jobVérifiez les logs des Pods échoués :
Fenêtre de terminal kubectl logs -l job-name=my-job --all-containers -
Le CronJob ne s'exécute pas
Vérifiez l'expression cron et le fuseau horaire :
Fenêtre de terminal kubectl get cronjob my-cronjobkubectl describe cronjob my-cronjob | grep "Last Schedule" -
CronJob suspendu accidentellement
Fenêtre de terminal kubectl get cronjob my-cronjob -o jsonpath='{.spec.suspend}'Si
true, réactivez aveckubectl patch.
Erreurs fréquentes
Section intitulée « Erreurs fréquentes »Les deux premières lignes du tableau correspondent aux raisons d'échec que Kubernetes inscrit lui-même dans le statut du Job, vous les retrouverez telles quelles dans kubectl describe.
| Erreur | Cause | Solution |
|---|---|---|
BackoffLimitExceeded | Trop d'échecs | Analyser les logs, corriger le code |
DeadlineExceeded | Job trop long | Augmenter activeDeadlineSeconds ou optimiser |
| Job ne démarre pas | Expression cron invalide | Vérifier la syntaxe et le timeZone |
| Jobs qui s'accumulent | Pas de TTL | Ajouter ttlSecondsAfterFinished |
À retenir
Section intitulée « À retenir »- Un Job exécute une tâche jusqu'à complétion, un CronJob la planifie
restartPolicyvaut obligatoirementNeverouOnFailure, et l'API parle de « valeur requise », pas de valeur interditebackoffLimitcompte les reprises, pas les tentatives : il produit toujoursbackoffLimit + 1Pods, et vaut 6 par défaut ;activeDeadlineSecondsimpose un timeout globalttlSecondsAfterFinishednettoie les Jobs terminés, échecs comprisparallelismetcompletionspermettent le traitement parallèleconcurrencyPolicy: Forbidévite les exécutions simultanées de CronJobs- Par défaut un CronJob ne garde qu'un seul Job échoué : remontez
failedJobsHistoryLimitavant d'en avoir besoin timeZonegarantit des exécutions à l'heure locale, sinon c'est le fuseau du contrôleur qui s'appliquesuspendmet un CronJob en pause sans le supprimer- Testez avec
kubectl create job --from=cronjob/..., en sachant que ce Job appartient au CronJob et disparaît avec lui
Contrôle de connaissances
Section intitulée « Contrôle de connaissances »Ce quiz porte sur les points qui coûtent le plus cher quand ils sont mal
compris : la restartPolicy autorisée, le rôle du backoffLimit et l'effet
de concurrencyPolicy sur les CronJobs.
Contrôle de connaissances
Validez vos connaissances avec ce quiz interactif
Informations
- Le chronomètre démarre au clic sur Démarrer
- Questions à choix multiples, vrai/faux et réponses courtes
- Vous pouvez naviguer entre les questions
- Les résultats détaillés sont affichés à la fin
Lance le quiz et démarre le chronomètre
Vérification
(0/0)Profil de compétences
Quoi faire maintenant
Ressources pour progresser
Des indices pour retenter votre chance ?
Nouveau quiz complet avec des questions aléatoires
Retravailler uniquement les questions ratées
Retour à la liste des certifications
Pour aller plus loin
Section intitulée « Pour aller plus loin »- Débugger une application : Les techniques pour inspecter un Job qui échoue avant d'avoir produit le moindre log.
- Requests et Limits : Le dimensionnement des Jobs, souvent oubliés quand un quota de namespace est atteint.
- RBAC Kubernetes : Les permissions du ServiceAccount qu'utilise un CronJob pour agir sur le cluster.