Immagini pronte per la produzione per k8s

Questa storia riguarda come utilizziamo i container in un ambiente di produzione, in particolare su Kubernetes. L'articolo è dedicato alla raccolta di metriche e log dai container, oltre alla creazione delle immagini.

Immagini pronte per la produzione per k8s

Siamo di Exness, una società fintech che sviluppa servizi per il trading online e prodotti fintech per B2B e B2C. Nel nostro R&D ci sono molti team diversi, con oltre 100 dipendenti nel dipartimento di sviluppo.

Rappresentiamo il team responsabile della piattaforma per la raccolta e l'esecuzione del codice dei nostri sviluppatori. In particolare, ci occupiamo della raccolta, archiviazione e fornitura di metriche, log ed eventi delle applicazioni. Attualmente operiamo con circa tremila container Docker in un ambiente di produzione, supportiamo il nostro magazzino di big data da 50 TB e forniamo soluzioni architetturali costruite attorno alla nostra infrastruttura: Kubernetes, Rancher e vari fornitori di cloud pubblici. 

La nostra motivazione

Cosa sta bruciando? Nessuno può rispondere. Dove si trova il fuoco? È difficile da capire. Quando è scoppiato? È possibile scoprirlo, ma non subito. 

Immagini pronte per la produzione per k8s

Perché alcuni container funzionano, mentre altri crollano? Quale container ha causato il problema? Infatti, esternamente i container sono identici, ma all'interno ognuno ha il suo Neo.

Immagini pronte per la produzione per k8s

I nostri sviluppatori sono bravi professionisti. Creano ottimi servizi che portano profitto all'azienda. Ma ci sono dei fallimenti quando i container con le applicazioni si disordinano. Un container consuma troppo CPU, un altro la rete, un terzo le operazioni di I/O, e il quarto non si capisce nemmeno cosa faccia con i socket. Tutto questo crolla e la nave affonda. 

Agenti

Per capire cosa succede all'interno, abbiamo deciso di installare agenti direttamente nei container.

Immagini pronte per la produzione per k8s

Questi agenti sono programmi di controllo che mantengono i container in uno stato tale da non rompere l'uno con l'altro. Gli agenti sono standardizzati, e questo ci consente di uniformare l'approccio alla manutenzione dei container. 

Nel nostro caso, gli agenti devono fornire log in formato standard, etichettati e con throttling. Devono anche fornirci metriche standardizzate, espandibili dal punto di vista delle applicazioni aziendali.

Per agenti si intendono anche utilità per l'operazione e la manutenzione, in grado di funzionare in diversi sistemi di orchestrazione, supportando diverse immagini (Debian, Alpine, Centos, ecc.).

Infine, gli agenti devono supportare un semplice CI/CD, che includa file Docker. Altrimenti, la nave si sgretolerà, poiché i container inizieranno a essere consegnati su binari "storti".

Il processo di costruzione e la creazione dell'immagine di destinazione

Affinché tutto sia standardizzato e gestibile, è necessario seguire un qualche processo di costruzione standard. Pertanto, abbiamo deciso di costruire container con container: una sorta di ricorsione.

Immagini pronte per la produzione per k8s

Qui i container sono rappresentati da contorni solidi. Abbiamo anche deciso di inserire in essi i pacchetti per non farci mancare nulla. Spiegheremo perché è stato fatto ciò più avanti.
 
Ne è risultato uno strumento di costruzione: un container di una certa versione, che fa riferimento a determinate versioni di pacchetti e a specifiche versioni di script.

Come lo utilizziamo? Abbiamo Docker Hub, dove si trova il container. Lo mirroriamo all'interno del nostro sistema per liberarci delle dipendenze esterne. È risultato un container contrassegnato in giallo. Creiamo un modello per installare nel container tutti i pacchetti e gli script necessari. Dopo di che, costruiamo un'immagine pronta per l'uso: gli sviluppatori vi inseriscono il codice e alcune delle loro dipendenze specifiche. 

