Skip to content
Des alertes qui ne savent pas dans quel cloud elles tournent
← ← Retour aux Réflexions Cloud

Des alertes qui ne savent pas dans quel cloud elles tournent

Chaque cloud vous vend des alarmes. CloudWatch Alarms, règles d'alerte Azure Monitor, politiques d'alerte Cloud Monitoring : chacune est préconfigurée, chacune se met en place en quelques clics, et chacune est définie dans un format que seul son propre cloud comprend. Si vous tournez sur plus d'un, vous finissez avec trois systèmes d'alerte, trois routages d'astreinte, trois définitions de « l'API est en mauvaise santé », et, tôt ou tard, trois seuils différents pour la même chose parce que quelqu'un en a ajusté un et a oublié les autres.

Nous avons défendu l'idée que les logs et les traces devraient être émis par l'application et stockés quelque part que le cloud ne possède pas. Les alertes sont la troisième pièce, et c'est celle qui compte à 02 h 40, parce qu'une alerte est ce qui décide si vous êtes réveillé. Voici comment nous les définissons une fois, à partir de métriques que l'application émet, pour que « taux d'erreur au-dessus de 1 % » veuille dire la même chose sur EKS, GKE et AKS, et que déplacer un service de l'un à l'autre ne change pas ce qui vous appelle.

Alerter sur ce que voit l'utilisateur, pas sur ce que voit le cloud

Les alarmes natives du cloud portent sur les ressources du cloud : CPU d'une instance, nombre de 5xx sur un équilibreur, limitations sur une table. Elles sont utiles, et ce sont les mauvaises pour appeler quelqu'un, parce qu'un utilisateur ne ressent pas le CPU. Un utilisateur ressent une requête lente ou échouée. Donc les alertes qui appellent portent sur trois nombres par service, mesurés du point de vue de l'application elle-même :

  • Le débit : requêtes par seconde, pour qu'une chute à zéro soit visible.
  • Les erreurs : fraction des requêtes qui ont échoué.
  • La durée : latence au 95e ou 99e percentile.

Ils proviennent du même pipeline OpenTelemetry qui transporte les traces. Le connecteur spanmetrics du collecteur transforme chaque span serveur en un histogramme de durées et un compteur d'appels, étiquetés avec service.name, la route HTTP, le code de statut et, parce que resourcedetection a tourné avant, cloud.provider. Aucune nouvelle instrumentation dans l'application. Les spans que vous émettez déjà deviennent les métriques sur lesquelles vous alertez.

connectors:
  spanmetrics:
    histogram:
      explicit: { buckets: [50ms, 100ms, 250ms, 500ms, 1s, 2s, 5s] }
    dimensions:
      - name: http.route
      - name: http.response.status_code
    exemplars: { enabled: true }
    resource_metrics_key_attributes: [service.name, cloud.provider, cloud.region]

service:
  pipelines:
    traces:  { receivers: [otlp], processors: [resourcedetection, batch], exporters: [otlphttp/tempo, spanmetrics] }
    metrics: { receivers: [spanmetrics, otlp], processors: [batch], exporters: [prometheusremotewrite/mimir] }

Les métriques vont dans Mimir (ou un simple Prometheus, ou VictoriaMetrics ; l'architecture s'en moque). Les exemplaires sont activés, donc un point sur le graphique de latence renvoie à une vraie trace qui l'a produit.

applicationsEKS · GKE · AKSspans en OTLPaucun code de métriques collecteurresourcedetection →connecteur spanmetricsdébit · erreurs · durée Mimirles labels incluentcloud_providerexemplaires → Tempo Grafana alertingrègles en tant que codeburn rate sur les SLOune règle, tous les clouds pagerune seule rotation alarmes natives : CPU RDS, erreurs NAT, ACU Aurora, quotasrestent dans chaque cloud · vers le même pager en basse priorité

Les règles, écrites une seule fois

Les règles d'alerte Grafana vivent dans un fichier YAML du dépôt et sont provisionnées à chaque déploiement, comme les tableaux de bord. Une règle par SLO, pas une par service et par cloud. La requête fait l'éventail :

