Infrastructure
Azure AKS : diagnostiquer un rollout bloqué avant de forcer le rollback
Un runbook de production pour localiser un rollout AKS bloqué entre Deployment, ReplicaSet, scheduling, image et readiness, puis reprendre ou revenir à une révision connue avec des critères d’arrêt.
Un déploiement AKS peut rester bloqué avec une nouvelle image déjà publiée, quelques pods encore sains et un pipeline qui attend kubectl rollout status. La pression monte vite : supprimer les pods, augmenter le timeout, relancer le pipeline ou exécuter un rollback immédiat. Ces actions changent pourtant les preuves et peuvent aggraver une panne partielle.
Le cas d’usage est une API déployée par Deployment avec plusieurs replicas et une stratégie RollingUpdate. La nouvelle révision ne devient pas disponible. Le runbook doit localiser le premier objet qui ne progresse plus, distinguer admission, capacité, image, démarrage et readiness, puis décider entre reprise bornée et retour à une révision connue. Il ne cherche pas à rendre le pipeline vert à tout prix.
Figer le rollout et son impact
Commencez par nommer le changement, la fenêtre et le symptôme utilisateur. Un rollout bloqué avec tous les anciens pods disponibles n’a pas le même impact qu’un rollout qui a déjà réduit la capacité utile.
scope:
cluster: aks-prod-weu
namespace: payments
deployment: payments-api
change: release-20260826.3
started_utc: 2026-08-26T06:42:00Z
expected:
replicas: 6
max_surge: 2
max_unavailable: 1
evidence:
previous_revision: 41
candidate_revision: 42
previous_image_digest: sha256:<known-good>
candidate_image_digest: sha256:<candidate>
stop_conditions:
- available replicas fall below 5
- error rate crosses the incident threshold
- no new pod reaches Ready during the observation window
rollback_requires:
- revision 41 still exists
- configuration remains compatible
- database changes are backward compatible Conservez les digests, pas seulement les tags. Notez aussi les changements externes au Deployment : ConfigMap, Secret, policy d’admission, migration de schéma ou dépendance. kubectl rollout undo ne les restaurera pas.
Lire la chaîne Deployment, ReplicaSet, Pod
Ne commencez pas par les logs applicatifs. Identifiez le premier niveau qui ne produit plus l’état attendu.
NAMESPACE="payments"
DEPLOYMENT="payments-api"
EVIDENCE="aks-rollout-evidence"
mkdir -p "$EVIDENCE"
kubectl -n "$NAMESPACE" get deployment "$DEPLOYMENT" -o yaml > "$EVIDENCE/deployment.yaml"
kubectl -n "$NAMESPACE" describe deployment "$DEPLOYMENT" > "$EVIDENCE/deployment-describe.txt"
kubectl -n "$NAMESPACE" rollout history deployment/"$DEPLOYMENT" > "$EVIDENCE/rollout-history.txt"
kubectl -n "$NAMESPACE" get rs -o wide > "$EVIDENCE/replicasets.txt"
kubectl -n "$NAMESPACE" get pods -o wide > "$EVIDENCE/pods.txt"
kubectl -n "$NAMESPACE" get events --sort-by=.lastTimestamp > "$EVIDENCE/events.txt" Lisez observedGeneration, updatedReplicas, readyReplicas, availableReplicas et les conditions Progressing et Available. Vérifiez ensuite quel ReplicaSet porte la nouvelle révision et combien de pods il désire, crée et rend disponibles.
La première bifurcation est simple : aucun nouveau pod n’est créé, un pod reste Pending, le conteneur ne démarre pas, ou il démarre sans devenir Ready. Chaque état pointe vers une couche différente.
Aucun pod créé : admission, quota ou manifeste
Si le nouveau ReplicaSet existe mais ne crée aucun pod, inspectez ses événements avant de modifier le cluster. Un quota, une LimitRange, une policy d’admission ou un champ invalide peut refuser la création. Si le ReplicaSet lui-même n’existe pas, relisez les conditions du Deployment et l’objet réellement appliqué par le pipeline.
kubectl -n "$NAMESPACE" get deployment "$DEPLOYMENT" -o jsonpath='{.metadata.generation}{" "}{.status.observedGeneration}{"
"}'
kubectl -n "$NAMESPACE" get rs -o custom-columns='NAME:.metadata.name,REVISION:.metadata.annotations.deployment.kubernetes.io/revision,DESIRED:.spec.replicas,CURRENT:.status.replicas,READY:.status.readyReplicas'
kubectl -n "$NAMESPACE" get resourcequota,limitrange
kubectl -n "$NAMESPACE" get events --sort-by=.lastTimestamp | tail -n 80 Un refus d’admission doit être corrigé dans le manifeste ou dans une exception ciblée et relue. Désactiver globalement le webhook ou la policy pour libérer un déploiement retire le même contrôle aux autres workloads. Si l’admission elle-même est indisponible, traitez son mode d’échec comme un incident de plateforme distinct.
Pod Pending : prouver le blocage de scheduling
Un pod Pending n’indique pas automatiquement un manque de CPU. kubectl describe pod donne la décision du scheduler : ressources insuffisantes, taint non toléré, affinité impossible, contrainte de topologie, volume non attachable ou nombre maximal de pods atteint sur les nœuds.
POD="<new-revision-pod>"
kubectl -n "$NAMESPACE" describe pod "$POD"
kubectl get nodes -o custom-columns='NODE:.metadata.name,READY:.status.conditions[?(@.type=="Ready")].status,TAINTS:.spec.taints,CAPACITY:.status.capacity.pods,ALLOCATABLE_CPU:.status.allocatable.cpu,ALLOCATABLE_MEMORY:.status.allocatable.memory'
kubectl top nodes
kubectl -n "$NAMESPACE" get pvc
kubectl get events --all-namespaces --sort-by=.lastTimestamp | tail -n 120 Corrigez la contrainte démontrée. Une augmentation temporaire de capacité peut être défendable si le rollout respecte toujours son budget et si le scale-out est observable. En revanche, supprimer une affinité, une tolérance ou une demande de ressources sans comprendre sa fonction peut placer le pod sur un nœud non conforme.
Un PodDisruptionBudget ne bloque pas, à lui seul, le remplacement standard géré par un Deployment. Il devient pertinent si un drain, un upgrade de node pool ou une éviction volontaire se déroule en parallèle. Ne supprimez donc pas un PDB simplement parce qu’un rollout est lent ; cherchez d’abord l’événement d’éviction qui le met réellement en cause.
Conteneur en attente ou en boucle : séparer image et démarrage
Pour ErrImagePull ou ImagePullBackOff, comparez la référence par digest, l’accès du kubelet au registre et les événements. Pour CrashLoopBackOff, utilisez l’état précédent et les logs du conteneur précédent avant toute suppression.
POD="<new-revision-pod>"
CONTAINER="api"
kubectl -n "$NAMESPACE" get pod "$POD" -o jsonpath='{range .status.containerStatuses[*]}{.name}{" image="}{.image}{" imageID="}{.imageID}{" waiting="}{.state.waiting.reason}{" last="}{.lastState.terminated.reason}{"
"}{end}'
kubectl -n "$NAMESPACE" describe pod "$POD"
kubectl -n "$NAMESPACE" logs "$POD" -c "$CONTAINER" --previous --timestamps --tail=300
kubectl -n "$NAMESPACE" logs "$POD" -c "$CONTAINER" --timestamps --tail=300 Une image introuvable ne se corrige pas en augmentant progressDeadlineSeconds. Un processus qui quitte sur configuration invalide ne se corrige pas en supprimant le pod : le ReplicaSet recréera le même état. La correction doit porter sur la référence, l’accès ACR, la commande de démarrage ou la configuration prouvée en défaut.
Running mais non Ready : tester la sonde depuis le pod
Un conteneur Running peut rester hors service parce que sa startupProbe ou sa readinessProbe échoue. Lisez le type de sonde, son port, son chemin, ses délais et les événements. Vérifiez ensuite l’écoute réelle et la dépendance que le endpoint de readiness teste.
kubectl -n "$NAMESPACE" get pod "$POD" -o jsonpath='{range .status.conditions[*]}{.type}{"="}{.status}{" reason="}{.reason}{"
"}{end}'
kubectl -n "$NAMESPACE" get pod "$POD" -o jsonpath='{.spec.containers[?(@.name=="api")].startupProbe}{"
"}{.spec.containers[?(@.name=="api")].readinessProbe}{"
"}'
kubectl -n "$NAMESPACE" exec "$POD" -c "$CONTAINER" -- sh -c 'wget -qSO- http://127.0.0.1:8080/ready || true'
kubectl -n "$NAMESPACE" get endpointslice -l kubernetes.io/service-name=payments-api -o wide N’allongez pas les délais de sonde sans mesure. Si l’application démarre réellement plus lentement, un ajustement borné de startupProbe peut être légitime. Si /ready dépend d’un service externe instable, décidez si cette dépendance doit retirer le pod du trafic ou seulement dégrader une fonctionnalité. Le runbook doit préserver cette intention.
Valider une reprise sur une seule révision
Une fois la cause corrigée, ne relancez pas plusieurs changements. Appliquez un seul patch relu, surveillez le nouveau ReplicaSet et gardez les anciens pods tant que la capacité cible n’est pas prouvée.
kubectl -n "$NAMESPACE" rollout status deployment/"$DEPLOYMENT" --timeout=10m
kubectl -n "$NAMESPACE" get deployment "$DEPLOYMENT" -o custom-columns='NAME:.metadata.name,DESIRED:.spec.replicas,UPDATED:.status.updatedReplicas,READY:.status.readyReplicas,AVAILABLE:.status.availableReplicas,UNAVAILABLE:.status.unavailableReplicas'
kubectl -n "$NAMESPACE" get pods -o custom-columns='POD:.metadata.name,REVISION:.metadata.labels.pod-template-hash,READY:.status.containerStatuses[*].ready,RESTARTS:.status.containerStatuses[*].restartCount,IMAGE_ID:.status.containerStatuses[*].imageID'
kubectl -n "$NAMESPACE" get events --sort-by=.lastTimestamp | tail -n 80 Ajoutez la validation depuis le chemin utilisateur : taux d’erreur, latence, dépendances et une transaction représentative. Un rollout Kubernetes terminé prouve que les pods sont disponibles selon leurs sondes ; il ne prouve pas que le parcours métier fonctionne.
Décider reprise, arrêt ou rollback
Reprenez le rollout si la cause est isolée, si un correctif unique rend les nouveaux pods Ready, si la capacité reste au-dessus du seuil et si la validation applicative passe. Arrêtez si plusieurs couches restent indéterminées ou si la correction exige de désactiver un contrôle global.
Si l’impact augmente, revenez à une révision connue seulement après avoir vérifié qu’elle existe encore et que les changements externes restent compatibles.
kubectl -n "$NAMESPACE" rollout history deployment/"$DEPLOYMENT"
kubectl -n "$NAMESPACE" rollout undo deployment/"$DEPLOYMENT" --to-revision=41
kubectl -n "$NAMESPACE" rollout status deployment/"$DEPLOYMENT" --timeout=10m
kubectl -n "$NAMESPACE" get deployment,rs,pods -o wide
kubectl -n "$NAMESPACE" get events --sort-by=.lastTimestamp | tail -n 80 Le rollback du Deployment restaure son pod template, pas une migration SQL, une policy, un secret remplacé ou un objet supprimé. Exécutez leurs retours arrière selon leurs propres procédures, dans l’ordre de dépendance. Si l’ancienne révision n’est plus compatible, stoppez l’automatisme et passez en changement explicite.
Conclusion
Un rollout AKS bloqué se diagnostique comme une chaîne d’objets, pas comme un timeout de pipeline. Deployment, ReplicaSet, Pod, scheduler, runtime et readiness produisent chacun une preuve exploitable.
La sortie attendue est une décision : reprendre avec une correction bornée et validée, maintenir la révision précédente pendant l’analyse, ou exécuter un rollback dont les dépendances sont compatibles. Forcer une relance sans avoir localisé le premier état bloqué ne résout pas l’incident ; cela efface seulement sa chronologie.