
Era il 2019 e noi non avevamo ancora una soluzione standard per l'aggregazione dei log in Kubernetes. In questo articolo vorremmo, utilizzando esempi dalla pratica reale, condividere le nostre ricerche, le problematiche incontrate e le loro soluzioni.
Tuttavia, per cominciare, chiarisco che diversi clienti intendono molto diversamente la raccolta dei log:
- chi vuole vedere i log di sicurezza e di audit;
- chi desidera una registrazione centralizzata dell'intera infrastruttura;
- e alcuni vogliono solo raccogliere i log delle applicazioni, escludendo, ad esempio, i bilanciatori.
Di come abbiamo realizzato diverse "richieste" e quali difficoltà abbiamo affrontato, parleremo sotto il tag.
Teoria: sugli strumenti per i log
Antefatti sui componenti del sistema di logging
Il logging ha fatto un lungo percorso, durante il quale si sono sviluppate metodologie per la raccolta e l'analisi dei log, che utilizziamo oggi. Già negli anni '50, in Fortran, è apparso un equivalente degli stream standard di input-output, che aiutava i programmatori nel debug dei loro programmi. Questi erano i primi log informatici, che semplificavano la vita ai programmatori dell'epoca. Oggi vediamo in essi il primo componente del sistema di logging – la fonte o "produttore" (producer) dei log.
La scienza informatica non è rimasta ferma: sono emerse le reti informatiche, i primi cluster... Hanno iniziato a funzionare sistemi complessi composti da più computer. Ora gli amministratori di sistema erano costretti a raccogliere i log da più macchine, e in casi particolari potevano aggiungere anche i messaggi del kernel del sistema operativo nel caso fosse necessario indagare su un guasto di sistema. Per descrivere i sistemi di raccolta centralizzata dei log, all'inizio degli anni 2000 è stato pubblicato , che ha standardizzato remote_syslog. Così è emerso un altro componente importante: il collezionista (aggregatore) dei log e il loro archiviazione.
Con l'aumento del volume di log e l'adozione diffusa delle tecnologie web, è emerso il problema di come mostrare i log in modo comodo agli utenti. A strumenti console semplici (awk/sed/grep) si sono sostituiti strumenti più avanzati visualizzatori di log – il terzo componente.
Con l'aumento del volume dei log è emersa un'altra verità: i log sono necessari, ma non tutti. Inoltre, diversi log richiedono diversi livelli di conservazione: alcuni possono essere persi dopo un giorno, mentre altri devono essere conservati per 5 anni. Così, nel sistema di registrazione è stato aggiunto un componente per la filtrazione e il routing dei flussi di dati — chiamiamolo filtro.
Anche gli archivi hanno fatto un serio balzo in avanti: da file normali si è passato a database relazionali e poi a archivi orientati ai documenti (ad esempio, Elasticsearch). Così, l'archivio si è separato dal collettore.
Alla fine, il concetto stesso di log si è ampliato in un certo flusso astratto di eventi che desideriamo conservare per la storia. O meglio — nel caso occorra condurre un'indagine o redigere un rapporto analitico...
Di conseguenza, in un intervallo di tempo relativamente breve, la raccolta dei log si è evoluta in un'importante sottosistema, che può essere giustamente considerato uno dei sottoinsiemi del Big Data.

Se un tempo i semplici print erano sufficienti per una "sistema di registrazione", ora la situazione è cambiata drasticamente.
Kubernetes e log
Quando Kubernetes è arrivato nell'infrastruttura, il problema della raccolta dei log, già esistente, non lo ha risparmiato. In un certo senso è diventato ancora più grave: la gestione della piattaforma infrastrutturale non solo è stata semplificata, ma anche complicata allo stesso tempo. Molti vecchi servizi hanno iniziato la migrazione verso un'architettura a microservizi. Nel contesto dei log, questo si è tradotto in un numero crescente di fonti di log, nel loro particolare ciclo di vita e nella necessità di monitorare attraverso i log le interrelazioni di tutti i componenti del sistema…
Guardando avanti, posso affermare che attualmente, purtroppo, non esiste una variante standardizzata di log per Kubernetes che si differenzi vantaggiosamente da tutte le altre. Gli schemi più popolari nella comunità si riducono a quanto segue:
- qualcuno sviluppa uno stack EFK (Elasticsearch, Fluentd, Kibana);
- qualcuno — prova il recentemente rilasciato oppure usa ;
- noi (e forse non solo noi?..) in molti casi è soddisfatto del proprio sviluppo — …
Di solito, utilizziamo tali combinazioni nei cluster K8s (per soluzioni self-hosted):
- ;
- .
Tuttavia, non mi soffermerò sulle istruzioni per la loro installazione e configurazione. Invece, mi concentrerò sui loro difetti e su conclusioni più globali riguardo alla situazione dei log in generale.
Pratica con i log in K8s

