Indsamling af logs fra Loke

Indsamling af logs fra Loke

Vi hos Badoo overvåger konstant nye teknologier og vurderer, om vi skal bruge dem i vores system. Vi ønsker at dele en af ​​disse undersøgelser med samfundet. Det er dedikeret til Loki, et log-aggregationssystem.

Loki er en løsning til lagring og visning af logfiler, og denne stak giver også et fleksibelt system til at analysere dem og sende data til Prometheus. I maj blev endnu en opdatering udgivet, som aktivt fremmes af skaberne. Vi var interesserede i, hvad Loki kan, hvilke funktioner det giver, og i hvilket omfang det kan fungere som et alternativ til ELK, den stak, vi bruger nu.

Hvad er Loke

Grafana Loki er et sæt komponenter til et komplet logningssystem. I modsætning til andre lignende systemer er Loki baseret på ideen om kun at indeksere logmetadata - etiketter (ligesom i Prometheus) og komprimere selve logfilerne side om side i separate bidder.

Hjemmeside, GitHub

Før jeg kommer ind på, hvad du kan gøre med Loki, vil jeg præcisere, hvad der menes med "ideen om kun at indeksere metadata". Lad os sammenligne Loki-tilgangen og indekseringstilgangen i traditionelle løsninger, såsom Elasticsearch, ved at bruge eksemplet på en linje fra nginx-loggen:

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"

Traditionelle systemer analyserer hele rækken, inklusive felter med mange unikke user_id og item_id værdier, og gemmer alt i store indekser. Fordelen ved denne tilgang er, at du kan køre komplekse forespørgsler hurtigt, da næsten alle data er i indekset. Men du skal betale for dette ved, at indekset bliver stort, hvilket udmønter sig i hukommelseskrav. Som følge heraf er fuldtekstindekset for logfiler i størrelse sammenligneligt med selve logfilerne. For hurtigt at søge igennem det, skal indekset indlæses i hukommelsen. Og jo flere logfiler, jo hurtigere stiger indekset og jo mere hukommelse bruger det.

Loki-tilgangen kræver, at kun de nødvendige data ekstraheres fra strengen, hvis antal værdier er lille. På denne måde får vi et lille indeks og kan søge i dataene ved at filtrere dem efter tid og indekserede felter, og derefter scanne resten med regulære udtryk eller substring-søgninger. Processen virker ikke den hurtigste, men Loki deler anmodningen op i flere dele og eksekverer dem parallelt og behandler en stor mængde data på kort tid. Antallet af shards og parallelle anmodninger i dem kan konfigureres; mængden af ​​data, der kan behandles pr. tidsenhed, afhænger således lineært af mængden af ​​tilførte ressourcer.

Denne afvejning mellem et stort hurtigt indeks og et lille parallelt brute-force-indeks giver Loki mulighed for at kontrollere omkostningerne ved systemet. Den kan konfigureres og udvides fleksibelt efter dine behov.

Loki-stakken består af tre komponenter: Promtail, Loki, Grafana. Promtail indsamler logfiler, behandler dem og sender dem til Loke. Loke beholder dem. Og Grafana kan anmode om data fra Loke og vise det. Generelt kan Loki ikke kun bruges til at gemme logfiler og søge gennem dem. Hele stakken giver store muligheder for at behandle og analysere indgående data ved hjælp af Prometheus-måden.
En beskrivelse af installationsprocessen kan findes her.

Logsøgning

Du kan søge i logfilerne i en speciel grænseflade Grafana — Explorer. Forespørgslerne bruger LogQL-sproget, som minder meget om PromQL, der bruges af Prometheus. I princippet kan det opfattes som et distribueret grep.

Søgegrænsefladen ser sådan ud:

Indsamling af logs fra Loke

Selve forespørgslen består af to dele: vælger og filter. Selector er en søgning efter indekserede metadata (etiketter), der er tildelt til logfiler, og filter er en søgestreng eller regexp, der frafiltrerer poster defineret af vælgeren. I det givne eksempel: I krøllede parenteser - vælgeren, alt efter - filteret.

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

