Contexte

Il s’avère que j’utilise le trio Promtail, Loki et Garafana pour (respectivement) collecter, centraliser et manipuler les logs de mon infra auto-hébergée. J’utilise cet ensemble d’outils en remplacement de Graylog, qui fonctionne bien mais qui consomme bien trop de ressources dans mon cas d’usage.

Ici, Promtail est installé sur les machines à surveiller et un conteneur fait tourner Loki (conteneur LXC dans Proxmox). Grafana est lui aussi installé sur un conteneur dédié. Les logs remontent bien vers Loki et cette source de donnée est correctement configurée dans Grafana. Cette partie est très rapide à mettre en place et ne présente pas de difficultés particulières.

Je me suis donc attelé à mettre en place deux choses dans Grafana :

  1. les dashboards
  2. Les alertes

Là, le travail est bien plus conséquent dans la mesure tout est à créer. Il est à noter que je ne pars pas totalement de zéro avec Grafana car j’ai déjà un autre serveur Grafana en prod avec des données moins sensibles.

Souhaitant configurer les panels me permettant de surveiller les requêtes externes sur mes serveurs publics, j’ai rapidement choisi de passer par un Bar Gauge. L’objectif ici est d’afficher les adresses IP publiques triées selon leur nombre de requêtes respectives sur la période de temps considérée. Ça semble simple dit comme ça mais non en fait. Je ne l’ai pas compris immédiatement mais le problème que je vais évoquer n’est pas spécifique à une visualisation en particulier.

Principe général de création d’un panel

Avant de poursuivre, une présentation des différentes étapes de création d’un panel s’impose. Sans rentrer dans les détails, voilà ce qu’il faut avoir en tête :

  1. Configuration de la source de données dans Grafana -> il faut donc déclarer la source Loki et vérifier que cette source est accessible depuis Grafana.
  2. Création d’un dashboard -> ce n’est pas impératif mais c’est quand même beaucoup plus simple pour la suite.
  3. Création d’un panel dans le dashboard qui pointe vers la source de donnée.
  4. Configuration de la requête.
  5. Configuration (éventuellement) des transformations sur la requête.
  6. Paramétrage de la visualisation.

Problème rencontré

En fait, je suis presque parvenu à le faire rapidement via la requête suivante :

sum by(ip) (count_over_time({hostname="serveurWeb", app="apache2"} !~ `(109.190.19.143|192.168.|127.0.0.1)` | pattern `<ip> - - <_> "<method> <_> <_>" <status> <_> <_> "<_>" <_>` | ip=~ "(^.+?((?:\\d+\\.){3}\\d+).+$)" [1m]))

Petite description de cette requête :

  1. {hostname="serveurWeb", app="apache2"} -> spécifie la partie des logs dans ma source de données que je veux traiter. À noter que ces labels sont paramétrés dans les conf Promtail.
  2. !~ '(109.190.19.143|192.168.|127.0.0.1)' -> permet de ne pas conserver les logs contenant les enregistrements avec mon adresse IP publique, les adresses IP privées et l’adresse locale.
  3. | pattern '<ip> - - <_> "<method> <_> <_>" <status> <_> <_> "<_>" <_>' -> Loki n’indexe pas le contenu des logs. La détection par pattern permet d’extraire des champs particuliers dès lors que les logs ont une structure connue. C’est le cas pour des logs HTTP. Sur cette partie, c’est l’adresse IP qui m’intéresse.
  4. | ip=~ "(^.+?((?:\\d+\\.){3}\\d+).+$)" -> Cette regex permet de conserver les lignes dont le champs IP contient une IPv4.
  5. count_over_time( ....... \[1m\]) -> compte des différentes valeurs par intervalle de 1 minute.
  6. sum by(ip) ( ....... ) -> fait la somme des différentes valeurs obtenues en les regroupant par adresses IP distinctes

Et, voici ce que ça donne :

Bar Gauge non trié

Comme on peut le voir, les données sont bien là mais pas triées selon les valeurs. En fait, le tri s’effectue selon les adresses IP mais ce n’est pas ce qui m’intéresse.

En faisant des recherches, j’ai bien vu que je n’étais pas le seul à rencontrer ce problème. Cela n’est spécifique à la visualisation retenue et le comportement est identique si l’on veut un affichage dans un tableau. Les données doivent donc être présentées déjà triées mais LogQL ne permet pas de le faire simplement, tel qu’il est par exemple possible de la faire en SQL par un ORDER BY mon_champs par exemple.

Solution retenue

La solution à ce problème est passée pour moi par l’utilisation de Transformations. Je ne décrirai pas ici le raisonnement que j’ai pu suivre mais ce qui m’a permis d’arriver au résultat désiré :

  1. Nettoyage de la requête :
{hostname="serveurWeb", app="apache2"} !~ `(109.190.19.143|192.168.|127.0.0.1)` | pattern `<ip> - - <_> "<method> <_> <_>" <status> <_> <_> "<_>" <_>` | ip=~ "(^.+?((?:\\d+\\.){3}\\d+).+$)"

Requête nettoyée

  1. Extraction des champs du contenu (les champs identifiés par le pattern sont ainsi récupérables pour la suite).
  2. Opération de Reduce sur l’adresse IP (sur chaque intervalle de temps de 1 minute ; voir les options au niveau de la source de données dans le panel).
  3. Opération Group By sur l’adresse IP avec une opération d’addition sur le champ Count généré à l’étape précédente.
  4. Opération de tri sur la valeur.

Transformations - partie 1 : extraction des champs et Reduce Transformations - partie 2 : Group By et opération de tri

Ce qui donne le résultat suivant :

Gauge Bar : résultat final

C’est quand même beaucoup mieux comme ça. La gestion des couleurs n’est pas décrite ici car elle est à paramétrer directement au niveau du Bar Gauge.