Quali sono i vantaggi di questo approccio? 

  • In primo luogo, pieno controllo delle versioni degli strumenti di costruzione – container di costruzione, versioni di script e pacchetti. 
  • In secondo luogo, abbiamo raggiunto la standardizzazione: creiamo modelli, intermedie e immagini pronte per l'uso in modo identico. 
  • In terzo luogo, i container ci garantiscono portabilità. Oggi utilizziamo Gitlab, e domani passeremo a TeamCity o Jenkins e potremo eseguire i nostri container allo stesso modo. 
  • In quarto luogo, minimizzazione delle dipendenze. Non abbiamo messo per caso i pacchetti nel container, poiché questo ci consente di non doverli scaricare ogni volta da Internet. 
  • In quinto luogo, è aumentata la velocità di costruzione: la presenza di copie locali delle immagini consente di risparmiare tempo su download, poiché c'è un'immagine locale. 

In altre parole, abbiamo raggiunto un processo di costruzione controllato e flessibile. Utilizziamo gli stessi strumenti per costruire qualsiasi container con pieno versioning. 

Come funziona la nostra procedura di costruzione

Immagini pronte per la produzione per k8s

La costruzione viene avviata con un comando, il processo si svolge all'interno dell'immagine (evidenziata in rosso). Lo sviluppatore dispone di un file Docker (evidenziato in giallo), lo rendiamo sostituendo le variabili con i valori. E nel frattempo aggiungiamo header e footer: sono i nostri agenti. 

L'header aggiunge i package dai rispettivi image. Il footer installa i nostri servizi all'interno, configura l'avvio del carico di lavoro, del logging e di altri agenti, sostituisce l'entrypoint, ecc. 

Immagini pronte per la produzione per k8s

Abbiamo a lungo discusso se installare un supervisore. Alla fine abbiamo deciso che ci serviva. Abbiamo scelto S6. Il supervisore gestisce il contenitore: consente di connettersi ad esso in caso di caduta del processo principale e fornisce gestione manuale del contenitore senza ricrearlo. I log e le metriche sono processi eseguiti all'interno del contenitore. Anche questi devono essere monitorati, e lo facciamo tramite il supervisore. Infine, S6 si occupa delle operazioni di housekeeping, dell'elaborazione dei segnali e di altri compiti.

Poiché utilizziamo diversi sistemi di orchestrazione, dopo la costruzione e l'avvio, il contenitore deve comprendere in quale ambiente si trova e agire di conseguenza. Ad esempio:
Questo ci permette di costruire un'unica immagine e di eseguirla in diversi sistemi di orchestrazione, tenendo conto della specificità di ciascun sistema di orchestrazione.

 Immagini pronte per la produzione per k8s

Per lo stesso contenitore otteniamo diversi alberi di processo in Docker e Kubernetes:

Immagini pronte per la produzione per k8s

Il payload viene eseguito sotto il supervisore S6. Nota il collector e gli events: sono i nostri agenti responsabili dei log e delle metriche. In Kubernetes non ci sono, mentre in Docker ci sono. Perché? 

Se guardiamo la specifica del "pod" (qui e oltre - Kubernetes pod), vedremo che il contenitore events viene eseguito nel pod, in cui c'è un contenitore collector separato che esegue la funzione di raccolta di metriche e log. Possiamo utilizzare le funzionalità di Kubernetes: esecuzione di contenitori nello stesso pod, in uno spazio di processo e/o rete unificato. In effetti, implementare i nostri agenti e svolgere determinate funzioni. E se lo stesso contenitore viene avviato in Docker, riceverà in uscita tutte le stesse capacità, cioè potrà fornire log e metriche, poiché gli agenti verranno eseguiti all'interno. 

Metriche e log

La consegna di metriche e log è un compito complesso. La sua risoluzione è legata a diversi aspetti.
L'infrastruttura è creata per eseguire carichi utili, non per la consegna massiva di log. Cioè, questo processo deve essere eseguito con requisiti minimi per le risorse dei contenitori. Ci sforziamo di aiutare i nostri sviluppatori: «Prendete un contenitore da Docker Hub, avviatelo e saremo in grado di consegnare i log». 

