technique:adminsys:monitoring:alerting:vmalert

Attention:

Il sera plus facile de lire cette article avec une compréhension basique de la pile de métrologie de Picasoft.

Pour résumer très rapidement ce qui est utile, Picasoft stocke des métriques (mesure de n’importe quoi dans l’infrastructure : nombre de requêtes, utilisation CPU…) dans une time series database (TSDB). Une TSDB est une base de données optimisée pour stocker des séries de données associées à un horodatage, nommées timeseries. Picasoft utilise Victoria Metrics.

Les métriques suivent des conventions de nommages permettant de deviner leur nature (e.g traefik_service_requests_total, qui s’auto-décrit bien).

Chaque métrique est associée à des labels, qui portent des métadonnées sur la mesure, par exemple pour préciser le service ou la machine concernée par la métrique.

On peut ensuite effectuer des requêtes, comme sur n’importe quelle base de données. Les requêtes sont souvent des calculs sur une timeserie avec un début et une fin. Exemple : « donne moi le nombre de requêtes HTTP par secondes qu’a reçu Mattermost les 5 dernières minutes ».

Le langage de requêtage s’appelle PromQL. La requête d’exemple s’écrirait alors :

rate(traefik_service_requests_total{service_name~="team.picasoft.net"}[5m])

Soit littéralement : par secondes, le nombre de requêtes dont le label service_name vaut team.picasoft.net pour les dernières 5 minutes.

Lien:

L’article PromQL for humans constitue une bonne introduction à PromQL.

vmalert est un outil compatible avec Victoria Metrics qui permet d’évaluer des règles sur des métriques et de déclencher des alertes selon les règles.

Note:

Dans vmalert, une règle est une condition sur le résultat d’une requête PromQL. PromQL est initialement développé pour faire des requêtes sur une TSDB Prometheus, mais nous utilisons Victoria Metrics, qui est compatible avec l’écosystème Prometheus. Le format de fichier d’alertes vmalert est compatible avec le format de fichier d’alertes utilisé dans l’écosystème Prometheus.

vmalert est exécuté depuis l’image Docker officielle et la configuration s’effectue avec les arguments de la ligne de commande (voir Docker Compose) et via un fichier de règles.

Lien:

Le but de cette documentation n’est pas d’être exhaustif mais de donner une intuition. Voir la documentation de vmalert pour un tour d’horizon de toutes les options. Le fichier de règles utilisé en production se trouve ici.

En ce qui nous concerne, les arguments essentiels sont :

  • -datasource.url : détermine à quelle adresse on va aller chercher les métriques sur lesquelles portent les règles. Typiquement, c’est l’adresse du conteneur Victoria Metrics dans le réseau Docker.
  • -remoteWrite.url et -remoteRead.url : typiquement la même chose que -datasource.url. Permet de persister l’état des règles malgré un redémarrage de vmalert, qui ne garde les données qu’en mémoire vive. Or une règle a parfois besoin d’être évaluée positivement pendant 6 heures avant de lever une alerte, donc il est utile de garder trace des états précédents quelque part.
  • -notifier.url : l’adresse où envoyer les alertes pour traitement ultérieur, typiquement une instance d’alertmanager.
  • -rule : le chemin vers le fichier de règles.
  • -evaluationInterval : l’intervalle par défaut entre l’évaluation des différentes règles. On conseille une durée faible (exemple 1m) afin de détecter rapidement des défaillance critiques.

Prenons un cas simple. Pour chaque service, Blackbox exporter expose une métrique nommée probe_success, qui correspond à l’état de santé du service et vaut 0 s’il est down et 1 s’il est up.

L’alerte correspondante sera :

