## Introduction

Les budgets de perturbation de pods (Pod Disruption Budgets, PDB) sont une protection essentielle de Kubernetes : ils limitent le nombre de pods pouvant être indisponibles simultanément lors de perturbations volontaires telles que les drainages de nœuds, l'autoscaling de cluster ou les mises à jour progressives. Bien configurés, les PDB garantissent la disponibilité de votre application pendant la maintenance. Mal configurés, ils peuvent bloquer les drainages de nœuds, empêcher la progression des déploiements ou échouer silencieusement, laissant vos charges de travail vulnérables.

Cet article s'adresse aux développeurs, ingénieurs DevOps et équipes plateforme qui exploitent des clusters Kubernetes en production. Il passe en revue les erreurs PDB les plus courantes, comment les déboguer avec `kubectl`, et des correctifs pratiques validés sur Kubernetes 1.28 à 1.30. Vous apprendrez :

- Comment les PDB interagissent avec les évictions et les perturbations volontaires
- Quels champs sont obligatoires et lesquels sont mutuellement exclusifs
- Comment interpréter la sortie de `kubectl get pdb` et les événements
- Comment récupérer d'un PDB qui bloque la maintenance
- Comment tester le comportement des PDB en toute sécurité avec un cluster local

L'objectif est la sécurité opérationnelle : observer avant de modifier, limiter le rayon d'impact, utiliser des valeurs de remplacement plutôt que des secrets, vérifier le résultat et documenter la récupération si l'état attendu n'est pas atteint.

## Inventaire des versions et de l'environnement

Avant de déboguer un PDB, confirmez la version de votre cluster et la version de l'API PDB utilisée. Les PDB sont stables depuis `policy/v1` dans Kubernetes 1.21, mais les clusters plus anciens peuvent encore utiliser `policy/v1beta1`, qui est dépréciée et supprimée dans 1.25. Vérifiez vos ressources d'API :

```bash
kubectl api-resources | grep poddisruptionbudget
```

Sortie attendue sur un cluster moderne :

```
poddisruptionbudgets   pdb   policy/v1   true   PodDisruptionBudget
```

Si vous voyez `policy/v1beta1`, prévoyez une mise à niveau. La sémantique est identique pour les champs discutés ici, mais les administrateurs de cluster doivent migrer les manifestes vers `policy/v1` pour éviter des ruptures futures.

Prérequis pour ce guide :

- `kubectl` configuré avec accès à un espace de noms de test
- Un cluster en cours d'exécution (kind, minikube ou cloud) avec Kubernetes 1.25+
- Compréhension de base des déploiements, ReplicaSets et opérations sur les nœuds

Commencez par une observation en lecture seule. Ne modifiez jamais un PDB ou ses charges de travail avant de comprendre l'état actuel. Capturez les informations suivantes dans un fichier de notes :

```bash
kubectl get pdb -n <namespace> -o wide
kubectl get pods -n <namespace> -o wide
kubectl get events -n <namespace> --sort-by=.lastTimestamp | grep -i pdb
```

Par exemple, un PDB sain lié à un déploiement avec 3 réplicas pourrait afficher :

```
NAME             MIN AVAILABLE   MAX UNAVAILABLE   ALLOWED DISRUPTIONS   AGE
my-app-pdb       1               N/A               2                     5m
```

Si le déploiement a déjà perdu des pods en raison d'une panne de nœud, `ALLOWED DISRUPTIONS` peut tomber à 0 ou même afficher un nombre négatif ? Non, le statut du PDB ne devient jamais négatif. Si les pods sains actuels sont inférieurs au budget, `ALLOWED DISRUPTIONS` devient 0 et les évictions volontaires supplémentaires sont bloquées.

**Vérification pratique :** Sur un cluster local, créez un déploiement simple et un PDB, puis drainez un nœud pour observer le PDB en action. Utilisez `kind` ou `minikube` :

```bash
kubectl create deployment web --image=nginx --replicas=3
kubectl apply -f - <<EOF
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: web-pdb
spec:
  minAvailable: 2
  selector:
    matchLabels:
      app: web
EOF
kubectl drain <node-name> --ignore-daemonsets --delete-emptydir-data
```

Observez la commande de drainage : elle n'évincera qu'un pod à la fois, attendant que les pods de remplacement soient prêts avant d'évincer le suivant.

Gardez le test local petit : appliquez un seul manifeste, inspectez les ressources générées et vérifiez le trafic avant de passer à un équilibreur de charge cloud ou à un contrôleur d'ingress.

## Chemin de configuration sûr

