Colectarea jurnalelor de la Loki

Colectarea jurnalelor de la Loki

La Badoo, monitorizăm în mod constant noile tehnologii și evaluăm dacă merită folosite în sistemul nostru. Am dori să împărtășim comunității unul dintre aceste studii. Este dedicat lui Loki, un sistem de agregare a jurnalelor.

Loki este o soluție pentru stocarea și vizualizarea jurnalelor, iar această stivă oferă, de asemenea, un sistem flexibil pentru analizarea acestora și trimiterea datelor către Prometheus. În mai, a fost lansată o altă actualizare, care este promovată activ de creatori. Ne-a interesat ce poate face Loki, ce capabilități oferă și în ce măsură poate acționa ca o alternativă la ELK, stiva pe care o folosim acum.

Ce este Loki

Grafana Loki este un set de componente pentru un sistem complet de lucru cu bușteni. Spre deosebire de alte sisteme similare, Loki se bazează pe ideea de a indexa doar metadatele de jurnal - etichete (la fel ca în Prometheus) și de a comprima jurnalele în bucăți separate.

Pagina principală, GitHub

Înainte de a intra în ceea ce puteți face cu Loki, vreau să clarific ce înțelegem prin „ideea de a indexa doar metadate”. Să comparăm abordarea Loki și abordarea indexării în soluții tradiționale, cum ar fi Elasticsearch, folosind exemplul unei linii din jurnalul nginx:

172.19.0.4 - - [01/Jun/2020:12:05:03 +0000] "GET /purchase?user_id=75146478&item_id=34234 HTTP/1.1" 500 8102 "-" "Stub_Bot/3.0" "0.001"

Sistemele tradiționale analizează întregul rând, inclusiv câmpurile cu un număr mare de valori unice user_id și item_id, și stochează totul în indecși mari. Avantajul acestei abordări este că puteți rula rapid interogări complexe, deoarece aproape toate datele sunt în index. Dar acest lucru are un cost prin faptul că indexul devine mare, ceea ce se traduce în cerințe de memorie. Ca rezultat, indexul jurnalului de text complet este comparabil ca dimensiune cu jurnalele în sine. Pentru a căuta rapid prin el, indexul trebuie să fie încărcat în memorie. Și cu cât mai multe loguri, cu atât indexul crește mai repede și cu atât consumă mai multă memorie.

Abordarea Loki presupune ca dintr-un șir de caractere să fie extrase doar datele necesare, al cărui număr de valori este mic. În acest fel obținem un index mic și putem căuta datele prin filtrarea lor după timp și după câmpuri indexate, apoi scanând restul cu expresii regulate sau căutare subșiruri. Procesul nu pare a fi cel mai rapid, dar Loki împarte cererea în mai multe părți și le execută în paralel, procesând o cantitate mare de date într-un timp scurt. Numărul de fragmente și solicitări paralele din acestea este configurabil; astfel, cantitatea de date care poate fi procesată pe unitatea de timp depinde liniar de cantitatea de resurse furnizate.

Acest compromis între un index mare și rapid și un index de forță brută paralel mic permite lui Loki să controleze costul sistemului. Poate fi configurat și extins flexibil în funcție de nevoi.

Stack-ul Loki este format din trei componente: Promtail, Loki, Grafana. Promtail colectează jurnalele, le procesează și le trimite lui Loki. Loki le păstrează. Iar Grafana poate solicita date de la Loki și le poate afișa. În general, Loki poate fi folosit nu numai pentru stocarea jurnalelor și căutarea prin ele. Întreaga stivă oferă oportunități excelente pentru procesarea și analiza datelor primite folosind modul Prometheus.
O descriere a procesului de instalare poate fi găsită aici.

Căutare în jurnal

Puteți căuta jurnalele într-o interfață specială Grafana - Explorer. Interogările folosesc limbajul LogQL, care este foarte asemănător cu PromQL folosit în Prometheus. În principiu, poate fi gândit ca un grep distribuit.

Interfața de căutare arată astfel:

Colectarea jurnalelor de la Loki

Cererea în sine constă din două părți: selector și filtru. Selectorul este o căutare care utilizează metadate indexate (etichete) care sunt atribuite jurnalelor, iar filtrul este un șir de căutare sau expresie regulată care filtrează înregistrările definite de selector. În exemplul dat: În bretele există un selector, totul după este un filtru.

