## Introduction

Les CronJobs Kubernetes sont un moyen courant de planifier des tâches récurrentes telles que le traitement par lots, la génération de rapports ou les tâches de nettoyage. Cependant, à mesure que les clusters évoluent et que les charges de travail deviennent plus complexes, les performances des CronJobs peuvent se dégrader, entraînant des exécutions différées, des conflits de ressources ou des échecs de tâches. Cet article fournit un guide pratique, étape par étape, pour optimiser les performances des CronJobs Kubernetes. Vous apprendrez à identifier les goulots d'étranglement, à appliquer des modifications de configuration sûres et à vérifier les améliorations à l'aide de commandes et d'exemples concrets.

Nous supposons que vous disposez d'un cluster Kubernetes fonctionnel (version 1.21 ou ultérieure pour l'API CronJob stable) et de kubectl configuré. Les exemples utilisent une charge de travail de type WordPress pour simuler un scénario réel, mais les techniques s'appliquent à n'importe quel CronJob.

## Inventaire de l'environnement et des versions

Avant de procéder à des modifications, établissez une base de référence. Déterminez votre version de Kubernetes, les paramètres du contrôleur CronJob et l'état actuel de vos tâches.

```bash
# Vérifier la version de Kubernetes
kubectl version --short

# Vérifier la version de l'API CronJob
kubectl api-versions | grep batch

# Lister les CronJobs existants
kubectl get cronjobs --all-namespaces
```

Sortie attendue pour Kubernetes 1.24 :

```
Client Version: v1.24.3
Server Version: v1.24.3
batch/v1
batch/v1beta1 (dépréciée)
```

> **Remarque :** L'API `batch/v1beta1` est dépréciée dans Kubernetes 1.21 et supprimée dans 1.25. Assurez-vous que vos CronJobs utilisent `batch/v1`.

Ensuite, inspectez les journaux du contrôleur CronJob pour voir s'il a du mal à suivre.

```bash
kubectl logs -n kube-system deployment/cronjob-controller --tail=50
```

Recherchez les messages concernant des planifications manquées ou une réconciliation lente. Surveillez également la latence des requêtes du serveur d'API liées aux CronJobs.

## Chemin de configuration sûr

Lors de l'optimisation d'un CronJob, le premier domaine à examiner est sa planification et sa politique de concurrence. Une erreur courante est de permettre trop de tâches simultanées, ce qui peut submerger le cluster. Utilisez `concurrencyPolicy: Forbid` si les tâches ne sont pas sûres à exécuter en parallèle.

Exemple de manifeste CronJob avec des paramètres sûrs :

```yaml
apiVersion: batch/v1
kind: CronJob
metadata:
  name: rapport-email
spec:
  schedule: "0 2 * * *"
  concurrencyPolicy: Forbid
  startingDeadlineSeconds: 300
  successfulJobsHistoryLimit: 3
  failedJobsHistoryLimit: 1
  jobTemplate:
    spec:
      backoffLimit: 4
      activeDeadlineSeconds: 600
      template:
        spec:
          restartPolicy: Never
          containers:
            - name: rapport
              image: rapport:1.2.3
              resources:
                requests:
                  cpu: "250m"
                  memory: "256Mi"
                limits:
                  cpu: "500m"
                  memory: "512Mi"
```

Champs clés :
- `concurrencyPolicy` : Empêche une nouvelle exécution si la précédente est toujours active.
- `startingDeadlineSeconds` : Si une planification est manquée (par exemple, contrôleur en panne), autorise jusqu'à 5 minutes de retard pour démarrer ; sinon, saute.
- `successfulJobsHistoryLimit` et `failedJobsHistoryLimit` : Gardez l'historique petit pour réduire la charge du serveur d'API.
- `backoffLimit` : Réessayez une tâche jusqu'à 4 fois avant de la marquer comme échouée.
- `activeDeadlineSeconds` : Tue la tâche si elle s'exécute plus de 10 minutes.
- `requests` et `limits` de ressources : Empêche la tâche de consommer des ressources excessives.

Appliquez la modification avec `kubectl apply -f cronjob.yaml`, puis vérifiez :

```bash
kubectl get cronjob rapport-email -o yaml | grep -E 'concurrencyPolicy|startingDeadlineSeconds'
```