Il secondo aspetto è il problema della limitazione del volume dei log. Se in più contenitori si verifica un picco nel volume dei log (l'applicazione stampa stack-trace in un ciclo), aumenta il carico sulla CPU, sulle linee di comunicazione, sul sistema di elaborazione dei log, e questo influisce sul funzionamento dell'host in generale e su altri contenitori sull'host, a volte portando al «crollo» dell'host. 

Il terzo aspetto è che è necessario supportare il maggior numero possibile di metodologie di raccolta delle metriche out of the box. Dalla lettura dei file e dal polling del endpoint Prometheus all'uso di protocolli specifici delle applicazioni.

E l'ultimo aspetto è che è necessario minimizzare il consumo delle risorse.

Abbiamo scelto una soluzione open-source in Go chiamata Telegraf. È un connettore universale che supporta oltre 140 tipi di ingressi (input plugin) e 30 tipi di uscite (output plugin). Lo abbiamo migliorato e ora vi parleremo di come viene utilizzato da noi nell'esempio di Kubernetes. 

Immagini pronte per la produzione per k8s

Supponiamo che uno sviluppatore distribuisca un carico, e Kubernetes riceve una richiesta per creare un pod. A questo punto, per ogni pod viene automaticamente creato un contenitore chiamato Collector (utilizziamo un webhook di mutazione). Collector è il nostro agente. All'avvio, questo contenitore si configura per lavorare con Prometheus e il sistema di raccolta dei log.

  • Per questo utilizza le annotazioni del pod, e a seconda del suo contenuto, crea, ad esempio, un endpoint Prometheus; 
  • In base alle specifiche del pod e alle impostazioni specifiche dei contenitori, decide come consegnare i log.

I log vengono raccolti attraverso l'API di Docker: gli sviluppatori devono semplicemente scriverli in stdout o stderr, e poi il Collector si occuperà del resto. I log vengono raccolti a chunk con un certo ritardo, per prevenire possibili sovraccarichi dell'host. 

Le metriche vengono raccolte per istanze di carico di lavoro (processi) nei contenitori. Tutto è etichettato: namespace, pod e così via, e poi viene convertito nel formato Prometheus – diventando disponibile per la raccolta (a parte i log). Inoltre, i log, le metriche e gli eventi vengono inviati a Kafka e oltre:

  • I log sono disponibili in Graylog (per analisi visive);
  • I log, le metriche e gli eventi vengono inviati a Clickhouse per la conservazione a lungo termine.

Funziona nello stesso modo in AWS, solo che sostituiamo Graylog con Kafka con Cloudwatch. Inviamo i log lì, ed è tutto molto comodo: è subito chiaro a quale cluster e contenitore appartengono. Lo stesso vale per Google Stackdriver. Quindi il nostro schema funziona sia on-premise con Kafka che nel cloud. 

Se non abbiamo Kubernetes con i pod, lo schema diventa un po' più complesso, ma funziona secondo gli stessi principi.

Immagini pronte per la produzione per k8s

All'interno del contenitore vengono eseguiti gli stessi processi, orchestrati con S6. Gli stessi processi sono avviati all'interno di un unico contenitore.

In definitiva

Abbiamo creato una soluzione integrata per l'assemblaggio e l'avvio delle immagini in produzione, con opzioni per la raccolta e la consegna di log e metriche:

  • Abbiamo sviluppato un approccio standardizzato per l'assemblaggio delle immagini, sulla base del quale abbiamo sviluppato modelli CI;
  • Gli agenti per la raccolta dei dati sono le nostre estensioni Telegraf. Li abbiamo testati con successo in produzione;
  • Utilizziamo un webhook di mutazione per integrare i contenitori con gli agenti nei pod; 
  • Ci siamo integrati nell'ecosistema Kubernetes/Rancher;
  • Possiamo eseguire gli stessi contenitori in diversi sistemi di orchestrazione e ottenere il risultato previsto;
  • Abbiamo creato una configurazione di gestione dei contenitori completamente dinamica. 

Co-autore: Il'ja Prudnikov

Fonte: habr.com

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