{image_name="nginx.promtail.test"} |= "index"

Datorită modului în care funcționează Loki, nu puteți face interogări fără un selector, dar etichetele pot fi făcute la fel de generale după cum doriți.

Un selector este o valoare cheie-valoare între acolade. Puteți combina selectoare și specifica diferite condiții de căutare folosind operatorii =, != sau expresii regulate:

{instance=~"kafka-[23]",name!="kafka-dev"} 
// Найдёт логи с лейблом instance, имеющие значение kafka-2, kafka-3, и исключит dev 

Un filtru este text sau expresie regulată care va filtra toate datele primite de selector.

Este posibil să se obțină grafice ad-hoc pe baza datelor primite în modul metrics. De exemplu, puteți afla cât de des apare o intrare care conține indexul șirurilor în jurnalele nginx:

Colectarea jurnalelor de la Loki

O descriere completă a capabilităților poate fi găsită în documentație LogQL.

Analiza jurnalelor

Există mai multe moduri de a colecta jurnalele:

  • Folosind Promtail, o componentă standard a stivei pentru colectarea buștenilor.
  • Direct din containerul docker folosind Driver de înregistrare Loki Docker.
  • Utilizați Fluentd sau Fluent Bit, care poate trimite date către Loki. Spre deosebire de Promtail, au parsere gata făcute pentru aproape orice tip de jurnal și pot gestiona, de asemenea, jurnalele cu mai multe linii.

De obicei, Promtail este folosit pentru parsare. Face trei lucruri:

  • Găsește surse de date.
  • Le atașează etichete.
  • Trimite date lui Loki.

În prezent, Promtail poate citi jurnalele din fișierele locale și din jurnalul systemd. Acesta trebuie să fie instalat pe fiecare mașină de la care sunt colectate jurnalele.

Există integrare cu Kubernetes: Promtail automat, prin API-ul Kubernetes REST, recunoaște starea cluster-ului și colectează jurnalele de la un nod, serviciu sau pod, postând imediat etichete pe baza metadatelor din Kubernetes (numele podului, numele fișierului etc.) .

De asemenea, puteți agăța etichete pe baza datelor din jurnal folosind Pipeline. Pipeline Promtail poate consta din patru tipuri de etape. Mai multe detalii în documentație oficială, voi observa imediat câteva nuanțe.

  1. Etape de analiză. Aceasta este etapa RegEx și JSON. În această etapă, extragem datele din jurnale în așa-numita hartă extrasă. Putem extrage din JSON prin simpla copiere a câmpurilor de care avem nevoie în harta extrasă sau prin expresii regulate (RegEx), unde grupurile numite sunt „mapate” în harta extrasă. Harta extrasă este un magazin cheie-valoare, unde cheia este numele câmpului, iar valoarea este valoarea sa din jurnale.
  2. Transformați etapele. Această etapă are două opțiuni: transform, unde setăm regulile de transformare și sursă - sursa de date pentru transformare din harta extrasă. Dacă nu există un astfel de câmp în harta extrasă, acesta va fi creat. În acest fel este posibil să se creeze etichete care nu se bazează pe harta extrasă. În această etapă putem manipula datele din harta extrasă folosind un instrument destul de puternic Şablon Golang. În plus, trebuie să ne amintim că harta extrasă este încărcată în întregime în timpul parsării, ceea ce face posibilă, de exemplu, verificarea valorii din ea: „{{if .tag}tag value exists{end}}”. Șablonul acceptă condiții, bucle și unele funcții șir, cum ar fi Înlocuire și Trim.
  3. Etape de acțiune. În acest moment, puteți face ceva cu conținutul extras:
    • Creați o etichetă din datele extrase, care va fi indexată de Loki.
    • Modificați sau setați ora evenimentului din jurnal.
    • Schimbați datele (textul jurnalului) care vor merge la Loki.
    • Creați valori.
  4. Etape de filtrare. Etapa de potrivire, în care putem fie să trimitem intrări pe care nu trebuie să le /dev/null, fie să le trimitem pentru procesare ulterioară.

