Infrastructure

Azure Monitor : contenir le fan-out d'une alerte de logs avant de modifier la règle KQL

Un runbook de production pour mesurer la cardinalité des dimensions, contenir le bruit, canaryer un regroupement stable et rollbacker une alerte Azure Monitor sans perdre la couverture.

25 sept. 2026 azureazure-monitorlog-alertsscheduled-query-ruleskqldimensionsobservabilityincidentcanaryrunbookrollbackproduction

Une alerte de logs Azure Monitor ouvre soudain des centaines d’instances. La requête KQL retourne toujours les erreurs attendues, mais l’action group page une fois par ressource, code d’erreur et identifiant de requête. Mettre l’action group en silence calme les notifications ; relever une limite repousse la panne. Aucune de ces mesures n’explique pourquoi un signal opérationnel est devenu des centaines de séries évaluées séparément.

Le cas fil rouge est une scheduled query rule sur les échecs d’une API. Elle découpe les résultats par _ResourceId, ErrorCode et OperationId. Les deux premières colonnes décrivent une cohorte exploitable. OperationId est presque unique pour chaque requête : chaque évaluation crée donc de nouvelles combinaisons, sans cycle de vie ni suppression de notification communs. Ce runbook mesure le fan-out, contient l’incident, reconstruit le regroupement puis décide entre promotion, attente et rollback.

Figer la règle et la fenêtre d’incident

Conservez la règle déployée avant toute édition dans le portail. Notez son resource ID, son état, ses scopes, sa requête, sa fréquence d’évaluation, sa fenêtre, son seuil, ses failing periods, ses dimensions, ses actions et le mode de résolution automatique. Figez aussi une fenêtre UTC qui couvre le trafic normal et la tempête d’alertes.

bash 01-export-scheduled-query-rule.sh
RG="rg-observability-prod"
RULE="api-errors-prod"

az monitor scheduled-query show --resource-group "$RG" --name "$RULE" --output json > scheduled-query-before.json

az monitor scheduled-query show --resource-group "$RG" --name "$RULE" --query "{enabled:enabled,scopes:scopes,evaluationFrequency:evaluationFrequency,windowSize:windowSize,autoMitigate:autoMitigate,criteria:criteria,actions:actions}" --output yaml

Ne modifiez pas simultanément la requête et les dimensions. La requête change la population observée ; les dimensions changent le nombre d’instances indépendantes. Gardez ces deux hypothèses séparées.

Reconstruire le contrat de fan-out

Les dimensions de découpage ne sont pas de simples libellés. Azure Monitor regroupe les résultats par chaque combinaison sélectionnée puis évalue chaque groupe séparément. Une règle accepte jusqu’à six dimensions, mais six colonnes à faible cardinalité et six identifiants de requête n’ont pas du tout le même effet opérationnel.

Écrivez le contrat attendu avant de lire les données de l’incident.

yaml alert-cardinality-contract.yml
signal: failed API requests
decision: page when one service and error family sustain failures
stable_dimensions:
- _ResourceId
- ErrorCode
forbidden_dimensions:
- OperationId
- RequestId
- TimeGenerated
expected_series:
normal: 8-20
incident: less-than-60
notification_budget:
paging_instances_per-evaluation: 5
rollback:
redeploy scheduled-query-before.json
keep notification suppression until the previous rule is healthy

Le budget exact appartient au service ; ce n’est pas un seuil universel de plateforme. Il sert à rendre une multiplication anormale visible avant que l’action group ne devienne le premier détecteur.

Mesurer les combinaisons sur la fenêtre réelle

Rejouez la requête de l’alerte sur la fenêtre figée, mais faites ressortir les colonnes de regroupement plutôt que le seul seuil final. Comptez les valeurs distinctes séparément, puis leurs combinaisons.

kusto 02-measure-dimension-cardinality.kql
let Start = datetime(2026-09-25T07:30:00Z);
let End = datetime(2026-09-25T08:00:00Z);
AppRequests
| where TimeGenerated between (Start .. End)
| where Success == false
| extend ErrorCode = tostring(ResultCode)
| summarize
  Rows=count(),
  Resources=dcount(_ResourceId),
  ErrorCodes=dcount(ErrorCode),
  Operations=dcount(OperationId),
  Combinations=dcount(strcat(_ResourceId, "|", ErrorCode, "|", OperationId))

Identifiez ensuite la colonne qui porte la croissance et vérifiez sa stabilité entre deux évaluations.

kusto 03-profile-alert-series.kql
let Start = datetime(2026-09-25T07:30:00Z);
let End = datetime(2026-09-25T08:00:00Z);
AppRequests
| where TimeGenerated between (Start .. End)
| where Success == false
| extend ErrorCode = tostring(ResultCode)
| summarize Hits=count(), FirstSeen=min(TimeGenerated), LastSeen=max(TimeGenerated)
  by _ResourceId, ErrorCode, OperationId
| order by Hits desc

Si presque chaque ligne possède un OperationId différent, cette colonne fournit une preuve de corrélation, pas une dimension de regroupement. Conservez-la dans les logs et, si nécessaire, dans le contexte de l’alerte, mais n’en faites pas l’identité de l’instance.

Séparer évaluation, cycle de vie et notification

Trois mécanismes peuvent produire la même impression de tempête.

La requête peut d’abord révéler plus de cohortes réelles après un déploiement qui touche plusieurs ressources. Des dimensions volatiles peuvent ensuite transformer chaque ligne en série nouvelle alors que l’incident reste unique. Enfin, une instance stable peut notifier plusieurs fois à cause de la résolution, d’une règle de traitement ou du récepteur.

