Praktische toepassing van ELK. Logstash instellen

Introductie

Tijdens de implementatie van een ander systeem werden we geconfronteerd met de noodzaak om een ​​groot aantal verschillende logbestanden te verwerken. Als instrument werd ELK gekozen. In dit artikel worden onze ervaringen met het opzetten van deze stapel besproken.

We stellen ons geen doel om alle mogelijkheden ervan te beschrijven, maar willen ons specifiek concentreren op het oplossen van praktische problemen. Dit komt door het feit dat, hoewel er een vrij grote hoeveelheid documentatie en kant-en-klare afbeeldingen is, er nogal wat valkuilen zijn, althans die hebben we gevonden.

We hebben de stack geïmplementeerd via docker-compose. Bovendien hadden we een goed geschreven docker-compose.yml, waarmee we de stapel vrijwel zonder problemen konden verhogen. En het leek ons ​​dat de overwinning al dichtbij was, nu zullen we het een beetje aanpassen aan onze behoeften en dat is alles.

Helaas was de poging om het systeem te configureren om logs van onze applicatie te ontvangen en te verwerken niet onmiddellijk succesvol. Daarom besloten we dat het de moeite waard was om elk onderdeel afzonderlijk te bestuderen en vervolgens terug te keren naar hun verbindingen.

Dus zijn we begonnen met logstash.

Omgeving, implementatie, Logstash draaien in een container

Voor de implementatie gebruiken we docker-compose; de ​​hier beschreven experimenten zijn uitgevoerd op MacOS en Ubuntu 18.0.4.

De logstash-afbeelding die is geregistreerd in onze oorspronkelijke docker-compose.yml is docker.elastic.co/logstash/logstash:6.3.2

We zullen het gebruiken voor experimenten.

We hebben een aparte docker-compose.yml geschreven om logstash uit te voeren. Natuurlijk was het mogelijk om de image vanaf de opdrachtregel te starten, maar we waren een specifiek probleem aan het oplossen, waarbij we alles vanuit docker-compose uitvoerden.

Kort over configuratiebestanden

Zoals volgt uit de beschrijving kan logstash worden uitgevoerd voor één kanaal, in welk geval het het bestand *.conf moet doorgeven, of voor meerdere kanalen, in welk geval het het bestand pipelines.yml moet doorgeven, dat op zijn beurt , linkt naar de bestanden .conf voor elk kanaal.
Wij namen de tweede weg. Het leek ons ​​universeler en schaalbaarder. Daarom hebben we pipelines.yml gemaakt en een pipelines-directory gemaakt waarin we .conf-bestanden voor elk kanaal zullen plaatsen.

In de container bevindt zich nog een configuratiebestand: logstash.yml. We raken het niet aan, we gebruiken het zoals het is.

Dus onze directorystructuur:

Praktische toepassing van ELK. Logstash instellen

Voor het ontvangen van invoergegevens gaan we er voorlopig vanuit dat dit tcp is op poort 5046, en voor uitvoer gebruiken we stdout.

Hier is een eenvoudige configuratie voor de eerste lancering. Omdat de eerste taak het lanceren is.

We hebben dus dit 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

Wat zien we hier?

  1. Netwerken en volumes zijn afkomstig van de originele docker-compose.yml (degene waar de hele stapel wordt gelanceerd) en ik denk dat ze het algemene beeld hier niet erg beïnvloeden.
  2. We maken één logstash-service(s) van de docker.elastic.co/logstash/logstash:6.3.2-afbeelding en noemen deze logstash_one_channel.
  3. Wij sturen poort 5046 binnen de container door naar dezelfde interne poort.
  4. We wijzen ons pijpconfiguratiebestand ./config/pipelines.yml toe aan het bestand /usr/share/logstash/config/pipelines.yml in de container, waar logstash het ophaalt en het alleen-lezen maakt, voor het geval dat.
  5. We wijzen de map ./config/pipelines, waar we bestanden met kanaalinstellingen hebben, toe aan de map /usr/share/logstash/config/pipelines en maken deze ook alleen-lezen.

Praktische toepassing van ELK. Logstash instellen

