Collecte des journaux de Loki

Collecte des journaux de Loki

Chez Badoo, nous surveillons constamment les nouvelles technologies et évaluons s'il faut ou non les utiliser dans notre système. Nous voulons partager une de ces études avec la communauté. Il est dédié à Loki, un système d'agrégation de journaux.

Loki est une solution de stockage et de visualisation des journaux, et cette pile fournit également un système flexible pour les analyser et envoyer des données à Prometheus. En mai, une autre mise à jour a été publiée, qui est activement promue par les créateurs. Nous étions intéressés par ce que Loki peut faire, quelles opportunités il offre et dans quelle mesure il peut agir comme une alternative à ELK, la pile que nous utilisons maintenant.

Qu'est-ce que Loki

Grafana Loki est un ensemble de composants pour un système de journalisation complet. Contrairement à d'autres systèmes similaires, Loki est basé sur l'idée d'indexer uniquement les métadonnées de journal - les étiquettes (comme dans Prometheus), et de compresser les journaux eux-mêmes côte à côte en morceaux séparés.

Page d'accueil, GitHub

Avant d'entrer dans ce que vous pouvez faire avec Loki, je souhaite clarifier ce que l'on entend par "l'idée d'indexer uniquement les métadonnées". Comparons l'approche Loki et l'approche d'indexation dans les solutions traditionnelles, telles qu'Elasticsearch, en utilisant l'exemple d'une ligne du journal nginx :

172.19.0.4 - - [01/Jun/2020:12:05:03 +0000] "GET /purchase?user_id=75146478&item_id=34234 HTTP/1.1" 500 8102 "-" "Stub_Bot/3.0" "0.001"

Les systèmes traditionnels analysent la ligne entière, y compris les champs avec de nombreuses valeurs uniques user_id et item_id, et stockent tout dans de grands index. L'avantage de cette approche est que vous pouvez exécuter rapidement des requêtes complexes, puisque presque toutes les données se trouvent dans l'index. Mais vous devez payer pour cela dans la mesure où l'index devient volumineux, ce qui se traduit par des besoins en mémoire. Par conséquent, la taille de l'index de texte intégral des journaux est comparable à celle des journaux eux-mêmes. Afin d'y effectuer une recherche rapide, l'index doit être chargé en mémoire. Et plus il y a de journaux, plus l'index augmente rapidement et plus il consomme de mémoire.

L'approche Loki nécessite que seules les données nécessaires soient extraites de la chaîne, dont le nombre de valeurs est faible. De cette façon, nous obtenons un petit index et pouvons rechercher les données en les filtrant par heure et par champs indexés, puis en analysant le reste avec des expressions régulières ou des recherches de sous-chaînes. Le processus ne semble pas le plus rapide, mais Loki scinde la requête en plusieurs parties et les exécute en parallèle, traitant une grande quantité de données en peu de temps. Le nombre de partitions et de requêtes parallèles qu'elles contiennent est configurable ; ainsi, la quantité de données pouvant être traitées par unité de temps dépend linéairement de la quantité de ressources fournies.

Ce compromis entre un grand index rapide et un petit index de force brute parallèle permet à Loki de contrôler le coût du système. Il peut être configuré de manière flexible et étendu en fonction de vos besoins.

La pile Loki se compose de trois composants : Promtail, Loki, Grafana. Promtail collecte les journaux, les traite et les envoie à Loki. Loki les garde. Et Grafana peut demander des données à Loki et les montrer. En général, Loki peut être utilisé non seulement pour stocker des journaux et les parcourir. L'ensemble de la pile offre de grandes possibilités de traitement et d'analyse des données entrantes à l'aide de la méthode Prometheus.
Vous trouverez une description du processus d'installation ici.

Recherche de journal

Vous pouvez rechercher les journaux dans une interface spéciale Grafana — Explorer. Les requêtes utilisent le langage LogQL, qui est très similaire au PromQL utilisé par Prometheus. En principe, il peut être considéré comme un grep distribué.

L'interface de recherche ressemble à ceci :

Collecte des journaux de Loki

La requête elle-même se compose de deux parties : sélecteur et filtre. Le sélecteur est une recherche par métadonnées indexées (étiquettes) qui sont attribuées aux journaux, et le filtre est une chaîne de recherche ou une expression régulière qui filtre les enregistrements définis par le sélecteur. Dans l'exemple donné : entre accolades - le sélecteur, tout après - le filtre.