Sortie attendue :

```
concurrencyPolicy: Forbid
startingDeadlineSeconds: 300
```

## Vérification et diagnostic

Après les modifications de configuration, observez le comportement du CronJob sur au moins un cycle planifié.

### Vérification de l'état des tâches

```bash
kubectl get jobs --selector=job-name=rapport-email
```

Recherchez le temps de complétion de la tâche et les codes de sortie. Si les tâches échouent, inspectez les journaux :

```bash
kubectl logs job/rapport-email-1658123400 --tail=100
```

Remplacez le nom de la tâche par le nom réel. Utilisez `kubectl describe job` pour voir les événements des pods :

```bash
kubectl describe job rapport-email-1658123400
```

Exemple d'échec lié aux ressources :

```
Events:
  Type     Reason            Age   From            Message
  ----     ------            ----  ----            -------
  Warning  FailedScheduling  2m    default-scheduler  0/3 nodes are available: 3 Insufficient memory.
```

Cela indique que le pod a demandé plus de mémoire que disponible. Ajustez les demandes de ressources ou ajoutez des nœuds.

### Mesure du temps de démarrage et d'exécution

Utilisez `kubectl get pod -o wide` pour voir l'heure de démarrage du pod et son placement sur le nœud. Pour un timing détaillé :

```bash
kubectl get events --field-selector involvedObject.name=rapport-email-1658123400-abcde
```

Vérifiez si le pod attend longtemps dans l'état `ContainerCreating`, ce qui peut indiquer des problèmes de tirage d'image ou de volume.

### Surveillance des ressources du cluster

Utilisez `kubectl top nodes` et `kubectl top pods` pour vérifier l'utilisation des ressources pendant l'exécution des tâches. Si les nœuds sont constamment au-dessus de 80 % de CPU ou de mémoire, la capacité du cluster peut limiter les performances des CronJobs.

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

Les CronJobs peuvent échouer de diverses manières. Voici les modes de défaillance courants et comment récupérer.

1. **Planification manquée**
   - **Symptôme :** Aucune tâche créée à l'heure planifiée.
   - **Cause :** Indisponibilité du contrôleur, surcharge du cluster ou `startingDeadlineSeconds` trop bas.
   - **Récupération :** Vérifiez les journaux du contrôleur. Augmentez `startingDeadlineSeconds` si approprié. Surveillez `kubectl get cronjob` pour `LAST SCHEDULE` et `LAST SUCCESSFUL TIME`.

2. **Tâche trop longue**
   - **Symptôme :** La tâche dépasse la durée prévue, bloquant potentiellement d'autres tâches.
   - **Cause :** Code inefficace, ressources insuffisantes ou goulots d'étranglement d'E/S.
   - **Récupération :** Définissez `activeDeadlineSeconds` pour tuer les tâches incontrôlables. Optimisez les demandes de ressources de la tâche. Envisagez de diviser la tâche en sous-tâches parallèles plus petites en utilisant `parallelism` et `completions`.

3. **Épuisement des ressources**
   - **Symptôme :** Pods bloqués dans l'état `Pending` avec des événements `Insufficient cpu` ou `Insufficient memory`.
   - **Cause :** Trop de tâches simultanées ou nœuds surchargés.
   - **Récupération :** Réduisez la `concurrency`, baissez les demandes de ressources par tâche, ou mettez à l'échelle le cluster. Utilisez `kubectl describe nodes` pour vérifier les ressources allouables.

4. **Échecs de tirage d'image**
   - **Symptôme :** Le pod échoue avec `ErrImagePull` ou `ImagePullBackOff`.
   - **Cause :** Étiquette d'image incorrecte, problèmes d'authentification au registre ou problèmes réseau.
   - **Récupération :** Mettez à jour le nom/l'étiquette de l'image dans la spécification du CronJob. Vérifiez les secrets pour le registre privé. Testez avec `kubectl run` manuellement.

5. **Accumulation d'historique**
   - **Symptôme :** De nombreuses anciennes tâches et pods restent, augmentant la charge du serveur d'API.
   - **Cause :** `successfulJobsHistoryLimit` / `failedJobsHistoryLimit` élevés.
   - **Récupération :** Définissez les limites à 3 et 1 respectivement. Supprimez manuellement les anciennes tâches avec `kubectl delete jobs --field-selector status.successful=1` (à utiliser avec prudence).