Comparez sur les mêmes timestamps le nombre de lignes KQL, les combinaisons de dimensions, les instances actives et les notifications livrées. Si les combinaisons progressent avec les lignes, corrigez le regroupement. Si elles restent stables alors que les notifications se répètent, examinez action groups et alert processing rules. Si les instances ne se résolvent jamais, vérifiez autoMitigate, la condition de retour à la normale et la présence persistante de la série dans la requête.

Resource Health de la scheduled query rule pose une autre frontière. Erreur de syntaxe, erreur sémantique, réponse trop volumineuse, requête trop coûteuse, erreur de validation ou limite d’alertes actives concernent la santé de la règle, pas l’absence de signal. Conservez cet état avant toute désactivation.

Contenir le bruit sans prétendre corriger le signal

Si le paging empêche de traiter l’incident, utilisez une alert processing rule bornée dans le temps ou retirez uniquement le récepteur de paging via le chemin de déploiement gouverné. Gardez si possible la création de ticket ou un canal de preuve non paging. Documentez le propriétaire, l’expiration et le scope exact.

Le silence des notifications ne réduit ni la cardinalité, ni la charge d’évaluation, ni le nombre d’instances. À l’inverse, désactiver la scheduled query rule arrête l’évaluation et crée un trou de couverture. Réservez cette mesure au cas où la règle menace elle-même la chaîne d’alerting et où un signal de remplacement est déjà actif.

text containment-decision.txt
Suppress notifications temporarily
signal remains queryable
incident team has another live channel
suppression has owner and expiry

Disable the rule temporarily
rule health or active-instance growth threatens alerting
replacement coverage is already active
exported configuration is retained

Do neither
notifications are actionable and within budget
fan-out represents distinct affected resources

Construire le regroupement autour d’une décision

Une bonne dimension modifie le propriétaire, l’impact ou la réponse. _ResourceId, la région, l’anneau de déploiement ou une famille d’erreurs bornée peuvent satisfaire ce critère. Un GUID, un timestamp, un request ID ou un message d’exception brut échouent généralement.

Dans le cas fil rouge, regroupez les requêtes par service et famille d’erreurs. Gardez un identifiant de corrélation représentatif hors du regroupement, ou retrouvez des exemples dans les logs après déclenchement.

kusto 04-stable-alert-query.kql
AppRequests
| where TimeGenerated > ago(15m)
| where Success == false
| extend ErrorCode = tostring(ResultCode)
| extend ErrorFamily = case(
  ErrorCode startswith "5", "server",
  ErrorCode == "429", "throttle",
  ErrorCode startswith "4", "client",
  "other")
| summarize FailedRequests=count() by _ResourceId, ErrorFamily

La règle ne doit se découper que par _ResourceId et ErrorFamily. La clause temporelle reste directement dans la requête d’alerte. Évitez les valeurs volatiles et les opérateurs non pris en charge par les log alerts ; chaque dimension doit être retournée sous forme de chaîne ou de nombre.

Canaryer la règle comme second signal

Ne remplacez pas une règle bruyante par une candidate sans observation. Déployez un canari avec une action non paging, un scope borné ou une seule ressource connue, tout en conservant fréquence et fenêtre. Donnez-lui un nom distinct et un marqueur de propriété.

Exécutez trois tests :

  1. Un échec connu dans la cohorte choisie doit ouvrir une instance avec la bonne ressource et la bonne famille.
  2. Plusieurs requêtes aux OperationId différents dans la même cohorte doivent rester une seule instance.
  3. Le retour à la normale doit résoudre l’instance selon le cycle de vie attendu.

Observez plusieurs cycles complets d’évaluation et de résolution. Comparez entre ancienne et nouvelle règles les totaux KQL, combinaisons, instances ouvertes, instances résolues et notifications. La seule arrivée d’un message ne valide pas le canari.

Promouvoir, attendre ou rollbacker

Promouvez la candidate lorsqu’elle détecte chaque cohorte requise, respecte le budget de cardinalité, se résout de façon prévisible et conserve le propriétaire de la ressource dans le payload. Levez la suppression temporaire uniquement après une évaluation saine et un cycle contrôlé de déclenchement puis résolution.

Attendez lorsque la requête est correcte mais que le budget de dimensions ou le retour à la normale reste incertain. Gardez le canari hors paging et l’ancienne règle comme signal de référence.

Rollbackez si des cohortes distinctes sont fusionnées, si les ressources touchées disparaissent du contexte, si la santé d’évaluation se dégrade ou si un test positif est manqué. Désactivez la candidate, redéployez l’export ou la dernière révision IaC approuvée, puis vérifiez état, actions, dimensions et Resource Health. Le rollback n’est terminé que lorsque couverture et routage des notifications sont restaurés.

Conclusion

Une tempête de log alerts ne signifie pas automatiquement que la requête KQL est mauvaise. Elle peut être correcte tout en associant une dimension volatile à chaque requête et en créant autant d’objets opérationnels. Mesurez séparément lignes, cardinalité, instances et notifications avant de modifier les seuils ou de relever les limites.

La décision finale devient observable : conserver des alertes réellement distinctes, remplacer le regroupement volatil par des cohortes stables, attendre davantage de preuves ou restaurer la règle précédente. L’objectif n’est pas de réduire les alertes à tout prix, mais d’obtenir une instance par décision que l’astreinte peut réellement prendre.