På grund af den måde Loki fungerer på, kan du ikke lave anmodninger uden en vælger, men etiketter kan gøres vilkårligt generiske.

Vælgeren er nøgleværdien for værdien i krøllede klammeparenteser. Du kan kombinere vælgere og angive forskellige søgebetingelser ved at bruge =, != operatorerne eller regulære udtryk:

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

Et filter er en tekst eller regexp, der vil bortfiltrere alle data modtaget af vælgeren.

Det er muligt at få ad hoc-grafer baseret på de modtagne data i metric-tilstanden. For eksempel kan du finde ud af hyppigheden af ​​forekomst i nginx-logfilerne for en post, der indeholder indeksstrengen:

Indsamling af logs fra Loke

En fuldstændig beskrivelse af funktionerne kan findes i dokumentationen LogQL.

Log parsing

Der er flere måder at indsamle logfiler på:

  • Ved hjælp af Promtail, en standardkomponent i stakken til indsamling af træstammer.
  • Direkte fra docker-containeren ved hjælp af Loki Docker Logging Driver.
  • Brug Fluentd eller Fluent Bit, som kan sende data til Loki. I modsætning til Promtail har de færdige parsere til næsten enhver type log og kan også håndtere multiline logs.

Normalt bruges Promtail til parsing. Den gør tre ting:

  • Finder datakilder.
  • Sæt etiketter på dem.
  • Sender data til Loke.

I øjeblikket kan Promtail læse logfiler fra lokale filer og fra systemd journal. Det skal installeres på hver maskine, hvorfra træstammer indsamles.

Der er integration med Kubernetes: Promtail finder automatisk ud af klyngens tilstand gennem Kubernetes REST API og indsamler logfiler fra en node, service eller pod, og sender straks etiketter baseret på metadata fra Kubernetes (podnavn, filnavn osv.).

Du kan også hænge etiketter baseret på data fra loggen ved hjælp af Pipeline. Pipeline Promtail kan bestå af fire typer faser. Flere detaljer - in officiel dokumentation, vil jeg straks bemærke nogle af nuancerne.

  1. Parsing stadier. Dette er stadiet af RegEx og JSON. På dette stadie trækker vi data fra loggene ind i det såkaldte udtrukne kort. Du kan udtrække fra JSON ved blot at kopiere de felter, vi har brug for, ind i det udtrukne kort, eller gennem regulære udtryk (RegEx), hvor navngivne grupper "mappes" til det udtrukne kort. Udtrukket kort er et nøgleværdilager, hvor nøgle er navnet på feltet, og værdi er dets værdi fra logfilerne.
  2. Transforme stadier. Denne fase har to muligheder: transformation, hvor vi sætter transformationsreglerne, og source - datakilden for transformationen fra det udtrukne kort. Hvis der ikke er et sådant felt i det udtrukne kort, vil det blive oprettet. Det er således muligt at oprette etiketter, der ikke er baseret på det udtrukne kort. På dette stadium kan vi manipulere dataene i det udtrukne kort ved hjælp af en ret kraftig golang skabelon. Derudover skal vi huske, at det udtrukne kort er fuldt indlæst under parsing, hvilket gør det muligt for eksempel at tjekke værdien i det: "{{if .tag}tag value exists{end}}". Skabelonen understøtter betingelser, sløjfer og nogle strengfunktioner såsom Replace og Trim.
  3. Handlingsstadier. På dette stadium kan du gøre noget med det udtrukne:
    • Opret en etiket fra de udtrukne data, som vil blive indekseret af Loki.
    • Skift eller indstil begivenhedstiden fra loggen.
    • Skift de data (logtekst), der vil gå til Loke.
    • Opret metrics.
  4. Filtreringsstadier. Kampfasen, hvor vi enten kan sende poster, som vi ikke behøver til /dev/null, eller sende dem til videre behandling.

Ved at bruge eksemplet med at behandle almindelige nginx-logfiler, vil jeg vise, hvordan du kan parse logfiler ved hjælp af Promtail.