## Pièges courants et comment les éviter

Voici quelques erreurs à éviter lorsque vous travaillez avec des CronJobs.

- **Ignorer les fuseaux horaires :** Le champ `schedule` utilise le fuseau horaire du contrôleur, généralement UTC. Si vos tâches nécessitent un fuseau horaire spécifique, définissez le champ `timeZone` (Kubernetes 1.24+) ou ajustez la planification en conséquence.
  - **Exemple :** Si vous voulez une tâche à 2 h 00, heure de New York, utilisez `timeZone: America/New_York` et planifiez `"0 2 * * *"`.

- **Négliger `restartPolicy` :** Les pods CronJob doivent avoir `restartPolicy: Never` ou `OnFailure` (les anciennes versions autorisaient `OnFailure` uniquement avec `batch/v1beta1`). Utiliser `Always` est invalide et sera rejeté.

- **Ne pas définir de limites de ressources :** Sans limites, une tâche peut consommer toutes les ressources du nœud, affectant les autres charges de travail. Définissez toujours `requests` et `limits` dans le modèle de pod.

- **Utiliser trop de tâches simultanées :** À moins que vos tâches soient idempotentes et légères en ressources, évitez `concurrencyPolicy: Allow` avec des planifications fréquentes. Cela peut provoquer un effet de foule.

- **Oublier de supprimer les anciennes tâches :** Avec le temps, l'historique des tâches s'accumule et peut affecter les performances de l'API. Configurez des limites d'historique et nettoyez périodiquement.

## Liste de contrôle des opérations

Utilisez cette liste de contrôle avant et après l'optimisation des CronJobs. Chaque élément comprend un propriétaire et une fréquence de révision.

| # | Tâche | Commande / Vérification | Propriétaire | Fréquence |
|---|------|-----------------|-------|-----------|
| 1 | Vérifier la version de Kubernetes et l'API CronJob | `kubectl version --short`, `kubectl api-versions | grep batch` | Responsable DevOps | Trimestriel |
| 2 | Examiner les planifications des CronJobs pour les chevauchements | `kubectl get cronjobs -A -o custom-columns=NAME:.metadata.name,SCHEDULE:.spec.schedule` | Gestionnaire SRE | Mensuel |
| 3 | Vérifier les temps de complétion des tâches | `kubectl get jobs -o json | jq '.items[] | {name: .metadata.name, startTime: .status.startTime, completionTime: .status.completionTime}'` | Ingénieur de production | Hebdomadaire |
| 4 | Surveiller l'utilisation des ressources pendant les exécutions | `kubectl top pods -l job-name` (après le démarrage de la tâche) | Ingénieur DevOps | Quotidien pendant les exécutions |
| 5 | S'assurer que les limites d'historique sont définies | `kubectl get cronjobs -o json | jq '.items[] | select(.spec.successfulJobsHistoryLimit == null or .spec.failedJobsHistoryLimit == null) | .metadata.name'` | Responsable SRE | Mensuel |
| 6 | Tester la procédure de récupération pour une tâche échouée | Simuler une panne en définissant `activeDeadlineSeconds` bas, puis restaurer | Ingénieur d'astreinte | Trimestriel |

## Exemples d'optimisation des performances

Parcourons un scénario concret : un CronJob d'agrégation de données qui s'exécute toutes les 5 minutes et traite les événements entrants. Initialement, la tâche prend 4 minutes pour se terminer, ne laissant qu'une minute de marge. Sous charge, il manque fréquemment des planifications.

### Étape 1 : Mesures de base

Surveillez la durée de la tâche :

```bash
kubectl get jobs -o json | jq '.items[] | select(.metadata.name | startswith("agregateur-evenements-")) | {name: .metadata.name, duration: ((.status.completionTime - .status.startTime) / 1000000000)}'
```

Exemple de sortie :

```
{
  "name": "agregateur-evenements-1658123400",
  "duration": 240
}
```

La durée est de 240 secondes (4 minutes).

### Étape 2 : Investiguer le goulot d'étranglement

