Praktisk tillämpning av ELK. Konfigurera logstash

Inledning

När vi implementerade ett annat system stod vi inför behovet av att bearbeta ett stort antal olika loggar. ELK valdes som verktyg. Den här artikeln kommer att diskutera vår erfarenhet av att ställa in denna stack.

Vi sätter inte som mål att beskriva alla dess förmågor, utan vi vill koncentrera oss specifikt på att lösa praktiska problem. Det beror på att även om det finns en ganska stor mängd dokumentation och färdiga bilder så finns det ganska många fallgropar, vi hittade dem i alla fall.

Vi distribuerade stacken via docker-compose. Dessutom hade vi en välskriven docker-compose.yml, som gjorde att vi kunde höja stacken nästan utan problem. Och det verkade för oss som om segern redan var nära, nu ska vi justera den lite för att passa våra behov och det är det.

Tyvärr lyckades inte försöket att konfigurera systemet för att ta emot och bearbeta loggar från vår applikation omedelbart. Därför bestämde vi oss för att det var värt att studera varje komponent separat och sedan återgå till deras anslutningar.

Så vi började med logstash.

Miljö, distribution, körning av Logstash i en container

För distribution använder vi docker-compose; experimenten som beskrivs här utfördes på MacOS och Ubuntu 18.0.4.

Logstash-bilden som registrerades i vår ursprungliga docker-compose.yml är docker.elastic.co/logstash/logstash:6.3.2

Vi kommer att använda den för experiment.

Vi skrev en separat docker-compose.yml för att köra logstash. Naturligtvis var det möjligt att starta bilden från kommandoraden, men vi löste ett specifikt problem, där vi kör allt från docker-compose.

Kort om konfigurationsfiler

Som följer av beskrivningen kan logstash köras antingen för en kanal, i vilket fall den behöver passera *.conf-filen, eller för flera kanaler, i vilket fall den behöver passera filen pipelines.yml, som i sin tur , länkar till filerna .conf för varje kanal.
Vi tog den andra vägen. Det verkade för oss mer universellt och skalbart. Därför skapade vi pipelines.yml och gjorde en pipelines-katalog där vi kommer att lägga .conf-filer för varje kanal.

Inuti behållaren finns en annan konfigurationsfil - logstash.yml. Vi rör det inte, vi använder det som det är.

Så vår katalogstruktur:

Praktisk tillämpning av ELK. Konfigurera logstash

För att ta emot indata antar vi för närvarande att detta är tcp på port 5046, och för utdata kommer vi att använda stdout.

Här är en enkel konfiguration för den första lanseringen. Eftersom den första uppgiften är att lansera.

Så vi har denna 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

Vad ser vi här?

  1. Nätverk och volymer togs från originalet docker-compose.yml (den där hela stacken lanseras) och jag tror att de inte påverkar den övergripande bilden särskilt mycket här.
  2. Vi skapar en eller flera logstash-tjänster från docker.elastic.co/logstash/logstash:6.3.2-bilden och döper den till logstash_one_channel.
  3. Vi skickar port 5046 inuti containern, till samma interna port.
  4. Vi mappar vår rörkonfigurationsfil ./config/pipelines.yml till filen /usr/share/logstash/config/pipelines.yml inuti behållaren, där logstash hämtar den och gör den skrivskyddad, för säkerhets skull.
  5. Vi mappar katalogen ./config/pipelines, där vi har filer med kanalinställningar, till katalogen /usr/share/logstash/config/pipelines och gör den även skrivskyddad.

Praktisk tillämpning av ELK. Konfigurera logstash

Pipelines.yml-filen

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

En kanal med HABR-identifieraren och sökvägen till dess konfigurationsfil beskrivs här.

Och slutligen filen "./config/pipelines/habr_pipeline.conf"

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

Låt oss inte gå in på dess beskrivning för nu, låt oss försöka köra den:

docker-compose up

Vad ser vi?

Containern har startat. Vi kan kontrollera dess funktion:

echo '13123123123123123123123213123213' | nc localhost 5046

Och vi ser svaret i containerkonsolen:

Praktisk tillämpning av ELK. Konfigurera logstash

Men samtidigt ser vi också:

logstash_one_channel | [2019-04-29T11:28:59,790][ERROR][logstash.licensechecker.licensereader] Det gick inte att hämta licensinformation från licensservern {:message=>“Elasticsearch Unreachable: [http://elasticsearch:9200/][Manticore ::ResolutionFailure] elasticsearch", ...

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

logstash_one_channel | [2019-04-29T11:28:59,988][INFO ][logstash.agent ] Pipelines som kör {: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 är installerat på Logstash men inte på Elasticsearch. Installera X-Pack på Elasticsearch för att använda övervakningsfunktionen. Andra funktioner kan vara tillgängliga.
logstash_one_channel | [2019-04-29T11:29:00,526][INFO ][logstash.agent ] Startade Logstash API-slutpunkt {:port=>9600}
logstash_one_channel | [2019-04-29T11:29:04,478][INFO ][logstash.outputs.elasticsearch] Kör hälsokontroll för att se om en Elasticsearch-anslutning fungerar {:healthcheck_url=>http://elasticsearch:9200/, :path=> "/"}
logstash_one_channel | [2019-04-29T11:29:04,487][WARN ][logstash.outputs.elasticsearch] Försökte återuppliva anslutningen till död ES-instans, men fick ett fel. {:url=>“elastisk sökning:9200/", :error_type=>LogStash::Outputs::ElasticSearch::HttpClient::Pool::HostUnreachableError, :error=>"Elasticsearch Unreachable: [http://elasticsearch:9200/][Manticore::ResolutionFailure] elasticsearch"}
logstash_one_channel | [2019-04-29T11:29:04,704][INFO ][logstash.licensechecker.licensereader] Kör hälsokontroll för att se om en Elasticsearch-anslutning fungerar {:healthcheck_url=>http://elasticsearch:9200/, :path=> "/"}
logstash_one_channel | [2019-04-29T11:29:04,710][WARN ][logstash.licensechecker.licensereader] Försökte återuppliva anslutningen till död ES-instans, men fick ett fel. {:url=>“elastisk sökning:9200/", :error_type=>LogStash::Outputs::ElasticSearch::HttpClient::Pool::HostUnreachableError, :error=>"Elasticsearch Unreachable: [http://elasticsearch:9200/][Manticore::ResolutionFailure] elasticsearch"}

Och vår stock kryper upp hela tiden.

Här har jag markerat i grönt meddelandet att pipeline har startat framgångsrikt, i rött felmeddelandet och i gult meddelandet om ett försök att kontakta elastisk sökning: 9200.
Detta händer eftersom logstash.conf, som ingår i bilden, innehåller en kontroll för tillgänglighet av elasticsearch. Trots allt antar logstash att det fungerar som en del av älgstacken, men vi separerade det.

Det går att jobba, men det är inte bekvämt.

Lösningen är att inaktivera denna kontroll via miljövariabeln XPACK_MONITORING_ENABLED.

Låt oss göra en ändring till docker-compose.yml och köra den igen:

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 är allt bra. Behållaren är redo för experiment.

Vi kan skriva igen i nästa konsol:

echo '13123123123123123123123213123213' | nc localhost 5046

Och se:

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

Arbeta inom en kanal

Så vi lanserade. Nu kan du faktiskt ta dig tid att konfigurera logstash själv. Låt oss inte röra pipelines.yml-filen för nu, låt oss se vad vi kan få genom att arbeta med en kanal.

Jag måste säga att den allmänna principen för att arbeta med kanalkonfigurationsfilen är väl beskriven i den officiella manualen här här
Om du vill läsa på ryska använde vi den här artikel(men frågesyntaxen där är gammal, vi måste ta hänsyn till detta).

Låt oss gå sekventiellt från Input-sektionen. Vi har redan sett arbete med TCP. Vad mer kan vara intressant här?

Testa meddelanden med hjälp av hjärtslag

Det finns en sådan intressant möjlighet att generera automatiska testmeddelanden.
För att göra detta måste du aktivera heartbean-plugin i ingångssektionen.

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

Slå på den, börja ta emot en gång i minuten

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

Om vi ​​vill ta emot oftare måste vi lägga till parametern intervall.
Så här kommer vi att få ett meddelande var tionde sekund.

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

Hämtar data från en fil

Vi bestämde oss också för att titta på filläget. Om det fungerar bra med filen, kanske ingen agent behövs, åtminstone för lokalt bruk.

Enligt beskrivningen ska driftläget likna tail -f, dvs. läser nya rader eller, som ett alternativ, läser hela filen.

Så vad vi vill få:

  1. Vi vill ta emot rader som läggs till en loggfil.
  2. Vi vill ta emot data som skrivs till flera loggfiler, samtidigt som vi kan separera det som tas emot varifrån.
  3. Vi vill se till att när logstash startas om så tar den inte emot denna data igen.
  4. Vi vill kontrollera att om logstash är avstängd och data fortsätter att skrivas till filer, kommer vi att ta emot denna data när vi kör den.

För att genomföra experimentet, låt oss lägga till ytterligare en rad i docker-compose.yml och öppna katalogen där vi lägger filerna.

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

Och ändra inmatningssektionen i habr_pipeline.conf

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

Låt oss börja:

docker-compose up

För att skapa och skriva loggfiler använder vi kommandot:


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

Japp, det funkar!

Samtidigt ser vi att vi automatiskt har lagt till sökvägsfältet. Det betyder att vi i framtiden kommer att kunna filtrera poster efter det.

Låt oss försöka igen:

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

Och nu till en annan fil:

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

Bra! Filen hämtades, sökvägen angavs korrekt, allt är bra.

Stoppa logstash och börja om. Låt oss vänta. Tystnad. De där. Vi får inte dessa register igen.

Och nu det mest vågade experimentet.

Installera logstash och kör:

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

Kör logstash igen och se:

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

Hurra! Allt plockades upp.

Men vi måste varna dig för följande. Om logstash-behållaren tas bort (docker stop logstash_one_channel && docker rm logstash_one_channel), kommer ingenting att hämtas. Positionen för filen till vilken den lästes lagrades inuti behållaren. Om du kör det från början kommer det bara att acceptera nya rader.

Läser befintliga filer

Låt oss säga att vi lanserar logstash för första gången, men vi har redan loggar och vi skulle vilja bearbeta dem.
Om vi ​​kör logstash med inmatningssektionen vi använde ovan får vi ingenting. Endast nya rader kommer att behandlas av logstash.

För att rader från befintliga filer ska dras upp bör du lägga till ytterligare en rad i inmatningsdelen:

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

Dessutom finns det en nyans: detta påverkar bara nya filer som logstash ännu inte har sett. För samma filer som redan fanns i synfältet för logstash har den redan kommit ihåg deras storlek och kommer nu bara att ta nya poster i dem.

Låt oss stanna här och studera inmatningsdelen. Det finns fortfarande många alternativ, men det räcker för oss för ytterligare experiment för nu.

Routing och datatransformation

Låt oss försöka lösa följande problem, låt oss säga att vi har meddelanden från en kanal, några av dem är informativa och andra är felmeddelanden. De skiljer sig åt efter tagg. Vissa är INFO, andra är ERROR.

Vi måste skilja dem åt vid utgången. De där. Vi skriver informationsmeddelanden i en kanal och felmeddelanden i en annan.

För att göra detta, flytta från ingångssektionen till filtrering och utmatning.

Med hjälp av filtersektionen kommer vi att analysera det inkommande meddelandet och få en hash (nyckel-värdepar) från det, som vi redan kan arbeta med, dvs. demontera enligt förhållandena. Och i utgångssektionen kommer vi att välja meddelanden och skicka var och en till sin egen kanal.

Parsar ett meddelande med grok

För att tolka textsträngar och få en uppsättning fält från dem finns det ett speciellt plugin i filtersektionen - grok.

Utan att sätta mig som mål att ge en detaljerad beskrivning av det här (för detta hänvisar jag till officiell dokumentation), Jag ska ge mitt enkla exempel.

För att göra detta måste du bestämma formatet på inmatningssträngarna. Jag har dem så här:

1 INFO-meddelande1
2 FEL-meddelande2

De där. Identifieraren kommer först, sedan INFO/ERROR, sedan något ord utan mellanslag.
Det är inte svårt, men det räcker för att förstå funktionsprincipen.

Så i filtersektionen av grok-pluginen måste vi definiera ett mönster för att analysera våra strängar.

Det kommer att se ut så här:

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

I grund och botten är det ett reguljärt uttryck. Färdiga mönster används, som INT, LOGLEVEL, WORD. Deras beskrivning, liksom andra mönster, finns här här

När vi passerar detta filter kommer vår sträng nu att förvandlas till en hash av tre fält: meddelande_id, meddelandetyp, meddelandetext.

De kommer att visas i utgångssektionen.

Dirigera meddelanden till utgångssektionen med if-kommandot

I utgångssektionen, som vi minns, skulle vi dela upp meddelandena i två strömmar. Vissa - som är iNFO, kommer att matas ut till konsolen, och med fel kommer vi att mata ut till en fil.

Hur skiljer vi dessa meddelanden åt? Tillståndet för problemet föreslår redan en lösning - trots allt har vi redan ett dedikerat meddelandetypfält, som bara kan ha två värden: INFO och ERROR. Det är på denna grund som vi kommer att göra ett val med hjälp av if-satsen.

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

En beskrivning av hur man arbetar med fält och operatörer finns i detta avsnitt officiella manual.

Nu om själva slutsatsen.

Konsolutgång, allt är klart här - stdout {}

Men utdata till en fil - kom ihåg att vi kör allt detta från en behållare och för att filen som vi skriver resultatet i ska vara tillgänglig från utsidan måste vi öppna den här katalogen i docker-compose.yml.

Totalt:

Utdatadelen av vår fil ser ut så här:


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

I docker-compose.yml lägger vi till ytterligare en volym för utdata:

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

Vi lanserar det, provar det och ser en uppdelning i två strömmar.

Källa: will.com

Lägg en kommentar