## Introduction

Les CronJobs Kubernetes sont idéaux pour les tâches récurrentes : synchronisation de données, génération de rapports, préchauffage de cache et nettoyage. Ce guide montre comment automatiser des CronJobs avec CI/CD (Continuous Integration/Continuous Deployment) : intégration continue et déploiement continu en utilisant des exemples pratiques en YAML, Python, Bash et GitLab CI . Vous apprendrez à :

- Construire et tester une image de job

- Valider les manifestes avant le déploiement

- Déployer en toute sécurité avec suspension préalable et tests à la demande

- Ajouter des tests de fumée et garde-fous

- Effectuer un rollback rapide en cas d’échec

- Diagnostiquer les échecs de pipeline courants

Pourquoi ça fonctionne : une approche guidée par l’exemple aide les équipes à passer de l’intention à une automatisation fonctionnelle, tandis qu’une séparation claire des étapes réduit le retravail et accélère les itérations.

## Vue d’ensemble du workflow

Le workflow de bout en bout ci-dessous est simple, sûr et prêt pour la production.

### 1. Écrire le code du job

Exemple de job Python avec codes de sortie explicites et logs :

# app/main.py
import logging, os, sys, time

logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(message)s" )

def validate_env():
 required = ["EXAMPLE_PARAM" ]
 missing = [k for k in required if not os.getenv(k)]
 if missing:
 logging.error("Missing required env: %s", " , " .join(missing) )
 return False
 return True

def run_job():
 logging.info("job start" )
 # Simuler du travail
 time.sleep(2)
 # Ajouter la logique réelle ici
 logging.info("job done" )
 return 0

if __name__ == "__main__":
 if not validate_env():
 sys.exit(2)
 code = run_job()
 sys.exit(code) 
 Point d’entrée avec gestion d’erreur stricte :

# app/entrypoint.sh
#!/usr/bin/env bash
set -Eeuo pipefail
trap 'echo "[ERROR] line $LINENO status $?" >&2' ERR
python /app/main.py 
 Dockerfile minimal :

# Dockerfile
FROM python:3.11-slim
WORKDIR /app
COPY app/requirements.txt /app/
RUN pip install --no-cache-dir -r requirements.txt || true
COPY app/ /app/
RUN useradd -u 10001 appuser && chown -R appuser /app
USER 10001
ENTRYPOINT ["bash", "/app/entrypoint.sh"] 

### 2. Définir le manifeste CronJob

 Des valeurs par défaut sûres limitent le rayon d’impact et facilitent le débogage.

# k8s/cronjob.yaml
apiVersion: batch/v1
kind: CronJob
metadata:
 name: report-cron
 namespace: staging
spec:
 schedule: "*/15 * * * *" # toutes les 15 minutes
 # timeZone: "UTC" # décommenter si votre cluster le supporte
 concurrencyPolicy: Forbid # éviter les exécutions chevauchées
 startingDeadlineSeconds: 300 # ne pas démarrer si trop en retard
 successfulJobsHistoryLimit: 3
 failedJobsHistoryLimit: 2
 suspend: true # démarrer suspendu ; test de fumée d’abord
 jobTemplate:
 spec:
 backoffLimit: 1
 activeDeadlineSeconds: 600 # limite dure sur la durée d’exécution
 template:
 spec:
 restartPolicy: Never
 containers:
 - name: report
 image: registry.example.com/report-cron:${IMAGE_TAG}
 imagePullPolicy: IfNotPresent
 env:
 - name: EXAMPLE_PARAM
 value: nightly
 resources:
 requests:
 cpu: "100m"
 memory: "128Mi"
 limits:
 cpu: "500m"
 memory: "512Mi"
 imagePullSecrets:
 - name: regcred 
 Notes :

- Utilisez des tags d’image immuables (ex. SHA du commit). Évitez latest .

- Utilisez suspend: true pour les premiers déploiements ; activez après un test de fumée réussi.

- Pour les secrets, montez depuis un Secret ou utilisez votre approche standard de gestion des secrets.

### 3. Ajouter les étapes CI/CD

Un exemple pratique GitLab CI avec build, validation, déploiement, test de fumée, promotion et rollback.

# .gitlab-ci.yml
stages: [test, build, validate, deploy_staging, smoke_test, deploy_prod, rollback]

variables:
 APP_NAME: "report-cron"
 IMAGE_REGISTRY: "registry.example.com"
 IMAGE_TAG: "$CI_COMMIT_SHA"
 KUBE_NAMESPACE_STAGING: "staging"
 KUBE_NAMESPACE_PROD: "prod"