Les erreurs de configuration des PDB proviennent souvent d'une mauvaise compréhension des deux champs mutuellement exclusifs : `minAvailable` et `maxUnavailable`. Vous devez spécifier exactement l'un d'eux. Si vous définissez les deux, le serveur d'API rejette le manifeste avec :

```
The PodDisruptionBudget "web-pdb" is invalid: spec: Invalid value: core.PodDisruptionBudgetSpec{...}: minAvailable and maxUnavailable cannot be both set
```

Si vous omettez les deux, vous verrez :

```
The PodDisruptionBudget "web-pdb" is invalid: spec: Invalid value: core.PodDisruptionBudgetSpec{...}: minAvailable or maxUnavailable must be specified
```

**Lequel utiliser ?**

- `minAvailable` est un nombre absolu ou un pourcentage de pods qui doivent rester disponibles. Exemple : `minAvailable: 2` signifie qu'au moins 2 pods doivent être sains et prêts à tout moment. Il exprime directement la disponibilité, ce qui facilite le raisonnement sur la capacité du service.
- `maxUnavailable` est le nombre maximal de pods pouvant être indisponibles. Exemple : `maxUnavailable: 1` autorise au plus 1 pod en panne. C'est pratique lorsque vous pensez en termes d'impact de perturbation.

Pour la plupart des charges de travail sans état, `minAvailable: 1` est une valeur par défaut sûre si vous avez au moins 2 réplicas. Pour les applications avec état ayant des exigences de quorum (par exemple etcd, bases de données), définissez `minAvailable` à la taille du quorum.

Voici un manifeste PDB complet et prêt pour la production avec des paramètres raisonnables :

```yaml
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: api-pdb
  namespace: production
spec:
  minAvailable: 50%
  selector:
    matchLabels:
      app: api
      tier: backend
```

Supposons que le déploiement a 6 pods. Un `minAvailable: 50%` signifie que 3 pods doivent toujours être prêts. Le PDB autorise au plus 3 perturbations volontaires à tout moment (en supposant que les 6 soient actuellement sains). Si un pod plante en raison d'un bogue d'application, ne laissant que 5 sains, les perturbations autorisées tombent à 2. Les drainages de nœuds n'évinceront que 2 pods puis se bloqueront jusqu'à ce que le pod en panne récupère ou soit remplacé.

**Le sélecteur doit correspondre aux pods.** Si le sélecteur du PDB ne correspond à aucun pod, le statut du PDB affiche `ALLOWED DISRUPTIONS: 0` même si des pods existent. Par exemple, une faute de frappe courante :

```yaml
selector:
  matchLabels:
    app: web # Le déploiement étiquette les pods avec app: frontend
```

Le PDB n'aura aucun effet et vous pourriez ne pas le remarquer jusqu'à ce qu'une fenêtre de maintenance échoue. Vérifiez toujours :

```bash
kubectl get pdb web-pdb -o yaml
```

Regardez `status.currentHealthy` et `status.desiredHealthy`. Si `currentHealthy` est 0 mais que des pods existent, votre sélecteur est incorrect.

**Autre écueil :** Les PDB ne s'appliquent pas aux pods nus ni aux pods non contrôlés par un contrôleur de niveau supérieur comme Deployment ou StatefulSet. Ils ne s'appliquent pas non plus aux pods de daemonset. Utilisez des Deployments ou StatefulSets pour les pods que vous souhaitez protéger.

**Migration de v1beta1 vers v1 :** Si votre cluster utilise encore `policy/v1beta1`, changez l'`apiVersion` et assurez-vous que les champs `spec` sont identiques. La seule différence est la version du groupe d'API. Il n'y a pas de dépréciation de champ au sein des PDB. Testez d'abord dans un environnement de préproduction :

```bash
kubectl apply --dry-run=client -f pdb.yaml
kubectl apply -f pdb.yaml
kubectl get pdb
```

## Vérification et diagnostic

Une fois un PDB appliqué, vous devez vérifier qu'il fonctionne comme prévu et déboguer tout problème. La commande principale est :

```bash
kubectl get pdb -n <namespace>
```

Colonnes de sortie :

- `NAME` : nom du PDB
- `MIN AVAILABLE` : valeur de `minAvailable`
- `MAX UNAVAILABLE` : valeur de `maxUnavailable`
- `ALLOWED DISRUPTIONS` : combien de pods supplémentaires peuvent être perturbés volontairement à ce moment
- `AGE` : âge du PDB

Par exemple :

