Aplicació pràctica d'ELK. Configuració de logstash

Introducció

Mentre desplegàvem un altre sistema, ens vam enfrontar a la necessitat de processar un gran nombre de registres diversos. ELK va ser escollit com a instrument. Aquest article parlarà de la nostra experiència en la configuració d'aquesta pila.

No ens fixem un objectiu per descriure totes les seves capacitats, però volem concentrar-nos a resoldre problemes pràctics. Això es deu al fet que amb una quantitat prou gran de documentació i imatges ja fetes, hi ha molts inconvenients, almenys els hem trobat.

Hem desplegat la pila mitjançant docker-compose. A més, teníem un docker-compose.yml ben escrit que ens permetia augmentar la pila gairebé sense problemes. I ens semblava que la victòria ja estava a prop, ara la retorçarem una mica per adaptar-nos a les nostres necessitats i ja està.

Malauradament, un intent d'ajustar el sistema per rebre i processar els registres de la nostra aplicació no va tenir èxit d'entrada. Per tant, vam decidir que val la pena estudiar cada component per separat i després tornar a les seves connexions.

Així que comencem amb logstash.

Entorn, desplegament, execució de Logstash en un contenidor

Per al desplegament, utilitzem docker-compose, els experiments que es descriuen aquí es van dur a terme a MacOS i Ubuntu 18.0.4.

La imatge de logstash que teníem al nostre docker-compose.yml original és docker.elastic.co/logstash/logstash:6.3.2

L'utilitzarem per a experiments.

Per executar logstash, hem escrit un docker-compose.yml separat. Per descomptat, va ser possible llançar la imatge des de la línia d'ordres, però al cap i a la fi, vam resoldre una tasca específica, on se'ns llança tot, des de docker-compose.

Breument sobre els fitxers de configuració

Com es desprèn de la descripció, logstash es pot executar com per a un canal, en aquest cas, ha de transferir el fitxer *.conf o per a diversos canals, en aquest cas ha de transferir el fitxer pipelines.yml, que, al seu torn , es referirà als fitxers .conf per a cada canal.
Vam agafar el segon camí. Ens va semblar més versàtil i escalable. Per tant, hem creat pipelines.yml i hem creat un directori pipelines en el qual posarem fitxers .conf per a cada canal.

Dins del contenidor hi ha un altre fitxer de configuració: logstash.yml. No el toquem, el fem servir tal com és.

Per tant, la nostra estructura de directoris és:

Aplicació pràctica d'ELK. Configuració de logstash

De moment, suposem que aquest és tcp al port 5046 per rebre dades d'entrada i utilitzarem stdout per a la sortida.

Aquí teniu una configuració tan senzilla per a la primera execució. Perquè la tasca inicial és posar en marxa.

Així que tenim aquest 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

Què veiem aquí?

  1. Les xarxes i els volums es van extreure del docker-compose.yml original (aquell on es llança tota la pila) i crec que no afecten gaire la imatge general aquí.
  2. Creem un servei (serveis) logstash, a partir de la imatge docker.elastic.co/logstash/logstash:6.3.2 i li donem el nom logstash_one_channel.
  3. Estem reenviant el port 5046 dins del contenidor, al mateix port intern.
  4. Mapem el nostre fitxer de configuració de canonades ./config/pipelines.yml al fitxer /usr/share/logstash/config/pipelines.yml dins del contenidor, on logstash el recollirà i el farà només de lectura, per si de cas.
  5. Mapem el directori ./config/pipelines, on tenim els fitxers de configuració de la canalització, al directori /usr/share/logstash/config/pipelines i també el fem només de lectura.

Aplicació pràctica d'ELK. Configuració de logstash

fitxer piping.yml

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

Descriu un canal amb l'identificador HABR i la ruta al seu fitxer de configuració.

I finalment el fitxer "./config/pipelines/habr_pipeline.conf"

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

No entrarem en la seva descripció de moment, intentem executar:

docker-compose up

Què veiem?

El contenidor ha començat. Podem comprovar el seu funcionament:

echo '13123123123123123123123213123213' | nc localhost 5046

I veiem la resposta a la consola del contenidor:

Aplicació pràctica d'ELK. Configuració de logstash

Però al mateix temps també veiem:

logstash_one_channel | [2019-04-29T11:28:59,790][ERROR][logstash.licensechecker.licensereader] No s'ha pogut recuperar la informació de la llicència del servidor de llicències {:message=>"Elasticsearch inaccessible: [http://elasticsearch:9200/][Manticore ::ResolutionFailure]elasticsearch",...

logstash_one_channel | [2019-04-29T11:28:59,894][INFO ][logstash.pipeline ] La canalització s'ha iniciat correctament {:pipeline_id=>".monitoring-logstash", :thread =>"# »}

