Deux symptômes mènent à la documentation du PodDisruptionBudget, et ils pointent dans des directions opposées. Le premier : kubectl drain node-7 reste bloqué sur « evicting pod … » sans jamais rendre la main, l’autoscaler de cluster n’arrive plus à réduire la voilure, une mise à jour de node pool cale une heure sur un seul nœud. Le second : une équipe ajoute un PDB en pensant garder son application en vie quand un nœud tombe, perd quand même la moitié de ses réplicas sur une panne matérielle, et se demande pourquoi le budget n’a rien fait. Les deux viennent de la même confusion sur ce à quoi sert réellement un PDB.
Perturbations volontaires et involontaires
Kubernetes range les perturbations de pods en deux familles, et un PDB n’en touche qu’une seule.
Les perturbations volontaires passent par l’API d’éviction : kubectl drain, l’autoscaler qui retire un nœud sous-utilisé, une mise à jour progressive d’un node pool managé, ou tout appel à POST .../pods/<nom>/eviction. Ce sont des actions délibérées, déclenchées par un opérateur ou un contrôleur.
Les perturbations involontaires sont tout le reste : un kernel panic, une panne matérielle, le nœud qui manque de mémoire et le OOM killer qui emporte votre pod, une partition réseau, une préemption au profit d’un pod plus prioritaire. Un cas surprend souvent : kubectl delete pod est une suppression directe, pas une éviction, donc le PDB ne le voit pas non plus.
Un PodDisruptionBudget est un garde-barrière devant l’API d’éviction, rien de plus. Il ne ressuscite pas un pod parti avec son nœud et ne planifie jamais de remplaçant. Si votre crainte porte sur les crashs de nœud, les leviers sont le nombre de réplicas, les topologySpreadConstraints et l’anti-affinité, pas un PDB. Ce qu’un budget vous achète, c’est une protection contre votre propre outillage qui retirerait trop de pods d’un coup pendant une opération planifiée. Il plafonne les évictions volontaires simultanées pour qu’un drain ou une réduction d’échelle ne vous fasse pas passer sous un plancher que vous fixez.
minAvailable contre maxUnavailable
Un PDB porte exactement l’un de ces deux champs, jamais les deux.
minAvailable: le nombre (ou pourcentage) de pods qui doivent rester sains. Une éviction est refusée si elle ferait passer le compte sous ce plancher.maxUnavailable: le nombre (ou pourcentage) de pods autorisés à être indisponibles simultanément. Une éviction est refusée dès que ce seuil est atteint.
Le contrôleur de perturbation ramène l’un ou l’autre à une seule valeur calculée. Il lit le nombre de pods attendus auprès du contrôleur propriétaire (le spec.replicas d’un Deployment, par exemple), compte combien de pods sont actuellement sains (au sens Ready, ce qui explique pourquoi votre readiness probe alimente directement ce calcul), puis en déduit disruptionsAllowed = sainsActuels − sainsSouhaités. Avec minAvailable: 4, sainsSouhaités vaut 4. Avec maxUnavailable: 1 sur 6 réplicas, il vaut 5. Quand disruptionsAllowed tombe à 0, l’API d’éviction renvoie un HTTP 429 et kubectl drain patiente puis réessaie jusqu’à ce que le budget se rouvre.
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: api
spec:
minAvailable: 4 # ne jamais descendre sous 4 pods Ready
selector:
matchLabels:
app: api
# unhealthyPodEvictionPolicy: AlwaysAllow # voir les pièges
$ kubectl get pdb api
NAME MIN AVAILABLE MAX UNAVAILABLE ALLOWED DISRUPTIONS AGE
api 4 N/A 2 3d
ALLOWED DISRUPTIONS: 2 signifie que deux pods peuvent être évincés à l’instant présent ; drainez un nœud qui porte trois pods api et la troisième éviction attendra.
Privilégiez maxUnavailable quand vous passez à l’échelle horizontalement : il reste correct quand replicas varie, là où un minAvailable fixe devient silencieusement plus strict à mesure que vous réduisez, et plus laxiste à mesure que vous montez.
Interaction avec les drains et l’autoscaler
kubectl drain cordonne le nœud, puis évince ses pods un par un via l’API d’éviction, en respectant chaque PDB concerné. Si un budget est épuisé, le drain n’échoue pas : il boucle, attendant que les pods soient replanifiés ailleurs et repassent Ready pour que le budget se rouvre. Un drain qui « bloque » est presque toujours un PDB qui fait son travail pendant que les remplaçants ne montent pas (pas de place pour planifier, ou un rollout coincé).
L’autoscaler de cluster s’appuie sur la même API pour réduire la voilure. Avant de retirer un nœud sous-utilisé, il vérifie que tous ses pods peuvent être évincés sans violer un PDB. Sinon, le nœud est marqué non supprimable et reste en place. Un PDB qui n’autorise jamais aucune perturbation cloue donc des nœuds au sol et sabote la consolidation, ce qui se traduit par une facture cloud qui ne baisse jamais.
Erreurs de configuration classiques
maxUnavailable: 0 ou minAvailable: 100%. Interdit toute éviction volontaire. Chaque drain bloque indéfiniment et l’autoscaler ne peut jamais récupérer le nœud. Cela ressemble à une sécurité maximale ; c’est un blocage opérationnel permanent.
Un seul réplica avec minAvailable: 1. L’unique pod ne peut jamais être évincé, donc tout drain de son nœud reste bloqué. Un PDB ne rend pas un workload mono-réplica hautement disponible ; il le rend seulement indrainable. La haute disponibilité commence par plusieurs réplicas, pas par un budget.
Des pourcentages sur de petits comptes de réplicas. Les pourcentages sont arrondis au supérieur, au pod entier le plus proche. minAvailable: 50% sur 3 réplicas donne 2, ce qui ne laisse la place qu’à une seule éviction, pas à la « moitié » imaginée. Faites le calcul pour votre nombre réel de réplicas avant de faire confiance à l’intention.
Un sélecteur qui vise les mauvais pods. Un PDB dont le sélecteur ne matche rien ne protège rien, en silence. Un sélecteur qui matche des pods appartenant à plusieurs contrôleurs empêche le contrôleur de calculer un nombre attendu à partir d’un pourcentage ou de maxUnavailable, et il refusera alors les évictions par prudence. Un PDB par workload, avec un sélecteur qui vise exactement ce workload.
Des pods cassés que vous ne pouvez pas évincer. Par défaut (unhealthyPodEvictionPolicy: IfHealthyBudget), un pod non-Ready ne peut être évincé que tant que le budget est satisfait. Si vous êtes déjà sous le plancher parce que des pods crashent, vous ne pouvez même plus évincer les pods cassés pour drainer le nœud : la récupération se bloque. Poser unhealthyPodEvictionPolicy: AlwaysAllow autorise l’éviction des pods non-Ready dans tous les cas, ce qui est généralement le comportement souhaité pour un drain fluide.
À retenir
Un PodDisruptionBudget est un limiteur de débit sur les évictions volontaires, pas une garantie de disponibilité. Il protège les opérations planifiées (drains, réductions par l’autoscaler, mises à jour de nœuds) contre le retrait de plus de pods que vous n’autorisez ; il ne fait rien contre les crashs de nœud, les OOM kills ou kubectl delete pod. Réglez-le avec maxUnavailable sur les workloads scalables, jamais à une valeur qui autorise zéro perturbation, jamais sur un réplica unique, et pensez à AlwaysAllow pour que des pods cassés ne coincent pas vos drains. Maîtriser ces primitives, c’est ce qui rend la maintenance de cluster ennuyeuse, et c’est tout l’objet de la fiabilité et de l’observabilité. Plus de contenu Kubernetes dans la catégorie kubernetes.