```
NAME      MIN AVAILABLE   MAX UNAVAILABLE   ALLOWED DISRUPTIONS   AGE
zookeeper 2               N/A               1                     3d
```

Cela signifie que 2 pods doivent rester disponibles. Parmi les pods sains actuels, un de plus peut être évincé sans violer le budget.

Pour plus de détails, y compris le sélecteur et le statut :

```bash
kubectl describe pdb zookeeper -n <namespace>
```

Regardez la section Events pour les tentatives d'éviction récentes. Vous pourriez voir des messages comme :

```
Events:
  Type     Reason             Age   From               Message
  ----     ------             ----  ----               -------
  Warning  EvictionBlocked    10m   disruption-controller  Cannot evict pod as it would violate the pod's disruption budget.
```

Si vous voyez `EvictionBlocked`, ce n'est pas nécessairement une erreur : cela signifie que le PDB fait son travail. Cependant, si vous attendiez une éviction réussie et qu'elle ne l'a pas été, vous devez rechercher pourquoi le PDB pense que le budget serait violé.

Vérifiez le nombre de pods sains actuels :

```bash
kubectl get pods -l app=zookeeper -o wide
```

Si un pod est en `CrashLoopBackOff` ou `Pending`, il n'est pas compté comme sain. Par conséquent, le `ALLOWED DISRUPTIONS` du PDB peut être 0 et aucune éviction supplémentaire ne peut avoir lieu. Dans ce cas, corrigez d'abord le pod en mauvaise santé.

**Débogage avec `kubectl drain` :** Lorsqu'un drainage de nœud est bloqué, vous pouvez voir :

```
error when evicting pods/<pod-name> -n <namespace> (will retry after 5s): Cannot evict pod as it would violate the pod's disruption budget.
```

Pour résoudre, vous avez quelques options :

1. **Attendre et augmenter l'échelle :** Si l'application peut gérer plus de réplicas, augmentez les réplicas du déploiement pour qu'après éviction, le budget soit toujours satisfait.
   ```bash
   kubectl scale deployment zookeeper --replicas=4
   ```
   Attendez que le nouveau pod soit prêt, puis réessayez le drainage.