snippet.yaml
  - alert: EndpointDown
    expr: probe_success == 0
    for: "2m"
    labels:
      severity: critical
    annotations:
      summary: "Service down"
      description: "{{ $labels.instance }} is down for more than 2 minutes"
      # Redirect to HTTP or DNS dashboard based on vmagent job name
      dashboard: '{{ if eq $labels.job "blackbox-http" -}}https://grafana.picasoft.net/d/8BOa8W47z/services-web?var-instance={{ $labels.instance }}{{- else if eq $labels.job "blackbox-dns" -}}https://grafana.picasoft.net/d/1twteMV7k/serveurs-dns?var-instance={{ $labels.instance }}{{- end -}}'
  • alert est le nom de l’alerte et doit être unique.
  • expr est la règle en elle-même, une condition sur une requête PromQL. Ici elle est simple et s’évalue positivement si probe_success est à 0.
  • for est la durée pendant laquelle la règle doit s’évaluer positivement pour déclencher une alerte. Ici, on considère qu’un service down pendant 2 minutes lève une alerte.
  • labels permet de rajouter des labels ou d’écraser des labels associés à la métrique qui seront envoyés à alertmanager. Pour connaître les labels associés à une métrique, voir cette section. Ici, on a choisi d’assigner une sévérité à l’alerte : warning ou critical, dépendant de l’urgence.
  • annotations permet de rajouter des méta-données décrivant l’alerte. On a choisi summary, qui donne une brève description de l’alerte, description, qui précise le problème et de quelle machine ou service il vient, et dashboard, un lien vers le dashboard Grafana pour visualiser le graphique correspondant à la métrique.

Note:

Tous les labels associés à la métrique peuvent être utilisés dans les annotations avec la syntaxe {{ $labels.nom_label }}. L’utilité principale est d’ajouter le nom de l’instance concernée (team.picasoft.net, par exemple) dans le message de l’alerte pour le rendre plus explicite.

Note:

Notez que l’expression pour dashboard est assez complexe. Elle utilise les templates Go et se basent simplement sur une condition. Si le label job associé à la métrique est blackbox-http, alors c’est un service web qui est down, et on renvoie vers le dashboard Grafana des services webs. Si c’est blackbox-dns, il faut renvoyer vers le dashboard Grafana des serveurs DNS.

En général, on se basera sur des métriques qui ont déjà un dashboard Grafana, et on récupère la requête utilisée pour afficher les graphiques.

Exemple

Question:

Comment lever une alerte quand un disque est trop rempli ?

Tout ce qui concerne les disques, la mémoire, le CPU… est collecté grâce à node_exporter, et associé à ce dashboard Grafana. On trouve le graphe associé aux disques, et on clique sur Edit.

On trouve alors l’inspecteur de requêtes et la métrique associée au graphe :

En arrangeant la requête pour obtenir un pourcentage, ça nous donne :

(1 - (node_filesystem_avail_bytes{fstype=~"ext."} / node_filesystem_size_bytes{fstype=~"ext."})) * 100 > 90

Elle évalue le pourcentage d’espace occupé sur tous les systèmes de fichier de type ext (ext2, ext4…) et regarde s’il est supérieur à 90%.

Note:

Tout ce qui se trouve entre accolades permet de filtrer sur les labels associés aux métriques, notamment avec des expressions régulières.

On aimerait pouvoir envoyer une description du genre : , c’est à dire s’il reste moins de 10% d’espace libre.

Le périphérique X monté sur le dossier Y est plein à Z%

Question:

Où trouver ces informations ?

Question qu’on se pose très souvent pendant l’écriture d’une règle, et notamment de la description : quelles métadonnées ai-je à ma disposition pour décrire le mieux possible le problème ? Ce sont les labels qui portent ces métadonnées. Alors, quels sont les labels associés à une métrique particulière ?

Deux manières de procéder. On peut utiliser l’inspecteur de métriques de Grafana et cliquer sur Expand all pour voir les labels renvoyées associées aux métriques :

On voit ici qu’on a pas mal de labels : device, mountpoint, fstype (utilisé dans la requête), instance… De quoi décrire tout ce qu’on veut, donc.

Cependant, il peut arriver qu’on écrive une requête assez différente de ce qu’on a sur le dashboard, ou on voudrait juste vérifier les labels associée à une métrique en particulier.

Dans ce cas, on peut utiliser l’API Prometheus sur Victoria Metrics, avec laquelle elle est compatible.

On se rend sur la machine où tourne Victoria Metrics, on rentre dans le conteneur :

docker exec -it victoria-metrics sh

Et on peut ensuite demander à chercher toutes les timeseries avec une certaine métrique, ou certains labels. Exemple :

curl -s http://victoria-metrics:8428/api/v1/series --data-urlencode 'match[]=node_filesystem_avail_bytes' | jq

