Les CronJobs sont le cœur battant de nombreux environnements Kubernetes : rapports quotidiens, maintenance de bases de données, nettoyage de cache, pipelines ETL, et plus encore. Lors d'un déplacement de cluster, d'une reconstruction de namespace ou d'une défaillance, vous avez besoin d'un moyen sûr et reproductible de sauvegarder et restaurer ces planifications et leurs dépendances d'exécution. Ce guide pratique propose :

- Une sauvegarde minimale et ciblée utilisable dès aujourd'hui

- Une restauration réversible évitant les exécutions en double

- Une vérification observable, les modes de défaillance courants et correctifs rapides

- Les étapes de reprise après sinistre et de retour arrière

- Une liste de contrôle pratique adoptable telle quelle

Les exemples sont conçus pour la clarté et n'utilisent que les outils Kubernetes de base (kubectl et manifests simples). Lorsque des volumes de données sont impliqués, nous montrons comment copier le contenu en toute sécurité.

## Inventaire des versions et de l'environnement

Avant de toucher à la production, capturez et figez les détails de l'environnement. Cela réduit la dérive et rend votre sauvegarde et restauration reproductibles et auditable. Enregistrez la version exacte de Kubernetes car le comportement des CronJobs a changé entre v1.27 et v1.28 concernant la gestion des fuseaux horaires.

## Commandes d'inventaire rapides

- Enregistrer les versions client et serveur :

kubectl version --short 

- Enregistrer les nœuds et leur OS/arch (utile pour déboguer les images) :

 kubectl get nodes -o wide 

- Confirmer les namespaces dans le périmètre et le plan d'étiquetage :

 kubectl get ns
kubectl get cronjob -A --show-labels 

- Vérifier que le groupe d'API CronJob est disponible :

 kubectl api-resources | grep -i cronjob 

## Ce qu'il faut enregistrer (exemples construits)

 Élément À enregistrer Exemple 
 Version Kubernetes Client et serveur semver v1.29.3 (client), v1.28.6 (serveur) 
 Namespaces concernés Namespaces où vivent les CronJobs jobs, analytics 
 Labels pour la sélection Labels définissant le périmètre de sauvegarde app=reporting, backup=enabled 
 Stockage et PVC Noms des PVC et chemins de montage utilisés par les jobs report-pvc sur /data 
 Identités RBAC ServiceAccounts et liaisons SA cron-runner, RoleBinding cron-runner-rb 

 Astuce : Stockez cet inventaire avec vos artefacts de sauvegarde.

</div>

## Chemin de configuration sûr

Le chemin le plus sûr commence étroitement, est facile à inspecter localement et évite les effets de bord. La séquence ci-dessous vous donne des garde-fous et des commandes concrètes pour agir d'abord sur un seul namespace et une seule charge de travail étiquetée.

## 0) Un CronJob étiqueté que vous pouvez cibler

Voici un manifest CronJob construit pour tester le processus de bout en bout. Il inclut des labels pour le périmètre, un ServiceAccount, des limites d'historique et un montage PVC pour une sauvegarde de données optionnelle.

apiVersion: batch/v1
kind: CronJob
metadata:
 name: report-daily
 namespace: jobs
 labels:
 app: reporting
 backup: enabled
spec:
 schedule: "0 2 * * *" # 02:00 quotidien (utilisez UTC pour éviter les surprises)
 concurrencyPolicy: Forbid
 successfulJobsHistoryLimit: 3
 failedJobsHistoryLimit: 1
 suspend: false
 jobTemplate:
 spec:
 backoffLimit: 1
 template:
 spec:
 serviceAccountName: cron-runner
 restartPolicy: Never
 containers:
 - name: report
 image: ghcr.io/example/report:1.2.3
 args: ["--generate"](The user wants me to translate the English article to French. I need to continue translating the rest of the markdown content. Let me continue from where I left off.)
 envFrom:
 - secretRef:
 name: report-secrets
 volumeMounts:
 - name: report-data
 mountPath: /data
 volumes:
 - name: report-data
 persistentVolumeClaim:
 claimName: report-pvc 
 Appliquez-le d'abord sur un namespace non critique :

kubectl apply -f report-daily.cronjob.yaml 

## 1) Sauvegarder les spécifications des CronJobs (YAML)

 Sauvegardez seulement ce dont vous avez besoin, en utilisant les labels pour délimiter le périmètre. Remplacez le namespace et les labels ci-dessous par les vôtres.