{image_name="nginx.promtail.test"} |= "index"

En raison du fonctionnement de Loki, vous ne pouvez pas faire de requêtes sans sélecteur, mais les étiquettes peuvent être arbitrairement génériques.

Le sélecteur est la valeur-clé de la valeur entre accolades. Vous pouvez combiner des sélecteurs et spécifier différentes conditions de recherche à l'aide des opérateurs =, != ou des expressions régulières :

{instance=~"kafka-[23]",name!="kafka-dev"} 
// Найдёт логи с лейблом instance, имеющие значение kafka-2, kafka-3, и исключит dev 

Un filtre est un texte ou une expression régulière qui filtrera toutes les données reçues par le sélecteur.

Il est possible d'obtenir des graphiques ad hoc basés sur les données reçues en mode métrique. Par exemple, vous pouvez connaître la fréquence d'occurrence dans les journaux nginx d'une entrée contenant la chaîne d'index :

Collecte des journaux de Loki

Une description complète des fonctionnalités peut être trouvée dans la documentation LogQL.

Analyse du journal

Il existe plusieurs façons de collecter les journaux :

  • Avec l'aide de Promtail, un composant standard de la pile pour la collecte des journaux.
  • Directement depuis le conteneur docker en utilisant Pilote de journalisation Loki Docker.
  • Utilisez Fluentd ou Fluent Bit qui peut envoyer des données à Loki. Contrairement à Promtail, ils disposent d'analyseurs prêts à l'emploi pour presque tous les types de journaux et peuvent également gérer les journaux multilignes.

Habituellement, Promtail est utilisé pour l'analyse. Il fait trois choses :

  • Recherche des sources de données.
  • Attachez-y des étiquettes.
  • Envoie des données à Loki.

Actuellement, Promtail peut lire les journaux des fichiers locaux et du journal systemd. Il doit être installé sur chaque machine à partir de laquelle les journaux sont collectés.

Il existe une intégration avec Kubernetes : Promtail découvre automatiquement l'état du cluster via l'API REST de Kubernetes et collecte les journaux d'un nœud, d'un service ou d'un pod, en publiant immédiatement des étiquettes basées sur les métadonnées de Kubernetes (nom du pod, nom du fichier, etc.).

Vous pouvez également accrocher des étiquettes basées sur les données du journal à l'aide de Pipeline. Pipeline Promtail peut se composer de quatre types d'étapes. Plus de détails - dans documents officiels, je noterai immédiatement certaines des nuances.

  1. Étapes d'analyse. C'est l'étape de RegEx et JSON. À ce stade, nous extrayons les données des journaux dans la carte dite extraite. Vous pouvez extraire de JSON en copiant simplement les champs dont nous avons besoin dans la carte extraite, ou via des expressions régulières (RegEx), où les groupes nommés sont "mappés" dans la carte extraite. La carte extraite est un stockage clé-valeur, où clé est le nom du champ et valeur est sa valeur à partir des journaux.
  2. Étapes de transformation. Cette étape comporte deux options : transformer, où nous définissons les règles de transformation, et source - la source de données pour la transformation à partir de la carte extraite. S'il n'y a pas un tel champ dans la carte extraite, alors il sera créé. Ainsi, il est possible de créer des étiquettes qui ne sont pas basées sur la carte extraite. A ce stade, nous pouvons manipuler les données de la carte extraite à l'aide d'un outil assez puissant modèle golang. De plus, il faut rappeler que la carte extraite est entièrement chargée lors du parsing, ce qui permet par exemple de vérifier la valeur qu'elle contient : « {{if .tag}tag value exists{end}} ». Le modèle prend en charge les conditions, les boucles et certaines fonctions de chaîne telles que Remplacer et Trim.
  3. Étapes d'action. À ce stade, vous pouvez faire quelque chose avec l'extrait :
    • Créez une étiquette à partir des données extraites, qui seront indexées par Loki.
    • Modifiez ou définissez l'heure de l'événement à partir du journal.
    • Modifiez les données (texte du journal) qui iront à Loki.
    • Créer des métriques.
  4. Étapes de filtrage. L'étape de correspondance, où nous pouvons soit envoyer les enregistrements dont nous n'avons pas besoin à /dev/null, soit les envoyer pour un traitement ultérieur.

