Raccolta dei registri da Loki

Raccolta dei registri da Loki

Noi di Badoo monitoriamo costantemente le nuove tecnologie e valutiamo se utilizzarle o meno nel nostro sistema. Vogliamo condividere uno di questi studi con la comunità. È dedicato a Loki, un sistema di aggregazione dei log.

Loki è una soluzione per archiviare e visualizzare i log e questo stack fornisce anche un sistema flessibile per analizzarli e inviare dati a Prometheus. A maggio è stato rilasciato un altro aggiornamento, promosso attivamente dai creatori. Eravamo interessati a cosa può fare Loki, quali opportunità offre e in che misura può fungere da alternativa a ELK, lo stack che utilizziamo ora.

Cos'è Loki

Grafana Loki è un insieme di componenti per un sistema di registrazione completo. A differenza di altri sistemi simili, Loki si basa sull'idea di indicizzare solo i metadati dei log - etichette (proprio come in Prometheus) e di comprimere i log stessi fianco a fianco in blocchi separati.

Homepage, GitHub

Prima di entrare nel dettaglio di cosa si può fare con Loki, voglio chiarire cosa si intende per "l'idea di indicizzare solo i metadati". Confrontiamo l'approccio Loki e l'approccio di indicizzazione nelle soluzioni tradizionali, come Elasticsearch, utilizzando l'esempio di una riga dal log 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"

I sistemi tradizionali analizzano l'intera riga, inclusi i campi con molti valori user_id e item_id univoci, e archiviano tutto in indici di grandi dimensioni. Il vantaggio di questo approccio è che è possibile eseguire rapidamente query complesse, poiché quasi tutti i dati si trovano nell'indice. Ma questo deve essere pagato in quanto l'indice diventa grande, il che si traduce in requisiti di memoria. Di conseguenza, l'indice full-text dei log ha dimensioni paragonabili a quelle dei log stessi. Per poterlo cercare rapidamente, l'indice deve essere caricato in memoria. E maggiore è il numero di log, più velocemente aumenta l'indice e maggiore sarà il consumo di memoria.

L'approccio Loki richiede che dalla stringa, il cui numero di valori sia piccolo, vengano estratti solo i dati necessari. In questo modo otteniamo un piccolo indice e possiamo cercare i dati filtrandoli per ora e campi indicizzati, quindi scansionando il resto con espressioni regolari o ricerche di sottostringhe. Il processo non sembra dei più veloci, ma Loki suddivide la richiesta in più parti e le esegue in parallelo, elaborando una grande quantità di dati in poco tempo. Il numero di frammenti e di richieste parallele in essi contenuti è configurabile; pertanto, la quantità di dati che possono essere elaborati per unità di tempo dipende linearmente dalla quantità di risorse fornite.

Questo compromesso tra un grande indice veloce e un piccolo indice parallelo di forza bruta consente a Loki di controllare il costo del sistema. Può essere configurato ed espanso in modo flessibile in base alle vostre esigenze.

Lo stack Loki è composto da tre componenti: Promtail, Loki, Grafana. Promtail raccoglie i log, li elabora e li invia a Loki. Loki li tiene. E Grafana può richiedere dati a Loki e mostrarli. In generale, Loki può essere utilizzato non solo per archiviare registri e cercarli. L'intero stack offre grandi opportunità per l'elaborazione e l'analisi dei dati in ingresso utilizzando il metodo Prometheus.
È possibile trovare una descrizione del processo di installazione qui.

Ricerca registro

Puoi cercare i registri in un'interfaccia speciale Grafana — Explorer. Le query utilizzano il linguaggio LogQL, che è molto simile al PromQL utilizzato da Prometheus. In linea di principio, può essere pensato come un grep distribuito.

L'interfaccia di ricerca si presenta così:

Raccolta dei registri da Loki

La query stessa è composta da due parti: selettore e filtro. Il selettore è una ricerca in base ai metadati indicizzati (etichette) assegnati ai log e il filtro è una stringa di ricerca o un'espressione regolare che filtra i record definiti dal selettore. Nell'esempio sopra: tra parentesi graffe - selettore, tutto dopo - filtro.

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