Pipelines.yml-bestand

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

Hier wordt één kanaal met de HABR-identificatie en het pad naar het configuratiebestand beschreven.

En tenslotte het bestand “./config/pipelines/habr_pipeline.conf”

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

Laten we voorlopig niet ingaan op de beschrijving, laten we proberen het uit te voeren:

docker-compose up

Wat zien we?

De container is gestart. We kunnen de werking ervan controleren:

echo '13123123123123123123123213123213' | nc localhost 5046

En we zien het antwoord in de containerconsole:

Praktische toepassing van ELK. Logstash instellen

Maar tegelijkertijd zien we ook:

logstash_one_channel | [2019-04-29T11:28:59,790][ERROR][logstash.licensechecker.licensereader] Kan licentie-informatie niet ophalen van licentieserver {:message=>“Elasticsearch onbereikbaar: [http://elasticsearch:9200/][Manticore ::ResolutionFailure] elasticsearch", ...

logstash_one_channel | [2019-04-29T11:28:59,894][INFO ][logstash.pipeline ] Pipeline is succesvol gestart {:pipeline_id=>".monitoring-logstash", :thread=>"# "}

logstash_one_channel | [2019-04-29T11:28:59,988][INFO ][logstash.agent ] Pijplijnen actief {: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 is geïnstalleerd op Logstash maar niet op Elasticsearch. Installeer X-Pack op Elasticsearch om de monitoringfunctie te gebruiken. Mogelijk zijn er nog andere functies beschikbaar.
logstash_one_channel | [2019-04-29T11:29:00,526][INFO ][logstash.agent ] Logstash API-eindpunt {:port=>9600} succesvol gestart
logstash_one_channel | [2019-04-29T11:29:04,478][INFO ][logstash.outputs.elasticsearch] Een statuscontrole uitvoeren om te zien of een Elasticsearch-verbinding werkt {:healthcheck_url=>http://elasticsearch:9200/, :path=> "/"}
logstash_one_channel | [2019-04-29T11:29:04,487][WARN ][logstash.outputs.elasticsearch] Er is geprobeerd de verbinding met een dode ES-instantie weer tot leven te wekken, maar er is een fout opgetreden. {:url=>“elasticsearch:9200/", :error_type=>LogStash::Outputs::ElasticSearch::HttpClient::Pool::HostUnreachableError, :error=>"Elasticsearch onbereikbaar: [http://elasticsearch:9200/][Manticore::ResolutionFailure] elastisch zoeken"}
logstash_one_channel | [2019-04-29T11:29:04,704][INFO ][logstash.licensechecker.licensereader] Een statuscontrole uitvoeren om te zien of een Elasticsearch-verbinding werkt {:healthcheck_url=>http://elasticsearch:9200/, :path=> "/"}
logstash_one_channel | [2019-04-29T11:29:04,710][WARN ][logstash.licensechecker.licensereader] Er is geprobeerd de verbinding met een dode ES-instantie weer tot leven te wekken, maar er is een fout opgetreden. {:url=>“elasticsearch:9200/", :error_type=>LogStash::Outputs::ElasticSearch::HttpClient::Pool::HostUnreachableError, :error=>"Elasticsearch onbereikbaar: [http://elasticsearch:9200/][Manticore::ResolutionFailure] elastisch zoeken"}

En ons logboek kruipt voortdurend omhoog.

Hier heb ik in het groen het bericht gemarkeerd dat de pijpleiding succesvol is gelanceerd, in rood de foutmelding en in geel het bericht over een poging om contact op te nemen elasticsearch: 9200.
Dit gebeurt omdat logstash.conf, opgenomen in de afbeelding, een controle bevat op de beschikbaarheid van elasticsearch. Logstash gaat er immers van uit dat het werkt als onderdeel van de Elk-stack, maar we hebben het gescheiden.

Het is mogelijk om te werken, maar het is niet handig.

De oplossing is om deze controle uit te schakelen via de omgevingsvariabele XPACK_MONITORING_ENABLED.

Laten we een wijziging aanbrengen in docker-compose.yml en het opnieuw uitvoeren:

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

Nu is alles in orde. De container is klaar voor experimenten.

We kunnen opnieuw typen in de volgende console:

echo '13123123123123123123123213123213' | nc localhost 5046

En zie:

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 | }

Werken binnen één kanaal

Dus zijn we gelanceerd. Nu kunt u daadwerkelijk de tijd nemen om logstash zelf te configureren. Laten we het bestand pipelines.yml voorlopig niet aanraken, laten we kijken wat we kunnen bereiken door met één kanaal te werken.

Ik moet zeggen dat het algemene principe van het werken met het kanaalconfiguratiebestand goed wordt beschreven in de officiële handleiding, hier hier
Als je in het Russisch wilt lezen, hebben we deze gebruikt artikel(maar de syntaxis van de query is oud, we moeten hiermee rekening houden).

Laten we opeenvolgend vanuit de sectie Invoer gaan. We hebben al werk gezien op TCP. Wat zou hier nog meer interessant kunnen zijn?

Test berichten met behulp van hartslag

Er is zo'n interessante mogelijkheid om automatische testberichten te genereren.
Om dit te doen, moet je de heartbean-plug-in in de invoersectie inschakelen.

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

Zet hem aan en ontvang één keer per minuut

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 | }

Als we vaker willen ontvangen, moeten we de intervalparameter toevoegen.
Zo ontvangen wij elke 10 seconden een bericht.

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

Gegevens uit een bestand ophalen

We hebben ook besloten om naar de bestandsmodus te kijken. Als het goed werkt met het bestand, is er misschien geen agent nodig, tenminste niet voor lokaal gebruik.

Volgens de beschrijving moet de bedrijfsmodus vergelijkbaar zijn met staart -f, d.w.z. leest nieuwe regels of, optioneel, leest het hele bestand.

Dus wat we willen bereiken:

  1. We willen regels ontvangen die aan één logbestand worden toegevoegd.
  2. We willen gegevens ontvangen die naar verschillende logbestanden zijn geschreven, terwijl we kunnen scheiden wat van waar wordt ontvangen.
  3. We willen ervoor zorgen dat wanneer logstash opnieuw wordt opgestart, deze gegevens niet opnieuw worden ontvangen.
  4. We willen controleren of als logstash is uitgeschakeld en er nog steeds gegevens naar bestanden worden geschreven, we deze gegevens zullen ontvangen wanneer we het uitvoeren.

Om het experiment uit te voeren, voegen we nog een regel toe aan docker-compose.yml, waarbij we de map openen waarin we de bestanden plaatsen.

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

En verander de invoersectie in habr_pipeline.conf

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

Laten we beginnen:

docker-compose up

Om logbestanden te maken en te schrijven gebruiken we de opdracht:


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 | }

Ja, het werkt!

Tegelijkertijd zien we dat we het padveld automatisch hebben toegevoegd. Dit betekent dat we in de toekomst records hierop kunnen filteren.

Laten we het opnieuw proberen:

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 | }

En nu naar een ander bestand:

 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 | }

Geweldig! Het bestand is opgehaald, het pad is correct opgegeven, alles is in orde.

Stop logstash en begin opnieuw. Laten we wachten. Stilte. Die. Wij ontvangen deze gegevens niet meer.

En nu het meest gewaagde experiment.

Installeer logstash en voer uit:

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

Voer logstash opnieuw uit en zie:

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 | }

Hoera! Alles werd opgehaald.

Maar we moeten u waarschuwen voor het volgende. Als de logstash-container wordt verwijderd (docker stop logstash_one_channel && docker rm logstash_one_channel), wordt er niets opgehaald. De positie van het bestand tot waar het werd gelezen, werd in de container opgeslagen. Als u het helemaal opnieuw uitvoert, accepteert het alleen nieuwe regels.

Bestaande bestanden lezen

Laten we zeggen dat we logstash voor de eerste keer lanceren, maar we hebben al logbestanden en we willen deze graag verwerken.
Als we logstash uitvoeren met de invoersectie die we hierboven hebben gebruikt, krijgen we niets. Alleen nieuwe regels worden door logstash verwerkt.

Om regels uit bestaande bestanden op te halen, moet u een extra regel toevoegen aan de invoersectie:

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

Bovendien is er een nuance: dit heeft alleen betrekking op nieuwe bestanden die logstash nog niet heeft gezien. Voor dezelfde bestanden die zich al in het gezichtsveld van logstash bevonden, heeft het hun grootte al onthouden en zal het nu alleen nieuwe vermeldingen erin opnemen.

Laten we hier stoppen en het invoergedeelte bestuderen. Er zijn nog veel opties, maar dat is voor ons voorlopig genoeg voor verdere experimenten.

Routing en gegevenstransformatie

Laten we proberen het volgende probleem op te lossen, laten we zeggen dat we berichten van één kanaal hebben, waarvan sommige informatief zijn en sommige foutmeldingen. Ze verschillen per tag. Sommige zijn INFO, andere zijn FOUT.

We moeten ze scheiden bij de uitgang. Die. We schrijven informatieberichten in het ene kanaal en foutmeldingen in het andere.

Om dit te doen, gaat u van de invoersectie naar filter en uitvoer.

Met behulp van de filtersectie zullen we het binnenkomende bericht parseren en er een hash (sleutel-waardeparen) van verkrijgen, waarmee we al kunnen werken, d.w.z. demonteer volgens de omstandigheden. En in het uitvoergedeelte selecteren we berichten en sturen ze elk naar een eigen kanaal.

Een bericht parseren met grok

Om tekstreeksen te ontleden en er een reeks velden uit te halen, is er een speciale plug-in in de filtersectie: grok.

Zonder mezelf ten doel te stellen hier een gedetailleerde beschrijving van te geven (hiervoor verwijs ik naar officiële documentatie), zal ik mijn eenvoudige voorbeeld geven.

Om dit te doen, moet u beslissen over het formaat van de invoerreeksen. Ik heb ze zo:

1 INFO-bericht1
2 FOUTmelding2

Die. De ID komt eerst, dan INFO/ERROR en vervolgens een woord zonder spaties.
Het is niet moeilijk, maar het is voldoende om het werkingsprincipe te begrijpen.

Dus in het filtergedeelte van de grok-plug-in moeten we een patroon definiëren voor het parseren van onze strings.

Het zal er als volgt uitzien:

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

In wezen is het een reguliere expressie. Er wordt gebruik gemaakt van kant-en-klare patronen, zoals INT, LOGLEVEL, WORD. Hun beschrijving, evenals andere patronen, kun je hier vinden hier

Als we nu door dit filter gaan, verandert onze string in een hash van drie velden: message_id, message_type, message_text.

Ze worden weergegeven in het uitvoergedeelte.

Berichten routeren naar de uitvoersectie met behulp van de if-opdracht

In het uitvoergedeelte gingen we, zoals we ons herinneren, de berichten in twee stromen splitsen. Sommige, die iNFO zijn, zullen naar de console worden uitgevoerd en bij fouten zullen we naar een bestand worden uitgevoerd.

Hoe scheiden we deze berichten? De toestand van het probleem suggereert al een oplossing - we hebben tenslotte al een speciaal message_type-veld, dat slechts twee waarden kan aannemen: INFO en ERROR. Op basis hiervan zullen we een keuze maken met behulp van de if-instructie.

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

Een beschrijving van het werken met velden en operators vindt u in dit hoofdstuk officiële handleiding.

Nu, over de eigenlijke conclusie zelf.

Console-uitvoer, alles is hier duidelijk - stdout {}

Maar de uitvoer naar een bestand - onthoud dat we dit allemaal vanuit een container uitvoeren en om het bestand waarin we het resultaat schrijven toegankelijk te maken van buitenaf, moeten we deze map openen in docker-compose.yml.

Totaal:

Het uitvoergedeelte van ons bestand ziet er als volgt uit:


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

In docker-compose.yml voegen we nog een volume toe voor uitvoer:

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

We lanceren het, proberen het en zien een verdeling in twee stromen.

Bron: www.habr.com

Voeg een reactie