Til testen, lad os tage et modificeret nginx jwilder/nginx-proxy:alpine billede og en lille dæmon, der kan forespørge sig selv via HTTP som nginx-proxy. Dæmonen har flere endepunkter, som den kan give svar af forskellig størrelse, med forskellige HTTP-statusser og med forskellige forsinkelser.

Vi vil indsamle logfiler fra docker-containere, som kan findes langs stien /var/lib/docker/containers/ / -json.log

I docker-compose.yml sætter vi Promtail op og angiver stien til konfigurationen:

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'
 // ...

Tilføj stien til logfilerne til promtail.yml (der er en "docker"-indstilling i konfigurationen, der gør det samme på én linje, men det ville ikke være så indlysende):

scrape_configs:
 - job_name: containers

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

Når denne konfiguration er aktiveret, vil Loki modtage logfiler fra alle containere. For at undgå dette ændrer vi indstillingerne for test nginx i docker-compose.yml - tilføj logning til tagfeltet:

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

Rediger promtail.yml og opsæt Pipeline. Logfilerne er som følger:

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

pipeline stadier:

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

Vi udtrækker stream, attrs, attrs.tag felterne (hvis nogen) fra den indkommende JSON og sætter dem ind i det udpakkede kort.

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

Hvis det var muligt at sætte tag-feltet i det udtrukne kort, udtrækker vi ved hjælp af regexp navnene på billedet og beholderen.

 - labels:
     image_name:
     container_name:

Vi tildeler etiketter. Hvis nøglerne image_name og container_name findes i de udtrukne data, vil deres værdier blive tildelt de relevante etiketter.

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

Vi kasserer alle logfiler, der ikke har etiketterne image_name og container_name sat.

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

For alle logfiler, hvis image_name er lig med nginx.promtail.test, udtrækker vi logfeltet fra kildeloggen og sætter det i det udpakkede kort med rækketasten.

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

Vi rydder inputstrengen med regulære udtryk og trækker den virtuelle nginx-vært og nginx-loglinjen ud.

     - 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.]+)")?

Parse nginx-log med regulære udtryk.

    - 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>[^/?.]+).*$

Parse request_url. Ved hjælp af regexp bestemmer vi formålet med anmodningen: til statik, til fotos, til API og indstiller den tilsvarende nøgle i det udtrukne kort.

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

Ved at bruge betingede operatører i skabelonen kontrollerer vi de installerede felter i det udtrukne kort og indstiller de nødvendige værdier for feltet request_type: foto, statisk, API. Tildel andre, hvis det mislykkedes. Nu indeholder request_type anmodningstypen.

       - labels:
           api_request:
           virtual_host:
           request_type:
           status:

Vi indstillede etiketterne api_request, virtual_host, request_type og status (HTTP-status) baseret på, hvad vi formåede at sætte i det udtrukne kort.

       - output:
           source: nginx_log_row

Skift output. Nu går den rensede nginx-log fra det udtrukne kort til Loke.

Indsamling af logs fra Loke

Efter at have kørt ovenstående konfiguration, kan du se, at hver post er mærket baseret på data fra loggen.

Husk, at udtrækning af etiketter med et stort antal værdier (kardinalitet) kan bremse Loki betydeligt. Det vil sige, at du ikke skal indsætte f.eks. bruger_id i indekset. Læs mere om dette i artiklenHvordan etiketter i Loki kan gøre logforespørgsler hurtigere og nemmere". Men det betyder ikke, at du ikke kan søge efter user_id uden indekser. Det er nødvendigt at bruge filtre ved søgning (“grab” i henhold til dataene), og indekset fungerer her som en stream identifikator.

Log visualisering

Indsamling af logs fra Loke

Loki kan fungere som en datakilde for Grafana-diagrammer ved hjælp af LogQL. Følgende funktioner understøttes:

  • rate - antal poster pr. sekund;
  • tælle over tid - antallet af poster i det givne interval.

Der er også aggregeringsfunktioner Sum, Avg og andre. Du kan bygge ret komplekse grafer, for eksempel en graf over antallet af HTTP-fejl:

Indsamling af logs fra Loke