Folosind un exemplu de procesare a jurnalelor nginx obișnuite, voi arăta cum puteți analiza jurnalele folosind Promtail.

Pentru test, să luăm ca nginx-proxy o imagine nginx modificată jwilder/nginx-proxy:alpine și un mic demon care se poate întreba prin HTTP. Demonul are mai multe puncte finale, cărora le poate oferi răspunsuri de diferite dimensiuni, cu diferite stări HTTP și cu diferite întârzieri.

Vom colecta jurnalele din containerele docker, care pot fi găsite de-a lungul căii /var/lib/docker/containers/ / -json.log

În docker-compose.yml configuram Promtail și specificăm calea către config:

promtail:
  image: grafana/promtail:1.4.1
 // ...
 volumes:
   - /var/lib/docker/containers:/var/lib/docker/containers:ro
   - promtail-data:/var/lib/promtail/positions
   - ${PWD}/promtail/docker.yml:/etc/promtail/promtail.yml
 command:
   - '-config.file=/etc/promtail/promtail.yml'
 // ...

Adăugați calea către jurnalele la promtail.yml (există o opțiune „docker” în configurație, care face același lucru într-o singură linie, dar nu ar fi atât de clar):

scrape_configs:
 - job_name: containers

   static_configs:
       labels:
         job: containerlogs
         __path__: /var/lib/docker/containers/*/*log  # for linux only

Când această configurație este activată, jurnalele din toate containerele vor fi trimise către Loki. Pentru a evita acest lucru, modificăm setările testului nginx în docker-compose.yml - adăugați un câmp de etichetă de înregistrare:

proxy:
 image: nginx.test.v3
//…
 logging:
   driver: "json-file"
   options:
     tag: "{{.ImageName}}|{{.Name}}"

Editarea promtail.yml și configurarea Pipeline. Intrarea include jurnalele de următorul tip:

{"log":"u001b[0;33;1mnginx.1    | u001b[0mnginx.test 172.28.0.3 - - [13/Jun/2020:23:25:50 +0000] "GET /api/index HTTP/1.1" 200 0 "-" "Stub_Bot/0.1" "0.096"n","stream":"stdout","attrs":{"tag":"nginx.promtail.test|proxy.prober"},"time":"2020-06-13T23:25:50.66740443Z"}
{"log":"u001b[0;33;1mnginx.1    | u001b[0mnginx.test 172.28.0.3 - - [13/Jun/2020:23:25:50 +0000] "GET /200 HTTP/1.1" 200 0 "-" "Stub_Bot/0.1" "0.000"n","stream":"stdout","attrs":{"tag":"nginx.promtail.test|proxy.prober"},"time":"2020-06-13T23:25:50.702925272Z"}

Etapa conductei:

 - json:
     expressions:
       stream: stream
       attrs: attrs
       tag: attrs.tag

Extragem câmpurile stream, attrs, attrs.tag (dacă există) din JSON primit și le punem în harta extrasă.

 - regex:
     expression: ^(?P<image_name>([^|]+))|(?P<container_name>([^|]+))$
     source: "tag"

Dacă am reușit să punem câmpul de etichetă în harta extrasă, atunci folosind regexp extragem numele imaginii și ale containerului.

 - labels:
     image_name:
     container_name:

Atribuim etichete. Dacă cheile image_name și container_name sunt găsite în datele extrase, atunci valorile lor vor fi atribuite etichetelor corespunzătoare.

 - match:
     selector: '{job="docker",container_name="",image_name=""}'
     action: drop

Renunțăm la toate jurnalele care nu au instalate etichetele nume_imagine și nume_container.

  - match:
     selector: '{image_name="nginx.promtail.test"}'
     stages:
       - json:
           expressions:
             row: log

Pentru toate jurnalele al căror nume_imagine este nginx.promtail.test, extrageți câmpul jurnal din jurnalul sursă și puneți-l în harta extrasă cu cheia rândului.

  - regex:
         # suppress forego colors
         expression: .+nginx.+|.+[0m(?P<virtual_host>[a-z_.-]+) +(?P<nginxlog>.+)
         source: logrow

Ștergem linia de intrare cu expresii regulate și scoatem gazda virtuală nginx și linia de jurnal nginx.

     - regex:
         source: nginxlog
         expression: ^(?P<ip>[w.]+) - (?P<user>[^ ]*) [(?P<timestamp>[^ ]+).*] "(?P<method>[^ ]*) (?P<request_url>[^ ]*) (?P<request_http_protocol>[^ ]*)" (?P<status>[d]+) (?P<bytes_out>[d]+) "(?P<http_referer>[^"]*)" "(?P<user_agent>[^"]*)"( "(?P<response_time>[d.]+)")?

Analizați jurnalul nginx folosind expresii regulate.

    - regex:
           source: request_url
           expression: ^.+.(?P<static_type>jpg|jpeg|gif|png|ico|css|zip|tgz|gz|rar|bz2|pdf|txt|tar|wav|bmp|rtf|js|flv|swf|html|htm)$
     - regex:
           source: request_url
           expression: ^/photo/(?P<photo>[^/?.]+).*$
       - regex:
           source: request_url
           expression: ^/api/(?P<api_request>[^/?.]+).*$

Să analizăm request_url. Folosind regexp determinăm scopul solicitării: la date statice, la fotografii, la API și setăm cheia corespunzătoare în harta extrasă.

       - template:
           source: request_type
           template: "{{if .photo}}photo{{else if .static_type}}static{{else if .api_request}}api{{else}}other{{end}}"

Folosind operatori condiționali în Template, verificăm câmpurile instalate în harta extrasă și setăm valorile necesare pentru câmpul request_type: fotografie, static, API. Atribuiți altul dacă nu reușește. request_type conține acum tipul de solicitare.

       - labels:
           api_request:
           virtual_host:
           request_type:
           status:

Am stabilit etichetele api_request, virtual_host, request_type și status (starea HTTP) în funcție de ceea ce am reușit să punem în harta extrasă.

       - output:
           source: nginx_log_row

Schimbați ieșirea. Acum jurnalul nginx curățat de pe harta extrasă merge la Loki.

Colectarea jurnalelor de la Loki

După rularea configurației de mai sus, puteți vedea că fiecărei intrări i se atribuie etichete pe baza datelor din jurnal.

Un lucru de reținut este că preluarea etichetelor cu un număr mare de valori (cardinalitate) poate încetini semnificativ Loki. Adică nu ar trebui să puneți, de exemplu, user_id în index. Citiți mai multe despre asta în articolul „Cum etichetele din Loki pot face interogările de jurnal mai rapide și mai ușoare" Dar asta nu înseamnă că nu poți căuta după user_id fără indexuri. Trebuie să utilizați filtre atunci când căutați („prindeți” datele), iar indexul de aici acționează ca un identificator de flux.

Vizualizarea jurnalelor

Colectarea jurnalelor de la Loki

Loki poate acționa ca o sursă de date pentru graficele Grafana folosind LogQL. Sunt acceptate următoarele caracteristici:

  • rate — numărul de înregistrări pe secundă;
  • numărare în timp — numărul de înregistrări din intervalul specificat.

Există și funcții de agregare Sum, Avg și altele. Puteți construi grafice destul de complexe, de exemplu un grafic al numărului de erori HTTP:

Colectarea jurnalelor de la Loki

Sursa de date standard Loki este oarecum redusă în funcționalitate în comparație cu sursa de date Prometheus (de exemplu, nu puteți schimba legenda), dar Loki poate fi conectat ca sursă cu tipul Prometheus. Nu sunt sigur dacă acesta este un comportament documentat, dar judecând după răspunsul dezvoltatorilor "Cum se configurează Loki ca sursă de date Prometheus? · Numărul #1222 · grafana/loki”, de exemplu, este complet legal, iar Loki este pe deplin compatibil cu PromQL.

Adăugați Loki ca sursă de date cu tipul Prometheus și adăugați URL /loki:

Colectarea jurnalelor de la Loki

Și putem face grafice, de parcă am lucra cu metrici de la Prometheus:

Colectarea jurnalelor de la Loki

Cred că discrepanța în funcționalitate este temporară și dezvoltatorii vor corecta acest lucru în viitor.

Colectarea jurnalelor de la Loki

Metrici

Loki oferă posibilitatea de a extrage valori numerice din jurnale și de a le trimite către Prometheus. De exemplu, jurnalul nginx conține numărul de octeți per răspuns, precum și, cu o anumită modificare a formatului standard de jurnal, timpul în secunde necesar pentru a răspunde. Aceste date pot fi extrase și trimise către Prometheus.

Adăugați o altă secțiune la promtail.yml:

- match:
   selector: '{request_type="api"}'
   stages:
     - metrics:
         http_nginx_response_time:
           type: Histogram
           description: "response time ms"
           source: response_time
           config:
             buckets: [0.010,0.050,0.100,0.200,0.500,1.0]
- match:
   selector: '{request_type=~"static|photo"}'
   stages:
     - metrics:
         http_nginx_response_bytes_sum:
           type: Counter
           description: "response bytes sum"
           source: bytes_out
           config:
             action: add
         http_nginx_response_bytes_count:
           type: Counter
           description: "response bytes count"
           source: bytes_out
           config:
             action: inc

Opțiunea vă permite să definiți și să actualizați valorile pe baza datelor de pe harta extrasă. Aceste valori nu sunt trimise către Loki - ele apar în punctul final Promtail /metrics. Prometheus trebuie configurat să primească datele primite în această etapă. În exemplul de mai sus, pentru request_type=“api” colectăm o metrică de histogramă. Cu acest tip de metrici este convenabil să se obțină percentile. Pentru statice și foto, colectăm suma octeților și numărul de rânduri în care am primit octeți pentru a calcula media.

Citiți mai multe despre valori aici.

Deschideți portul pe Promtail:

promtail:
     image: grafana/promtail:1.4.1
     container_name: monitoring.promtail
     expose:
       - 9080
     ports:
       - "9080:9080"

Asigurați-vă că apar valorile cu prefixul promtail_custom:

Colectarea jurnalelor de la Loki

Înființarea lui Prometeu. Adăugați promtail:

- job_name: 'promtail'
 scrape_interval: 10s
 static_configs:
   - targets: ['promtail:9080']

Și desenăm un grafic:

Colectarea jurnalelor de la Loki

Astfel, puteți afla, de exemplu, cele mai lente patru interogări. De asemenea, puteți configura monitorizarea acestor valori.

Scalare

Loki poate fi fie în modul binar unic, fie în modul fragmentat (mod scalabil orizontal). În al doilea caz, poate salva date în cloud, iar bucățile și indexul sunt stocate separat. Versiunea 1.5 introduce capacitatea de a stoca într-un singur loc, dar nu este încă recomandat să o folosești în producție.

Colectarea jurnalelor de la Loki

Bucățile pot fi stocate în stocare compatibilă cu S3, iar bazele de date scalabile orizontal pot fi folosite pentru a stoca indici: Cassandra, BigTable sau DynamoDB. Alte părți ale Loki - Distribuitori (pentru scriere) și Interogare (pentru interogări) - sunt apatride și, de asemenea, sunt scalate pe orizontală.

La conferința DevOpsDays Vancouver 2019, unul dintre participanții, Callum Styan, a anunțat că, împreună cu Loki, proiectul său are petabytes de jurnale cu un indice mai mic de 1% din dimensiunea totală: „Cum corelează Loki valorile și jurnalele – și vă economisește bani".

Comparația dintre Loki și ELK

Dimensiunea indexului

Pentru a testa dimensiunea indexului rezultat, am luat jurnalele din containerul nginx pentru care a fost configurat Pipeline-ul de mai sus. Fișierul jurnal conținea 406 de linii cu un volum total de 624 MB. Jurnalele au fost generate într-o oră, aproximativ 109 de intrări pe secundă.

Exemplu de două rânduri din jurnal:

Colectarea jurnalelor de la Loki

Când a fost indexat de ELK, aceasta a dat o dimensiune de index de 30,3 MB:

Colectarea jurnalelor de la Loki

În cazul lui Loki, acest lucru a dus la aproximativ 128 KB de index și aproximativ 3,8 MB de date în bucăți. Este de remarcat faptul că jurnalul a fost generat artificial și nu a avut o mare varietate de date. Un simplu gzip pe jurnalul Docker JSON original cu date a dat o compresie de 95,4% și, ținând cont de faptul că doar jurnalul nginx curățat a fost trimis către Loki însuși, compresia de până la 4 MB este de înțeles. Numărul total de valori unice pentru etichetele Loki a fost de 35, ceea ce explică dimensiunea mică a indexului. Pentru ELK, jurnalul a fost șters. Astfel, Loki a comprimat datele originale cu 96%, iar ELK cu 70%.

Consumul de memorie

Colectarea jurnalelor de la Loki

Dacă comparăm întreaga stivă Prometheus și ELK, atunci Loki „mănâncă” de câteva ori mai puțin. Este clar că un serviciu Go consumă mai puțin decât un serviciu Java și compararea dimensiunii JVM Heap Elasticsearch și a memoriei alocate pentru Loki este incorectă, dar cu toate acestea merită remarcat faptul că Loki utilizează mult mai puțină memorie. Avantajul său CPU nu este atât de evident, dar este și prezent.

Viteză

Loki „devorează” buștenii mai repede. Viteza depinde de mulți factori - ce fel de jurnale sunt, cât de sofisticați le analizăm, rețeaua, discul etc. - dar este cu siguranță mai mare decât cea a ELK (în testul meu - de aproximativ două ori). Acest lucru se explică prin faptul că Loki pune mult mai puține date în index și, în consecință, petrece mai puțin timp pe indexare. Cu viteza de căutare, situația este inversă: Loki încetinește vizibil pe datele mai mari de câțiva gigaocteți, în timp ce viteza de căutare a ELK nu depinde de dimensiunea datelor.

Căutare în jurnal

Loki este semnificativ inferior ELK în ceea ce privește capabilitățile de căutare în jurnal. Grep cu expresii regulate este puternic, dar este inferior unei baze de date mature. Lipsa interogărilor de interval, agregarea doar după etichete, incapacitatea de a căuta fără etichete - toate acestea ne limitează în căutarea informațiilor de interes în Loki. Acest lucru nu înseamnă că nu poate fi găsit nimic folosind Loki, dar definește fluxul de lucru cu jurnalele atunci când găsiți pentru prima dată o problemă în diagramele Prometheus și apoi utilizați aceste etichete pentru a căuta ce s-a întâmplat în jurnale.

interfață

În primul rând, este frumos (scuze, nu am putut rezista). Grafana are o interfață frumoasă, dar Kibana este mult mai bogată în funcții.

Avantaje și dezavantaje ale lui Loki

Unul dintre avantaje este că Loki se integrează cu Prometheus, așa că obținem valori și alerte din cutie. Este convenabil pentru colectarea jurnalelor și stocarea lor din Podurile Kubernetes, deoarece are serviciul de descoperire moștenit de la Prometheus și atașează automat etichete.

Dezavantajul este documentarea slabă. Unele lucruri, de exemplu, caracteristicile și capacitățile lui Promtail, le-am descoperit doar în procesul de studiu a codului, din fericire, este open-source. Un alt dezavantaj este capacitatea slabă de analiză. De exemplu, Loki nu poate analiza jurnalele cu mai multe linii. Un alt dezavantaj este că Loki este o tehnologie relativ tânără (versiunea 1.0 a fost în noiembrie 2019).

Concluzie

Loki este o tehnologie 100% interesantă, potrivită pentru proiectele mici și mijlocii, permițându-vă să rezolvați multe probleme de agregare a jurnalelor, căutare a jurnalelor, monitorizare și analiză a jurnalelor.

Nu folosim Loki în Badoo pentru că avem o stivă ELK care ni se potrivește și care a fost copleșită cu diverse soluții personalizate de-a lungul anilor. Pentru noi, piatra de poticnire este căutarea prin bușteni. Cu aproape 100 GB de jurnale pe zi, este important pentru noi să putem găsi totul și puțin mai mult și să o facem rapid. Pentru diagrame și monitorizare, folosim alte soluții care sunt adaptate nevoilor noastre și integrate unele cu altele. Stack-ul Loki are beneficii tangibile, dar nu ne va oferi mai mult decât avem deja, iar beneficiile sale cu siguranță nu vor depăși costul migrației.

Și deși în urma cercetărilor a devenit clar că nu putem folosi Loki, sperăm că această postare te va ajuta în alegerea ta.

Se află depozitul cu codul folosit în articol aici.

Sursa: www.habr.com

Adauga un comentariu