# Fournir DOCKER_AUTH, KUBECONFIG et kubectl dans vos runners

unit-test:
 stage: test
 image: python:3.11-slim
 script:
 - pip install -r app/requirements.txt || true
 - python -m py_compile app/main.py

build-image:
 stage: build
 image: docker:24
 services: ["docker:24-dind" ]
 script:
 - echo "$DOCKER_AUTH" | docker login -u "$DOCKER_USER" --password-stdin $IMAGE_REGISTRY
 - docker build -t $IMAGE_REGISTRY/$APP_NAME:$IMAGE_TAG .
 - docker push $IMAGE_REGISTRY/$APP_NAME:$IMAGE_TAG
 needs: ["unit-test" ]

validate-manifest:
 stage: validate
 image: bitnami/kubectl:1.30
 script:
 - sed "s/\${IMAGE_TAG}/$IMAGE_TAG/g" k8s/cronjob.yaml > k8s/rendered.yaml
 - kubectl apply --dry-run=client -f k8s/rendered.yaml
 needs: ["build-image" ]

deploy-staging:
 stage: deploy_staging
 image: bitnami/kubectl:1.30
 script:
 - kubectl config use-context "$KUBE_CONTEXT_STAGING"
 - kubectl -n $KUBE_NAMESPACE_STAGING apply -f k8s/rendered.yaml
 - kubectl -n $KUBE_NAMESPACE_STAGING annotate cronjob/$APP_NAME ci-commit=$CI_COMMIT_SHA --overwrite
 needs: ["validate-manifest" ]
 environment:
 name: staging

smoke-test-staging:
 stage: smoke_test
 image: bitnami/kubectl:1.30
 script:
 - kubectl config use-context "$KUBE_CONTEXT_STAGING"
 - export SMOKE_JOB=${APP_NAME}-smoke-$(date +%s)
 - kubectl -n $KUBE_NAMESPACE_STAGING create job $SMOKE_JOB --from=cronjob/$APP_NAME
 - kubectl -n $KUBE_NAMESPACE_STAGING wait --for=condition=complete job/$SMOKE_JOB --timeout=10m || (kubectl -n $KUBE_NAMESPACE_STAGING logs job/$SMOKE_JOB; exit 1)
 - kubectl -n $KUBE_NAMESPACE_STAGING logs job/$SMOKE_JOB
 - kubectl -n $KUBE_NAMESPACE_STAGING delete job/$SMOKE_JOB --ignore-not-found
 - echo "Smoke test passed"
 needs: ["deploy-staging" ]

promote-prod:
 stage: deploy_prod
 image: bitnami/kubectl:1.30
 script:
 - kubectl config use-context "$KUBE_CONTEXT_PROD"
 - sed "s/namespace: staging/namespace: $KUBE_NAMESPACE_PROD/g" k8s/rendered.yaml > k8s/prod.yaml
 - kubectl -n $KUBE_NAMESPACE_PROD apply -f k8s/prod.yaml
 - kubectl -n $KUBE_NAMESPACE_PROD annotate cronjob/$APP_NAME ci-commit=$CI_COMMIT_SHA --overwrite
 - # Optionnellement désuspendre après test de fumée en prod
 needs: ["smoke-test-staging" ]
 environment:
 name: production

rollback:
 stage: rollback
 image: bitnami/kubectl:1.30
 when: manual
 variables:
 ROLLBACK_TAG: "" # définir cette variable au déclenchement
 script:
 - test -n "$ROLLBACK_TAG" || (echo "Set ROLLBACK_TAG" && exit 1)
 - kubectl config use-context "$KUBE_CONTEXT_PROD"
 - kubectl -n $KUBE_NAMESPACE_PROD patch cronjob $APP_NAME --type='json' \
 -p='[{"op":"replace","path":"/spec/jobTemplate/spec/template/spec/containers/0/image","value":"'$IMAGE_REGISTRY'/'$APP_NAME':'$ROLLBACK_TAG'"}]'
 - # Vérifier via un job one-off avant de réactiver la planification
 - export SMOKE_JOB=${APP_NAME}-rb-$(date +%s)
 - kubectl -n $KUBE_NAMESPACE_PROD create job $SMOKE_JOB --from=cronjob/$APP_NAME
 - kubectl -n $KUBE_NAMESPACE_PROD wait --for=condition=complete job/$SMOKE_JOB --timeout=10m || (kubectl -n $KUBE_NAMESPACE_PROD logs job/$SMOKE_JOB; exit 1)
 - kubectl -n $KUBE_NAMESPACE_PROD logs job/$SMOKE_JOB
 - kubectl -n $KUBE_NAMESPACE_PROD delete job/$SMOKE_JOB --ignore-not-found 

