Samler tømmerstokker fra Loke

Samler tømmerstokker fra Loke

Vi i Badoo overvåker stadig nye teknologier og vurderer om vi skal bruke dem i systemet vårt eller ikke. Vi ønsker å dele en av disse studiene med samfunnet. Det er dedikert til Loki, et loggaggregeringssystem.

Loki er en løsning for lagring og visning av logger, og denne stabelen gir også et fleksibelt system for å analysere dem og sende data til Prometheus. I mai ble en annen oppdatering utgitt, som aktivt promoteres av skaperne. Vi var interessert i hva Loki kan gjøre, hvilke muligheter det gir, og i hvilken grad det kan fungere som et alternativ til ELK, stabelen vi bruker nå.

Hva er Loke

Grafana Loki er et sett med komponenter for et komplett loggesystem. I motsetning til andre lignende systemer, er Loki basert på ideen om å kun indeksere loggmetadata - etiketter (akkurat som i Prometheus), og komprimere selve loggene side om side i separate biter.

Hjemmeside, GitHub

Før jeg går inn på hva du kan gjøre med Loki, vil jeg avklare hva som menes med "ideen om kun å indeksere metadata". La oss sammenligne Loki-tilnærmingen og indekseringsmetoden i tradisjonelle løsninger, for eksempel Elasticsearch, ved å bruke 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"

Tradisjonelle systemer analyserer hele raden, inkludert felt med mange unike user_id- og item_id-verdier, og lagrer alt i store indekser. Fordelen med denne tilnærmingen er at du kan kjøre komplekse spørringer raskt, siden nesten alle dataene er i indeksen. Men du må betale for dette ved at indeksen blir stor, noe som gir seg utslag i minnekrav. Som et resultat er fulltekstindeksen for logger sammenlignbar i størrelse med selve loggene. For å raskt søke gjennom den, må indeksen lastes inn i minnet. Og jo flere logger, jo raskere øker indeksen og jo mer minne bruker den.

Loki-tilnærmingen krever at bare de nødvendige dataene trekkes ut fra strengen, hvor antallet verdier er lite. På denne måten får vi en liten indeks og kan søke i dataene ved å filtrere dem etter tid og indekserte felt, og deretter skanne resten med regulære uttrykk eller understrengsøk. Prosessen virker ikke den raskeste, men Loki deler opp forespørselen i flere deler og utfører dem parallelt, og behandler en stor mengde data på kort tid. Antallet shards og parallelle forespørsler i dem kan konfigureres; dermed avhenger mengden data som kan behandles per tidsenhet lineært av mengden ressurser som tilbys.

Denne avveiningen mellom en stor rask indeks og en liten parallell brute-force-indeks lar Loki kontrollere kostnadene for systemet. Den kan konfigureres og utvides fleksibelt etter dine behov.

Loki-stabelen består av tre komponenter: Promtail, Loki, Grafana. Promtail samler inn logger, behandler dem og sender dem til Loke. Loke beholder dem. Og Grafana kan be om data fra Loke og vise dem. Generelt kan Loki ikke bare brukes til å lagre logger og søke gjennom dem. Hele stabelen gir store muligheter for å behandle og analysere innkommende data ved hjelp av Prometheus-måten.
Du finner en beskrivelse av installasjonsprosessen her.

Loggsøk

Du kan søke i loggene i et spesielt grensesnitt Grafana — Explorer. Spørringene bruker LogQL-språket, som er veldig likt PromQL som brukes av Prometheus. I prinsippet kan det betraktes som et distribuert grep.

Søkegrensesnittet ser slik ut:

Samler tømmerstokker fra Loke

Selve spørringen består av to deler: velger og filter. Velger er et søk etter indekserte metadata (etiketter) som er tilordnet loggene, og filter er en søkestreng eller regexp som filtrerer ut postene definert av velgeren. I det gitte eksempelet: I krøllete parentes - velgeren, alt etter - filteret.

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

På grunn av måten Loki fungerer på, kan du ikke lage forespørsler uten en velger, men etiketter kan gjøres vilkårlig generiske.