Lokis standarddatakilde er en smule mindre funktionel end Prometheus-datakilden (du kan f.eks. ikke ændre forklaringen), men Loki kan tilsluttes som en Prometheus-typekilde. Jeg er ikke sikker på, om dette er dokumenteret adfærd, men at dømme efter responsen fra udviklerne "Hvordan konfigureres Loki som Prometheus-datakilde? · Udgave #1222 · grafana/loki”, for eksempel er det helt lovligt, og Loki er fuldt ud kompatibel med PromQL.

Tilføj Loki som datakilde med typen Prometheus og tilføj URL /loki:

Indsamling af logs fra Loke

Og du kan lave grafer, som om vi arbejdede med metrics fra Prometheus:

Indsamling af logs fra Loke

Jeg tror, ​​at uoverensstemmelsen i funktionalitet er midlertidig, og udviklerne vil rette op på det i fremtiden.

Indsamling af logs fra Loke

Metrics

Loki giver mulighed for at udtrække numeriske metrikker fra logfiler og sende dem til Prometheus. For eksempel indeholder nginx-loggen antallet af bytes pr. svar, og også, med en vis ændring af standardlogformatet, den tid i sekunder, det tog at svare. Disse data kan udtrækkes og sendes til Prometheus.

Tilføj endnu et afsnit til 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

Indstillingen giver dig mulighed for at definere og opdatere metrics baseret på data fra det udtrukne kort. Disse metrics sendes ikke til Loki - de vises i Promtail /metrics-slutpunktet. Prometheus skal konfigureres til at modtage data fra denne fase. I ovenstående eksempel indsamler vi for request_type="api" en histogrammetrik. Med denne type metrik er det praktisk at få percentiler. For statik og fotos indsamler vi summen af ​​bytes og antallet af rækker, hvor vi modtog bytes for at beregne gennemsnittet.

Læs mere om metrics her.

Åbn en port på Promtail:

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

Vi sørger for, at metrics med promtail_custom-præfikset er blevet vist:

Indsamling af logs fra Loke

Opsætning af Prometheus. Tilføj jobprofil:

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

Og tegn en graf:

Indsamling af logs fra Loke

På denne måde kan du f.eks. finde ud af de fire langsomste anmodninger. Du kan også konfigurere overvågning for disse metrics.

Skalering

Loki kan være i både enkelt binær tilstand og shard (vandret skalerbar tilstand). I det andet tilfælde kan den gemme data til skyen, og chunks og indeks gemmes separat. I version 1.5 er muligheden for at gemme ét sted implementeret, men det anbefales endnu ikke at bruge det i produktionen.

Indsamling af logs fra Loke

Chunks kan gemmes i S3-kompatibelt lager, til lagring af indekser, brug horisontalt skalerbare databaser: Cassandra, BigTable eller DynamoDB. Andre dele af Loki - Distributører (til skrivning) og Querier (til forespørgsler) - er statsløse og skaleres også vandret.

På DevOpsDays Vancouver 2019-konferencen annoncerede en af ​​deltagerne Callum Styan, at hans projekt med Loki har petabytes af logfiler med et indeks på mindre end 1% af den samlede størrelse: "Hvordan Loki korrelerer metrics og logs - og sparer dig penge".

Sammenligning af Loke og ELK

Indeks størrelse

For at teste den resulterende indeksstørrelse tog jeg logfiler fra nginx-beholderen, som Pipeline ovenfor var konfigureret til. Logfilen indeholdt 406 linjer med en samlet volumen på 624 MB. Logfiler blev genereret inden for en time, cirka 109 poster i sekundet.

Et eksempel på to linjer fra loggen:

Indsamling af logs fra Loke

Ved indeksering af ELK gav dette en indeksstørrelse på 30,3 MB:

Indsamling af logs fra Loke

I Lokis tilfælde gav dette omkring 128 KB indeks og omkring 3,8 MB data i bidder. Det er værd at bemærke, at loggen blev kunstigt genereret og ikke indeholdt en bred vifte af data. En simpel gzip på den originale Docker JSON-log med data gav en komprimering på 95,4%, og givet at kun den rensede nginx-log blev sendt til Loki selv, er komprimeringen til 4 MB forståelig. Det samlede antal unikke værdier for Loki-etiketter var 35, hvilket forklarer den lille størrelse af indekset. For ELK blev loggen også ryddet. Således komprimerede Loke de originale data med 96 % og ELK med 70 %.

