Automation

Azure DevOps : diagnostiquer un déploiement annulé avant de relancer la production

Un runbook de production pour reconstruire les effets d'un déploiement Azure DevOps annulé, qualifier l'état réel, puis choisir reprise idempotente, compensation ou rollback.

05 oct. 2026 azureazure-devopspipelinesdeploymentautomationdevopsenvironmentsactivity-logobservabilityidempotencyrunbookrollbackproduction

Un pipeline Azure DevOps est annulé pendant un déploiement de production. Le run apparaît Canceled, mais la nouvelle image est déjà référencée, une variable applicative a changé et le trafic n’a peut-être pas encore basculé. Relancer immédiatement semble naturel. C’est aussi le moyen le plus rapide de superposer un second changement à un état que personne n’a qualifié.

Le cas fil rouge est un deployment job qui publie une API Azure, applique sa configuration puis exécute un smoke test. L’annulation survient entre ces étapes. Ce runbook vise une décision bornée : reprendre depuis un checkpoint prouvé, compenser les effets partiels, restaurer la dernière version saine, ou maintenir le gel tant que l’état réel reste ambigu.

Figer le run avant toute nouvelle écriture

Notez l’organisation, le projet, le pipeline, le run ID, le stage, l’environnement, le commit et l’artefact. Ajoutez l’heure UTC de la demande d’annulation et celle de la dernière preuve produite par l’agent. Ne remplacez pas ces références par « dernier run » ou « dernière release ».

yaml incident-deploiement-annule.yml
incident: INC-DEPLOY-20261005-01
pipeline: deploy-orders-api
run_id: 18427
stage: production
environment: prod-orders
source_commit: 8d2c1f4
artifact:
name: orders-api
digest: sha256:<digest>
cancellation_requested_utc: 2026-10-05T15:42:18Z
last_pipeline_evidence_utc: 2026-10-05T15:42:31Z
change_window_end_utc: 2026-10-05T16:30:00Z
writers_frozen:
- scheduled_release
- manual_hotfix
- configuration_automation
decision_owner: platform-on-call

Gelez les autres writers qui ciblent le même environnement : pipelines concurrents, jobs planifiés et changements manuels. Ce gel protège l’enquête ; il ne suppose pas que le processus déjà lancé s’est arrêté.

Distinguer état du run et état de la cible

Canceled décrit la conclusion du run Azure DevOps, pas une transaction distribuée annulée. Une commande distante peut avoir réussi avant que l’agent reçoive le signal. Une étape avec une condition always() peut encore s’exécuter pendant le délai d’annulation. À l’inverse, un agent déconnecté peut ne jamais publier son dernier résultat.

text couches-etat-deploiement.txt
Azure DevOps
Run, stage, job et task: queued | inProgress | completed
Résultat final: canceled

Agent
Signal d'annulation reçu ou non
Processus enfant terminé ou encore actif
Dernier log et dernier heartbeat connus

Cible Azure
Déploiement ARM accepté, terminé ou en cours
Configuration effectivement écrite
Version ou révision qui reçoit le trafic
Migrations ou messages déjà produits

Application
Version observée par les probes
Santé, erreurs et effets métier
Compatibilité avec le schéma et les dépendances

Le run fournit une chronologie de commandes. La cible fournit la vérité opérationnelle. Il faut réconcilier les deux avant de décider.

Capturer le commit, l’artefact et la chronologie

L’historique de déploiement de l’environnement aide à identifier les pipelines et commits qui ont ciblé la production. Complétez-le avec les détails du run et les artefacts publiés. Une relance depuis la branche peut reconstruire autre chose ; le digest déployé reste donc la référence.

bash 01-figer-run-et-artefact.sh
ORG="https://dev.azure.com/example"
PROJECT="platform-prod"
RUN_ID="18427"

az pipelines runs show --org "$ORG" --project "$PROJECT" --id "$RUN_ID" --query '{id:id,name:name,state:state,result:result,branch:sourceBranch,commit:sourceVersion,created:createdDate,finished:finishedDate}' --output json

az pipelines runs artifact list --org "$ORG" --project "$PROJECT" --run-id "$RUN_ID" --output table

Dans les logs de chaque task, marquez non démarrée, démarrée sans preuve de fin, terminée avec preuve ou terminée mais résultat non validé. Un message de succès du script n’est pas suffisant pour une opération asynchrone : conservez l’operation ID et relisez son état côté Azure.

Reconstruire les effets depuis la cible

Partez des writes possibles du pipeline : déploiement ARM, image, paramètres, secrets référencés, routes de trafic, migrations, files de messages ou invalidation de cache. Pour chaque write, interrogez l’état effectif et son horodatage. Évitez les commandes correctives pendant cette phase.

bash 02-lire-changements-azure.sh
SUBSCRIPTION="<subscription-id>"
RESOURCE_GROUP="rg-orders-prod"
START="2026-10-05T15:30:00Z"
END="2026-10-05T16:00:00Z"

az monitor activity-log list --subscription "$SUBSCRIPTION" --resource-group "$RESOURCE_GROUP" --start-time "$START" --end-time "$END" --query '[].{time:eventTimestamp,status:status.localizedValue,operation:operationName.localizedValue,caller:caller,correlationId:correlationId,resourceId:resourceId}' --output table

