Log in Kubernetes (e non solo) oggi: aspettative e realtà

Log in Kubernetes (e non solo) oggi: aspettative e realtà

È arrivato il 2019 e ancora non abbiamo una soluzione standard per l'aggregazione dei log in Kubernetes. In questo articolo, vorremmo condividere le nostre ricerche, le problematiche incontrate e le loro soluzioni, utilizzando esempi tratti dalla pratica.

Tuttavia, vorrei precisare che diversi clienti hanno interpretazioni molto differenti quando si parla di raccolta dei log:

  • c'è chi desidera visualizzare i log di sicurezza e audit;
  • chi cerca un logging centralizzato per l'intera infrastruttura;
  • e chi si accontenta di raccogliere solo i log delle applicazioni, escludendo, ad esempio, i bilanciatorei.

Riguardo a come abbiamo implementato questi "desideri" e quali difficoltà abbiamo affrontato, leggi oltre.

Teoria: sugli strumenti per i log

Storia dei componenti del sistema di logging

Il logging ha fatto un lungo cammino, durante il quale si sono sviluppate metodologie di raccolta e analisi dei log, che utilizziamo oggi. Già negli anni '50, il Fortran introdusse un equivalente degli stream standard di input/output, che assistevano i programmatori nel debug dei loro programmi. Erano i primi log informatici, che facilitavano la vita ai programmatori dell'epoca. Oggi, vediamo in essi il primo componente del sistema di logging - la sorgente o "produttore" (producer) dei log.

La scienza informatica non stava ferma: apparvero reti informatiche, i primi cluster... Iniziarono a operare 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 anche includere 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, nei primi anni 2000 fu pubblicato RFC 3164, che standardizzò il remote_syslog. E così emerse un altro componente importante: il collezionista (aggregatore) di log e il loro deposito.

Con l'aumento del volume dei log e l'adozione diffusa delle tecnologie web, sorse la necessità di mostrare i log in modo conveniente agli utenti. Gli strumenti console semplici (awk/sed/grep) furono sostituiti da più sofisticati visualizzatori di log - un terzo componente.

Con l'aumento del volume dei log, divenne chiaro anche un altro aspetto: i log sono necessari, ma non tutti. Inoltre, diversi log richiedono diversi livelli di conservazione: alcuni possono essere persi dopo un giorno, altri devono essere conservati per 5 anni. Così, nel sistema di logging è stato aggiunto un componente di filtraggio e instradamento dei flussi di dati - chiamiamolo filtro.

Gli archivi hanno fatto un salto significativo: si è passati da normali file a database relazionali, e poi a archivi orientati ai documenti (come Elasticsearch). Così il deposito si è separato dal collezionista.

Alla fine, il concetto stesso di log si è ampliato fino a diventare un astratto flusso di eventi che desideriamo preservare per la storia. Più precisamente - nel caso fosse necessario condurre un'inchiesta o compilare un rapporto analitico...

In definitiva, in un lasso di tempo relativamente breve, la raccolta di log si è evoluta in un'importante sotto-sistema, che può essere giustamente considerato uno dei rami del Big Data.

Log in Kubernetes (e non solo) oggi: aspettative e realtà
Se in passato erano sufficienti semplici print per un "sistema di logging", ora la situazione è notevolmente cambiata.

Kubernetes e log

Quando Kubernetes è entrato nell'infrastruttura, il problema esistente della raccolta dei log non lo ha risparmiato. In un certo senso, è diventato addirittura più problematico: la gestione della piattaforma infrastrutturale è stata semplificata, ma allo stesso tempo complicata. Molti servizi obsoleti hanno iniziato a migrare verso l'architettura a microservizi. Nel contesto dei log, questo si è tradotto nell'aumento del numero di sorgenti log, nel loro ciclo di vita particolare e nella necessità di monitorare attraverso i log le interrelazioni di tutti i componenti del sistema...