# Répertoire de sauvegarde
mkdir -p backups/jobs

# CronJobs (délimités par label)
kubectl get cronjob -n jobs -l backup=enabled -o yaml > backups/jobs/cronjobs.yaml

# Optionnel : ServiceAccounts, Roles, RoleBindings utilisés par les CronJobs
kubectl get sa,role,rolebinding -n jobs -l app=reporting -o yaml > backups/jobs/rbac.yaml

# ConfigMaps et Secrets référencés par les CronJobs
kubectl get configmap,secret -n jobs -l app=reporting -o yaml > backups/jobs/config-secrets.yaml 

 Notes :

</div>
<div class="my-stack-md p-6 bg-surface-container-low border-l-4 border-primary rounded-r-lg">
- Les Secrets sont encodés en base64 dans le YAML. Traitez les fichiers de sauvegarde de manière sécurisée (chiffrement au repos et accès restreint).

</div>
<div class="my-stack-md p-6 bg-surface-container-low border-l-4 border-primary rounded-r-lg">
- Vous avez rarement besoin de sauvegarder les Jobs créés par les CronJobs ; ils sont éphémères. Cependant, il peut être utile de conserver un YAML de Job récent pour comparaison forensique lors des tests.

</div>

## 2) Sauvegarder le contenu des PVC quand pertinent (optionnel)

Si votre CronJob écrit sur un PVC, capturez une archive à un instant T. Ce qui suit crée un pod éphémère qui monte le PVC et diffuse un tarball vers votre poste de travail. Adaptez claimName, namespace et chemins pour votre cas.

# pvc-reader.yaml (exemple construit)
apiVersion: v1
kind: Pod
metadata:
 name: pvc-reader
 namespace: jobs
 labels:
 app: reporting
spec:
 restartPolicy: Never
 containers:
 - name: reader
 image: busybox:1.36
 command: ["sleep", "3600"]
 volumeMounts:
 - name: report-data
 mountPath: /data
 volumes:
 - name: report-data
 persistentVolumeClaim:
 claimName: report-pvc 
 Exécutez la sauvegarde :

kubectl apply -f pvc-reader.yaml
kubectl wait --for=condition=Ready pod/pvc-reader -n jobs --timeout=60s
kubectl exec -n jobs pvc-reader -- tar -C /data -cf - . > backups/jobs/report-pvc-$(date +%F).tar
kubectl delete pod pvc-reader -n jobs --wait=true 

## 3) Séquence de restauration avec garde-fous

 Lors de la restauration, évitez les exécutions planifiées en double en suspendant temporairement les CronJobs pendant le processus.

# Créer ou s'assurer que le namespace cible existe
kubectl create namespace jobs 2>/dev/null || true

# 3.1 Dry-run pour valider les manifests contre l'API du cluster
kubectl apply --server-side --dry-run=server -f backups/jobs/rbac.yaml
kubectl apply --server-side --dry-run=server -f backups/jobs/config-secrets.yaml
kubectl apply --server-side --dry-run=server -f backups/jobs/cronjobs.yaml

# 3.2 Appliquer RBAC et configuration d'abord
kubectl apply -f backups/jobs/rbac.yaml
kubectl apply -f backups/jobs/config-secrets.yaml

# 3.3 Appliquer les CronJobs, puis les suspendre immédiatement
kubectl apply -f backups/jobs/cronjobs.yaml
kubectl patch cronjob -n jobs -l backup=enabled -p '{"spec":{"suspend":true}}' 
 Si vous avez sauvegardé des données PVC, restaurez-les avant de reprendre les planifications :

# pvc-writer.yaml (exemple construit)
apiVersion: v1
kind: Pod
metadata:
 name: pvc-writer
 namespace: jobs
spec:
 restartPolicy: Never
 containers:
 - name: writer
 image: busybox:1.36
 command: ["sleep", "3600"]
 volumeMounts:
 - name: report-data
 mountPath: /data
 volumes:
 - name: report-data
 persistentVolumeClaim:
 claimName: report-pvc 
 kubectl apply -f pvc-writer.yaml
kubectl wait --for=condition=Ready pod/pvc-writer -n jobs --timeout=60s
cat backups/jobs/report-pvc-*.tar | kubectl exec -i -n jobs pvc-writer -- tar -C /data -xpf -
kubectl delete pod pvc-writer -n jobs --wait=true 
 Ne reprenez les planifications qu'après que les données sont en place (voir la section Vérification pour valider d'abord).