Velgeren er nøkkelverdien til verdien i krøllete klammeparenteser. Du kan kombinere velgere og spesifisere forskjellige søkebetingelser ved å bruke =, !=-operatorene eller regulære uttrykk:

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

Et filter er en tekst eller regexp som vil filtrere ut alle dataene som mottas av velgeren.

Det er mulig å få ad hoc-grafer basert på de mottatte dataene i metrikkmodus. For eksempel kan du finne ut hyppigheten av forekomst i nginx-loggene til en oppføring som inneholder indeksstrengen:

Samler tømmerstokker fra Loke

En fullstendig beskrivelse av funksjonene finnes i dokumentasjonen LogQL.

Loggparsing

Det er flere måter å samle logger på:

  • Ved hjelp av Promtail, en standardkomponent i stabelen for å samle tømmerstokker.
  • Direkte fra docker-containeren ved hjelp av Loki Docker Logging Driver.
  • Bruk Fluentd eller Fluent Bit som kan sende data til Loki. I motsetning til Promtail har de ferdige parsere for nesten alle typer tømmerstokker og kan håndtere flerlinjelogger også.

Vanligvis brukes Promtail for parsing. Den gjør tre ting:

  • Finner datakilder.
  • Fest etiketter til dem.
  • Sender data til Loke.

For øyeblikket kan Promtail lese logger fra lokale filer og fra systemd journal. Den må installeres på hver maskin som tømmerstokkene samles inn fra.

Det er integrasjon med Kubernetes: Promtail finner automatisk ut statusen til klyngen gjennom Kubernetes REST API og samler inn logger fra en node, tjeneste eller pod, og legger umiddelbart ut etiketter basert på metadata fra Kubernetes (podnavn, filnavn, etc.).

Du kan også henge etiketter basert på data fra loggen ved å bruke Pipeline. Pipeline Promtail kan bestå av fire typer trinn. Flere detaljer - in offisiell dokumentasjon, vil jeg umiddelbart legge merke til noen av nyansene.

  1. Parsing stadier. Dette er stadiet av RegEx og JSON. På dette stadiet trekker vi ut data fra loggene til det såkalte ekstraherte kartet. Du kan trekke ut fra JSON ved ganske enkelt å kopiere feltene vi trenger inn i det utpakkede kartet, eller gjennom regulære uttrykk (RegEx), der navngitte grupper "tilordnes" til det utpakkede kartet. Uttrukket kart er et nøkkelverdilager, der nøkkel er navnet på feltet, og verdi er verdien fra loggene.
  2. Forvandle stadier. Dette stadiet har to alternativer: transformasjon, der vi setter transformasjonsreglene, og kilde - datakilden for transformasjonen fra det ekstraherte kartet. Hvis det ikke er et slikt felt i det utpakkede kartet, vil det bli opprettet. Dermed er det mulig å lage etiketter som ikke er basert på det utpakkede kartet. På dette stadiet kan vi manipulere dataene i det utpakkede kartet ved hjelp av en ganske kraftig golang mal. I tillegg må vi huske at det utpakkede kartet er fulllastet under parsing, noe som gjør det mulig for eksempel å sjekke verdien i det: «{{if .tag}tag value exists{end}}». Malen støtter betingelser, løkker og noen strengfunksjoner som Replace og Trim.
  3. Handlingsstadier. På dette stadiet kan du gjøre noe med det ekstraherte:
    • Lag en etikett fra de utpakkede dataene, som vil bli indeksert av Loki.
    • Endre eller angi hendelsestid fra loggen.
    • Endre dataene (loggteksten) som skal gå til Loke.
    • Lag beregninger.
  4. Filtreringsstadier. Kampstadiet, hvor vi enten kan sende poster som vi ikke trenger til /dev/null, eller sende dem for videre behandling.

Ved å bruke eksemplet med å behandle vanlige nginx-logger, vil jeg vise hvordan du kan analysere logger ved å bruke Promtail.

