Application pratique d'ELK. Configuration de logstash

introduction

Lors du déploiement d'un autre système, nous avons été confrontés à la nécessité de traiter un grand nombre de journaux divers. ELK a été choisi comme instrument. Cet article parlera de notre expérience dans la mise en place de cette pile.

Nous ne nous fixons pas pour objectif de décrire toutes ses capacités, mais nous voulons nous concentrer sur la résolution de problèmes pratiques. Cela est dû au fait qu'avec une documentation suffisamment importante et des images toutes faites, il y a beaucoup d'embûches, du moins nous les avons trouvées.

Nous avons déployé la pile via docker-compose. De plus, nous avions un docker-compose.yml bien écrit qui nous permettait de remonter la pile sans presque aucun problème. Et il nous semblait que la victoire était déjà proche, maintenant nous allons la tordre un peu pour l'adapter à nos besoins et c'est tout.

Malheureusement, une tentative de réglage du système pour recevoir et traiter les journaux de notre application n'a pas réussi dès le départ. Par conséquent, nous avons décidé qu'il valait la peine d'étudier chaque composant séparément, puis de revenir à leurs connexions.

Commençons donc par logstash.

Environnement, déploiement, exécution de Logstash dans un conteneur

Pour le déploiement, nous utilisons docker-compose, les expériences décrites ici ont été réalisées sur MacOS et Ubuntu 18.0.4.

L'image logstash que nous avions dans notre docker-compose.yml d'origine est docker.elastic.co/logstash/logstash:6.3.2

Nous l'utiliserons pour des expériences.

Pour exécuter logstash, nous avons écrit un docker-compose.yml séparé. Bien sûr, il était possible de lancer l'image depuis la ligne de commande, mais après tout, nous avons résolu une tâche spécifique, où tout de docker-compose est lancé pour nous.

En bref sur les fichiers de configuration

Comme il ressort de la description, logstash peut être exécuté comme pour un canal, dans ce cas, il doit transférer le fichier *.conf ou pour plusieurs canaux, auquel cas il doit transférer le fichier pipelines.yml, qui, à son tour , fera référence aux fichiers .conf pour chaque canal.
Nous avons pris le deuxième chemin. Il nous a semblé plus polyvalent et évolutif. Par conséquent, nous avons créé pipelines.yml et créé un répertoire de pipelines dans lequel nous placerons des fichiers .conf pour chaque canal.

À l'intérieur du conteneur, il y a un autre fichier de configuration - logstash.yml. On n'y touche pas, on l'utilise tel quel.

Donc, notre structure de répertoire est :

Application pratique d'ELK. Configuration de logstash

Pour le moment, nous supposons qu'il s'agit de tcp sur le port 5046 pour recevoir les données d'entrée, et nous utiliserons stdout pour la sortie.

Voici une configuration si simple pour la première exécution. Parce que la tâche initiale est de lancer.

Nous avons donc ce docker-compose.yml

version: '3'

networks:
  elk:

volumes:
  elasticsearch:
    driver: local

services:

  logstash:
    container_name: logstash_one_channel
    image: docker.elastic.co/logstash/logstash:6.3.2
    networks:
      	- elk
    ports:
      	- 5046:5046
    volumes:
      	- ./config/pipelines.yml:/usr/share/logstash/config/pipelines.yml:ro
	- ./config/pipelines:/usr/share/logstash/config/pipelines:ro

Que voyons-nous ici ?

  1. Les réseaux et les volumes ont été tirés du docker-compose.yml d'origine (celui où toute la pile est lancée) et je pense qu'ils n'affectent pas beaucoup l'image globale ici.
  2. Nous créons un logstash de service (services) à partir de l'image docker.elastic.co/logstash/logstash:6.3.2 et lui donnons le nom logstash_one_channel.
  3. Nous transférons le port 5046 à l'intérieur du conteneur, vers le même port interne.
  4. Nous mappons notre fichier de configuration de canal ./config/pipelines.yml au fichier /usr/share/logstash/config/pipelines.yml à l'intérieur du conteneur, où logstash le récupérera et le rendra en lecture seule, juste pour être sur le côté sécuritaire.
  5. Nous mappons le répertoire ./config/pipelines, où se trouvent les fichiers de configuration du canal, au répertoire /usr/share/logstash/config/pipelines et le rendons également en lecture seule.

Application pratique d'ELK. Configuration de logstash

fichier tuyauterie.yml