2. **Assouplir temporairement le PDB :** Si vous devez absolument évincer plus de pods que permis (par exemple, maintenance d'urgence), vous pouvez patcher le PDB pour un `maxUnavailable` plus élevé ou un `minAvailable` plus bas. Par exemple, définissez temporairement `minAvailable: 1` :
   ```bash
   kubectl patch pdb zookeeper -p '{"spec":{"minAvailable":1}}'
   ```
   Après la maintenance, revenez à la valeur d'origine. **Cela devrait être un dernier recours et nécessiter une approbation de changement.**

3. **Supprimer le PDB :** Si le PDB n'est plus nécessaire ou est mal configuré au-delà de toute réparation, supprimez-le :
   ```bash
   kubectl delete pdb zookeeper
   ```
   Cela supprime toute protection. Recréez-le avec la configuration correcte ensuite.

**Simuler une perturbation localement :** Pour tester le comportement du PDB sans affecter la production, utilisez un outil comme `kubectl drain` sur un nœud de test, ou utilisez l'API d'éviction directement via un petit script. Voici un extrait Python utilisant le client `kubernetes` :

```python
from kubernetes import client, config
config.load_kube_config()
v1 = client.PolicyV1Api()
body = client.V1Eviction(metadata=client.V1ObjectMeta(name="my-pod", namespace="default"))
try:
    v1.create_namespaced_pod_eviction("my-pod", "default", body)
    print("Eviction succeeded")
except client.exceptions.ApiException as e:
    print(f"Eviction failed: {e}")
```

Si le PDB bloque l'éviction, vous obtiendrez une erreur 429 Too Many Requests du serveur d'API. Cela confirme que le PDB fonctionne.

**Erreur de diagnostic courante :** Supposer que le PDB bloque les évictions de l'API d'éviction mais pas de `kubectl delete pod`. En fait, `kubectl delete pod` est une suppression directe et contourne le PDB. Les PDB ne s'appliquent qu'aux perturbations volontaires initiées via l'API d'éviction, utilisée par `kubectl drain`, l'autoscaler de cluster et certains opérateurs. Si vous supprimez un pod manuellement, le PDB ne l'arrêtera pas. C'est voulu : le PDB protège contre les pannes involontaires et la maintenance, pas contre les suppressions administratives. Pour imposer la disponibilité même en cas de suppression manuelle, vous avez besoin de politiques supplémentaires comme Kyverno ou OPA Gatekeeper.

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

Examinons trois modes de défaillance courants et comment s'en remettre.

### Mode de défaillance 1 : Le PDB avec zéro perturbation autorisée bloque toute maintenance

**Symptôme :** `kubectl get pdb` affiche `ALLOWED DISRUPTIONS: 0`, et les drainages de nœuds sont bloqués. `kubectl describe pdb` montre des événements `EvictionBlocked` pour chaque pod.

**Cause :** Le `minAvailable` ou `maxUnavailable` du PDB est réglé de manière trop stricte pour le nombre de réplicas actuel. Par exemple, un déploiement avec 3 réplicas et un PDB `minAvailable: 3` signifie que les 3 doivent être opérationnels en permanence. Si un pod tombe en panne, les perturbations autorisées deviennent 0 et aucune éviction ne peut avoir lieu. C'est souvent une mauvaise configuration : vous vouliez `minAvailable: 2` mais avez mis 3.

**Récupération :**

1. Identifiez le PDB mal configuré :
   ```bash
   kubectl get pdb -A
   ```
2. Vérifiez le nombre de réplicas du déploiement et la spécification du PDB :
   ```bash
   kubectl get deploy <deployment-name> -o yaml
   kubectl get pdb <pdb-name> -o yaml
   ```
3. Corrigez le PDB avec une valeur raisonnable. Si vous avez 3 réplicas, `minAvailable: 2` est typique :
   ```bash
   kubectl patch pdb <pdb-name> --type merge -p '{"spec":{"minAvailable":2}}'
   ```
4. Vérifiez que les perturbations autorisées sont maintenant > 0 :
   ```bash
   kubectl get pdb <pdb-name>
   ```

### Mode de défaillance 2 : Le sélecteur du PDB ne correspond à aucun pod

**Symptôme :** Le PDB existe, mais `ALLOWED DISRUPTIONS` est 0 même si des pods sont en cours d'exécution. Aucun événement de blocage d'éviction n'apparaît.

**Cause :** Les étiquettes du `selector` ne correspondent pas aux étiquettes des pods. Par exemple, le déploiement étiquette les pods avec `app: frontend`, mais le sélecteur du PDB recherche `app: web`.

**Récupération :**

1. Comparez les étiquettes des pods et le sélecteur du PDB :
   ```bash
   kubectl get pods --show-labels
   kubectl get pdb <pdb-name> -o jsonpath='{.spec.selector}'
   ```
2. Mettez à jour le sélecteur du PDB pour qu'il corresponde. Cela nécessite souvent de modifier le PDB car le `selector` est immuable après la création. Supprimez et recréez :
   ```bash
   kubectl delete pdb <pdb-name>
   kubectl apply -f corrected-pdb.yaml
   ```
3. Confirmez que `currentHealthy` dans le statut du PDB est maintenant > 0 :
   ```bash
   kubectl get pdb <pdb-name> -o yaml
   ```

### Mode de défaillance 3 : Le PDB bloque la mise à jour progressive ou le drainage de nœud pendant une panne

**Symptôme :** Lors d'un incident, vous devez drainer un nœud immédiatement, mais le PDB empêche l'éviction, causant des retards. Vous pourriez voir des erreurs comme :

```
error when evicting pods/... : Cannot evict pod as it would violate the pod's disruption budget.
```

**Cause :** Le PDB remplit son rôle, mais l'urgence opérationnelle exige une dérogation temporaire.

**Récupération (avec prudence) :**

1. Documentez l'incident et obtenez l'approbation pour assouplir le PDB.
2. Ajustez temporairement le PDB pour autoriser plus de perturbations. Par exemple, définissez `minAvailable: 0` (désactivez effectivement la protection) ou supprimez le PDB.
   ```bash
   kubectl patch pdb <pdb-name> --type merge -p '{"spec":{"minAvailable":0}}'
   # ou
   kubectl delete pdb <pdb-name>
   ```
3. Effectuez le drainage/la maintenance.
4. Restaurez immédiatement le PDB à sa configuration d'origine.
5. Examinez pourquoi l'urgence s'est produite et si le réglage du PDB était trop restrictif pour les opérations réelles.

**Prévention :** Définissez `minAvailable` de manière appropriée pour vos objectifs de disponibilité. Testez les procédures de drainage en préproduction. Utilisez `maxUnavailable` au lieu de `minAvailable` si vous préférez penser en termes de nombre de pods pouvant être en panne à la fois. Pour un service à 3 réplicas, `maxUnavailable: 1` et `minAvailable: 2` sont équivalents.

## Liste de contrôle opérationnelle

Utilisez cette liste de contrôle pour vous assurer que vos PDB sont correctement déployés et maintenus.

### Liste de contrôle avant déploiement du PDB

- [ ] Confirmez que la version du cluster prend en charge `policy/v1` (Kubernetes 1.21+).
- [ ] Identifiez la charge de travail (Deployment, StatefulSet, Operator) et ses étiquettes.
- [ ] Décidez entre `minAvailable` et `maxUnavailable` en fonction du SLO de disponibilité.
- [ ] Rédigez le manifeste du PDB avec un sélecteur correspondant aux étiquettes du modèle de pod de la charge de travail.
- [ ] Définissez une valeur raisonnable : pour N réplicas, `minAvailable: N-1` ou `maxUnavailable: 1` pour les applications sans état ; pour les applications avec état, utilisez la taille du quorum.
- [ ] Appliquez d'abord avec un essai à blanc :
   ```bash
   kubectl apply -f pdb.yaml --dry-run=client
   ```
- [ ] Appliquez le PDB et vérifiez le statut :
   ```bash
   kubectl apply -f pdb.yaml
   kubectl get pdb
   ```
- [ ] Vérifiez que `ALLOWED DISRUPTIONS` est supérieur à 0 lorsque tous les pods sont sains.

### Audit périodique du PDB

- [ ] Listez tous les PDB du cluster :
   ```bash
   kubectl get pdb -A
   ```
- [ ] Pour chaque PDB, vérifiez que le sélecteur correspond aux pods prévus :
   ```bash
   kubectl get pods -l <selector> -n <namespace>
   ```
- [ ] Confirmez que `currentHealthy` dans le statut du PDB est égal au nombre de pods prêts désirés (ou dans une plage acceptable).
- [ ] Vérifiez si un PDB bloque la maintenance : vérifiez `ALLOWED DISRUPTIONS` et les journaux de drainage des nœuds.
- [ ] Supprimez les PDB obsolètes qui n'ont plus de charges de travail correspondantes.

### Aide-mémoire des commandes de dépannage

| Tâche | Commande |
|------|---------|
| Lister les PDB avec statut | `kubectl get pdb -n <namespace>` |
| Décrire un PDB et ses événements | `kubectl describe pdb <name> -n <namespace>` |
| Voir le YAML du PDB | `kubectl get pdb <name> -n <namespace> -o yaml` |
| Vérifier les pods correspondant au sélecteur | `kubectl get pods -l <label-key>=<value> -n <namespace>` |
| Simuler une éviction (Python) | Utilisez l'extrait d'API d'éviction ci-dessus |
| Désactiver temporairement le PDB | `kubectl patch pdb <name> --type merge -p '{"spec":{"minAvailable":0}}'` |
| Supprimer le PDB | `kubectl delete pdb <name> -n <namespace>` |
| Observer les événements de drainage | `kubectl drain <node> --ignore-daemonsets --delete-emptydir-data` et observez |

## Conclusion

Les budgets de perturbation de pods sont un mécanisme petit mais puissant pour garder vos applications disponibles pendant les opérations volontaires sur le cluster. Les erreurs les plus courantes — sélecteurs mal configurés, objectifs de disponibilité impossibles et mauvaise version d'API — peuvent être évitées par une revue attentive des manifestes et des audits réguliers.

Rappelez-vous de ces pratiques clés :

- Spécifiez toujours soit `minAvailable` soit `maxUnavailable`, pas les deux.
- Assurez-vous que le sélecteur du PDB correspond exactement aux étiquettes des pods contrôlés par votre Deployment ou StatefulSet.
- Vérifiez régulièrement `kubectl get pdb` ; une chute soudaine de `ALLOWED DISRUPTIONS` à 0 indique un problème sous-jacent.
- Traitez l'assouplissement du PDB comme un dernier recours lors d'incidents ; documentez et revenez rapidement.
- Testez le comportement du PDB dans un cluster de préproduction en utilisant `kubectl drain` ou l'API d'éviction.

En suivant les étapes de vérification et de récupération de ce guide, vous pouvez maintenir un environnement Kubernetes résilient où les fenêtres de maintenance se déroulent sans heurts et la disponibilité des applications reste dans les SLO.

Comme prochaine étape, choisissez une vérification à faible risque dans la liste de contrôle opérationnelle pour un PDB de votre cluster, enregistrez l'état actuel, exécutez la vérification documentée et comparez le résultat avec le signal attendu. Ensuite, examinez les dépendances telles que le nombre de réplicas du déploiement et la santé des nœuds.