A causa del modo in cui funziona Loki, non è possibile effettuare richieste senza un selettore, ma le etichette possono essere rese arbitrariamente generiche.

Il selettore è il valore-chiave del valore tra parentesi graffe. Puoi combinare selettori e specificare diverse condizioni di ricerca utilizzando gli operatori =, != o le espressioni regolari:

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

Un filtro è un testo o un'espressione regolare che filtrerà tutti i dati ricevuti dal selettore.

È possibile ottenere grafici ad hoc basati sui dati ricevuti in modalità metrica. Ad esempio, puoi scoprire la frequenza con cui si verifica nei log nginx una voce contenente la stringa dell'indice:

Raccolta dei registri da Loki

Una descrizione completa delle funzionalità è disponibile nella documentazione LogQL.

Analisi del registro

Esistono diversi modi per raccogliere i log:

  • Con l'aiuto di Promtail, un componente standard dello stack per la raccolta dei log.
  • Direttamente dal contenitore docker utilizzando Driver di registrazione Loki Docker.
  • Usa Fluentd o Fluent Bit che possono inviare dati a Loki. A differenza di Promtail, dispongono di parser già pronti per quasi tutti i tipi di log e possono gestire anche log multilinea.

Di solito Promtail viene utilizzato per l'analisi. Fa tre cose:

  • Trova origini dati.
  • Attacca loro delle etichette.
  • Invia dati a Loki.

Attualmente Promtail può leggere i registri dai file locali e dal journal systemd. Deve essere installato su ogni macchina da cui vengono raccolti i log.

C'è integrazione con Kubernetes: Promtail rileva automaticamente lo stato del cluster attraverso l'API REST di Kubernetes e raccoglie i log da un nodo, servizio o pod, pubblicando immediatamente etichette basate sui metadati di Kubernetes (nome pod, nome file, ecc.).

Puoi anche appendere etichette in base ai dati del registro utilizzando Pipeline. La pipeline Promtail può essere costituita da quattro tipi di fasi. Maggiori dettagli - in documentazione ufficiale, noterò immediatamente alcune sfumature.

  1. Fasi di analisi. Questa è la fase di RegEx e JSON. In questa fase estraiamo i dati dai log nella cosiddetta mappa estratta. Puoi estrarre da JSON semplicemente copiando i campi di cui abbiamo bisogno nella mappa estratta o tramite espressioni regolari (RegEx), dove i gruppi denominati vengono “mappati” nella mappa estratta. La mappa estratta è un archivio di valori-chiave, dove chiave è il nome del campo e valore è il suo valore dai log.
  2. Fasi di trasformazione. Questa fase ha due opzioni: trasformazione, in cui impostiamo le regole di trasformazione, e origine: l'origine dati per la trasformazione dalla mappa estratta. Se non è presente alcun campo di questo tipo nella mappa estratta, verrà creato. Pertanto, è possibile creare etichette che non siano basate sulla mappa estratta. In questa fase, possiamo manipolare i dati nella mappa estratta utilizzando uno strumento abbastanza potente modello golang. Inoltre, dobbiamo ricordare che la mappa estratta viene caricata completamente durante l'analisi, il che rende possibile, ad esempio, verificare il valore in essa contenuto: “{{if .tag}il valore del tag esiste{end}}”. Il modello supporta condizioni, cicli e alcune funzioni di stringa come Sostituisci e Taglia.
  3. Fasi dell'azione. A questo punto, puoi fare qualcosa con l'estratto:
    • Crea un'etichetta dai dati estratti, che verrà indicizzata da Loki.
    • Modificare o impostare l'ora dell'evento dal registro.
    • Cambia i dati (testo di registro) che andranno a Loki.
    • Crea metriche.
  4. Fasi di filtraggio. La fase di corrispondenza, in cui possiamo inviare i record di cui non abbiamo bisogno a /dev/null o inviarli per un'ulteriore elaborazione.

Utilizzando l'esempio dell'elaborazione dei normali log nginx, mostrerò come analizzare i log utilizzando Promtail.