- pipeline.id: HABR
  pipeline.workers: 1
  pipeline.batch.size: 1
  path.config: "./config/pipelines/habr_pipeline.conf"

Il décrit un canal avec l'identifiant HABR et le chemin vers son fichier de configuration.

Et enfin le fichier "./config/pipelines/habr_pipeline.conf"

input {
  tcp {
    port => "5046"
   }
  }
filter {
  mutate {
    add_field => [ "habra_field", "Hello Habr" ]
    }
  }
output {
  stdout {
      
    }
  }

Nous n'entrerons pas dans sa description pour l'instant, nous essayons de lancer :

docker-compose up

Que voit-on?

Le conteneur a commencé. Nous pouvons vérifier son travail:

echo '13123123123123123123123213123213' | nc localhost 5046

Et nous voyons la réponse dans la console du conteneur :

Application pratique d'ELK. Configuration de logstash

Mais en même temps, on voit aussi :

logstash_one_channel | [2019-04-29T11:28:59,790][ERROR][logstash.licensechecker.licensereader] Impossible de récupérer les informations de licence du serveur de licence {:message=>"Elasticsearch Unreachable : [http://elasticsearch:9200/][Manticore ::ResolutionFailure]elasticsearch", ...

logstash_one_channel | [2019-04-29T11:28:59,894][INFO ][logstash.pipeline ] Le pipeline a démarré avec succès {:pipeline_id=>".monitoring-logstash", :thread=>"# »}

logstash_one_channel | [2019-04-29T11:28:59,988][INFO ][logstash.agent ] Pipelines exécutant {:count=>2, :running_pipelines=>[:HABR, :".monitoring-logstash"], :non_running_pipelines=>[ ]}
logstash_one_channel | [2019-04-29T11:29:00,015][ERROR][logstash.inputs.metrics ] X-Pack est installé sur Logstash mais pas sur Elasticsearch. Veuillez installer X-Pack sur Elasticsearch pour utiliser la fonction de surveillance. D'autres fonctionnalités peuvent être disponibles.
logstash_one_channel | [2019-04-29T11:29:00,526][INFO ][logstash.agent ] Point de terminaison de l'API Logstash {:port=>9600} démarré avec succès
logstash_one_channel | [2019-04-29T11:29:04,478][INFO ][logstash.outputs.elasticsearch] Exécution d'un bilan de santé pour voir si une connexion Elasticsearch fonctionne {:healthcheck_url=>http://elasticsearch:9200/, :path=> "/"}
logstash_one_channel | [2019-04-29T11:29:04,487][WARN ][logstash.outputs.elasticsearch] Tentative de ressusciter la connexion à une instance ES morte, mais une erreur s'est produite. {:url=>"elasticsearch:9200/", :error_type=>LogStash::Outputs::ElasticSearch::HttpClient::Pool::HostUnreachableError, :error=>"Elasticsearch inaccessible : [http://elasticsearch:9200/][Manticore::ResolutionFailure] recherche élastique"}
logstash_one_channel | [2019-04-29T11:29:04,704][INFO ][logstash.licensechecker.licensereader] Exécution d'un bilan de santé pour voir si une connexion Elasticsearch fonctionne {:healthcheck_url=>http://elasticsearch:9200/, :path=> "/"}
logstash_one_channel | [2019-04-29T11:29:04,710][WARN ][logstash.licensechecker.licensereader] Tentative de ressusciter la connexion à une instance ES morte, mais une erreur s'est produite. {:url=>"elasticsearch:9200/", :error_type=>LogStash::Outputs::ElasticSearch::HttpClient::Pool::HostUnreachableError, :error=>"Elasticsearch inaccessible : [http://elasticsearch:9200/][Manticore::ResolutionFailure] recherche élastique"}

Et notre journal rampe tout le temps.

Ici, j'ai surligné en vert le message indiquant que le pipeline a démarré avec succès, en rouge le message d'erreur et en jaune le message concernant la tentative de contact elasticsearch: 9200.
Cela est dû au fait que dans le logstash.conf inclus dans l'image, il y a une vérification de la disponibilité d'elasticsearch. Après tout, logstash suppose qu'il fonctionne dans le cadre de la pile Elk, et nous l'avons séparé.

Vous pouvez travailler, mais ce n'est pas pratique.

La solution est de désactiver cette vérification via la variable d'environnement XPACK_MONITORING_ENABLED.

Modifions docker-compose.yml et réexécutons-le :

version: '3'

networks:
  elk:

volumes:
  elasticsearch:
    driver: local

services:

  logstash:
    container_name: logstash_one_channel
    image: docker.elastic.co/logstash/logstash:6.3.2
    networks:
      - elk
    environment:
      XPACK_MONITORING_ENABLED: "false"
    ports:
      - 5046:5046
   volumes:
      - ./config/pipelines.yml:/usr/share/logstash/config/pipelines.yml:ro
      - ./config/pipelines:/usr/share/logstash/config/pipelines:ro

Maintenant, tout va bien. Le conteneur est prêt pour les expériences.

On peut retaper dans la console adjacente :

echo '13123123123123123123123213123213' | nc localhost 5046

Et voir :

logstash_one_channel | {
logstash_one_channel |         "message" => "13123123123123123123123213123213",
logstash_one_channel |      "@timestamp" => 2019-04-29T11:43:44.582Z,
logstash_one_channel |        "@version" => "1",
logstash_one_channel |     "habra_field" => "Hello Habr",
logstash_one_channel |            "host" => "gateway",
logstash_one_channel |            "port" => 49418
logstash_one_channel | }

Travaillez sur un seul canal

Alors nous avons commencé. Vous pouvez désormais prendre le temps de configurer directement logstash. Ne touchons pas au fichier pipelines.yml pour l'instant, voyons ce que nous pouvons obtenir en travaillant avec un seul canal.

Je dois dire que le principe général de travail avec le fichier de configuration des canaux est bien décrit dans le manuel officiel, ici ici
Si vous voulez lire en russe, alors nous avons utilisé celui-ci un article(mais la syntaxe de la requête est ancienne, vous devez en tenir compte).

Partons séquentiellement à partir de la section Input. Nous avons déjà vu le travail sur tcp. Quoi d'autre peut être intéressant ici?

Tester les messages à l'aide du rythme cardiaque

Il existe une possibilité intéressante de générer des messages de test automatiques.
Pour ce faire, vous devez inclure le plugin heartbean dans la section d'entrée.

input {
  heartbeat {
    message => "HeartBeat!"
   }
  } 

On l'allume, on commence à recevoir une fois par minute

logstash_one_channel | {
logstash_one_channel |      "@timestamp" => 2019-04-29T13:52:04.567Z,
logstash_one_channel |     "habra_field" => "Hello Habr",
logstash_one_channel |         "message" => "HeartBeat!",
logstash_one_channel |        "@version" => "1",
logstash_one_channel |            "host" => "a0667e5c57ec"
logstash_one_channel | }

Nous voulons recevoir plus souvent, nous devons ajouter le paramètre d'intervalle.
C'est ainsi que nous recevrons un message toutes les 10 secondes.

input {
  heartbeat {
    message => "HeartBeat!"
    interval => 10
   }
  }

Obtenir des données d'un fichier

Nous avons également décidé de regarder le mode fichier. Si cela fonctionne correctement avec le fichier, il est possible qu'aucun agent ne soit requis, du moins pour une utilisation locale.

Selon la description, le mode de fonctionnement devrait être similaire à tail -f, c'est-à-dire lit les nouvelles lignes ou, éventuellement, lit le fichier entier.

Donc, ce que nous voulons obtenir :

  1. Nous voulons recevoir des lignes qui sont ajoutées à un fichier journal.
  2. Nous voulons recevoir des données écrites dans plusieurs fichiers journaux, tout en étant capable de séparer ce qui a été reçu d'où.
  3. Nous voulons nous assurer que lorsque logstash sera redémarré, il ne recevra plus ces données.
  4. Nous voulons vérifier que si logstash est désactivé et que les données continuent d'être écrites dans des fichiers, alors lorsque nous l'exécuterons, nous recevrons ces données.

Pour mener l'expérience, ajoutons une ligne de plus à docker-compose.yml, en ouvrant le répertoire où nous mettons les fichiers.

version: '3'

networks:
  elk:

volumes:
  elasticsearch:
    driver: local

services:

  logstash:
    container_name: logstash_one_channel
    image: docker.elastic.co/logstash/logstash:6.3.2
    networks:
      - elk
    environment:
      XPACK_MONITORING_ENABLED: "false"
    ports:
      - 5046:5046
   volumes:
      - ./config/pipelines.yml:/usr/share/logstash/config/pipelines.yml:ro
      - ./config/pipelines:/usr/share/logstash/config/pipelines:ro
      - ./logs:/usr/share/logstash/input

Et changez la section d'entrée dans habr_pipeline.conf

input {
  file {
    path => "/usr/share/logstash/input/*.log"
   }
  }

Nous commençons:

docker-compose up

Pour créer et écrire des fichiers journaux, nous utiliserons la commande :


echo '1' >> logs/number1.log

{
logstash_one_channel |            "host" => "ac2d4e3ef70f",
logstash_one_channel |     "habra_field" => "Hello Habr",
logstash_one_channel |      "@timestamp" => 2019-04-29T14:28:53.876Z,
logstash_one_channel |        "@version" => "1",
logstash_one_channel |         "message" => "1",
logstash_one_channel |            "path" => "/usr/share/logstash/input/number1.log"
logstash_one_channel | }

Ouais, ça marche !

En même temps, nous voyons que nous avons automatiquement ajouté le champ path. Ainsi, à l'avenir, nous pourrons filtrer les enregistrements en fonction de celui-ci.

Essayons encore:

echo '2' >> logs/number1.log

{
logstash_one_channel |            "host" => "ac2d4e3ef70f",
logstash_one_channel |     "habra_field" => "Hello Habr",
logstash_one_channel |      "@timestamp" => 2019-04-29T14:28:59.906Z,
logstash_one_channel |        "@version" => "1",
logstash_one_channel |         "message" => "2",
logstash_one_channel |            "path" => "/usr/share/logstash/input/number1.log"
logstash_one_channel | }

Et maintenant dans un autre fichier :

 echo '1' >> logs/number2.log

{
logstash_one_channel |            "host" => "ac2d4e3ef70f",
logstash_one_channel |     "habra_field" => "Hello Habr",
logstash_one_channel |      "@timestamp" => 2019-04-29T14:29:26.061Z,
logstash_one_channel |        "@version" => "1",
logstash_one_channel |         "message" => "1",
logstash_one_channel |            "path" => "/usr/share/logstash/input/number2.log"
logstash_one_channel | }

Super! Le fichier a été récupéré, le chemin a été correctement spécifié, tout va bien.

Arrêtez logstash et redémarrez. Attendons. Silence. Ceux. Nous ne recevons plus ces enregistrements.

Et maintenant l'expérience la plus audacieuse.

Nous mettons logstash et exécutons :

echo '3' >> logs/number2.log
echo '4' >> logs/number1.log

Exécutez à nouveau logstash et voyez :

logstash_one_channel | {
logstash_one_channel |            "host" => "ac2d4e3ef70f",
logstash_one_channel |     "habra_field" => "Hello Habr",
logstash_one_channel |         "message" => "3",
logstash_one_channel |        "@version" => "1",
logstash_one_channel |            "path" => "/usr/share/logstash/input/number2.log",
logstash_one_channel |      "@timestamp" => 2019-04-29T14:48:50.589Z
logstash_one_channel | }
logstash_one_channel | {
logstash_one_channel |            "host" => "ac2d4e3ef70f",
logstash_one_channel |     "habra_field" => "Hello Habr",
logstash_one_channel |         "message" => "4",
logstash_one_channel |        "@version" => "1",
logstash_one_channel |            "path" => "/usr/share/logstash/input/number1.log",
logstash_one_channel |      "@timestamp" => 2019-04-29T14:48:50.856Z
logstash_one_channel | }

Hourra ! Tout ramassé.

Mais, il est nécessaire de mettre en garde sur ce qui suit. Si le conteneur logstash est supprimé (docker stop logstash_one_channel && docker rm logstash_one_channel), rien ne sera récupéré. La position du fichier jusqu'à laquelle il a été lu était stockée à l'intérieur du conteneur. Si vous partez de zéro, il n'acceptera que les nouvelles lignes.

Lecture de fichiers existants

Disons que nous exécutons logstash pour la première fois, mais nous avons déjà des journaux et nous aimerions les traiter.
Si nous exécutons logstash avec la section d'entrée que nous avons utilisée ci-dessus, nous n'obtiendrons rien. Seules les nouvelles lignes seront traitées par logstash.

Pour extraire des lignes de fichiers existants, ajoutez une ligne supplémentaire à la section d'entrée :

input {
  file {
    start_position => "beginning"
    path => "/usr/share/logstash/input/*.log"
   }
  }

De plus, il y a une nuance, cela n'affecte que les nouveaux fichiers que logstash n'a pas encore vus. Pour les mêmes fichiers qui étaient déjà dans le champ de vision de logstash, il s'est déjà souvenu de leur taille et n'y prendra désormais que de nouveaux enregistrements.

Arrêtons-nous là-dessus en étudiant la section d'entrée. Il y a beaucoup plus d'options, mais pour l'instant, nous en avons assez pour d'autres expériences.

Routage et transformation des données

Essayons de résoudre le problème suivant, disons que nous avons des messages d'un canal, certains d'entre eux sont informatifs et d'autres sont des messages d'erreur. Ils diffèrent par l'étiquette. Certains sont INFO, d'autres sont ERROR.

Nous devons les séparer à la sortie. Ceux. Nous écrivons des messages d'information dans un canal et des messages d'erreur dans un autre.

Pour ce faire, passez de la section d'entrée au filtre et à la sortie.

À l'aide de la section de filtre, nous analyserons le message entrant, en obtenant un hachage (paires clé-valeur), avec lequel nous pouvons déjà travailler, c'est-à-dire analyser selon les conditions. Et dans la section de sortie, nous sélectionnerons des messages et enverrons chacun à son propre canal.

Analyser un message avec grok

Afin d'analyser les lignes de texte et d'en obtenir un ensemble de champs, il existe un plugin spécial dans la section filtre - grok.

Sans me fixer l'objectif d'en donner ici une description détaillée (pour cela je renvoie à documents officiels), je vais donner mon exemple simple.

Pour ce faire, vous devez décider du format des lignes d'entrée. je les ai comme ça:

1 message INFO1
2 Message d'ERREUR2

Ceux. Identifiant d'abord, puis INFO/ERROR, puis un mot sans espaces.
Pas difficile, mais suffisant pour comprendre le principe de fonctionnement.

Ainsi, dans la section filtre, dans le plugin grok, nous devons définir un modèle pour analyser nos chaînes.

Il ressemblera à ceci:

filter {
  grok {
    match => { "message" => ["%{INT:message_id} %{LOGLEVEL:message_type} %{WORD:message_text}"] }
   }
  } 

Fondamentalement, c'est une expression régulière. Des modèles prêts à l'emploi sont utilisés, tels que INT, LOGLEVEL, WORD. Leur description, ainsi que d'autres modèles, peuvent être consultés ici. ici

Maintenant, en passant par ce filtre, notre chaîne se transformera en un hachage de trois champs : message_id, message_type, message_text.

Ils seront affichés dans la section de sortie.

Routage des messages dans la section de sortie avec la commande if

Dans la section de sortie, comme nous nous en souvenons, nous allions diviser les messages en deux flux. Certains - qui sont des iNFO, nous les sortirons sur la console, et avec des erreurs, nous les sortirons dans un fichier.

Comment pouvons-nous partager ces messages ? L'état du problème suggère déjà une solution - après tout, nous avons déjà un champ message_type dédié, qui ne peut prendre que deux valeurs INFO et ERROR. C'est sur elle que nous ferons un choix en utilisant l'instruction if.

if [message_type] == "ERROR" {
        # Здесь выводим в файл
       } else
     {
      # Здесь выводим в stdout
    }

La description du travail avec les champs et les opérateurs se trouve dans cette section manuel officiel.

Maintenant, à propos de la conclusion elle-même.

Sortie console, tout est clair ici - stdout {}

Mais la sortie vers le fichier - rappelez-vous que nous exécutons tout cela à partir du conteneur et pour que le fichier dans lequel nous écrivons le résultat soit accessible de l'extérieur, nous devons ouvrir ce répertoire dans docker-compose.yml.

Total:

La section de sortie de notre fichier ressemble à ceci :


output {
  if [message_type] == "ERROR" {
    file {
          path => "/usr/share/logstash/output/test.log"
          codec => line { format => "custom format: %{message}"}
         }
    } else
     {stdout {
             }
     }
  }

Ajoutez un volume supplémentaire à docker-compose.yml pour la sortie :

version: '3'

networks:
  elk:

volumes:
  elasticsearch:
    driver: local

services:

  logstash:
    container_name: logstash_one_channel
    image: docker.elastic.co/logstash/logstash:6.3.2
    networks:
      - elk
    environment:
      XPACK_MONITORING_ENABLED: "false"
    ports:
      - 5046:5046
   volumes:
      - ./config/pipelines.yml:/usr/share/logstash/config/pipelines.yml:ro
      - ./config/pipelines:/usr/share/logstash/config/pipelines:ro
      - ./logs:/usr/share/logstash/input
      - ./output:/usr/share/logstash/output

On commence, on essaie, on voit la division en deux flux.

Source: habr.com

Ajouter un commentaire