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.
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 ».
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.
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.
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.
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.
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 ».
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.
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.