logstash_one_channel | [2019-04-29T11:28:59,988][INFO ][logstash.agent ] Conduccions que s'executen {: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à instal·lat a Logstash però no a Elasticsearch. Instal·leu X-Pack a Elasticsearch per utilitzar la funció de supervisió. Altres funcions poden estar disponibles.
logstash_one_channel | [2019-04-29T11:29:00,526][INFO ][logstash.agent ] S'ha iniciat correctament el punt final de l'API de Logstash {:port=>9600}
logstash_one_channel | [2019-04-29T11:29:04,478][INFO ][logstash.outputs.elasticsearch] S'està executant la comprovació de l'estat per veure si una connexió Elasticsearch funciona {:healthcheck_url=>http://elasticsearch:9200/, :path=> "/"}
logstash_one_channel | [2019-04-29T11:29:04,487][WARN ][logstash.outputs.elasticsearch] S'ha intentat reactivar la connexió a la instància ES morta, però s'ha produït un error. {:url =>"cerca elastica:9200/", :error_type=>LogStash::Outputs::ElasticSearch::HttpClient::Pool::HostUnreachableError, :error=>"Elasticsearch inaccessible: [http://elasticsearch:9200/][Manticore::ResolutionFailure] elasticsearch"}
logstash_one_channel | [2019-04-29T11:29:04,704][INFO ][logstash.licensechecker.licensereader] S'està executant la comprovació de l'estat per veure si una connexió Elasticsearch funciona {:healthcheck_url=>http://elasticsearch:9200/, :path=> "/"}
logstash_one_channel | [2019-04-29T11:29:04,710][WARN ][logstash.licensechecker.licensereader] S'ha intentat reactivar la connexió a la instància ES morta, però s'ha produït un error. {:url =>"cerca elastica:9200/", :error_type=>LogStash::Outputs::ElasticSearch::HttpClient::Pool::HostUnreachableError, :error=>"Elasticsearch inaccessible: [http://elasticsearch:9200/][Manticore::ResolutionFailure] elasticsearch"}

I el nostre registre s'arrossega tot el temps.

Aquí he destacat en verd el missatge que el pipeline s'ha iniciat correctament, en vermell el missatge d'error i en groc el missatge sobre intentar contactar cerca elastica: 9200.
Això passa pel fet que al logstash.conf inclòs a la imatge, hi ha una comprovació de la disponibilitat d'elasticsearch. Al cap i a la fi, logstash assumeix que funciona com a part de la pila d'Elk i la vam separar.

Pots treballar, però no és convenient.

La solució és desactivar aquesta comprovació mitjançant la variable d'entorn XPACK_MONITORING_ENABLED.

Fem un canvi a docker-compose.yml i tornem a executar-lo:

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

Ara, tot està bé. El recipient està preparat per a experiments.

Podem tornar a escriure a la consola adjacent:

echo '13123123123123123123123213123213' | nc localhost 5046

I veure:

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

Treballa dins d'un canal

Així doncs, vam començar. Ara podeu prendre-vos el temps per configurar logstash directament. De moment, no toquem el fitxer pipelines.yml, anem a veure què podem obtenir treballant amb un canal.

He de dir que el principi general de treballar amb el fitxer de configuració del canal està ben descrit al manual oficial, aquí aquí
Si voleu llegir en rus, hem utilitzat aquest article(però la sintaxi de la consulta és antiga, cal tenir-ho en compte).

Anem seqüencialment des de la secció d'entrada. Ja hem vist el treball a tcp. Què més pot ser interessant aquí?

Prova missatges utilitzant el batec del cor

Hi ha una possibilitat tan interessant de generar missatges de prova automàtics.
Per fer-ho, heu d'incloure el connector heartbean a la secció d'entrada.

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

L'encenem, comencem a rebre un cop per minut

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

Volem rebre més sovint, hem d'afegir el paràmetre d'interval.
Així és com rebrem un missatge cada 10 segons.

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

Obtenció de dades d'un fitxer

També vam decidir mirar el mode de fitxer. Si funciona bé amb el fitxer, és possible que no calgui cap agent, bé, almenys per a ús local.

Segons la descripció, el mode de funcionament hauria de ser similar al de la cua -f, és a dir. llegeix les noves línies o, opcionalment, llegeix tot el fitxer.

Així que el que volem aconseguir:

  1. Volem rebre línies que s'afegeixen a un fitxer de registre.
  2. Volem rebre dades que s'escriuen en diversos fitxers de registre, alhora que podem separar el que s'ha rebut d'on.
  3. Volem assegurar-nos que quan es reiniciï logstash, no tornarà a rebre aquestes dades.
  4. Volem comprovar que si el logstash està desactivat i les dades es continuen escrivint als fitxers, quan l'executem, rebrem aquestes dades.

Per dur a terme l'experiment, afegim una línia més a docker-compose.yml, obrint el directori on posem els fitxers.

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

I canvieu la secció d'entrada a habr_pipeline.conf

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

Comencem:

docker-compose up

Per crear i escriure fitxers de registre, farem servir l'ordre:


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

Sí, funciona!

Al mateix temps, veiem que hem afegit automàticament el camp camí. Així, en el futur, podrem filtrar els registres per això.

Intenta-ho una altre vegada:

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

I ara a un altre fitxer:

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

Genial! El fitxer s'ha recollit, el camí s'ha especificat correctament, tot està bé.

Atura logstash i reinicia. Esperem. Silenci. Aquells. No tornem a rebre aquests registres.

I ara l'experiment més agosarat.

Posem logstash i executem:

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

Torna a executar logstash i mira:

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

Hura! Tot recollit.

Però, cal advertir sobre el següent. Si s'elimina el contenidor de logstash (docker stop logstash_one_channel && docker rm logstash_one_channel), no es recollirà res. La posició del fitxer fins a la qual es va llegir s'emmagatzemava dins del contenidor. Si comenceu des de zero, només acceptarà noves línies.

Lectura de fitxers existents

Suposem que estem executant logstash per primera vegada, però ja tenim registres i ens agradaria processar-los.
Si executem logstash amb la secció d'entrada que hem utilitzat anteriorment, no obtindrem res. Logstash només processarà les noves línies.

Per extreure línies dels fitxers existents, afegiu una línia addicional a la secció d'entrada:

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

A més, hi ha un matís, això només afecta els fitxers nous que logstash encara no ha vist. Per als mateixos fitxers que ja estaven al camp de visió de logstash, ja n'ha recordat la mida i ara només hi prendrà registres nous.

Aturem-nos en això estudiant la secció d'entrada. Hi ha moltes més opcions, però de moment, en tenim prou per a més experiments.

Enrutament i transformació de dades

Intentem resoldre el següent problema, posem per cas que tenim missatges d'un canal, alguns d'ells són informatius i altres són missatges d'error. Es diferencien en l'etiqueta. Alguns són INFO, altres són ERROR.

Hem de separar-los a la sortida. Aquells. Escrivim missatges informatius en un canal i missatges d'error en un altre.

Per fer-ho, aneu de la secció d'entrada a filtrar i sortir.

Mitjançant la secció de filtres, analitzarem el missatge entrant, obtenint-ne un hash (parells clau-valor), amb el qual ja podem treballar, és a dir. analitzar segons les condicions. I a la secció de sortida, seleccionarem missatges i enviarem cadascun al seu propi canal.

Analitzant un missatge amb grok

Per analitzar les cadenes de text i obtenir-ne un conjunt de camps, hi ha un connector especial a la secció de filtres: grok.

Sense proposar-me l'objectiu de fer-ne una descripció detallada aquí (per això em refereixo a documentació oficial), posaré el meu exemple senzill.

Per fer-ho, heu de decidir el format de les línies d'entrada. Els tinc així:

1 missatge d'informació 1
2 Missatge d'ERROR2

Aquells. Primer l'identificador, després INFO/ERROR i després alguna paraula sense espais.
No és difícil, però suficient per entendre el principi de funcionament.

Per tant, a la secció de filtres, al connector grok, hem de definir un patró per analitzar les nostres cadenes.

Es veurà així:

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

Bàsicament, és una expressió regular. S'utilitzen patrons ja fets, com ara INT, LOGLEVEL, WORD. La seva descripció, així com altres patrons, es poden veure aquí. aquí

Ara, passant per aquest filtre, la nostra cadena es convertirà en un hash de tres camps: message_id, message_type, message_text.

Es mostraran a la secció de sortida.

Enrutament de missatges a la secció de sortida amb l'ordre if

A la secció de sortida, com recordem, anàvem a dividir els missatges en dos fluxos. Alguns, que són iNFO, els sortirem a la consola i, amb errors, els sortirem a un fitxer.

Com podem compartir aquests missatges? La condició del problema ja suggereix una solució; després de tot, ja tenim un camp dedicat al tipus de missatge, que només pot prendre dos valors INFO i ERROR. És sobre ell on farem una elecció utilitzant la declaració if.

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

La descripció del treball amb camps i operadors es pot trobar en aquesta secció manual oficial.

Ara, sobre la conclusió en si.

Sortida de la consola, aquí tot està clar - stdout {}

Però la sortida al fitxer: recordeu que estem executant tot això des del contenidor i perquè el fitxer en què escrivim el resultat estigui disponible des de l'exterior, hem d'obrir aquest directori a docker-compose.yml.

Total:

La secció de sortida del nostre fitxer té aquest aspecte:


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

Afegiu un volum més a docker-compose.yml per a la sortida:

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

Comencem, intentem, veiem la divisió en dos corrents.

Font: www.habr.com

Afegeix comentari