Infrastructure
Azure AKS : débloquer un upgrade de node pool arrêté par un PDB
Un runbook de production pour diagnostiquer un drain AKS bloqué par un PodDisruptionBudget, retrouver la marge de disruption, corriger capacité ou readiness, puis valider ou reporter l’upgrade sans supprimer les garde-fous.
Un upgrade de node pool AKS démarre correctement, ajoute ou prépare un nœud, puis reste bloqué pendant le drain. Les événements Kubernetes montrent des évictions refusées et un PodDisruptionBudget avec disruptionsAllowed: 0. La réaction rapide consiste souvent à supprimer le PDB ou à augmenter brutalement maxUnavailable. C’est précisément le moment où un garde-fou utile risque d’être transformé en indisponibilité applicative.
Le cas d’usage est un cluster AKS de production avec plusieurs workloads, des PDB gérés dans Git et une fenêtre de maintenance limitée. L’objectif du runbook est de distinguer quatre causes : budget réellement trop strict, replicas insuffisants, pods non ready ou capacité de surge indisponible. La sortie attendue n’est pas seulement un upgrade qui repart, mais une décision documentée : corriger, relancer dans une nouvelle fenêtre ou restaurer les paramètres précédents.
Figer l’opération et le nœud bloqué
Commencez par capturer le périmètre exact. Un message « drain impossible » ne dit pas quel workload consomme le budget, si un nouveau nœud est prêt, ni si le problème existait avant l’upgrade.
cluster:
resource_group: rg-platform-prod
name: aks-prod-weu
node_pool: userpool01
target_version: version-approuvee
operation:
maintenance_window_utc: 2026-08-01T20:00:00Z/2026-08-01T22:00:00Z
blocked_node: aks-userpool01-000123-vmss00000a
first_eviction_failure_utc: 2026-08-01T20:17:00Z
last_known_good_release: orders-api-2026.08.01.2
evidence:
- node_pool_configuration
- node_conditions_and_allocatable_capacity
- warning_events_for_blocked_pods
- pdb_status_and_selectors
- replicas_readiness_and_topology
- decision_and_rollback_owner Capturez ensuite l’état Azure et Kubernetes avant toute modification.
RG="rg-platform-prod"
CLUSTER="aks-prod-weu"
POOL="userpool01"
NODE="aks-userpool01-000123-vmss00000a"
az aks nodepool show --resource-group "$RG" --cluster-name "$CLUSTER" --name "$POOL" --output json
kubectl get node "$NODE" -o wide
kubectl describe node "$NODE"
kubectl get events -A --field-selector type=Warning --sort-by=.lastTimestamp
kubectl get pods -A --field-selector spec.nodeName="$NODE" -o wide Si le nœud de remplacement n’est pas Ready, si le pool ne peut pas allouer de nouvelle VM ou si le subnet manque d’adresses, le PDB n’est peut-être que le dernier symptôme visible. Traitez d’abord la capacité Azure, les quotas, l’adressage et l’état du nœud de surge.
Identifier le budget qui refuse l’éviction
Un PDB protège les pods sélectionnés contre les disruptions volontaires, dont les évictions utilisées pendant un drain. Le champ opérationnel central est disruptionsAllowed. À zéro, Kubernetes ne peut pas évincer un pod supplémentaire tout en respectant le budget actuel.
kubectl get pdb -A -o custom-columns='NAMESPACE:.metadata.namespace,NAME:.metadata.name,MIN_AVAILABLE:.spec.minAvailable,MAX_UNAVAILABLE:.spec.maxUnavailable,EXPECTED:.status.expectedPods,CURRENT_HEALTHY:.status.currentHealthy,DESIRED_HEALTHY:.status.desiredHealthy,ALLOWED:.status.disruptionsAllowed'
NS="orders-prod"
PDB="orders-api"
kubectl -n "$NS" describe pdb "$PDB"
kubectl -n "$NS" get pdb "$PDB" -o yaml
kubectl -n "$NS" get pods -l app=orders-api -o custom-columns='NAME:.metadata.name,NODE:.spec.nodeName,READY:.status.containerStatuses[*].ready,PHASE:.status.phase' Ne vous arrêtez pas au nom du PDB. Vérifiez son selector et les pods réellement couverts. Un selector trop large, un label réutilisé par deux versions ou plusieurs PDB superposés peuvent bloquer davantage de pods que prévu.
Expliquer pourquoi la marge vaut zéro
Le diagnostic utile relie le PDB au contrôleur du workload, à sa readiness et à la topologie des pods. Un budget raisonnable peut tomber à zéro parce qu’un replica est déjà indisponible, qu’une readiness probe échoue ou que tous les replicas sont concentrés sur le nœud à drainer.
NS="orders-prod"
APP="orders-api"
kubectl -n "$NS" get deploy,statefulset -l app="$APP" -o wide
kubectl -n "$NS" get pods -l app="$APP" -o custom-columns='NAME:.metadata.name,NODE:.spec.nodeName,READY:.status.containerStatuses[*].ready,RESTARTS:.status.containerStatuses[*].restartCount'
kubectl -n "$NS" describe pods -l app="$APP"
kubectl -n "$NS" get events --field-selector type=Warning --sort-by=.lastTimestamp | tail -n 80 Classez la cause avant de choisir la correction.
Un replica est non ready avant le drain
Corriger readiness, dependance, secret, image ou saturation
Ne pas assouplir le PDB pour masquer une panne existante
Tous les replicas sont ready mais aucun emplacement n'est disponible
Verifier nœud de surge, quotas, subnet, taints, affinity et ressources demandees
Restaurer de la capacite avant de reprendre l'upgrade
Le nombre de replicas ne permet aucune disruption
Ajouter temporairement un replica si l'application et la capacite le supportent
Revalider readiness et repartition avant le drain
Le PDB ne correspond plus au SLO du service
Modifier le budget dans Git avec approbation, duree et rollback explicites
Ne jamais supprimer le PDB comme simple commande de depannage
Plusieurs PDB ou un selector large couvrent le meme pod
Corriger selectors et ownership
Prouver le budget effectif avant de reprendre Restaurer de la marge sans désactiver la protection
La correction préférée consiste à rendre un replica supplémentaire sain et planifiable. Elle conserve le principe du PDB au lieu de le contourner. Avant de scaler, vérifiez la capacité aval : connexions base de données, partitions, licences, files et limites externes peuvent ne pas accepter un replica supplémentaire.
NS="orders-prod"
DEPLOYMENT="orders-api"
PDB="orders-api"
kubectl -n "$NS" get deployment "$DEPLOYMENT" -o yaml
kubectl -n "$NS" get pdb "$PDB" -o yaml
kubectl -n "$NS" scale deployment "$DEPLOYMENT" --replicas=4
kubectl -n "$NS" rollout status deployment "$DEPLOYMENT" --timeout=10m
kubectl -n "$NS" get pdb "$PDB" -o jsonpath='{.status.currentHealthy}{" current / "}{.status.desiredHealthy}{" desired; disruptionsAllowed="}{.status.disruptionsAllowed}{"
"}'
kubectl -n "$NS" get pods -l app=orders-api -o wide N’autorisez la reprise que si le nouveau pod est Ready, placé hors du nœud bloqué et si disruptionsAllowed est devenu strictement positif. Un replica Pending ne crée aucune marge.
Si le pool manque de capacité pendant le remplacement, ajustez le surge dans le mécanisme IaC ou d’administration déjà utilisé par l’équipe. Vérifiez auparavant quotas VM, adresses du subnet, limites de pods et capacité des zones. Augmenter maxSurge sans ces contrôles déplace simplement l’échec vers le provisioning.
Assouplir le PDB seulement comme changement de production
Il existe des cas où le budget est devenu incohérent avec le SLO ou le nombre réel de replicas. Dans ce cas, traitez sa modification comme un changement de production : diff relu, propriétaire, fenêtre, métriques de santé, critère d’arrêt et restauration automatique ou planifiée.
change:
object: orders-prod/PodDisruptionBudget/orders-api
reason: "Allow one voluntary disruption during approved AKS upgrade"
previous_policy: "minAvailable: 100%"
temporary_policy: "maxUnavailable: 1"
approval: CHG-20260801-042
expires_at_utc: 2026-08-01T22:00:00Z
preconditions:
- all application replicas are ready
- replicas are distributed across at least two nodes
- error rate and latency are within the service baseline
- replacement node is ready and schedulable
stop_conditions:
- available replicas fall below the service SLO
- a replacement pod remains pending or not ready
- dependency saturation increases
- another node becomes unhealthy
restore:
- reapply the previous PDB manifest from Git
- return the temporary replica count to baseline
- verify disruptionsAllowed and application health Le changement doit vivre dans la source de vérité. Une modification directe non reprise par GitOps sera soit annulée au mauvais moment, soit oubliée après l’upgrade.
Valider la reprise et préparer le retour
Après correction, laissez l’opération AKS suivre son mécanisme géré. Pendant chaque remplacement, surveillez simultanément nœuds, pods, PDB, événements et signaux applicatifs. L’absence d’erreur dans le portail Azure ne suffit pas si la disponibilité métier régresse.
Validation
Le nœud de remplacement est Ready et schedulable
disruptionsAllowed reste compatible avec l'eviction suivante
Chaque pod evince revient Ready dans le delai attendu
La repartition des replicas respecte topology spread ou anti-affinity
Taux d'erreur, latence, saturation et backlog restent dans la baseline
Aucun workload critique n'est reste Pending
La version cible du pool est confirmee apres l'operation
Retour et report
Restaurer le manifeste PDB precedent
Revenir au nombre de replicas nominal apres stabilisation
Restaurer le parametre de surge temporaire via l'IaC
Reporter la suite si la marge de disruption retombe a zero
Conserver nœud, pod, PDB, evenements et horodatages dans le dossier de preuve Un upgrade Kubernetes déjà appliqué à un nœud ne se « rollbacke » pas comme un déploiement applicatif. Le chemin de retour consiste à restaurer les contrôles temporaires, arrêter d’élargir l’impact, stabiliser les workloads et replanifier la suite. Ne promettez pas un downgrade de version comme filet de sécurité.
Conclusion
Un drain AKS bloqué par un PDB n’est pas une invitation à supprimer le garde-fou. Il signale qu’au moment de l’éviction, le cluster ne peut pas prouver qu’un replica supplémentaire peut disparaître sans violer la disponibilité déclarée.
La décision finale s’appuie sur une preuve simple : reprendre uniquement quand un pod de remplacement est Ready, que la capacité existe et que disruptionsAllowed est positif. Sinon, corrigez readiness ou capacité, révisez le budget par un changement approuvé, ou reportez l’upgrade. Le bon résultat est un node pool à jour avec des protections intactes, pas un upgrade réussi au prix d’une indisponibilité silencieuse.