# alerting/rules.yaml
groups:
  - name: api-slo
    interval: 1m
    rules:
      - alert: ApiErrorBudgetBurn
        for: 2m
        labels: { severity: page, team: platform }
        annotations:
          summary: 'api error budget burning {{ $labels.cloud_provider }} · {{ $value | humanizePercentage }}'
          runbook: https://runbooks.internal/api-errors
        expr: |
          (
            sum by (cloud_provider) (rate(calls_total{service_name="api", http_response_status_code=~"5.."}[5m]))
            /
            sum by (cloud_provider) (rate(calls_total{service_name="api"}[5m]))
          ) > (14.4 * 0.001)
          and
          (
            sum by (cloud_provider) (rate(calls_total{service_name="api", http_response_status_code=~"5.."}[1h]))
            /
            sum by (cloud_provider) (rate(calls_total{service_name="api"}[1h]))
          ) > (14.4 * 0.001)

C'est une alerte de burn rate multi-fenêtres pour un SLO de disponibilité de 99,9 % : appeler quand le taux d'erreur des cinq dernières minutes et de la dernière heure dépassent tous deux 14,4 fois le budget d'erreur, ce qui correspond à brûler tout le budget de 30 jours en environ deux jours. Le sum by (cloud_provider) est toute l'histoire multi-cloud. Une règle, une expression, et elle se déclenche séparément pour aws, gcp et azure, avec le cloud dans le titre de l'alerte, si un seul d'entre eux brûle. Déplacer l'API d'EKS vers GKE change quelle valeur de label se déclenche. Ça ne change pas la règle.

Une seconde règle avec une fenêtre plus longue (6 heures et 3 jours, seuil 1×) attrape les combustions lentes comme un ticket plutôt qu'un appel. La latence reçoit le même traitement sur histogram_quantile(0.99, sum by (le, cloud_provider) (rate(duration_bucket{...}[5m]))).

taux d'erreur api par cloud · une règle, trois valeurs de label SLO 0,1 % 14,4× · appel aws gcp azure 02:40 · appel : gcp 14:00–19:00 · combustion lente : ticket, azure 00:0024:00

Ce qui reste natif, et comment ça se raccorde

Les alarmes propres au cloud ne disparaissent pas ; elles changent de rôle. CPU RDS, utilisation des ACU Aurora, erreurs d'allocation de ports sur la NAT gateway, limitations DynamoDB, quotas de service : ce sont des propriétés d'une infrastructure que le cloud possède, et la supervision du cloud les voit en premier et le mieux. Nous gardons ces alarmes natives, définies dans le même CDK qui définit la ressource, et les routons vers le même pager en basse priorité. Elles informent ; elles ne réveillent personne. Si un NAT commence à perdre des paquets, l'alerte de taux d'erreur de l'API appelle de toute façon dans les deux minutes, et l'alarme NAT est là dans le canal d'incident pour expliquer pourquoi.

Cette inversion est tout l'intérêt. Les symptômes appellent. Les causes annotent. Les alertes de symptôme sont neutres vis-à-vis du cloud parce que l'application a émis les données ; les alertes de cause sont spécifiques au cloud parce que le cloud a émis les données. Aucune ne prétend être l'autre.

Trois choses qui nous ont pris plus de temps qu'elles n'auraient dû

La cardinalité. spanmetrics avec http.route comme dimension, ça va. Avec http.target (le chemin brut, identifiants inclus), ça génère une série temporelle par commande et Mimir s'effondre. Utilisez le gabarit de route, jamais le chemin.

Le silence pendant les déploiements. Un déploiement progressif produit une rafale de connexions réinitialisées pendant quelques secondes. Le for: 2m sur la règle d'appel l'absorbe. Sans lui, chaque déploiement a appelé quelqu'un la première semaine.

Le pager doit être une seule chose. Nous avions CloudWatch vers un outil d'astreinte et Grafana vers un autre, parce qu'ils avaient été mis en place à des moments différents. Deux rotations, deux applications sur le téléphone, et une nuit où la personne d'astreinte pour l'un ne l'était pas pour l'autre. Tout passe maintenant par les points de contact de Grafana vers une seule rotation. Les alarmes natives y arrivent via un topic SNS → webhook, ce qui a pris une heure à configurer et aurait dû être fait le premier jour.

La version courte

Alerter sur les trois nombres que l'utilisateur ressent, dérivés de spans que vous émettez déjà, stockés dans un backend de métriques qui vous appartient, évalués par des règles que vous gardez dans git avec un sum by (cloud_provider) dedans. Garder les alarmes du cloud pour les choses du cloud, en basse priorité, vers le même pager. Quand un service change de cloud, une valeur de label change et personne n'édite une alerte.

Vous voulez le fichier de règles pour vos propres services, calibré sur de vrais SLO plutôt que sur des chiffres ronds ? Parlons-en.