Anticipando, posso affermare che al momento, purtroppo, non esiste una variante standardizzata di logging per Kubernetes che si distingua favorevolmente da tutte le altre. Le configurazioni più popolari nella comunità si riducono a:

  • c'è chi implementa il stack EFK (Elasticsearch, Fluentd, Kibana);
  • chi provano il appena rilasciato Loki o utilizzano Logging operator;
  • noi (e forse non solo noi?) in gran parte sono soddisfatti della soluzione sviluppata internamente - loghouse

In generale, 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 sulla situazione dei log in generale.

Pratica con i log in K8s

Log in Kubernetes (e non solo) oggi: aspettative e realtà

"Log quotidiani", quanti siete?...

La raccolta centralizzata dei log da un'infrastruttura piuttosto ampia richiede notevoli risorse dedicate alla raccolta, archiviazione e elaborazione dei log. Durante l'esercizio di vari progetti, ci siamo confrontati con diverse esigenze e le problematiche emergenti che ne risultano nell'operatività.

Proviamo ClickHouse

Consideriamo uno storage centralizzato nel progetto con un'applicazione che genera log in modo piuttosto attivo: più di 5000 righe al secondo. Iniziamo a lavorare con i suoi log, archiviandoli in ClickHouse.

Non appena sarà necessario un massimo di realtime, un server a 4 core con ClickHouse sarà già sovraccarico dal sottosistema di archiviazione:

Log in Kubernetes (e non solo) oggi: aspettative e realtà

Questo tipo di carico è legato al fatto che stiamo cercando di scrivere a massima velocità in ClickHouse. E questo DB reagisce con un aumento del carico sul disco, il che può causare errori come:

DB::Exception: Troppi parti (300). I merge vengono elaborati significativamente più lentamente rispetto agli insert

Il fatto è che le tabelle MergeTree in ClickHouse (in cui risiedono i dati dei log) presentano delle complessità nelle operazioni di scrittura. I dati inseriti generano una partizione temporanea, che viene poi fusa con la tabella principale. Di conseguenza, la scrittura risulta molto esigente nei confronti del disco e si applica un vincolo, di cui abbiamo ricevuto l'avviso sopra: in 1 secondo possono essere unite non più di 300 sub-partizioni (in sostanza, corrispondono a 300 insert al secondo).

Per evitare tale comportamento, è consigliabile scrivere in ClickHouse il più possibile in blocchi grandi e non più frequentemente di 1 volta ogni 2 secondi. Tuttavia, scrivere in grandi blocchi implica che dobbiamo scrivere meno frequentemente in ClickHouse. Questo, a sua volta, può portare a un sovraccarico del buffer e alla perdita di log. La soluzione consiste nell'aumentare il buffer di Fluentd, ma ciò aumenterebbe anche il consumo di memoria.

Nota: Un'altra problematica del nostro approccio con ClickHouse era legata al fatto che la partizionatura nel nostro caso (loghouse) è implementata attraverso tabelle esterne, collegate alla tabella Merge. Questo porta al fatto che nelle query su ampi intervalli temporali è necessaria una memoria operativa eccessiva, poiché la metatabella esamina tutte le partizioni - anche quelle che di certo non contengono i dati richiesti. Tuttavia, attualmente questo approccio può essere dichiarato obsoleto per le versioni recenti di ClickHouse (c 18.16).

Di conseguenza, diventa chiaro che per la raccolta di log in tempo reale in ClickHouse non ci sono sufficienti risorse per ogni progetto (piuttosto, la loro distribuzione non sarebbe ragionevole). Inoltre, sarà necessario utilizzare un accumulator, a cui torneremo. Il caso descritto sopra è reale. E in quel momento non siamo stati in grado di offrire una soluzione affidabile e stabile che potesse soddisfare il cliente e permettesse di raccogliere i log con la minima latenza...

E Elasticsearch?

È noto che Elasticsearch gestisce carichi elevati. Proviamo a utilizzarlo nello stesso progetto. Ora il carico si presenta come segue:

Log in Kubernetes (e non solo) oggi: aspettative e realtà