Per il test, prendiamo un'immagine nginx jwilder/nginx-proxy:alpine modificata e un piccolo demone che può interrogarsi tramite HTTP come nginx-proxy. Il demone ha diversi endpoint, ai quali può dare risposte di diverse dimensioni, con diversi stati HTTP e con diversi ritardi.

Raccoglieremo i log dai contenitori docker, che possono essere trovati lungo il percorso /var/lib/docker/containers/ / -json.log

In docker-compose.yml impostiamo Promtail e specifichiamo il percorso della configurazione:

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

Aggiungi il percorso ai log a promtail.yml (c'è un'opzione "docker" nella configurazione che fa lo stesso in una riga, ma non sarebbe così ovvio):

scrape_configs:
 - job_name: containers

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

Quando questa configurazione è abilitata, Loki riceverà i log da tutti i contenitori. Per evitare ciò, modifichiamo le impostazioni del test nginx in docker-compose.yml - aggiungiamo logging al campo tag:

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

Modifica promtail.yml e configura la pipeline. I log sono i seguenti:

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

fasi della pipeline:

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

Estraiamo i campi stream, attrs, attrs.tag (se presenti) dal JSON in entrata e li inseriamo nella mappa estratta.

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

Se fosse possibile inserire il campo tag nella mappa estratta, utilizzando l'espressione regolare estraiamo i nomi dell'immagine e del contenitore.

 - labels:
     image_name:
     container_name:

Assegniamo etichette. Se nei dati estratti vengono trovate le chiavi image_name e container_name, i loro valori verranno assegnati alle etichette appropriate.

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

Scartiamo tutti i log per cui non sono impostate le etichette nome_immagine e nome_contenitore.

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

Per tutti i log il cui image_name è uguale a nginx.promtail.test, estraiamo il campo log dal log di origine e lo inseriamo nella mappa estratta con la chiave di riga.

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

Cancellamo la stringa di input con espressioni regolari ed estraiamo l'host virtuale nginx e la riga di registro 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.]+)")?

Analizza il log nginx con espressioni regolari.

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

Analizza request_url. Con l'aiuto di regexp, determiniamo lo scopo della richiesta: statica, foto, API e impostiamo la chiave corrispondente nella mappa estratta.

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

Utilizzando gli operatori condizionali in Template, controlliamo i campi installati nella mappa estratta e impostiamo i valori richiesti per il campo request_type: photo, static, API. Assegna altro se fallisce. Ora request_type contiene il tipo di richiesta.

       - labels:
           api_request:
           virtual_host:
           request_type:
           status:

Impostiamo le etichette api_request, virtual_host, request_type e status (stato HTTP) in base a cosa siamo riusciti a inserire nella mappa estratta.

       - output:
           source: nginx_log_row

Cambia uscita. Ora il registro nginx ripulito dalla mappa estratta va a Loki.

Raccolta dei registri da Loki

Dopo aver eseguito la configurazione precedente, puoi vedere che ogni voce è etichettata in base ai dati del registro.

Tieni presente che l'estrazione di etichette con un numero elevato di valori (cardinalità) può rallentare notevolmente Loki. Cioè, non dovresti inserire nell'indice, ad esempio, user_id. Maggiori informazioni su questo argomento nell'articoloIn che modo le etichette in Loki possono rendere le query di registro più rapide e semplici". Ma questo non significa che non puoi cercare per user_id senza indici. È necessario utilizzare i filtri durante la ricerca ("catturare" in base ai dati) e l'indice qui funge da identificatore del flusso.

Visualizzazione del registro

Raccolta dei registri da Loki

Loki può fungere da origine dati per i grafici Grafana utilizzando LogQL. Sono supportate le seguenti funzionalità:

  • rate - numero di record al secondo;
  • conteggio nel tempo: il numero di record nell'intervallo specificato.

Esistono anche funzioni di aggregazione Sum, Avg e altre. Puoi creare grafici piuttosto complessi, ad esempio un grafico del numero di errori HTTP:

Raccolta dei registri da Loki