«Log quotidiani», quanti siete?..
La raccolta centralizzata dei log da un'infrastruttura piuttosto grande richiede risorse significative, che saranno utilizzate per la raccolta, l'archiviazione e l'elaborazione dei log. Durante l'esecuzione di vari progetti abbiamo incontrato diverse esigenze e i problemi derivanti da esse durante l'operatività.
Proviamo ClickHouse
Analizziamo un'archiviazione centralizzata in un progetto con un'applicazione che genera log in modo abbastanza attivo: oltre 5000 righe al secondo. Cominceremo a lavorare con i suoi log, archiviandoli in ClickHouse.
Non appena sarà richiesto un massimo in tempo reale, un server a 4 core con ClickHouse sarà già sovraccarico dal punto di vista del sistema di archiviazione:

Questo tipo di carico è legato al fatto che cerchiamo di scrivere in ClickHouse il più velocemente possibile. E questo il DB risponde con un carico elevato sul disco, motivo per cui può generare errori del tipo:
DB::Exception: Troppi parti (300). I merge vengono elaborati significativamente più lentamente degli insert.
Il fatto è che in ClickHouse (dove sono memorizzati i dati dei log) presentano alcune complessità durante le operazioni di scrittura. I dati inseriti generano una partizione temporanea, che poi viene unita alla tabella principale. Di conseguenza, la scrittura diventa molto esigente per il disco, e su di essa si applica una limitazione, di cui abbiamo ricevuto notifica sopra: in 1 secondo non possono essere unite più di 300 sub-partizioni (fino a 300 insert al secondo).
Per evitare tale comportamento, il più possibile in blocchi grandi e non più di una volta ogni 2 secondi. Tuttavia, scrivere in grandi blocchi implica che dobbiamo scrivere in ClickHouse meno frequentemente. Questo, a sua volta, può portare a un sovraccarico del buffer e alla perdita di log. La soluzione è aumentare il buffer di Fluentd, ma ciò comporterà anche un aumento dell'uso della memoria.
Nota: Un'altra problematica del nostro approccio con ClickHouse era legata al fatto che la partizione nel nostro caso (loghouse) è realizzata tramite tabelle esterne collegate . Questo porta al fatto che, quando si estraggono grandi intervalli temporali, è necessaria una memoria RAM eccessiva, poiché la metatabella scorre attraverso tutte le partizioni, anche quelle che non contengono i dati necessari. Tuttavia, al momento questo approccio può essere dichiarato superato per le versioni attuali di ClickHouse (c ).
Di conseguenza, diventa chiaro che per la raccolta dei log in tempo reale in ClickHouse non ci sono risorse sufficienti in molti progetti (per essere precisi, la loro distribuzione non sarà conveniente). Inoltre, sarà necessario utilizzare un accumulatore, a cui torneremo più tardi. Il caso descritto sopra è reale. E in quel momento non siamo riusciti a proporre una soluzione affidabile e stabile che soddisfacesse il cliente e consentisse di raccogliere i log con la minima latenza…
E Elasticsearch?
È noto che Elasticsearch gestisce carichi di lavoro elevati. Proviamo a utilizzarlo nello stesso progetto. Ora il carico appare come segue:

Elasticsearch è riuscito a gestire il flusso di dati, ma la registrazione di tali volumi sfrutta molto il CPU. Questo può essere risolto organizzando un cluster. Puramente tecnicamente non è un problema, ma risulta che solo per far funzionare il sistema di raccolta dei log utilizziamo già circa 8 core e abbiamo un componente ad alta richiesta nel sistema…
Risultato: questa opzione può essere giustificata, ma solo se il progetto è grande e la sua direzione è disposta a investire risorse significative in un sistema di logging centralizzato.
Nasce allora la domanda legittima:
Quali log sono realmente necessari?
Proviamo a cambiare l'approccio: i log devono essere sia informativi che non coprire ogni evento nel sistema.
Supponiamo di avere un negozio online di successo. Quali log sono importanti? Raccogliere il massimo di informazioni, ad esempio, dal gateway di pagamento è una brillante idea. Ma per il servizio di ritaglio delle immagini nel catalogo dei prodotti, non tutti i log sono critici: bastano solo gli errori e un monitoraggio approfondito (ad esempio, sulla percentuale di errori 500 generati da questo componente).
Ecco che siamo giunti alla conclusione che il logging centralizzato non è giustificato in molte occasioni. Molto spesso, il cliente desidera raccogliere tutti i log in un unico posto, anche se in realtà dal log completo servono solo circa il 5% dei messaggi, che sono critici per il business:
- A volte è sufficiente configurare, ad esempio, solo la dimensione del log del contenitore e il raccoglitore di errori (ad esempio, Sentry).
- Per indagare sugli incidenti, spesso è sufficiente una segnalazione di errore e un ampio log locale.
- Abbiamo avuto progetti che si sono basati esclusivamente su test funzionali e sistemi di raccolta errori. Gli sviluppatori non avevano bisogno dei log in quanto tali — vedevano tutto attraverso i trace degli errori.
Illustrazione dalla vita reale
Un buon esempio può essere un'altra storia. Abbiamo ricevuto una richiesta dal team di sicurezza di uno dei clienti, che già utilizzava una soluzione commerciale sviluppata molto prima dell'implementazione di Kubernetes.
È stato necessario "mettere a punto" il sistema di raccolta log centralizzata con il sensore aziendale di rilevamento problemi — QRadar. Questo sistema può ricevere log tramite il protocollo syslog e prelevare tramite FTP. Tuttavia, non siamo riusciti a integrarlo immediatamente con il plugin remote_syslog per fluentd. (come si è scoperto, ). I problemi nella configurazione di QRadar si trovavano dalla parte del team di sicurezza del cliente.
Di conseguenza, parte dei log critici per il business veniva esportata su QRadar tramite FTP, mentre un'altra parte veniva reindirizzata direttamente dai nodi tramite remote syslog. Per questo abbiamo persino scritto — forse aiuterà qualcuno a risolvere un problema simile... Grazie a questa configurazione, il cliente riceveva e analizzava i log critici (con il suo strumento preferito), mentre noi siamo riusciti a ridurre i costi del sistema di logging, mantenendo solo l'ultimo mese.
Un altro esempio è piuttosto indicativo di come non bisognerebbe comportarsi. Uno dei nostri clienti, per l'elaborazione ogni di eventi provenienti dagli utenti, generava un output multi-linea non strutturato di informazioni nel log. Come è facile intuire, tali log erano estremamente scomodi da leggere e conservare.
Criteri per i log
Esempi come questi portano alla conclusione che, oltre alla scelta del sistema di raccolta log, è necessario progettare anche i log stessi! Quali sono i requisiti qui?
- I log devono essere in un formato leggibile dalla macchina (ad esempio, JSON).
- I log devono essere compatti e consentire la modifica del livello di logging, per poter diagnosticare eventuali problemi. Nelle produzioni, è opportuno eseguire i sistemi con un livello di logging simile a Attenzione o Error.
- I log devono essere normalizzati, cioè in un oggetto di log tutte le stringhe devono avere lo stesso tipo di campo.
I log non strutturati possono causare problemi nel caricamento dei log nell'archivio e possono fermare completamente la loro elaborazione. A titolo esemplificativo, un esempio di errore 400, con il quale molti si sono sicuramente imbattuti nei log di fluentd:
2019-10-29 13:10:43 +0000 [warn]: dump an error event: error_class=Fluent::Plugin::ElasticsearchErrorHandler::ElasticsearchError error="400 - Rejected by Elasticsearch"
L'errore significa che stai inviando a un indice con un mapping già pronto un campo il cui tipo è instabile. Un esempio semplice è un campo nel log nginx con una variabile $upstream_status. Può contenere sia un numero che una stringa. Ad esempio:
{ "ip": "1.2.3.4", "http_user": "-", "request_id": "17ee8a579e833b5ab9843a0aca10b941", "time": "29/Ott/2019:16:18:57 +0300", "method": "GET", "uri": "\/staffs\/265.png", "protocol": "HTTP\/1.1", "status": "200", "body_size": "906", "referrer": "https:\/\/example.com\/staff", "user_agent": "Mozilla\/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit\/537.36 (KHTML, like Gecko) Chrome\/78.0.3904.70 Safari\/537.36", "request_time": "0.001", "cache_status": "-", "upstream_response_time": "0.001, 0.007", "upstream_addr": "127.0.0.1:9000", "upstream_status": "200", "upstream_response_length": "906", "location": "staff"}
{ "ip": "1.2.3.4", "http_user": "-", "request_id": "47fe42807f2a7d8d5467511d7d553a1b", "time": "29/Ott/2019:16:18:57 +0300", "method": "GET", "uri": "\/staff", "protocol": "HTTP\/1.1", "status": "200", "body_size": "2984", "referrer": "-", "user_agent": "Mozilla\/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit\/537.36 (KHTML, like Gecko) Chrome\/78.0.3904.70 Safari\/537.36", "request_time": "0.010", "cache_status": "-", "upstream_response_time": "0.001, 0.007", "upstream_addr": "10.100.0.10:9000, 10.100.0.11:9000", "upstream_status": "404, 200", "upstream_response_length": "0, 2984", "location": "staff"}
Nei log si vede che il server 10.100.0.10 ha risposto con un errore 404 e la richiesta è andata a un altro archivio di contenuti. Di conseguenza, nel log il valore è diventato così:
"upstream_response_time": "0.001, 0.007"
Questa situazione è così comune che ha persino meritato un .
E riguardo all'affidabilità?
Ci sono casi in cui è vitale avere tutti i log senza eccezioni. E con gli schemi tipici di raccolta dei log per K8s, proposti/discusso sopra, ci sono problemi.
Ad esempio, fluentd non può raccogliere log da contenitori di breve vita. In uno dei nostri progetti, un contenitore con migrazione di database viveva meno di 4 secondi e poi veniva rimosso - secondo la relativa annotazione:
"helm.sh/hook-delete-policy": hook-succeeded
A causa di questo, il log dell'esecuzione della migrazione non entrava nell'archivio. In questo caso, può aiutare la politica before-hook-creation.
Un altro esempio è la rotazione dei log di Docker. Supponiamo di avere un'applicazione che scrive attivamente nei log. In condizioni normali, riusciamo a trattare tutti i log, ma non appena si presenta un problema - ad esempio, come descritto sopra con un formato errato - l'elaborazione si ferma e Docker ruota il file. Il risultato è che potrebbero andare persi log critici per il business.
È per questo che È importante separare i flussi di log, integrando l'invio dei dati più preziosi direttamente nell'applicazione, per garantirne la protezione. Inoltre, non sarebbe superfluo creare una sorta di «accumulatore» di log, che possa sopravvivere a brevi periodi di inacessibilità dell'archivio mantenendo i messaggi critici.
Infine, non bisogna dimenticare che è fondamentale monitorare ogni sottosistema in modo efficace. Altrimenti, è facile trovarsi in una situazione in cui fluentd si trova in uno stato CrashLoopBackOff e non invia nulla, e questo implica la perdita di informazioni importanti.
Conclusioni
In questo articolo non trattiamo soluzioni SaaS come Datadog. Molti dei problemi descritti qui sono stati già risolti in un modo o nell'altro da aziende commerciali specializzate nella raccolta di log, ma non tutti possono utilizzare soluzioni SaaS per vari motivi. (i principali sono il costo e il rispetto della legge 152-FZ).
La raccolta centralizzata dei log sembra inizialmente un compito semplice, ma in realtà non lo è affatto. È importante ricordare che:
- È opportuno registrare in dettaglio solo i componenti critici, mentre per gli altri sistemi si può impostare il monitoraggio e la raccolta degli errori.
- I log in produzione dovrebbero essere minimali per non creare un carico eccessivo.
- I log devono essere leggibili dalle macchine, normalizzati e avere un formato rigoroso.
- I log realmente critici dovrebbero essere inviati tramite un flusso separato, che deve essere distinto da quello principale.
- È utile pensare a un accumulatore di log, che può prevenire picchi di carico elevato e rendere il carico di archiviazione più uniforme.

Queste semplici regole, se applicate ovunque, potrebbero far funzionare anche gli schemi descritti sopra - anche se mancano componenti importanti (accumulatore). Se non si seguono tali principi, il compito porterà facilmente te e l'infrastruttura a un altro componente di sistema ad alta intensità di carico (e allo stesso tempo poco efficiente).
P.S.
Leggi anche nel nostro blog:
- «»;
- «»;
- «».
Fonte: habr.com