Elasticsearch è riuscito a elaborare il flusso di dati, ma la scrittura di tali volumi lo utilizza notevolmente. Questo si risolve organizzando un cluster. Puramente dal punto di vista tecnico non è un problema, tuttavia, risulta che solo per il funzionamento del sistema di raccolta log utilizziamo già circa 8 core e abbiamo un ulteriore componente ad alta intensità di carico nel sistema...

In sintesi: tale opzione può essere giustificata, ma solo se il progetto è grande e la sua direzione è disposta a destinare risorse significative a un sistema di log centralizzato.

Sorge quindi la legittima domanda:

Quali log sono realmente necessari?

Log in Kubernetes (e non solo) oggi: aspettative e realtà Proviamo a cambiare l'approccio stesso: i log devono essere sia informativi sia non coprire ogni evento nel sistema.

Supponiamo di avere un negozio online di successo. Quali log sono importanti? Raccogliere il massimo delle informazioni, ad esempio dal gateway di pagamento, è un'ottima idea. Ma i log del servizio di ritaglio delle immagini nel catalogo dei prodotti non sono tutti critici per noi: bastano solo gli errori e il monitoraggio esteso (ad esempio, sulla percentuale di errori 500 che genera questo componente).

Così siamo giunti al punto in cui la registrazione centralizzata non è sempre giustificata.Spesso il cliente desidera raccogliere tutti i log in un unico posto, anche se in realtà solo una condizionata del 5% dei messaggi è critica per il business:

  • A volte è sufficiente configurare, per esempio, solo la dimensione del log del contenitore e il raccoglitore di errori (ad esempio, Sentry).
  • Per indagare sugli incidenti, spesso può bastare una segnalazione d'errore e un grande log locale.
  • Abbiamo avuto progetti che riuscivano a sopravvivere unicamente con test funzionali e sistemi di raccolta errori. Ai programmatori non servivano i log come tali: vedevano tutto tramite i trace degli errori.

Illustrazione dalla vita reale

Un buon esempio può essere rappresentato da un'altra storia. Abbiamo ricevuto una richiesta dal team di sicurezza di uno dei nostri clienti, che già utilizzava una soluzione commerciale sviluppata molto prima dell'implementazione di Kubernetes.

È stato necessario "collaborare" il sistema di raccolta centralizzata dei log con un sensore aziendale per la rilevazione dei problemi — QRadar. Questo sistema è in grado di ricevere log tramite il protocollo syslog e prelevarli da FTP. Tuttavia, non siamo riusciti a integrarlo subito con il plugin remote_syslog per fluentd. (come si è scoperto, non siamo stati gli unici a riscontrare questo problema). I problemi con la configurazione di QRadar erano dalla parte del team di sicurezza del cliente.

Di conseguenza, parte dei log critici per il business veniva caricata su FTP da QRadar, mentre un'altra parte veniva reindirizzata tramite remote syslog direttamente dai nodi. A tal fine, abbiamo anche scritto un semplice chart — potrebbe essere utile a qualcuno per risolvere un compito simile... Grazie allo schema ottenuto, il cliente stesso riceveva e analizzava i log critici (utilizzando il proprio strumento preferito), mentre noi riuscivamo a ridurre i costi del sistema di logging, mantenendo solo l'ultimo mese.

Un altro esempio è piuttosto indicativo di come non si dovrebbe fare. Uno dei nostri clienti produceva un'uscita di log multilinea ogni non strutturata per un evento proveniente dall'utente. Come si può facilmente intuire, tali log erano estremamente difficili da leggere e gestire. Criteri per i log

Questi esempi portano alla conclusione che, oltre alla scelta del sistema di raccolta dei 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 con la possibilità di modificare il livello di logging per risolvere possibili problemi. In ambienti di produzione, è consigliabile avviare i sistemi con un livello di logging come
  • I log devono essere compatti e con la possibilità di modificare il livello di registrazione, per facilitare la risoluzione dei problemi. In ambienti di produzione, è consigliabile eseguire i sistemi con un livello di registrazione come Attenzione o Error.
  • I log devono essere normalizzati, cioè in un oggetto log tutte le stringhe devono avere lo stesso tipo di campo.