Vérifiez l'utilisation des ressources du pod :

```bash
kubectl top pod agregateur-evenements-1658123400-abcde
```

Sortie :

```
NAME                                CPU(cores)   MEMORY(bytes)
agregateur-evenements-1658123400-abcde   950m         1800Mi
```

L'utilisation du CPU est proche de la limite (si limite=1 CPU). La tâche est limitée par le CPU. Augmentez la limite CPU à 2 et la demande à 1 CPU.

Mettez à jour le manifeste CronJob :

```yaml
resources:
  requests:
    cpu: "1"
    memory: "1Gi"
  limits:
    cpu: "2"
    memory: "2Gi"
```

Appliquez et attendez la prochaine exécution.

### Étape 3 : Vérifier l'amélioration

Après la complétion de la prochaine tâche, mesurez à nouveau la durée :

```bash
kubectl get jobs -o json | jq '.items[] | select(.metadata.name | startswith("agregateur-evenements-")) | {name: .metadata.name, duration: ((.status.completionTime - .status.startTime) / 1000000000)}' | tail -1
```

Nouvelle durée : 180 secondes (3 minutes). La tâche a maintenant 2 minutes de marge.

### Étape 4 : Optimisation supplémentaire

Envisagez de paralléliser la tâche en divisant les données en fragments. Mettez à jour la spécification `jobTemplate` :

```yaml
spec:
  parallelism: 2
  completions: 2
```

Cela exécute deux pods simultanément, chacun traitant la moitié des données. La durée peut tomber à moins de 2 minutes.

## Optimisation avancée : paramètres du contrôleur CronJob

Si vous gérez de nombreux CronJobs (des centaines), les paramètres par défaut du contrôleur peuvent devenir un goulot d'étranglement. Vous pouvez ajuster les drapeaux du contrôleur :

- `--concurrent-cronjob-syncs` : Nombre de CronJobs pouvant être synchronisés simultanément. La valeur par défaut est 5. Augmentez si vous avez beaucoup de tâches.
- `--cronjob-controller-period` : Fréquence à laquelle le contrôleur vérifie les tâches dues. La valeur par défaut est 10 secondes. Réduisez pour une planification plus précise (mais charge API plus élevée).

Pour modifier ces paramètres, éditez le déploiement dans `kube-system` :

```bash
kubectl edit deployment cronjob-controller -n kube-system
```

Ajoutez ou modifiez les arguments :

```yaml
spec:
  containers:
    - name: cronjob-controller
      args:
        - --concurrent-cronjob-syncs=10
        - --cronjob-controller-period=5s
```

> **Attention :** Modifier les paramètres du contrôleur affecte tous les CronJobs du cluster. Surveillez l'utilisation des ressources du contrôleur et la charge du serveur d'API après les modifications.

## Surveillance et alertes

Mettez en place une surveillance des performances des CronJobs. Utilisez les métriques Prometheus exposées par kube-state-metrics :

- `kube_cronjob_status_last_schedule_time` : Quand la tâche a été planifiée pour la dernière fois.
- `kube_cronjob_spec_starting_deadline_seconds` : La date limite configurée.
- `kube_job_status_completion_time` : Temps de complétion de la tâche.

Exemple d'alerte PromQL pour les planifications manquées :

```promql
time() - kube_cronjob_status_last_schedule_time > (60 * 10)
```

Cela se déclenche si un CronJob n'a pas été planifié depuis 10 minutes.

## Conclusion

L'optimisation des performances des CronJobs Kubernetes nécessite une approche systématique : inventorier votre environnement, appliquer des modifications de configuration sûres, vérifier avec des diagnostics concrets et se préparer aux modes de défaillance courants. Commencez toujours par l'observation, limitez le rayon d'impact et documentez les étapes de récupération. En mettant en œuvre les pratiques de cet article, vous pouvez garantir que vos CronJobs s'exécutent de manière fiable et efficace, même sous forte charge.

Comme prochaine étape, choisissez un CronJob dans votre cluster, parcourez la liste de contrôle des opérations et appliquez une modification d'optimisation telle que la définition de `concurrencyPolicy` ou l'ajustement des limites de ressources. Enregistrez les métriques avant et après pour valider l'amélioration.