Hukommelsesforbrug

Indsamling af logs fra Loke

Hvis vi sammenligner hele stakken af ​​Prometheus og ELK, så "spiser" Loke flere gange mindre. Det er klart, at Go-tjenesten bruger mindre end Java-tjenesten, og at sammenligne størrelsen af ​​Heap Elasticsearch JVM og den allokerede hukommelse til Loki er forkert, men ikke desto mindre er det værd at bemærke, at Loki bruger meget mindre hukommelse. Dens CPU-fordel er ikke så indlysende, men den er også til stede.

hastighed

Loke "sluger" log hurtigere. Hastigheden afhænger af mange faktorer - hvilken slags logs, hvor sofistikeret vi analyserer dem, netværk, disk osv. - men den er bestemt højere end ELK's (i min test - omkring to gange). Dette forklares ved, at Loki lægger meget mindre data ind i indekset og derfor bruger mindre tid på indeksering. I dette tilfælde er situationen vendt med søgehastigheden: Loki sænker mærkbart farten på data større end et par gigabyte, mens for ELK er søgehastigheden ikke afhængig af datastørrelsen.

Logsøgning

Loki er betydeligt ringere end ELK med hensyn til logsøgningsmuligheder. Grep med regulære udtryk er en stærk ting, men det er ringere end en voksendatabase. Manglen på rækkeviddeforespørgsler, aggregering kun af etiketter, manglende evne til at søge uden etiketter - alt dette begrænser os i at søge efter information af interesse i Loki. Dette betyder ikke, at der ikke kan findes noget ved hjælp af Loki, men det definerer strømmen af ​​arbejde med logfiler, når du først finder et problem på Prometheus-kortene og derefter ser efter, hvad der skete i logfilerne ved hjælp af disse etiketter.

grænseflade

For det første er det smukt (undskyld, kunne ikke lade være). Grafana har en flot grænseflade, men Kibana er meget mere funktionel.

Loki fordele og ulemper

Af plusserne kan det bemærkes, at Loki integrerer med henholdsvis Prometheus, vi får metrics og alarmering ud af boksen. Det er praktisk til at indsamle logfiler og gemme dem med Kubernetes Pods, da det har en serviceopdagelse, der er arvet fra Prometheus og automatisk vedhæfter etiketter.

Af minusser - dårlig dokumentation. Nogle ting, såsom funktionerne og mulighederne i Promtail, opdagede jeg kun i processen med at studere koden, fordelen ved open source. En anden ulempe er de svage parsing-funktioner. For eksempel kan Loke ikke parse multiline logs. Ulemperne inkluderer også, at Loki er en relativt ung teknologi (udgivelse 1.0 var i november 2019).

Konklusion

Loki er en 100% interessant teknologi, der er velegnet til små og mellemstore projekter, som giver dig mulighed for at løse mange problemer med log-aggregering, logsøgning, overvågning og analyse af logs.

Vi bruger ikke Loki hos Badoo, for vi har en ELK stack, der passer til os, og som er blevet tilgroet med forskellige specialløsninger gennem årene. For os er anstødssten søgningen i loggene. Med næsten 100 GB logs om dagen er det vigtigt for os at kunne finde alt og lidt til og gøre det hurtigt. Til kortlægning og overvågning bruger vi andre løsninger, der er skræddersyet til vores behov og integreret med hinanden. Loki-stakken har håndgribelige fordele, men den vil ikke give os mere, end hvad vi har, og dens fordele vil ikke ligefrem opveje omkostningerne ved at migrere.

Og selvom det efter research blev klart, at vi ikke kan bruge Loki, håber vi, at dette indlæg vil hjælpe dig med at vælge.

Depotet med den kode, der er brugt i artiklen, er placeret her.

Kilde: www.habr.com

Tilføj en kommentar