L'origine dati predefinita di Loki è un po' meno funzionale dell'origine dati Prometheus (ad esempio, non è possibile modificare la legenda), ma Loki può essere collegato come origine di tipo Prometeo. Non sono sicuro che si tratti di un comportamento documentato, ma a giudicare dalla risposta degli sviluppatori "Come configurare Loki come origine dati Prometheus? · Numero 1222 · grafana/loki”, ad esempio, è perfettamente legale e Loki è pienamente compatibile con PromQL.

Aggiungi Loki come origine dati con tipo Prometeo e aggiungi URL /loki:

Raccolta dei registri da Loki

E puoi creare grafici, come se stessimo lavorando con le metriche di Prometheus:

Raccolta dei registri da Loki

Penso che la discrepanza nella funzionalità sia temporanea e gli sviluppatori la risolveranno in futuro.

Raccolta dei registri da Loki

Metrica

Loki offre la possibilità di estrarre metriche numeriche dai log e inviarle a Prometheus. Ad esempio, il registro nginx contiene il numero di byte per risposta e anche, con una certa modifica del formato di registro standard, il tempo in secondi impiegato per rispondere. Questi dati possono essere estratti e inviati a Prometheus.

Aggiungi un'altra sezione a 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

L'opzione consente di definire e aggiornare le metriche in base ai dati della mappa estratta. Queste metriche non vengono inviate a Loki: appaiono nell'endpoint Promtail /metrics. Prometheus deve essere configurato per ricevere dati da questa fase. Nell'esempio precedente, per request_type="api" raccogliamo una metrica dell'istogramma. Con questo tipo di metriche è conveniente ottenere i percentili. Per i dati statici e le foto, raccogliamo la somma dei byte e il numero di righe in cui abbiamo ricevuto i byte per calcolare la media.

Ulteriori informazioni sulle metriche qui.

Apri una porta su Promtail:

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

Ci assicuriamo che siano apparse le metriche con il prefisso promtail_custom:

Raccolta dei registri da Loki

Impostazione di Prometeo. Aggiungi annuncio di lavoro:

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

E traccia un grafico:

Raccolta dei registri da Loki

In questo modo puoi scoprire, ad esempio, le quattro query più lente. Puoi anche configurare il monitoraggio per questi parametri.

scalata

Loki può essere sia in modalità binaria singola che in modalità sharded (modalità scalabile orizzontalmente). Nel secondo caso, può salvare i dati nel cloud e i blocchi e l'indice vengono archiviati separatamente. Nella versione 1.5 è implementata la possibilità di archiviare in un unico posto, ma non è ancora consigliabile utilizzarla in produzione.

Raccolta dei registri da Loki

I blocchi possono essere archiviati in uno storage compatibile con S3; per l'archiviazione degli indici, utilizzare database scalabili orizzontalmente: Cassandra, BigTable o DynamoDB. Altre parti di Loki - Distributors (per la scrittura) e Querier (per le query) - sono apolidi e anch'esse scalano orizzontalmente.

Alla conferenza DevOpsDays Vancouver 2019, uno dei partecipanti Callum Styan ha annunciato che con Loki il suo progetto conta petabyte di log con un indice inferiore all’1% della dimensione totale: “Come Loki correla metriche e log e ti fa risparmiare denaro".

Confronto tra Loki ed ELK

Dimensione dell'indice

Per testare la dimensione dell'indice risultante, ho preso i log dal contenitore nginx per il quale era configurata la pipeline sopra. Il file di registro conteneva 406 righe per un volume totale di 624 MB. I log sono stati generati in un'ora, circa 109 record al secondo.

Un esempio di due righe dal registro:

Raccolta dei registri da Loki

Quando indicizzato da ELK, ha dato una dimensione dell'indice di 30,3 MB:

Raccolta dei registri da Loki

Nel caso di Loki, ciò ha fornito circa 128 KB di indice e circa 3,8 MB di dati in blocchi. Vale la pena notare che il registro è stato generato artificialmente e non conteneva un'ampia varietà di dati. Un semplice gzip sul registro JSON Docker originale con i dati ha fornito una compressione del 95,4% e dato che solo il registro nginx pulito è stato inviato a Loki stesso, la compressione a 4 MB è comprensibile. Il numero totale di valori univoci per le etichette Loki era 35, il che spiega la piccola dimensione dell'indice. Per ELK, anche il registro è stato cancellato. Pertanto, Loki ha compresso i dati originali del 96% e ELK del 70%.