### Vérifications de déploiement sécurisées

- Faire un dry-run des manifestes avant l’application.

- Déployer les CronJobs suspendus dans les nouveaux environnements.

- Tester avec un Job one-off créé depuis le template du CronJob.

- Limiter la durée des exécutions avec activeDeadlineSeconds et empêcher le chevauchement avec concurrencyPolicy: Forbid .

- Utiliser des tags immuables et annoter les ressources avec le SHA du commit pour la traçabilité.

### Activer les planifications après vérification

 Une fois les tests de fumée passés, désuspendre la planification :

kubectl -n staging patch cronjob report-cron -p '{"spec": {"suspend": false}}' 
 Répéter en production après promotion et validation.

## Plan pilote local

Commencez avec un seul CronJob et gardez une portée étroite.

### Objectifs du pilote

- Un CronJob dans un namespace bac à sable

- Build, validation, déploiement suspendu et test de fumée réussi

- Mesuré par un code de sortie 0 et les logs attendus

### Étapes

- Choisir un job à faible risque (ex. génération de rapport sans effets de bord externes).

- Conteneuriser et ajouter codes de sortie stricts et logs (comme ci-dessus).

- Écrire un manifeste CronJob avec valeurs par défaut sûres et suspend: true .

- Exécuter des vérifications locales :

- Linter le Python, lancer tests rapides

- Rendre le manifeste avec le SHA du commit

- kubectl apply --dry-run=client -f k8s/rendered.yaml

- Déployer dans un namespace bac à sable.

- Créer un job one-off depuis le CronJob et attendre la fin.

- Inspecter logs et statut de sortie ; corriger les problèmes.

- Documenter les critères de succès et seulement alors désuspendre la planification.

Pourquoi ce plan : un pilote étroit, mesurable et inspectable localement est le chemin le plus rapide vers la valeur et l’apprentissage sans risquer la production.

## Modèles pratiques de rollback

Les CronJobs ne supportent pas kubectl rollout undo . Gardez le rollback simple :

- Tags immuables : construire des images taguées avec le SHA du commit ; conserver une trace du dernier tag connu bon.

- Patch retour au dernier tag connu bon et vérifier avec un job one-off.

- Alternative : revenir au commit Git qui a mis à jour le tag d’image et redéployer.

Exemple de patch (déjà dans le job CI) :

kubectl -n prod patch cronjob report-cron --type='json' \
 -p='[{"op":"replace","path":"/spec/jobTemplate/spec/template/spec/containers/0/image","value":"registry.example.com/report-cron:abc123"}]' 

## Échecs de pipeline courants et correctifs

- ImagePullBackOff : identifiants de registre manquants ou mauvais tag d’image.

- Correctif : vérifier imagePullSecrets et tags immuables ; confirmer que le push a réussi.

- CrashLoopBackOff ou sortie non nulle : code ou configuration d’environnement incorrecte.

- Correctif : ajouter variables d’environnement requises ; renforcer la gestion d’erreur du point d’entrée ; inspecter les logs.

- Job dépasse la limite de temps :

- Correctif : définir activeDeadlineSeconds et revoir la charge ; diviser en tâches plus petites.

- Exécutions chevauchées causent des conflits :

- Correctif : concurrencyPolicy: Forbid et espacement raisonnable de la planification.

- Heures de début manquées quand le cluster est occupé :

- Correctif : définir startingDeadlineSeconds et envisager d’augmenter les ressources.

- CronJob suspendu ne s’exécute jamais :

- Correctif : désuspendre après test de fumée réussi.

- Erreurs RBAC ou de contexte dans CI :

- Correctif : confirmer contexte kubectl, namespace et permissions pour apply, create job et patch.

- Confusion de fuseau horaire Cron :

- Correctif : planifier en UTC ou valider le comportement du fuseau horaire du cluster ; documenter les attentes.

## Conclusion

 Automatiser les CronJobs Kubernetes avec CI/CD est simple quand on sépare les préoccupations et qu’on intègre la sécurité :

- Construire et tester l’image avec codes de sortie explicites

- Valider les manifestes avec dry runs

- Déployer suspendu, puis tester via un Job one-off

- Promouvoir avec tags immuables et annotations claires

- Garder un rollback simple et rapide en patchant le tag d’image

Commencez avec un CronJob en bac à sable, rendez-le mesurable, et n’étendez qu’une fois votre pilote vert et observable.