For testen, la oss ta et modifisert nginx jwilder/nginx-proxy:alpine-bilde og en liten demon som kan spørre seg selv via HTTP som nginx-proxy. Daemonen har flere endepunkter, som den kan gi svar av forskjellige størrelser, med forskjellige HTTP-statuser og med forskjellige forsinkelser.

Vi vil samle logger fra docker-containere, som kan finnes langs stien /var/lib/docker/containers/ / -json.log

I docker-compose.yml setter vi opp Promtail og spesifiserer banen til konfigurasjonen:

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

Legg til banen til loggene til promtail.yml (det er et "docker"-alternativ i konfigurasjonen som gjør det samme på én linje, men det ville ikke være så åpenbart):

scrape_configs:
 - job_name: containers

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

Når denne konfigurasjonen er aktivert, vil Loki motta logger fra alle containere. For å unngå dette endrer vi innstillingene til testen nginx i docker-compose.yml - legg til logging i tag-feltet:

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

Rediger promtail.yml og sett opp Pipeline. Loggene 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 trekker ut stream, attrs, attrs.tag-feltene (hvis noen) fra den innkommende JSON og legger dem inn i det utpakkede kartet.

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

Hvis det var mulig å sette tag-feltet i det utpakkede kartet, trekker vi ut navnene på bildet og beholderen ved å bruke regexp.

 - labels:
     image_name:
     container_name:

Vi tildeler etiketter. Hvis nøklene image_name og container_name finnes i de utpakkede dataene, vil verdiene deres bli tildelt de riktige etikettene.

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

Vi forkaster alle logger som ikke har etikettene image_name og container_name satt.

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

For alle logger hvis image_name er lik nginx.promtail.test, trekker vi ut loggfeltet fra kildeloggen og legger det i det utpakkede kartet med radtasten.

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

Vi sletter inndatastrengen med regulære uttrykk og trekker ut den virtuelle nginx-verten og nginx-logglinjen.

     - 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-logg med regulære uttrykk.

    - 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 hjelp av regexp bestemmer vi formålet med forespørselen: til statikk, til bilder, til API og setter den tilsvarende nøkkelen i det utpakkede kartet.

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

Ved å bruke betingede operatører i malen sjekker vi de installerte feltene i det utpakkede kartet og angir de nødvendige verdiene for request_type-feltet: foto, statisk, API. Tilordne andre hvis det mislyktes. Nå inneholder request_type forespørselstypen.

       - labels:
           api_request:
           virtual_host:
           request_type:
           status:

Vi setter etikettene api_request, virtual_host, request_type og status (HTTP-status) basert på hva vi klarte å legge inn i det utpakkede kartet.

       - output:
           source: nginx_log_row

Endre utgang. Nå går den rensede nginx-loggen fra det utpakkede kartet til Loki.

Samler tømmerstokker fra Loke

Etter å ha kjørt konfigurasjonen ovenfor, kan du se at hver oppføring er merket basert på data fra loggen.

Husk at å trekke ut etiketter med et stort antall verdier (kardinalitet) kan redusere Loki betydelig. Det vil si at du ikke skal legge inn for eksempel bruker_id i indeksen. Les mer om dette i artikkelenHvordan etiketter i Loki kan gjøre loggforespørsler raskere og enklere". Men dette betyr ikke at du ikke kan søke etter user_id uten indekser. Det er nødvendig å bruke filtre når du søker ("grab" i henhold til dataene), og indeksen fungerer her som en strømidentifikator.

Loggvisualisering

Samler tømmerstokker fra Loke

Loki kan fungere som en datakilde for Grafana-diagrammer ved hjelp av LogQL. Følgende funksjoner støttes:

  • rate - antall poster per sekund;
  • telle over tid - antall poster i det gitte området.

Det er også aggregeringsfunksjoner Sum, Avg og andre. Du kan bygge ganske komplekse grafer, for eksempel en graf over antall HTTP-feil:

Samler tømmerstokker fra Loke