## Vérification et diagnostics

La vérification doit être explicite et observable. Le but est de prouver que chaque CronJob peut s'exécuter une fois à la demande et que l'exécution planifiée fonctionnera une fois reprise.

## 1) Vérifier les objets et leur état

# Confirmer que les CronJobs existent et sont suspendus
kubectl get cronjob -n jobs -l backup=enabled -o wide

# Inspecter un CronJob en détail
kubectl describe cronjob -n jobs report-daily 
 Résultats attendus :

- CronJobs présents dans le bon namespace

- concurrencyPolicy, limites d'historique et serviceAccountName correspondent aux attentes

- suspend est true (temporairement)

## 2) Exécuter un Job ad-hoc depuis le template du CronJob

Cela ne modifie pas la planification du CronJob mais prouve que le template peut s'exécuter.

# Créer un job ponctuel basé sur le template du CronJob
kubectl create job -n jobs run-once-$(date +%s) --from=cronjob/report-daily

# Observer l'état et les logs
kubectl get jobs -n jobs -o wide
JOB=$(kubectl get jobs -n jobs -o jsonpath='{.items[0].metadata.name}')
POD=$(kubectl get pod -n jobs -l job-name=$JOB -o jsonpath='{.items[0].metadata.name}')
kubectl logs -n jobs $POD -f 
 Critères de succès :

- Le Job se termine (statut Succeeded)

- Les logs montrent la sortie attendue de votre tâche

- Si un PVC est utilisé, les fichiers de sortie apparaissent dans le chemin monté

## 3) Inspecter les événements pour les problèmes de planification

kubectl get events -n jobs --sort-by=.lastTimestamp | tail -n 20 
 Recherchez les messages sur les planifications manquées, les backoffs ou les erreurs de permission.

## 4) Reprendre les planifications une fois validées

Ne reprenez qu'après que l'exécution ad-hoc a réussi.

kubectl patch cronjob/report-daily -n jobs -p '{"spec":{"suspend":false}}'
# Ou reprendre tous les CronJobs étiquetés
kubectl patch cronjob -n jobs -l backup=enabled -p '{"spec":{"suspend":false}}' 
 Confirmez que la prochaine heure planifiée est dans le futur et que le contrôleur rapporte Active=0 au repos.

## Modes de défaillance et récupération

Les problèmes lors de la restauration tombent généralement dans quelques catégories. Identifiez le symptôme, suivez le correctif rapide, et re-vérifiez.

<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">Symptôme</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">Cause probable</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">Correctif rapide</th></tr></thead>
<tbody><tr><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Jobs échouent avec erreurs ImagePull</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Tag d&#39;image absent du registre cible</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Fixer sur un tag ou digest connu bon ; mettre à jour l&#39;image et réappliquer</td></tr>
<tr><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">CrashLoopBackOff ou erreurs de permission</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Secrets ou ConfigMaps manquants/incorrects</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Réappliquer config-secrets.yaml ; confirmer clés et noms correspondent au spec du pod</td></tr>
<tr><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">RBAC forbidden lors d&#39;accès API</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">ServiceAccount ou RoleBinding manquant</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Réappliquer rbac.yaml ; vérifier serviceAccountName sur template CronJob</td></tr>
<tr><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Exécutions en double après restauration</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Planifications reprises avant données ou vérifications d&#39;idempotence</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Suspendre, nettoyer les Jobs en double, restaurer données, puis reprendre</td></tr>
<tr><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Jobs ne s&#39;exécutent jamais</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">CronJobs restés suspendus</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Patcher suspend=false et vérifier la prochaine planification</td></tr>
<tr><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">PVC vide après restauration</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Restauration de données ignorée ou chemin incorrect</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Réexécuter l&#39;extraction tar vers le bon mountPath ; vérifier avec kubectl exec ls</td></tr>
<tr><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Trop de Jobs historiques</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Limites d&#39;historique trop hautes</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Définir successfulJobsHistoryLimit et failedJobsHistoryLimit appropriément</td></tr></tbody>
</table>
</div>
Garde-fous supplémentaires :

- Utilisez UTC pour les expressions de planification afin d'éviter les surprises entre clusters.

- Définissez startingDeadlineSeconds si vous voulez exécuter les jobs manqués à la récupération du contrôleur ; mettez une valeur sensée ou omettez si non désiré.

