{{indexmenu_n>10}}
## Surveiller les métriques avec vmalert
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).
### 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.
L'article [PromQL for humans](https://timber.io/blog/promql-for-humans/) constitue une bonne introduction à PromQL.
### 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.
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.
`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.
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).
### 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.
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.
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.
### É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
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 à [[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%.
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%
```
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 :
{{ :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
```
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 :
```yaml
dashboard: https://grafana.picasoft.net/d/VIb73SGWa/server-overview?var-node={{ $labels.instance }}
```
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](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.
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.
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.
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.
Plus d'explications et d'exemples en anglais [ici](https://www.robustperception.io/left-joins-in-promql).