Lokis standarddatakilde er litt mindre funksjonell enn Prometheus-datakilden (du kan for eksempel ikke endre forklaringen), men Loki kan kobles til som en Prometheus-typekilde. Jeg er ikke sikker på om dette er dokumentert oppførsel, men å dømme etter responsen fra utviklerne "Hvordan konfigurere Loki som Prometheus-datakilde? · Utgave #1222 · grafana/loki”, for eksempel, det er helt lovlig og Loki er fullt kompatibel med PromQL.

Legg til Loki som datakilde med typen Prometheus og legg til URL /loki:

Samler tømmerstokker fra Loke

Og du kan lage grafer, som om vi jobbet med beregninger fra Prometheus:

Samler tømmerstokker fra Loke

Jeg tror at avviket i funksjonalitet er midlertidig og utviklerne vil fikse det i fremtiden.

Samler tømmerstokker fra Loke

Beregninger

Loki gir muligheten til å trekke ut numeriske beregninger fra logger og sende dem til Prometheus. For eksempel inneholder nginx-loggen antall byte per svar, og også, med en viss modifikasjon av standard loggformat, tiden i sekunder det tok å svare. Disse dataene kan trekkes ut og sendes til Prometheus.

Legg til en annen seksjon 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

Alternativet lar deg definere og oppdatere beregninger basert på data fra det utpakkede kartet. Disse beregningene sendes ikke til Loki - de vises i Promtail /metrics-endepunktet. Prometheus må konfigureres til å motta data fra dette stadiet. I eksemplet ovenfor, for request_type="api" samler vi inn en histogramberegning. Med denne typen beregninger er det praktisk å få persentiler. For statikk og bilder samler vi inn summen av byte og antall rader der vi mottok byte for å beregne gjennomsnittet.

Les mer om beregninger her.

Åpne 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 beregningene med promtail_custom-prefikset har dukket opp:

Samler tømmerstokker fra Loke

Sette opp Prometheus. Legg til jobbinformasjon:

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

Og tegn en graf:

Samler tømmerstokker fra Loke

På denne måten kan du for eksempel finne ut de fire tregeste spørringene. Du kan også konfigurere overvåking for disse beregningene.

skalering

Loki kan være i både singel binær modus og sharded (horisontalt skalerbar modus). I det andre tilfellet kan den lagre data til skyen, og bitene og indeksen lagres separat. I versjon 1.5 er muligheten til å lagre på ett sted implementert, men det anbefales ennå ikke å bruke det i produksjon.

Samler tømmerstokker fra Loke

Chunks kan lagres i S3-kompatibel lagring, for lagring av indekser, bruk horisontalt skalerbare databaser: Cassandra, BigTable eller DynamoDB. Andre deler av Loki - Distributører (for skriving) og Querier (for spørringer) - er statsløse og skaleres også horisontalt.

På DevOpsDays Vancouver 2019-konferansen kunngjorde en av deltakerne Callum Styan at prosjektet hans med Loki har petabyte med logger med en indeks på mindre enn 1% av den totale størrelsen: "Hvordan Loki korrelerer beregninger og logger - og sparer penger".

Sammenligning av Loki og ELK

Indeksstørrelse

For å teste den resulterende indeksstørrelsen tok jeg logger fra nginx-beholderen som Pipeline ovenfor var konfigurert for. Loggfilen inneholdt 406 624 linjer med et totalt volum på 109 MB. Logger ble generert i løpet av en time, omtrent 100 poster per sekund.

Et eksempel på to linjer fra loggen:

Samler tømmerstokker fra Loke

Ved indeksering av ELK ga dette en indeksstørrelse på 30,3 MB:

Samler tømmerstokker fra Loke

I Lokis tilfelle ga dette omtrent 128 KB indeks og omtrent 3,8 MB data i biter. Det er verdt å merke seg at loggen ble kunstig generert og ikke inneholdt et bredt utvalg av data. En enkel gzip på den originale Docker JSON-loggen med data ga en komprimering på 95,4 %, og gitt at kun den rensede nginx-loggen ble sendt til Loki selv, er komprimeringen til 4 MB forståelig. Det totale antallet unike verdier for Loki-etiketter var 35, noe som forklarer den lille størrelsen på indeksen. For ELK ble loggen også ryddet. Dermed komprimerte Loki de originale dataene med 96 %, og ELK med 70 %.

