Un Deployment redémarre indéfiniment ses pods : c’est exactement le comportement voulu pour un serveur HTTP, et exactement le comportement à éviter pour une migration de base de données qui doit s’exécuter une fois puis s’arrêter. Job répond à ce besoin structurellement différent : garantir qu’une tâche se termine avec succès, pas qu’elle reste vivante.
Un Job termine, ne reste jamais vivant
Un pod géré par un Job a un restartPolicy fixé à OnFailure ou Never, jamais Always : quand le conteneur se termine avec un code de sortie 0, le Job est marqué complet et ne relance rien.
apiVersion: batch/v1
kind: Job
metadata:
name: db-migration
spec:
backoffLimit: 3
template:
spec:
restartPolicy: OnFailure
containers:
- name: migrate
image: migrate-tool:1.2
command: ["migrate", "up"]
backoffLimit plafonne le nombre de tentatives avant que le Job ne soit marqué en échec définitif, avec un délai croissant entre chaque essai (comme le backoff qui produit un CrashLoopBackOff sur un pod géré autrement). C’est exactement le mécanisme qui protège une migration de base de données déployée comme hook Helm : trois échecs consécutifs arrêtent la tentative plutôt que de boucler indéfiniment sur une migration cassée.
Completions et parallelism : un batch, pas juste une tâche unique
Un Job peut exécuter plusieurs instances de la même tâche, en série ou en parallèle, utile pour du traitement par lot.
spec:
completions: 10
parallelism: 3
completions: 10 exige dix exécutions réussies au total ; parallelism: 3 en autorise trois simultanément au maximum. Sans ces champs, un Job exécute une seule instance par défaut, le cas le plus courant (une migration, un script d’initialisation).
CronJob : un Job créé sur un calendrier
CronJob ne remplace pas Job, il en crée un nouveau à chaque déclenchement, selon une syntaxe cron classique.
apiVersion: batch/v1
kind: CronJob
metadata:
name: nightly-report
spec:
schedule: "0 2 * * *"
concurrencyPolicy: Forbid
jobTemplate:
spec:
template:
spec:
restartPolicy: OnFailure
containers:
- name: report
image: report-generator:2.0
concurrencyPolicy décide quoi faire si une exécution précédente tourne encore quand la suivante devrait démarrer : Forbid saute l’exécution suivante plutôt que d’en lancer une deuxième en parallèle, Replace tue l’ancienne pour démarrer la nouvelle, Allow (le défaut) laisse les deux coexister, rarement ce qu’on veut pour une tâche qui touche les mêmes données. Un CronJob n’attend jamais un signal externe : s’il manque son créneau (nœud indisponible, cluster en maintenance), le comportement par défaut est de sauter l’exécution manquée, pas de la rattraper automatiquement.
Le nettoyage qu’on oublie
Par défaut, les Jobs terminés (et les pods qu’ils ont créés) ne se suppriment jamais automatiquement, ce qui accumule des objets Completed qui polluent kubectl get pods avec le temps, surtout pour un CronJob qui en crée un nouveau à chaque exécution.
spec:
ttlSecondsAfterFinished: 86400
ttlSecondsAfterFinished fait supprimer automatiquement le Job (et ses pods) un délai donné après sa fin, réussie ou non. Sans ce champ, le nettoyage reste manuel, un détail facile à oublier qui finit par transformer un namespace en musée de Jobs terminés depuis des mois.
À retenir
Job garantit qu’une tâche se termine, avec retry borné par backoffLimit, contrairement à un Deployment qui redémarre indéfiniment. completions/parallelism traitent un lot d’exécutions plutôt qu’une seule. CronJob crée des Jobs sur un calendrier, sans jamais garantir de rattrapage automatique d’une exécution manquée, et concurrencyPolicy décide du comportement en cas de chevauchement. ttlSecondsAfterFinished évite l’accumulation silencieuse de Jobs terminés, un détail de propreté qui compte dès qu’un pipeline CI/CD en génère régulièrement.