Automation
Azure Functions : diagnostiquer un Timer Trigger avant de rejouer un job
Un runbook de production pour qualifier un Azure Functions Timer Trigger en échec avec historique d'exécution, verrous de planification, stockage, Application Insights, idempotence, validation et rollback avant relance manuelle.
Un Timer Trigger Azure Functions paraît souvent simple : une expression CRON, une fonction, un stockage de coordination et un traitement planifié. En production, le risque arrive quand le job ne tourne pas, tourne en retard, se chevauche ou échoue au milieu d’une action partielle. La relance manuelle semble alors être le geste le plus rapide. C’est aussi celui qui peut dupliquer des traitements, rejouer des messages déjà consommés ou masquer une panne de runtime.
Le cas d’usage est un job planifié qui consolide des données, purge des objets expirés, déclenche une synchronisation, appelle une API métier ou prépare un lot de facturation. L’équipe voit une absence de résultat ou une erreur dans Application Insights. Avant de cliquer sur “Run”, de redéployer la Function App ou de modifier la planification, elle doit qualifier trois points : le trigger a-t-il réellement manqué son créneau, le traitement est-il idempotent, et quelle relance est sûre.
Nommer la décision de relance
Commencez par décider ce que vous devez produire. Le diagnostic ne vise pas seulement à expliquer une erreur, mais à choisir entre attendre, relancer, corriger la configuration, rejouer partiellement ou rollbacker la dernière modification.
Decision a produire
Ne rien relancer et attendre le prochain creneau
Relancer manuellement avec un perimetre borne
Relancer seulement les items non traites
Corriger configuration ou identite avant relance
Desactiver temporairement le trigger
Rollbacker le dernier changement applicatif ou de planification
Preuves minimales
Function App, fonction, slot et region
Expression CRON attendue et timezone effective
Derniere execution reussie
Execution en echec ou manquante
Etat du runtime et du stockage AzureWebJobsStorage
Trace d'idempotence ou cle de deduplication
Effets deja produits pendant l'execution en echec
Commande de rollback ou de desactivation connue Sans ce contrat, la conversation glisse vite vers “on relance pour voir”. En exploitation, une relance est une action de production. Elle doit avoir un périmètre, un propriétaire et une preuve de retour arrière.
Vérifier si le trigger a réellement manqué son créneau
Un job peut sembler absent parce que la vue est filtrée, parce que l’ingestion est en retard ou parce que la fonction a démarré mais échoué avant le premier log métier. Reconstituez d’abord la chronologie depuis Application Insights.
let startTime = datetime(2026-07-23T00:00:00Z);
let endTime = datetime(2026-07-23T08:00:00Z);
let functionName = "NightlyReconciliation";
traces
| where timestamp between (startTime .. endTime)
| where cloud_RoleName has "func-prod-billing"
| where message has functionName or tostring(customDimensions["Category"]) has functionName
| project timestamp,
operation_Id,
severityLevel,
message,
InvocationId = tostring(customDimensions["InvocationId"]),
Category = tostring(customDimensions["Category"])
| order by timestamp asc Si aucune trace n’apparaît, ne concluez pas encore à un trigger muet. Vérifiez aussi les exceptions, les requests internes et la latence d’ingestion. Un problème d’observabilité peut ressembler à une absence d’exécution.
Contrôler runtime, scale et stockage de coordination
Le Timer Trigger dépend du runtime Functions et du stockage utilisé pour les verrous, leases et états de planification. Une panne de AzureWebJobsStorage, une restriction réseau, une rotation de clé ou une identité cassée peut empêcher la coordination sans que le code métier soit en cause.
Controles runtime
Function App demarree et dans le bon slot
Version runtime compatible avec le code deploye
WEBSITE_RUN_FROM_PACKAGE ou package deploye lisible
Parametres de planification identiques entre slot et production
Always On actif si le plan d'hebergement l'exige
Controles stockage
AzureWebJobsStorage resolu vers le bon compte
Acces reseau autorise depuis la Function App
Secret ou identite valide
Blob leases disponibles
Pas de throttling ou 403 sur le compte de stockage
Aucun changement recent de firewall, DNS ou Key Vault reference Ce bloc évite de corriger le mauvais niveau. Si le stockage de coordination est inaccessible, relancer manuellement ne répare pas la cause. Vous risquez seulement de créer une exécution hors cadence en plus du prochain créneau.
Lire l’échec comme une action partielle
Un Timer Trigger qui échoue après trois minutes n’est pas équivalent à un trigger jamais démarré. Il a peut-être déjà écrit des lignes, envoyé des notifications, déplacé des fichiers ou validé une étape externe. Traitez l’échec comme une action partielle jusqu’à preuve du contraire.
let invocationId = "00000000-0000-0000-0000-000000000000";
union traces, exceptions, dependencies
| where tostring(customDimensions["InvocationId"]) == invocationId
or operation_Id == invocationId
| extend dependencyTarget = column_ifexists("target", "")
| extend dependencyResult = column_ifexists("resultCode", "")
| project timestamp,
itemType,
severityLevel,
message,
dependencyTarget,
dependencyResult,
success = column_ifexists("success", bool(null))
| order by timestamp asc Cherchez les marqueurs de progression : lot ouvert, item traité, checkpoint écrit, appel externe réussi, message publié, verrou libéré. La bonne relance dépend moins de l’erreur finale que de ce qui a déjà été fait.
Prouver l’idempotence avant le replay
La question centrale est simple : que se passe-t-il si la même entrée est traitée deux fois ? Si la réponse est floue, la relance doit être bornée ou remplacée par une correction manuelle ciblée.
timer_job:
function: NightlyReconciliation
schedule: "0 30 2 * * *"
business_window: previous_day
idempotency:
replay_key: billing_date + customer_id
checkpoint_store: reconciliation_runs
duplicate_policy: skip_if_completed
external_calls:
invoice_api: requires_idempotency_key
notification_api: send_only_after_commit
manual_replay_allowed_when:
- failed_invocation_identified
- completed_items_exported
- pending_items_query_reviewed
- duplicate_policy_tested
- rollback_owner_available Un job réellement idempotent laisse des traces de déduplication. Un job qui se contente de “normalement ça passe” n’est pas idempotent du point de vue exploitation.
Préparer une relance bornée
Une relance sûre évite de rejouer tout le monde par défaut. Préférez un périmètre explicite : fenêtre temporelle, identifiants métier, batch incomplet, mode dry-run ou paramètre de reprise. Si le code ne permet aucune borne, c’est une dette opérationnelle à traiter avant d’élargir l’usage du job.
FUNCTION_APP="func-prod-billing"
FUNCTION_NAME="NightlyReconciliation"
RESOURCE_GROUP="rg-prod-automation"
# Exemple de relance bornee: adapter les parametres au contrat reel de la fonction.
az functionapp function invoke --resource-group "$RESOURCE_GROUP" --name "$FUNCTION_APP" --function-name "$FUNCTION_NAME" --data '{"mode":"replay","businessDate":"2026-07-22","dryRun":true,"maxItems":50}' Le premier replay devrait idéalement être un dry-run. S’il n’existe pas, validez au moins la liste des items à reprendre et la politique de doublon avant l’exécution réelle.
Surveiller la relance et le prochain créneau
La relance manuelle ne clôt pas l’incident. Il faut vérifier son résultat, puis observer le prochain déclenchement automatique. Beaucoup de problèmes reviennent au créneau suivant parce que la cause était la configuration, pas l’instance échouée.
let replayStart = datetime(2026-07-23T08:10:00Z);
let watchUntil = datetime(2026-07-23T10:00:00Z);
traces
| where timestamp between (replayStart .. watchUntil)
| where cloud_RoleName has "func-prod-billing"
| where message has "NightlyReconciliation"
| summarize events=count(),
errors=countif(severityLevel >= 3),
sampleError=anyif(message, severityLevel >= 3)
by bin(timestamp, 5m), operation_Id
| order by timestamp asc Ajoutez une validation métier hors logs : nombre d’objets traités, absence de doublon, état des checkpoints, métrique aval et ticket de changement. Les logs disent que la fonction a tourné ; ils ne prouvent pas toujours que l’effet métier est correct.
Décider correction, rollback ou changement de contrat
La sortie du runbook doit être explicite. Si la relance réussit mais que la cause reste inconnue, gardez l’incident ouvert en surveillance. Si la cause est un changement récent, rollbackez la plus petite surface possible : configuration, secret, Key Vault reference, firewall du storage, slot, package ou expression CRON.
Relance acceptee
Echec qualifie
Effets partiels identifies
Items a rejouer listes
Idempotence ou deduplication prouvee
Dry-run ou relance limitee validee
Prochain creneau surveille
Rollback
Le runtime ne peut plus coordonner le timer
Le stockage de coordination est inaccessible
La nouvelle expression CRON declenche hors fenetre
La relance cree des doublons
Les effets partiels ne sont pas reconciliables
Changer le contrat
Ajouter dry-run
Ajouter replay_key metier
Ecrire un checkpoint par item
Exposer un parametre de reprise borne
Journaliser InvocationId, business window et compteur d'items Un rollback propre peut être aussi simple que revenir à l’ancienne expression CRON ou restaurer une référence Key Vault précédente. L’important est de réduire le risque avant de rejouer, pas après.
Conclusion
Un Timer Trigger n’est pas seulement une horloge. C’est une automatisation de production avec coordination, état, effets métiers et fenêtres de relance. Quand il échoue, la bonne réaction n’est pas de déclencher un replay immédiat. C’est de qualifier la chronologie, le runtime, le stockage, l’action partielle et l’idempotence.
Avec ce runbook, l’équipe peut décider entre attendre le prochain créneau, relancer un périmètre borné, corriger la configuration, rollbacker un changement ou améliorer le contrat du job. La relance devient alors une décision exploitable, pas un pari sur un bouton.