Minneforbruk

Samler tømmerstokker fra Loke

Hvis vi sammenligner hele stabelen med Prometheus og ELK, så "spiser" Loke flere ganger mindre. Det er klart at Go-tjenesten bruker mindre enn Java-tjenesten, og å sammenligne størrelsen på Heap Elasticsearch JVM og det tildelte minnet for Loki er feil, men likevel er det verdt å merke seg at Loki bruker mye mindre minne. CPU-fordelen er ikke så åpenbar, men den er også tilstede.

Fart

Loke «sluker» logger raskere. Hastigheten avhenger av mange faktorer - hva slags logger, hvor sofistikert vi analyserer dem, nettverk, disk osv. - men den er definitivt høyere enn for ELK (i min test - omtrent to ganger). Dette forklares av det faktum at Loki legger mye mindre data inn i indeksen og bruker derfor mindre tid på indeksering. I dette tilfellet er situasjonen snudd med søkehastigheten: Loki bremser merkbart ned på data større enn noen få gigabyte, mens for ELK er søkehastigheten ikke avhengig av datastørrelsen.

Loggsøk

Loki er betydelig dårligere enn ELK når det gjelder loggsøkemuligheter. Grep med regulære uttrykk er en sterk ting, men det er dårligere enn en voksendatabase. Mangelen på rekkeviddespørringer, aggregering kun av etiketter, manglende evne til å søke uten etiketter - alt dette begrenser oss i å søke etter informasjon av interesse i Loki. Dette betyr ikke at ingenting kan bli funnet ved å bruke Loki, men det definerer flyten av arbeid med logger, når du først finner et problem på Prometheus-diagrammene, og deretter ser etter hva som skjedde i loggene ved å bruke disse etikettene.

grensesnitt

For det første er det vakkert (beklager, kunne ikke motstå). Grafana har et pent grensesnitt, men Kibana er mye mer funksjonelt.

Loki fordeler og ulemper

Av plussene kan det bemerkes at Loki integreres med henholdsvis Prometheus, vi får metrikk og varsling ut av boksen. Den er praktisk for å samle logger og lagre dem med Kubernetes Pods, siden den har en tjenesteoppdagelse som er arvet fra Prometheus og automatisk fester etiketter.

Av minusene - dårlig dokumentasjon. Noen ting, for eksempel funksjonene og egenskapene til Promtail, oppdaget jeg bare i prosessen med å studere koden, fordelen med åpen kildekode. En annen ulempe er de svake analysemulighetene. Loke kan for eksempel ikke analysere logger med flere linjer. Ulempene inkluderer også det faktum at Loki er en relativt ung teknologi (utgivelse 1.0 var i november 2019).

Konklusjon

Loki er en 100 % interessant teknologi som er egnet for små og mellomstore prosjekter, som lar deg løse mange problemer med loggaggregering, loggsøk, overvåking og analyse av logger.

Vi bruker ikke Loki på Badoo, fordi vi har en ELK-stack som passer oss og som har blitt bevokst med ulike tilpassede løsninger gjennom årene. For oss er snublesteinen søket i loggene. Med nesten 100 GB logger per dag er det viktig for oss å kunne finne alt og litt til og gjøre det raskt. For kartlegging og overvåking bruker vi andre løsninger som er tilpasset våre behov og integrert med hverandre. Loki-stakken har konkrete fordeler, men den vil ikke gi oss mer enn det vi har, og fordelene vil ikke akkurat oppveie kostnadene ved å migrere.

Og selv om det etter forskning ble klart at vi ikke kan bruke Loki, håper vi at dette innlegget vil hjelpe deg med å velge.

Depotet med koden brukt i artikkelen er lokalisert her.

Kilde: www.habr.com

Legg til en kommentar