Ici, on cherche toutes les timeseries associées à la métrique node_filesystem_avail_bytes. Extrait de réponse :

snippet.json
{
  "status": "success",
  "data": [
    {
      "__name__": "node_filesystem_avail_bytes",
      "job": "pica01",
      "instance": "pica01",
      "device": "/dev/mapper/vg00-data",
      "fstype": "ext4",
      "mountpoint": "/DATA/docker"
    }
  ]
}

On retrouve tous les labels disponibles.

Les annotations pourront alors ressembler à :

snippet.yaml
annotations:
  summary: Disk 90% full on {{ $labels.instance }}
  description: Device {{ $labels.device }} mounted on {{ $labels.mountpoint }} is {{ printf "%.0f" $value }}% full

Note:

Remarquer l’utilisation de la fonction printf pour formater la valeur en enlevant les virgules, et de $value qui contient la valeur ayant fait évaluer la règle positivement.

Ne reste plus qu’à ajouter le lien du dashboard Grafana, où on ajoutera une variable à l’URL pour filtrer directement par la machine concernée :

snippet.yaml
dashboard: https://grafana.picasoft.net/d/VIb73SGWa/server-overview?var-node={{ $labels.instance }}

Note:

Le nom de la variable (var-node) se déduit simplement en se rendant sur le dashboard, en changeant d’instance (pica01, pica02…) et en regardant l’URL, ou alors en se rendant dans la section Variables des paramètres du dashboard.

Enrichir une métrique avec des métadonnées

C’est un cas très spécifique mais qui peut rendre perplexe. Parfois, certaines métriques ne portent pas toutes les métadonnées qui les concernent, pour plusieurs bonnes raisons : notamment, ne pas dupliquer des informations sur les disques dans toutes les métriques concernant les disques.

Par exemple, la métrique pve_disk_usage_bytes, de l'exporter Proxmox, ne contient que quelques labels, par exemple :

snippet.json
{
  "__name__": "pve_disk_usage_bytes",
  "job": "proxmox",
  "instance": "alice.picasoft.net:9221",
  "id": "storage/alice/save"
}

Or, pour notre message d’alerte, on aimerait bien savoir à quel stockage Proxmox celui-ci correspond.

A Savoir:

La façon idiomatique de stocker des métadonnées associées à une métrique sans lui ajouter trop de labels est de créer une pseudo-métrique, suffixée par _info, et valant 1. De la sorte, on peut combiner les labels de la métrique dont la valeur nous intéresse avec les labels de la pseudo-métrique via une multiplication, qui ne change rien au résultat, puisque la pseudo-métrique vaut 1.

Par exemple, pour Proxmox, la pseudo-métrique est pve_storage_info, dont voici un extrait :

{
  "__name__": "pve_storage_info",
  "job": "proxmox",
  "instance": "alice.picasoft.net:9221",
  "node": "alice",
  "id": "storage/alice/save",
  "storage": "save"
}

On voit que cette métrique contient bien storage, le nom du stockage Proxmox. Voici une requête qui permet de combiner les deux :

pve_disk_usage_bytes * on (id) group_left(storage) pve_storage_info

On récupère la valeur de pve_disk_usage_bytes et on la multiplie par pve_storage_info, qui vaut 1. Entre les deux, l’opération ressemble à une jointure au sens SQL :

  • La clause on(id) va récupérer, pour chaque timeserie associée pve_disk_usage_bytes, la timeserie associée à pve_storage_info dont le label id a la même valeur.
  • Ensuite, la clause group_left(storage) va enrichir l’opérande de gauche (pve_disk_usage_bytes) de la valeur du label storage de l’opérande de droite (pve_storage_info).

On se retrouve donc, à la fin de cette expression, avec un label storage utilisable pour la description de l’alerte.

Attention:

Si on avait essayé l’expression pve_disk_usage_bytes * on (id) pve_storage_info, le seul label qui aurait été conservé serait id. On a donc pas le choix que d’utiliser group_left pour enrichir les labels de l’opérande de gauche avec un ou plusieurs labels de l’opérande de gauche.

Lien:

Plus d’explications et d’exemples en anglais ici.

  • technique/adminsys/monitoring/alerting/vmalert.txt
  • de qduchemi