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.

26 août 2026 azureakskubernetesdeploymentrolloutreadinessschedulingobservabilitydevopsautomationrunbookrollbackproduction

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.

yaml rollout-incident-card.yml
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.

bash 01-capture-rollout-state.sh
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.

bash 02-check-admission-and-quota.sh
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.

bash 03-diagnose-pending-pod.sh
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.

bash 04-check-container-start.sh
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.

bash 05-diagnose-readiness.sh
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.

bash 06-validate-rollout.sh
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.

bash 07-controlled-rollback.sh
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.