Aplikasi praktikal ELK. Menyediakan logstash

Pengenalan

Semasa menggunakan sistem lain, kami berhadapan dengan keperluan untuk memproses sejumlah besar log yang berbeza. ELK dipilih sebagai alat. Artikel ini akan membincangkan pengalaman kami dalam menyediakan tindanan ini.

Kami tidak menetapkan matlamat untuk menerangkan semua keupayaannya, tetapi kami ingin menumpukan perhatian khusus untuk menyelesaikan masalah praktikal. Ini disebabkan oleh fakta bahawa walaupun terdapat sejumlah besar dokumentasi dan imej siap, terdapat banyak masalah, sekurang-kurangnya kami menemuinya.

Kami menggunakan timbunan melalui docker-compose. Selain itu, kami mempunyai docker-compose.yml yang ditulis dengan baik, yang membolehkan kami menaikkan timbunan hampir tanpa masalah. Dan nampaknya kepada kami bahawa kemenangan sudah hampir, kini kami akan mengubahnya sedikit untuk memenuhi keperluan kami dan itu sahaja.

Malangnya, percubaan untuk mengkonfigurasi sistem untuk menerima dan memproses log daripada aplikasi kami tidak berjaya serta-merta. Oleh itu, kami memutuskan bahawa ia patut mengkaji setiap komponen secara berasingan, dan kemudian kembali kepada sambungan mereka.

Jadi, kami mulakan dengan logstash.

Persekitaran, penempatan, menjalankan Logstash dalam bekas

Untuk penempatan kami menggunakan docker-compose; eksperimen yang diterangkan di sini telah dijalankan pada MacOS dan Ubuntu 18.0.4.

Imej logstash yang telah didaftarkan dalam docker-compose.yml asal kami ialah docker.elastic.co/logstash/logstash:6.3.2

Kami akan menggunakannya untuk eksperimen.

Kami menulis docker-compose.yml yang berasingan untuk menjalankan logstash. Sudah tentu, adalah mungkin untuk melancarkan imej dari baris arahan, tetapi kami telah menyelesaikan masalah tertentu, di mana kami menjalankan segala-galanya dari docker-compose.

Secara ringkas tentang fail konfigurasi

Seperti berikut daripada huraian, logstash boleh dijalankan sama ada untuk satu saluran, dalam hal ini ia perlu lulus fail *.conf, atau untuk beberapa saluran, dalam hal ini ia perlu lulus fail pipelines.yml, yang seterusnya , akan memaut ke fail .conf untuk setiap saluran.
Kami mengambil jalan kedua. Ia kelihatan kepada kami lebih universal dan berskala. Oleh itu, kami mencipta pipelines.yml, dan membuat direktori pipelines di mana kami akan meletakkan fail .conf untuk setiap saluran.

Di dalam bekas terdapat fail konfigurasi lain - logstash.yml. Kami tidak menyentuhnya, kami menggunakannya seadanya.

Jadi, struktur direktori kami:

Aplikasi praktikal ELK. Menyediakan logstash

Untuk menerima data input, buat masa ini kami menganggap bahawa ini adalah tcp pada port 5046, dan untuk output kami akan menggunakan stdout.

Berikut ialah konfigurasi mudah untuk pelancaran pertama. Kerana tugas awal adalah untuk melancarkan.

Jadi, kami mempunyai docker-compose.yml ini

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

Apa yang kita lihat di sini?

  1. Rangkaian dan jilid telah diambil daripada docker-compose.yml asal (yang di mana keseluruhan timbunan dilancarkan) dan saya fikir ia tidak banyak menjejaskan gambaran keseluruhan di sini.
  2. Kami mencipta satu perkhidmatan logstash daripada imej docker.elastic.co/logstash/logstash:6.3.2 dan namakannya logstash_one_channel.
  3. Kami memajukan port 5046 di dalam bekas, ke port dalaman yang sama.
  4. Kami memetakan fail konfigurasi paip kami ./config/pipelines.yml kepada fail /usr/share/logstash/config/pipelines.yml di dalam bekas, di mana logstash akan mengambilnya dan menjadikannya baca sahaja, untuk berjaga-jaga.
  5. Kami memetakan direktori ./config/pipelines, di mana kami mempunyai fail dengan tetapan saluran, ke dalam direktori /usr/share/logstash/config/pipelines dan juga menjadikannya baca sahaja.