En utilisant l'exemple du traitement des journaux nginx ordinaires, je montrerai comment vous pouvez analyser les journaux à l'aide de Promtail.

Pour le test, prenons une image nginx modifiée jwilder/nginx-proxy:alpine en tant que nginx-proxy et un petit démon qui peut s'interroger via HTTP. Le démon a plusieurs points de terminaison, auxquels il peut donner des réponses de différentes tailles, avec différents statuts HTTP et avec différents délais.

Nous collecterons les journaux des conteneurs Docker, qui se trouvent le long du chemin /var/lib/docker/containers/ / -json.log

Dans docker-compose.yml, nous configurons Promtail et spécifions le chemin d'accès à la configuration :

promtail:
  image: grafana/promtail:1.4.1
 // ...
 volumes:
   - /var/lib/docker/containers:/var/lib/docker/containers:ro
   - promtail-data:/var/lib/promtail/positions
   - ${PWD}/promtail/docker.yml:/etc/promtail/promtail.yml
 command:
   - '-config.file=/etc/promtail/promtail.yml'
 // ...

Ajoutez le chemin vers les journaux à promtail.yml (il y a une option "docker" dans la configuration qui fait la même chose en une seule ligne, mais ce ne serait pas si évident) :

scrape_configs:
 - job_name: containers

   static_configs:
       labels:
         job: containerlogs
         __path__: /var/lib/docker/containers/*/*log  # for linux only

Lorsque cette configuration est activée, Loki recevra les journaux de tous les conteneurs. Pour éviter cela, nous modifions les paramètres du nginx de test dans docker-compose.yml - ajoutez la journalisation au champ tag :

proxy:
 image: nginx.test.v3
//…
 logging:
   driver: "json-file"
   options:
     tag: "{{.ImageName}}|{{.Name}}"

Modifiez promtail.yml et configurez Pipeline. Les journaux sont les suivants :

{"log":"u001b[0;33;1mnginx.1    | u001b[0mnginx.test 172.28.0.3 - - [13/Jun/2020:23:25:50 +0000] "GET /api/index HTTP/1.1" 200 0 "-" "Stub_Bot/0.1" "0.096"n","stream":"stdout","attrs":{"tag":"nginx.promtail.test|proxy.prober"},"time":"2020-06-13T23:25:50.66740443Z"}
{"log":"u001b[0;33;1mnginx.1    | u001b[0mnginx.test 172.28.0.3 - - [13/Jun/2020:23:25:50 +0000] "GET /200 HTTP/1.1" 200 0 "-" "Stub_Bot/0.1" "0.000"n","stream":"stdout","attrs":{"tag":"nginx.promtail.test|proxy.prober"},"time":"2020-06-13T23:25:50.702925272Z"}

étapes du pipeline :

 - json:
     expressions:
       stream: stream
       attrs: attrs
       tag: attrs.tag

Nous extrayons les champs stream, attrs, attrs.tag (le cas échéant) du JSON entrant et les plaçons dans la carte extraite.

 - regex:
     expression: ^(?P<image_name>([^|]+))|(?P<container_name>([^|]+))$
     source: "tag"

S'il était possible de mettre le champ de balise dans la carte extraite, alors en utilisant l'expression rationnelle, nous extrayons les noms de l'image et du conteneur.

 - labels:
     image_name:
     container_name:

Nous attribuons des étiquettes. Si les clés image_name et container_name sont trouvées dans les données extraites, alors leurs valeurs seront affectées aux étiquettes appropriées.

 - match:
     selector: '{job="docker",container_name="",image_name=""}'
     action: drop

Nous supprimons tous les journaux qui n'ont pas d'étiquettes image_name et container_name définies.

  - match:
     selector: '{image_name="nginx.promtail.test"}'
     stages:
       - json:
           expressions:
             row: log

Pour tous les journaux dont image_name est égal à nginx.promtail.test, extrayez le champ de journal du journal source et placez-le dans la carte extraite avec la clé de ligne.

  - regex:
         # suppress forego colors
         expression: .+nginx.+|.+[0m(?P<virtual_host>[a-z_.-]+) +(?P<nginxlog>.+)
         source: logrow

Nous effaçons la chaîne d'entrée avec des expressions régulières et extrayons l'hôte virtuel nginx et la ligne de journal nginx.

     - regex:
         source: nginxlog
         expression: ^(?P<ip>[w.]+) - (?P<user>[^ ]*) [(?P<timestamp>[^ ]+).*] "(?P<method>[^ ]*) (?P<request_url>[^ ]*) (?P<request_http_protocol>[^ ]*)" (?P<status>[d]+) (?P<bytes_out>[d]+) "(?P<http_referer>[^"]*)" "(?P<user_agent>[^"]*)"( "(?P<response_time>[d.]+)")?

Analyser le journal nginx avec des expressions régulières.

    - regex:
           source: request_url
           expression: ^.+.(?P<static_type>jpg|jpeg|gif|png|ico|css|zip|tgz|gz|rar|bz2|pdf|txt|tar|wav|bmp|rtf|js|flv|swf|html|htm)$
     - regex:
           source: request_url
           expression: ^/photo/(?P<photo>[^/?.]+).*$
       - regex:
           source: request_url
           expression: ^/api/(?P<api_request>[^/?.]+).*$

Analyser request_url. Avec l'aide de regexp, nous déterminons le but de la requête : vers des statiques, vers des photos, vers des API et définissons la clé correspondante dans la carte extraite.

       - template:
           source: request_type
           template: "{{if .photo}}photo{{else if .static_type}}static{{else if .api_request}}api{{else}}other{{end}}"

À l'aide d'opérateurs conditionnels dans Template, nous vérifions les champs installés dans la carte extraite et définissons les valeurs requises pour le champ request_type : photo, static, API. Attribuez un autre en cas d'échec. Maintenant, request_type contient le type de requête.

       - labels:
           api_request:
           virtual_host:
           request_type:
           status:

Nous définissons les étiquettes api_request, virtual_host, request_type et status (statut HTTP) en fonction de ce que nous avons réussi à mettre dans la carte extraite.

       - output:
           source: nginx_log_row

Modifier la sortie. Maintenant, le journal nginx nettoyé de la carte extraite va à Loki.

Collecte des journaux de Loki

Après avoir exécuté la configuration ci-dessus, vous pouvez voir que chaque entrée est étiquetée en fonction des données du journal.

Gardez à l'esprit que l'extraction d'étiquettes avec un grand nombre de valeurs (cardinalité) peut considérablement ralentir Loki. Autrement dit, vous ne devez pas mettre dans l'index, par exemple, user_id. En savoir plus à ce sujet dans l'articleComment les étiquettes dans Loki peuvent rendre les requêtes de journal plus rapides et plus faciles". Mais cela ne signifie pas que vous ne pouvez pas rechercher par user_id sans index. Il est nécessaire d'utiliser des filtres lors de la recherche ("grab" selon les données), et l'index agit ici comme un identifiant de flux.

Visualisation des journaux

Collecte des journaux de Loki

Loki peut servir de source de données pour les graphiques Grafana à l'aide de LogQL. Les fonctionnalités suivantes sont prises en charge :

  • taux - nombre d'enregistrements par seconde ;
  • compter dans le temps - le nombre d'enregistrements dans la plage donnée.

Il existe également des fonctions d'agrégation Sum, Avg et autres. Vous pouvez créer des graphiques assez complexes, par exemple un graphique du nombre d'erreurs HTTP :

Collecte des journaux de Loki

La source de données par défaut de Loki est un peu moins fonctionnelle que la source de données Prometheus (par exemple, vous ne pouvez pas modifier la légende), mais Loki peut être connecté en tant que source de type Prometheus. Je ne sais pas s'il s'agit d'un comportement documenté, mais à en juger par la réponse des développeurs "Comment configurer Loki comme source de données Prometheus ? · Numéro #1222 · grafana/loki”, par exemple, c'est parfaitement légal et Loki est entièrement compatible avec PromQL.

Ajoutez Loki comme source de données avec le type Prometheus et ajoutez l'URL /loki :

Collecte des journaux de Loki

Et vous pouvez créer des graphiques, comme si nous travaillions avec des métriques de Prometheus :

Collecte des journaux de Loki

Je pense que l'écart de fonctionnalité est temporaire et que les développeurs le corrigeront à l'avenir.

Collecte des journaux de Loki

Métrique

Loki offre la possibilité d'extraire des métriques numériques des journaux et de les envoyer à Prometheus. Par exemple, le journal nginx contient le nombre d'octets par réponse, ainsi que, avec une certaine modification du format de journal standard, le temps en secondes qu'il a fallu pour répondre. Ces données peuvent être extraites et envoyées à Prometheus.

Ajoutez une autre section à promtail.yml :

- match:
   selector: '{request_type="api"}'
   stages:
     - metrics:
         http_nginx_response_time:
           type: Histogram
           description: "response time ms"
           source: response_time
           config:
             buckets: [0.010,0.050,0.100,0.200,0.500,1.0]
- match:
   selector: '{request_type=~"static|photo"}'
   stages:
     - metrics:
         http_nginx_response_bytes_sum:
           type: Counter
           description: "response bytes sum"
           source: bytes_out
           config:
             action: add
         http_nginx_response_bytes_count:
           type: Counter
           description: "response bytes count"
           source: bytes_out
           config:
             action: inc

L'option vous permet de définir et de mettre à jour des métriques basées sur les données de la carte extraite. Ces métriques ne sont pas envoyées à Loki - elles apparaissent dans le point de terminaison Promtail /metrics. Prometheus doit être configuré pour recevoir les données de cette étape. Dans l'exemple ci-dessus, pour request_type="api", nous collectons une métrique d'histogramme. Avec ce type de mesures, il est pratique d'obtenir des centiles. Pour les statiques et les photos, nous collectons la somme des octets et le nombre de lignes dans lesquelles nous avons reçu des octets pour calculer la moyenne.

En savoir plus sur les métriques ici.

Ouvrez un port sur Promtail :

promtail:
     image: grafana/promtail:1.4.1
     container_name: monitoring.promtail
     expose:
       - 9080
     ports:
       - "9080:9080"

Nous nous assurons que les métriques avec le préfixe promtail_custom sont bien apparues :

Collecte des journaux de Loki

Mise en place de Prometheus. Ajouter une offre d'emploi :

- job_name: 'promtail'
 scrape_interval: 10s
 static_configs:
   - targets: ['promtail:9080']

Et tracez un graphique :

Collecte des journaux de Loki

De cette façon, vous pouvez découvrir, par exemple, les quatre requêtes les plus lentes. Vous pouvez également configurer la surveillance de ces métriques.

Mise à l'échelle

Loki peut être à la fois en mode binaire unique et fragmenté (mode évolutif horizontalement). Dans le second cas, il peut enregistrer des données dans le cloud, et les morceaux et l'index sont stockés séparément. Dans la version 1.5, la possibilité de stocker en un seul endroit est implémentée, mais il n'est pas encore recommandé de l'utiliser en production.

Collecte des journaux de Loki

Les blocs peuvent être stockés dans un stockage compatible S3, et des bases de données évolutives horizontalement peuvent être utilisées pour stocker des index : Cassandra, BigTable ou DynamoDB. D'autres parties de Loki - les distributeurs (pour l'écriture) et Querier (pour les requêtes) - sont sans état et évoluent également horizontalement.

Lors de la conférence DevOpsDays Vancouver 2019, l'un des participants Callum Styan a annoncé qu'avec Loki son projet a des pétaoctets de logs avec un indice de moins de 1% de la taille totale : "Comment Loki met en corrélation les métriques et les journaux - et vous fait économiser de l'argent ».

Comparaison de Loki et ELK

Taille de l'index

Pour tester la taille d'index résultante, j'ai pris les journaux du conteneur nginx pour lequel le Pipeline ci-dessus a été configuré. Le fichier journal contenait 406 624 lignes avec un volume total de 109 Mo. Les journaux ont été générés en une heure, environ 100 enregistrements par seconde.

Un exemple de deux lignes du journal :

Collecte des journaux de Loki

Une fois indexé par ELK, cela a donné une taille d'index de 30,3 Mo :

Collecte des journaux de Loki

Dans le cas de Loki, cela a donné environ 128 Ko d'index et environ 3,8 Mo de données en morceaux. Il convient de noter que le journal a été généré artificiellement et ne comportait pas une grande variété de données. Un simple gzip sur le journal Docker JSON d'origine avec des données a donné une compression de 95,4 %, et étant donné que seul le journal nginx nettoyé a été envoyé à Loki lui-même, la compression à 4 Mo est compréhensible. Le nombre total de valeurs uniques pour les étiquettes Loki était de 35, ce qui explique la petite taille de l'index. Pour ELK, le journal a également été effacé. Ainsi, Loki a compressé les données d'origine à 96 % et ELK à 70 %.

Consommation mémoire

Collecte des journaux de Loki

Si nous comparons l'ensemble de la pile de Prometheus et ELK, alors Loki "mange" plusieurs fois moins. Il est clair que le service Go consomme moins que le service Java, et comparer la taille de la JVM Heap Elasticsearch et la mémoire allouée pour Loki est incorrect, mais néanmoins, il convient de noter que Loki utilise beaucoup moins de mémoire. Son avantage CPU n'est pas si évident, mais il est également présent.

vitesse

Loki "dévore" les journaux plus rapidement. La vitesse dépend de nombreux facteurs - quel type de journaux, à quel point nous les analysons, réseau, disque, etc. - mais elle est nettement supérieure à celle d'ELK (dans mon test - environ deux fois). Cela s'explique par le fait que Loki met beaucoup moins de données dans l'index et, par conséquent, passe moins de temps sur l'indexation. Dans ce cas, la situation est inversée avec la vitesse de recherche : Loki ralentit sensiblement sur les données supérieures à quelques gigaoctets, tandis que pour ELK, la vitesse de recherche ne dépend pas de la taille des données.

Recherche de journal

Loki est nettement inférieur à ELK en termes de capacités de recherche de journaux. Grep avec des expressions régulières est une chose forte, mais elle est inférieure à une base de données pour adultes. Le manque de requêtes de plage, l'agrégation uniquement par étiquettes, l'impossibilité de rechercher sans étiquettes - tout cela nous limite dans la recherche d'informations d'intérêt dans Loki. Cela n'implique pas que rien ne peut être trouvé à l'aide de Loki, mais cela définit le flux de travail avec les journaux, lorsque vous trouvez d'abord un problème sur les graphiques Prometheus, puis recherchez ce qui s'est passé dans les journaux à l'aide de ces étiquettes.

Interface

Tout d'abord, c'est magnifique (désolé, je n'ai pas pu résister). Grafana a une belle interface, mais Kibana est beaucoup plus fonctionnel.

Avantages et inconvénients de Loki

Parmi les avantages, on peut noter que Loki s'intègre à Prometheus, respectivement, nous obtenons des métriques et des alertes prêtes à l'emploi. Il est pratique de collecter des journaux et de les stocker avec des pods Kubernetes, car il dispose d'une découverte de service héritée de Prometheus et attache automatiquement des étiquettes.

Parmi les inconvénients - mauvaise documentation. Certaines choses, telles que les fonctionnalités et les capacités de Promtail, j'ai découvert seulement dans le processus d'étude du code, l'avantage de l'open-source. Un autre inconvénient est la faiblesse des capacités d'analyse. Par exemple, Loki ne peut pas analyser les journaux multilignes. De plus, les inconvénients incluent le fait que Loki est une technologie relativement jeune (la version 1.0 était en novembre 2019).

Conclusion

Loki est une technologie 100% intéressante qui convient aux petits et moyens projets, permettant de résoudre de nombreux problèmes d'agrégation de logs, de recherche de logs, de suivi et d'analyse des logs.

Nous n'utilisons pas Loki chez Badoo, car nous avons une pile ELK qui nous convient et qui a été envahie par diverses solutions personnalisées au fil des ans. Pour nous, la pierre d'achoppement est la recherche dans les logs. Avec près de 100 Go de logs par jour, il est important pour nous de pouvoir tout trouver et un peu plus et le faire rapidement. Pour la cartographie et la surveillance, nous utilisons d'autres solutions adaptées à nos besoins et intégrées les unes aux autres. La pile Loki a des avantages tangibles, mais elle ne nous donnera pas plus que ce que nous avons, et ses avantages ne compenseront pas exactement le coût de la migration.

Et bien qu'après des recherches, il soit devenu clair que nous ne pouvons pas utiliser Loki, nous espérons que cet article vous aidera à choisir.

Le référentiel avec le code utilisé dans l'article se trouve ici.

Source: habr.com

Ajouter un commentaire