Consumo di memoria

Raccolta dei registri da Loki

Se confrontiamo l'intero stack di Prometeo ed ELK, Loki "mangia" molte volte meno. È chiaro che il servizio Go consuma meno del servizio Java e il confronto tra la dimensione della JVM Heap Elasticsearch e la memoria allocata per Loki non è corretto, ma vale comunque la pena notare che Loki utilizza molta meno memoria. Il vantaggio CPU non è così evidente, ma è comunque presente.

velocità

Loki "divora" i tronchi più velocemente. La velocità dipende da molti fattori - che tipo di log, quanto sofisticati li analizziamo, rete, disco, ecc. - ma è decisamente superiore a quella di ELK (nel mio test - circa due volte). Ciò è spiegato dal fatto che Loki inserisce molti meno dati nell'indice e, di conseguenza, dedica meno tempo all'indicizzazione. In questo caso la situazione si inverte per quanto riguarda la velocità di ricerca: Loki rallenta notevolmente su dati più grandi di qualche gigabyte, mentre per ELK la velocità di ricerca non dipende dalla dimensione dei dati.

Ricerca registro

Loki è significativamente inferiore a ELK in termini di capacità di ricerca dei log. Grep con le espressioni regolari è una cosa forte, ma è inferiore a un database per adulti. La mancanza di query di intervallo, l'aggregazione solo per etichette, l'impossibilità di effettuare ricerche senza etichette: tutto ciò ci limita nella ricerca di informazioni di interesse su Loki. Ciò non implica che non sia possibile trovare nulla utilizzando Loki, ma definisce il flusso di lavoro con i log, quando si trova prima un problema sulle carte Prometheus e poi si cerca cosa è successo nei log utilizzando queste etichette.

Interfaccia

Prima di tutto, è bellissimo (scusate, non ho potuto resistere). Grafana ha un'interfaccia gradevole, ma Kibana è molto più funzionale.

Pro e contro di Loki

Tra i vantaggi, si può notare che Loki si integra con Prometheus, rispettivamente, otteniamo metriche e avvisi pronti all'uso. È comodo per raccogliere log e archiviarli con Kubernetes Pods, poiché dispone di un rilevamento dei servizi ereditato da Prometheus e allega automaticamente le etichette.

Tra gli svantaggi: scarsa documentazione. Alcune cose, come le caratteristiche e le capacità di Promtail, le ho scoperte solo durante il processo di studio del codice, i vantaggi dell'open source. Un altro svantaggio sono le deboli capacità di analisi. Ad esempio, Loki non può analizzare i log su più righe. Inoltre, gli svantaggi includono il fatto che Loki è una tecnologia relativamente giovane (la versione 1.0 è stata rilasciata a novembre 2019).

conclusione

Loki è una tecnologia interessante al 100% adatta a progetti piccoli e medi, che consente di risolvere molti problemi di aggregazione dei log, ricerca dei log, monitoraggio e analisi dei log.

Non usiamo Loki su Badoo perché abbiamo uno stack ELK adatto a noi e che è stato invaso da varie soluzioni personalizzate nel corso degli anni. Per noi l’ostacolo è la ricerca nei log. Con quasi 100 GB di log al giorno, per noi è importante riuscire a trovare tutto e qualcosa in più e farlo velocemente. Per la cartografia e il monitoraggio utilizziamo altre soluzioni adattate alle nostre esigenze e integrate tra loro. Lo stack Loki ha vantaggi tangibili, ma non ci darà più di quello che abbiamo, e i suoi benefici non supereranno esattamente il costo della migrazione.

E anche se dopo la ricerca è diventato chiaro che non possiamo usare Loki, speriamo che questo post ti aiuti nella scelta.

Si trova il repository con il codice utilizzato nell'articolo qui.

Fonte: habr.com

Aggiungi un commento