az deployment group list --resource-group "$RESOURCE_GROUP" --query '[].{name:name,state:properties.provisioningState,time:properties.timestamp,correlationId:properties.correlationId}' --output table

L’Activity Log prouve des opérations de control plane ; il ne prouve pas seul la version servie, une écriture data plane ou la réussite métier. Complétez avec l’état du service, ses logs et un test de lecture sans effet.

Corréler l’identité et la fenêtre dans KQL

Si l’Activity Log est exporté vers Log Analytics, utilisez la fenêtre, le resource group, l’identité de service connection et les correlation IDs. Cherchez aussi des writes après la demande d’annulation : ils signalent un processus qui a continué ou une autre source de changement.

kusto 03-correlate-canceled-deployment.kql
let Start = datetime(2026-10-05T15:30:00Z);
let CancelRequested = datetime(2026-10-05T15:42:18Z);
let End = datetime(2026-10-05T16:00:00Z);
let DeploymentCaller = "<service-connection-principal>";
AzureActivity
| where TimeGenerated between (Start .. End)
| where ResourceGroup =~ "rg-orders-prod"
| where Caller =~ DeploymentCaller
| where OperationNameValue endswith "/write" or OperationNameValue endswith "/action"
| extend AfterCancellation = TimeGenerated > CancelRequested
| project TimeGenerated, AfterCancellation, ActivityStatusValue,
  OperationNameValue, ResourceId, CorrelationId, Caller
| order by TimeGenerated asc

Une ligne postérieure à l’annulation n’est pas automatiquement fautive : une opération acceptée avant le signal peut se terminer ensuite. Elle oblige cependant à attendre son état terminal avant toute compensation.

Produire une matrice des checkpoints

Transformez la chronologie en matrice de décision. Chaque étape doit avoir une preuve côté cible, une propriété d’idempotence et une compensation. « Rejouable » ne veut pas dire « probablement sans danger ».

yaml checkpoints-deploiement.yml
checkpoints:
- name: publish_image
  target_evidence: registry_digest_exists
  observed: true
  idempotent_key: image_digest
  compensation: none

- name: apply_configuration
  target_evidence: app_settings_fingerprint
  observed: true
  idempotent_key: configuration_version
  compensation: restore_previous_fingerprint

- name: route_traffic
  target_evidence: active_revision_and_weight
  observed: false
  idempotent_key: revision_name
  compensation: restore_previous_weights

- name: migrate_schema
  target_evidence: migration_ledger
  observed: unknown
  idempotent_key: migration_id
  compensation: forward_fix_or_tested_down_migration

terminal_rule: unknown_blocks_rerun

Un checkpoint unknown bloque la relance globale. Qualifiez-le par une lecture directe, ou traitez-le comme potentiellement appliqué si aucune preuve plus forte n’existe.

Choisir reprise, compensation ou rollback

Une reprise est acceptable si l’artefact est identique, chaque effet précédent est prouvé et les étapes restantes sont idempotentes. Une compensation convient lorsqu’un write partiel est isolé et que son inverse est testé. Un rollback restaure un état cohérent connu ; il ne consiste pas à relancer aveuglément l’ancien pipeline.

text decision-deploiement-annule.txt
Reprendre depuis un checkpoint
Même commit et même digest
États précédents prouvés côté cible
Étapes restantes idempotentes et bornées
Aucun writer concurrent

Compenser puis valider
Effet partiel identifié et isolé
Commande inverse testée
Pas de migration ou effet métier irréversible
Validation avant reprise du trafic

Rollbacker
Version active incohérente ou probes en régression
Dernier artefact sain et configuration associés disponibles
Compatibilité schéma confirmée
Même matrice de validation rejouée après restauration

Maintenir le gel
Un checkpoint critique reste inconnu
Une opération Azure est encore en cours
Plusieurs writers ont modifié la cible
Artefact, identité ou rollback ne sont pas attribuables

Si une migration de données ou un appel externe a pu réussir, privilégiez une clé d’idempotence, un ledger métier ou une réconciliation dédiée. Le statut du pipeline ne permet pas de déduire l’absence d’effet.

Rendre la prochaine annulation observable

Pour les prochains runs, utilisez un deployment job lié à un environnement, publiez commit et digest avant le premier write, puis écrivez un checkpoint après chaque état confirmé côté cible. Une étape always() peut publier des preuves pendant le délai d’annulation, mais elle ne doit pas lancer un rollback automatique sans relire l’état réel.

Le pipeline doit aussi sérialiser les writers, séparer déploiement et bascule de trafic, donner un identifiant stable aux opérations et rendre les compensations appelables indépendamment. Testez l’annulation en préproduction à plusieurs checkpoints : avant le premier write, pendant une opération asynchrone et après la bascule.

Conclusion

Un run Azure DevOps annulé n’est ni une preuve d’absence de changement ni un rollback. C’est une rupture de chronologie à réconcilier entre le pipeline, l’agent, Azure et l’application.

La décision finale doit nommer un état prouvé : reprendre le même artefact depuis un checkpoint idempotent, compenser un effet borné, restaurer la dernière version cohérente, ou garder la production gelée. La validation se termine seulement lorsque version, configuration, trafic, dépendances et effets métier racontent la même histoire.