Différences

Ci-dessous, les différences entre deux révisions de la page.

Lien vers cette vue comparative

Les deux révisions précédentes Révision précédente
Prochaine révision
Révision précédente
technique:adminsys:monitoring:alerting:vmalert [2021/09/01 15:15] qduchemitechnique:adminsys:monitoring:alerting:vmalert [2022/05/24 20:54] (Version actuelle) ppom
Ligne 1: Ligne 1:
 +{{indexmenu_n>10}}
  
 +## Surveiller les métriques avec vmalert
 +
 +<bootnote warning>
 +Il sera plus facile de lire cette article avec une compréhension basique de la [pile de métrologie de Picasoft](https://wiki.picasoft.net/doku.php?id=technique:adminsys:monitoring:metrologie:start). 
 +</bootnote>
 +
 +### Rappels
 +
 +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 [[technique:adminsys:monitoring:metrologie:victoriametrics|Victoria Metrics]].
 +
 +Les métriques suivent des [conventions de nommages](https://prometheus.io/docs/practices/naming) 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](https://prometheus.io/docs/prometheus/latest/querying/basics/). 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.
 +
 +<bootnote web>
 +L'article [PromQL for humans](https://timber.io/blog/promql-for-humans/) constitue une bonne introduction à PromQL.
 +</bootnote>
 +
 +### Préambule
 +
 +`vmalert` est un outil compatible avec [[technique:adminsys:monitoring:metrologie:victoriametrics|Victoria Metrics]] qui permet d'évaluer des **règles** sur des métriques et de déclencher des alertes selon les règles.
 +
 +<bootnote>
 +Dans `vmalert`, une règle est une condition sur le résultat d'une requête [PromQL](https://prometheus.io/docs/prometheus/latest/querying/basics/). 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.
 +</bootnote>
 +
 +`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](https://gitlab.utc.fr/picasoft/projets/services/monitoring/-/blob/master/docker-compose.yml)) et via un fichier de règles.
 +
 +<bootnote web>
 +Le but de cette documentation n'est pas d'être exhaustif mais de donner une intuition. Voir la [documentation de vmalert](https://docs.victoriametrics.com/vmalert.html) pour un tour d'horizon de toutes les options. Le fichier de règles utilisé en production se trouve [ici](https://gitlab.utc.fr/picasoft/projets/services/monitoring/-/blob/master/vmalert-rules.yml).
 +</bootnote>
 +
 +### Arguments de ligne de commande
 +
 +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'[[technique:adminsys:monitoring:alerting:alertmanager|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.
 +
 +### Exemple de règle
 +
 +Prenons un cas simple. Pour chaque service, [[technique:adminsys:monitoring:collect:blackbox|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 :
 +
 +```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 [[technique:adminsys:monitoring:alerting:vmalert#exemple|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 [[technique:adminsys:monitoring:metrologie:grafana|Grafana]] pour visualiser le graphique correspondant à la métrique.
 +
 +<bootnote>
 +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.</bootnote>
 +
 +<bootnote>
 +Notez que l'expression pour `dashboard` est assez complexe. Elle utilise les [templates Go](https://pkg.go.dev/text/template) 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.
 +</bootnote>
 +
 +### Écrire une nouvelle règle
 +
 +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
 +
 +<bootnote question>
 +Comment lever une alerte quand un disque est trop rempli ?
 +</bootnote>
 +
 +Tout ce qui concerne les disques, la mémoire, le CPU... est collecté grâce à [[technique:adminsys:monitoring:collect:system_metrics|node_exporter]], et associé à [ce dashboard Grafana](https://grafana.picasoft.net/d/VIb73SGWa/server-overview). On trouve le graphe associé aux disques, et on clique sur `Edit`.
 +
 +{{ :technique:adminsys:monitoring:alerting:disk_usage_grafana.png |}}
 +
 +On trouve alors l'inspecteur de requêtes et la métrique associée au graphe : 
 +
 +{{ :technique:adminsys:monitoring:alerting:disk_usage_query.png |}}
 +
 +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%.
 +
 +<bootnote>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.</bootnote>
 +
 +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%
 +```
 +
 +<bootnote question>
 +Où trouver ces informations ?
 +</bootnote>
 +
 +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 :
 +
 +{{ :technique:adminsys:monitoring:alerting:disk_usage_query_inspector.png?600 |}}
 +
 +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](https://prometheus.io/docs/prometheus/latest/querying/api/) 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 :
 +
 +```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 à :
 +
 +```yaml
 +annotations:
 +  summary: Disk 90% full on {{ $labels.instance }}
 +  description: Device {{ $labels.device }} mounted on {{ $labels.mountpoint }} is {{ printf "%.0f" $value }}% full
 +```
 +
 +<bootnote>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.</bootnote>
 +
 +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 :
 +
 +```yaml
 +dashboard: https://grafana.picasoft.net/d/VIb73SGWa/server-overview?var-node={{ $labels.instance }}
 +```
 +
 +<bootnote>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.</bootnote>
 +
 +#### 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](https://www.robustperception.io/target-labels-are-for-life-not-just-for-christmas) : 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 [[technique:adminsys:monitoring:collect:proxmox_metrics|l'exporter Proxmox]], ne contient que quelques labels, par exemple :
 +
 +```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 [[technique:infrastructure:install_proxmox#configuration_du_stockage_proxmox|stockage Proxmox]] celui-ci correspond.
 +
 +<bootnote learn>
 +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](https://prometheus.io/docs/practices/naming/), 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.
 +</bootnote>
 +
 +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.
 +
 +<bootnote warning>
 +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.
 +</bootnote>
 +
 +<bootnote web>
 +Plus d'explications et d'exemples en anglais [ici](https://www.robustperception.io/left-joins-in-promql).
 +</bootnote>