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.

23 juil. 2026 azureazure-functionstimer-triggerautomationobservabilityapplication-insightskqlstorageidempotencyrunbookrollbackproduction

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.

text timer-trigger-decision.txt
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.

kusto 01-timer-trigger-history.kql
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.

text runtime-storage-checks.txt
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.

kusto 02-partial-execution.kql
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.

yaml idempotency-contract.yml
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.

bash 03-manual-replay-contract.sh
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.

kusto 04-replay-and-next-schedule.kql
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.

text timer-trigger-outcome.txt
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.