Log non strutturati possono causare problemi nel caricamento dei log nel storage e nell'interruzione totale del loro trattamento. A titolo di esempio, ecco un caso con l'errore 400, con cui molti sicuramente hanno avuto a che fare 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 tentando di inviare a un indice con un mapping predefinito un campo il cui tipo è instabile. Un semplice esempio è un campo nel log di nginx con la variabile $upstream_status. Può contentere sia numeri che stringhe. 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 restituito un errore 404 e la richiesta è andata a un altro storage di contenuti. Di conseguenza, nei log il valore è diventato:

"upstream_response_time": "0.001, 0.007"

Questa situazione è così comune che ha meritato anche una menzione speciale nella documentazione. E riguardo all'affidabilità?.

Ci sono casi in cui è vitale avere tutti i log senza eccezione. Con le tipiche schemi di raccolta log per K8s, come quelli proposti/considerati sopra, ci sono delle problematiche.

Ad esempio, fluentd non può raccogliere log da container a vita breve. In uno dei nostri progetti, un container con la migrazione del database viveva meno di 4 secondi e poi veniva rimosso — secondo la relativa annotazione:

"helm.sh/hook-delete-policy": hook-succeeded

A causa di ciò, il log dell'esecuzione della migrazione non finiva nel storage. Una politica che potrebbe aiutare in questo caso è

before-hook-creation. Un altro esempio è la rotazione dei log di Docker. Supponiamo che ci sia un'applicazione che scrive attivamente nei log. In condizioni normali riusciamo a elaborare tutti i log, ma non appena si presenta un problema — come descritto sopra con il formato errato — l'elaborazione si ferma e Docker ruota il file. Risultato: log critici per il business possono andare persi..

È importante separare i flussi di log

Ecco perché , integrando l'invio dei più preziosi direttamente nell'applicazione, per garantirne la conservazione. Inoltre, non sarebbe superfluo creare una sorta di"accumulatore" di log , che possa sopportare una breve indisponibilità dello storage mantenendo i messaggi critici.Infine, non bisogna dimenticare che

è fondamentale monitorare adeguatamente ogni sottosistema . Altrimenti, si può facilmente incorrere nella situazione in cui fluentd non è in uno statoe non invia nulla, il che porta a una perdita di informazioni importanti. CrashLoopBackOff 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 usare SaaS per vari motivi

Conclusioni

(i principali sono costo e conformità con la legge 152-ФЗ). (i principali sono il costo e il rispetto della legge 152-FZ).

La raccolta centralizzata dei log può sembrare 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 è possibile configurare il monitoraggio e la raccolta degli errori.
  • I log in produzione dovrebbero essere minimalisti, per non creare un carico eccessivo.
  • I log devono essere leggibili dalle macchine, normalizzati e avere un formato rigoroso.
  • I log veramente critici dovrebbero essere trasmessi tramite un flusso separato, distinto da quelli principali.
  • È consigliabile progettare un accumulatore di log che possa proteggere dai picchi di carico e rendere il carico sullo storage più uniforme.

Log in Kubernetes (e non solo) oggi: aspettative e realtà
Queste semplici regole, se applicate ovunque, potrebbero consentire il funzionamento anche degli schemi descritti sopra, nonostante la mancanza di componenti importanti (accumulatore). Se non si seguono tali principi, la questione potrebbe facilmente condurvi e la vostra infrastruttura a un altro componente di sistema ad alto carico (e allo stesso tempo poco efficace).

P.S.

Leggete anche nel nostro blog:

Fonte: habr.com

Acquista hosting affidabile per siti web con protezione DDoS, server VPS VDS 🔥 Acquista hosting affidabile per siti web con protezione DDoS, server VPS VDS | ProHoster