Aplikasi praktikal ELK. Menyediakan logstash

Fail Pipelines.yml

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

Satu saluran dengan pengecam HABR dan laluan ke fail konfigurasinya diterangkan di sini.

Dan akhirnya fail "./config/pipelines/habr_pipeline.conf"

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

Jangan pergi ke penerangannya buat masa ini, mari cuba jalankan:

docker-compose up

Apa yang kita nampak?

Bekas telah bermula. Kami boleh menyemak operasinya:

echo '13123123123123123123123213123213' | nc localhost 5046

Dan kita melihat tindak balas dalam konsol kontena:

Aplikasi praktikal ELK. Menyediakan logstash

Tetapi pada masa yang sama, kita juga melihat:

logstash_one_channel | [2019-04-29T11:28:59,790][ERROR][logstash.licensechecker.licensereader] Tidak dapat mendapatkan maklumat lesen daripada pelayan lesen {:message=>β€œElasticsearch Unreachable: [http://elasticsearch:9200/][Manticore ::ResolutionFailure] elasticsearch", ...

logstash_one_channel | [2019-04-29T11:28:59,894][INFO ][logstash.pipeline ] Saluran paip berjaya dimulakan {:pipeline_id=>".monitoring-logstash", :thread=>"# "}

logstash_one_channel | [2019-04-29T11:28:59,988][INFO ][logstash.agent ] Saluran paip berjalan {: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 dipasang pada Logstash tetapi bukan pada Elasticsearch. Sila pasang X-Pack pada Elasticsearch untuk menggunakan ciri pemantauan. Ciri-ciri lain mungkin tersedia.
logstash_one_channel | [2019-04-29T11:29:00,526][INFO ][logstash.agent ] Berjaya memulakan titik akhir API Logstash {:port=>9600}
logstash_one_channel | [2019-04-29T11:29:04,478][INFO ][logstash.outputs.elasticsearch] Menjalankan pemeriksaan kesihatan untuk melihat sama ada sambungan Elasticsearch berfungsi {:healthcheck_url=>http://elasticsearch:9200/, :path=> "/"}
logstash_one_channel | [2019-04-29T11:29:04,487][WARN ][logstash.outputs.elasticsearch] Cuba menghidupkan semula sambungan ke tika ES yang mati, tetapi mendapat ralat. {:url=>β€œelasticsearch: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] Menjalankan pemeriksaan kesihatan untuk melihat sama ada sambungan Elasticsearch berfungsi {:healthcheck_url=>http://elasticsearch:9200/, :path=> "/"}
logstash_one_channel | [2019-04-29T11:29:04,710][WARN ][logstash.licensechecker.licensereader] Cuba menghidupkan semula sambungan kepada contoh ES yang mati, tetapi mendapat ralat. {:url=>β€œelasticsearch:9200/", :error_type=>LogStash::Outputs::ElasticSearch::HttpClient::Pool::HostUnreachableError, :error=>"Elasticsearch Unreachable: [http://elasticsearch:9200/][Manticore::ResolutionFailure] elasticsearch"}

Dan log kami menjalar sepanjang masa.

Di sini saya telah menyerlahkan dalam warna hijau mesej bahawa saluran paip telah berjaya dilancarkan, dalam warna merah mesej ralat dan dalam warna kuning mesej tentang percubaan untuk menghubungi elasticsearch: 9200.
Ini berlaku kerana logstash.conf, disertakan dalam imej, mengandungi semakan untuk ketersediaan elasticsearch. Lagipun, logstash menganggap bahawa ia berfungsi sebagai sebahagian daripada timbunan Elk, tetapi kami memisahkannya.

Ia mungkin untuk bekerja, tetapi ia tidak mudah.

Penyelesaiannya adalah untuk melumpuhkan semakan ini melalui pembolehubah persekitaran XPACK_MONITORING_ENABLED.

Mari buat perubahan pada docker-compose.yml dan jalankannya semula:

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

Sekarang, semuanya baik-baik saja. Bekas sedia untuk eksperimen.

Kita boleh menaip semula dalam konsol seterusnya:

echo '13123123123123123123123213123213' | nc localhost 5046

Dan lihat:

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

Bekerja dalam satu saluran

Jadi kami melancarkan. Kini anda sebenarnya boleh meluangkan masa untuk mengkonfigurasi logstash itu sendiri. Jangan sentuh fail pipelines.yml buat masa ini, mari lihat apa yang kita boleh dapat dengan bekerja dengan satu saluran.

Saya mesti mengatakan bahawa prinsip umum bekerja dengan fail konfigurasi saluran diterangkan dengan baik dalam manual rasmi, di sini di sini
Jika anda ingin membaca dalam bahasa Rusia, kami menggunakan yang ini artikel(tetapi sintaks pertanyaan di sana sudah lama, kita perlu mengambil kira perkara ini).

Mari kita pergi secara berurutan dari bahagian Input. Kami telah melihat kerja pada TCP. Apa lagi yang menarik di sini?

Uji mesej menggunakan degupan jantung

Terdapat peluang yang menarik untuk menjana mesej ujian automatik.
Untuk melakukan ini, anda perlu mendayakan pemalam heartbean di bahagian input.

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

Hidupkan ia, mula menerima sekali seminit

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

Jika kita ingin menerima lebih kerap, kita perlu menambah parameter selang.
Ini adalah bagaimana kami akan menerima mesej setiap 10 saat.

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

Mendapatkan semula data daripada fail

Kami juga memutuskan untuk melihat mod fail. Jika ia berfungsi dengan baik dengan fail, maka mungkin tiada ejen diperlukan, sekurang-kurangnya untuk kegunaan tempatan.

Menurut penerangan, mod pengendalian harus serupa dengan ekor -f, i.e. membaca baris baharu atau, sebagai pilihan, membaca keseluruhan fail.

Jadi apa yang kita nak dapat:

  1. Kami mahu menerima baris yang dilampirkan pada satu fail log.
  2. Kami mahu menerima data yang ditulis kepada beberapa fail log, sambil dapat memisahkan apa yang diterima dari mana.
  3. Kami ingin memastikan bahawa apabila logstash dimulakan semula, ia tidak menerima data ini lagi.
  4. Kami ingin menyemak bahawa jika logstash dimatikan, dan data terus ditulis ke fail, maka apabila kami menjalankannya, kami akan menerima data ini.

Untuk menjalankan percubaan, mari tambah satu lagi baris pada docker-compose.yml, membuka direktori tempat kami meletakkan fail.

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

Dan tukar bahagian input dalam habr_pipeline.conf

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

Mari mulakan:

docker-compose up

Untuk membuat dan menulis fail log kami akan menggunakan arahan:


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

Ya, ia berkesan!

Pada masa yang sama, kami melihat bahawa kami telah menambah medan laluan secara automatik. Ini bermakna pada masa hadapan, kami akan dapat menapis rekod dengannya.

Mari cuba lagi:

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

Dan sekarang ke fail lain:

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

Hebat! Fail telah diambil, laluan telah ditentukan dengan betul, semuanya baik-baik saja.

Hentikan logstash dan mulakan semula. Jom tunggu. senyap. Itu. Kami tidak menerima rekod ini lagi.

Dan kini percubaan yang paling berani.

Pasang logstash dan laksanakan:

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

Jalankan logstash sekali lagi dan lihat:

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

Hooray! Semuanya diangkat.

Tetapi kami mesti memberi amaran kepada anda tentang perkara berikut. Jika bekas logstash dipadamkan (docker stop logstash_one_channel && docker rm logstash_one_channel), maka tiada apa yang akan diambil. Kedudukan fail sehingga ia dibaca disimpan di dalam bekas. Jika anda menjalankannya dari awal, ia hanya akan menerima baris baharu.

Membaca fail sedia ada

Katakan kami melancarkan logstash buat kali pertama, tetapi kami sudah mempunyai log dan kami ingin memprosesnya.
Jika kami menjalankan logstash dengan bahagian input yang kami gunakan di atas, kami tidak akan mendapat apa-apa. Hanya baris baharu akan diproses oleh logstash.

Agar baris daripada fail sedia ada ditarik ke atas, anda harus menambah baris tambahan pada bahagian input:

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

Lebih-lebih lagi, terdapat nuansa: ini hanya menjejaskan fail baharu yang belum dilihat oleh logstash. Untuk fail yang sama yang sudah berada dalam bidang paparan logstash, ia telah mengingati saiznya dan kini hanya akan mengambil entri baharu di dalamnya.

Mari berhenti di sini dan kaji bahagian input. Masih terdapat banyak pilihan, tetapi itu sudah cukup untuk kami untuk percubaan selanjutnya buat masa ini.

Penghalaan dan Transformasi Data

Mari cuba selesaikan masalah berikut, katakan kita mempunyai mesej daripada satu saluran, sebahagian daripadanya bermaklumat, dan ada juga mesej ralat. Mereka berbeza mengikut tag. Ada yang INFO, ada yang RALAT.

Kita perlu memisahkan mereka di pintu keluar. Itu. Kami menulis mesej maklumat dalam satu saluran, dan mesej ralat di saluran lain.

Untuk melakukan ini, beralih dari bahagian input ke penapis dan output.

Menggunakan bahagian penapis, kami akan menghuraikan mesej masuk, mendapatkan cincang (pasangan nilai kunci) daripadanya, yang kami sudah boleh bekerjasama, i.e. buka mengikut keadaan. Dan dalam bahagian output, kami akan memilih mesej dan menghantar setiap satu ke salurannya sendiri.

Menghuraikan mesej dengan grok

Untuk menghuraikan rentetan teks dan mendapatkan satu set medan daripadanya, terdapat pemalam khas di bahagian penapis - grok.

Tanpa menetapkan matlamat saya untuk memberikan penerangan terperinci mengenainya di sini (untuk ini saya merujuk kepada dokumentasi rasmi), saya akan berikan contoh mudah saya.

Untuk melakukan ini, anda perlu memutuskan format rentetan input. Saya mempunyai mereka seperti ini:

1 mesej INFO1
2 Mesej RALAT2

Itu. Pengecam didahulukan, kemudian INFO/ERROR, kemudian beberapa perkataan tanpa ruang.
Ia tidak sukar, tetapi sudah cukup untuk memahami prinsip operasi.

Jadi, dalam bahagian penapis pemalam grok, kita mesti menentukan corak untuk menghuraikan rentetan kita.

Ia akan kelihatan seperti ini:

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

Pada asasnya ia adalah ungkapan biasa. Corak siap digunakan, seperti INT, LOGLEVEL, WORD. Penerangan mereka, serta corak lain, boleh didapati di sini di sini

Sekarang, melalui penapis ini, rentetan kami akan bertukar menjadi cincang tiga medan: message_id, message_type, message_text.

Mereka akan dipaparkan dalam bahagian output.

Menghalakan mesej ke bahagian output menggunakan arahan if

Dalam bahagian output, seperti yang kita ingat, kita akan membahagikan mesej kepada dua aliran. Beberapa - iaitu iNFO, akan dikeluarkan ke konsol, dan dengan ralat, kami akan keluarkan ke fail.

Bagaimanakah kita memisahkan mesej ini? Keadaan masalah sudah mencadangkan penyelesaian - lagipun, kami sudah mempunyai medan message_type khusus, yang hanya boleh mengambil dua nilai: INFO dan ERROR. Atas dasar inilah kita akan membuat pilihan menggunakan pernyataan if.

if [message_type] == "ERROR" {
        # Π—Π΄Π΅ΡΡŒ Π²Ρ‹Π²ΠΎΠ΄ΠΈΠΌ Π² Ρ„Π°ΠΉΠ»
       } else
     {
      # Π—Π΄Π΅ΡΡŒ Π²Ρ‹Π²ΠΎΠ΄ΠΈΠΌ Π² stdout
    }

Penerangan bekerja dengan bidang dan operator boleh didapati dalam bahagian ini manual rasmi.

Sekarang, tentang kesimpulan sebenar itu sendiri.

Output konsol, semuanya jelas di sini - stdout {}

Tetapi output kepada fail - ingat bahawa kita menjalankan semua ini dari bekas dan agar fail di mana kita menulis hasilnya boleh diakses dari luar, kita perlu membuka direktori ini dalam docker-compose.yml.

Jumlah:

Bahagian output fail kami kelihatan seperti ini:


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

Dalam docker-compose.yml kami menambah volum lain untuk output:

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

Kami melancarkannya, mencubanya dan melihat pembahagian kepada dua aliran.

Sumber: www.habr.com

Tambah komen