- Préférez concurrencyPolicy=Forbid pour les tâches sensibles à l'idempotence.

## Liste de contrôle opérationnelle

Utilisez cette liste concise comme runbook pour chaque namespace.

## Inventaire

- [ ] kubectl version --short sauvegardé

- [ ] Namespaces, labels, PVCs enregistrés

- [ ] ServiceAccounts et Roles identifiés

## Sauvegarde (commandes construites)

- [ ] kubectl get cronjob -n <ns> -l backup=enabled -o yaml > cronjobs.yaml

- [ ] kubectl get sa,role,rolebinding -n <ns> -l <labels> -o yaml > rbac.yaml

- [ ] kubectl get configmap,secret -n <ns> -l <labels> -o yaml > config-secrets.yaml

- [ ] Si PVC utilisé : exécuter pvc-reader, diffuser tar vers backups/

- [ ] Stocker les artefacts de manière sécurisée (chiffrer, accès restreint)

## Restauration

- [ ] kubectl create namespace <ns> (si nécessaire)

- [ ] kubectl apply --server-side --dry-run=server -f rbac.yaml

- [ ] kubectl apply --server-side --dry-run=server -f config-secrets.yaml

- [ ] kubectl apply --server-side --dry-run=server -f cronjobs.yaml

- [ ] kubectl apply -f rbac.yaml, config-secrets.yaml, cronjobs.yaml

- [ ] kubectl patch cronjob -n <ns> -l backup=enabled -p '{"spec":{"suspend":true}}'

- [ ] Si PVC utilisé : exécuter pvc-writer et extraire tar dans /data

## Validation

- [ ] kubectl get/describe cronjob montrent les paramètres attendus

- [ ] Exécuter : kubectl create job --from=cronjob/<nom>

- [ ] Vérifier logs et artefacts ; inspecter événements

## Reprise

- [ ] kubectl patch cronjob/<nom> -p '{"spec":{"suspend":false}}'

- [ ] Confirmer prochaine planification, aucun Job Active inattendu

## Retour arrière / Récupération

- [ ] En cas de problème : suspendre immédiatement

- [ ] Réappliquer le cronjobs.yaml précédent

- [ ] Supprimer Jobs non voulus : kubectl delete job <nom>

- [ ] Ré-restaurer Secrets/ConfigMaps ou données PVC si nécessaire

## Conclusion

Vous disposez maintenant d'un chemin pratique et testé pour sauvegarder et restaurer les CronJobs Kubernetes :

- Commencez petit : un namespace, un CronJob étiqueté, et prouvez le flux de bout en bout.

- Sauvegardez le bon périmètre : specs des CronJobs, RBAC, configuration, et contenu PVC quand applicable.

- Restaurez avec des garde-fous : appliquez les configs d'abord, suspendez les planifications, validez avec une exécution ad-hoc, puis reprenez.

- Anticipez et corrigez rapidement les modes de défaillance courants, et gardez un chemin de retour arrière court prêt.

Améliorez ceci en automatisant les étapes pour chaque namespace, en standardisant les labels (par exemple backup=enabled), et en planifiant des restaurations de test périodiques. La combinaison de pilotes étroits, de validation explicite et de changements réversibles réduira le retravail et rendra les opérations CronJob prévisibles même lors d'incidents.

## Annexe : Référence rapide du périmètre de sauvegarde

<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">Ressource</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">Pourquoi la sauvegarder</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">Exemple de commande (construit)</th></tr></thead>
<tbody><tr><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">CronJobs</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Définit la planification et le template de job</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">kubectl get cronjob -n jobs -l backup=enabled -o yaml &gt; cronjobs.yaml</td></tr>
<tr><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">RBAC (SA/Role/RoleBinding)</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Accorde les permissions d&#39;exécution</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">kubectl get sa,role,rolebinding -n jobs -l app=reporting -o yaml &gt; rbac.yaml</td></tr>
<tr><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">ConfigMaps/Secrets</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Configuration et identifiants d&#39;exécution</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">kubectl get configmap,secret -n jobs -l app=reporting -o yaml &gt; config-secrets.yaml</td></tr>
<tr><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Données PVC (optionnel)</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Entrées/sorties nécessaires aux jobs</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">kubectl exec pvc-reader -- tar -C /data -cf - . &gt; report-pvc.tar</td></tr></tbody>
</table>
</div>