Immagini pronte per la produzione per k8s

Questa storia riguarda il modo in cui utilizziamo i contenitori in un ambiente di produzione, in particolare Kubernetes. L'articolo è dedicato alla raccolta di parametri e log dai contenitori, nonché alla creazione di immagini.

Immagini pronte per la produzione per k8s

Siamo della società fintech Exness, che sviluppa servizi per il trading online e prodotti fintech per B2B e B2C. La nostra ricerca e sviluppo ha molti team diversi, il reparto di sviluppo ha oltre 100 dipendenti.

Rappresentiamo il team responsabile della piattaforma affinché i nostri sviluppatori raccolgano ed eseguano il codice. In particolare, siamo responsabili della raccolta, archiviazione e reporting di parametri, registri ed eventi dalle applicazioni. Attualmente gestiamo circa tremila contenitori Docker in un ambiente di produzione, manteniamo il nostro storage di big data da 50 TB e forniamo soluzioni architetturali costruite attorno alla nostra infrastruttura: Kubernetes, Rancher e vari provider di cloud pubblici. 

La nostra motivazione

Cosa sta bruciando? Nessuno può rispondere. Dov'è il focolare? È difficile da capire. Quando ha preso fuoco? Puoi scoprirlo, ma non subito. 

Immagini pronte per la produzione per k8s

Perché alcuni contenitori sono in piedi mentre altri sono caduti? Quale contenitore era la colpa? Dopotutto, l'esterno dei contenitori è lo stesso, ma all'interno ognuno ha il proprio Neo.

Immagini pronte per la produzione per k8s

I nostri sviluppatori sono ragazzi competenti. Fanno buoni servizi che portano profitto all'azienda. Ma si verificano fallimenti quando i contenitori con le applicazioni vanno fuori strada. Un contenitore consuma troppa CPU, un altro consuma la rete, un terzo consuma operazioni di I/O e il quarto non è completamente chiaro cosa faccia con i socket. Tutto cade e la nave affonda. 

Agenti

Per capire cosa sta succedendo all'interno, abbiamo deciso di posizionare gli agenti direttamente nei container.

Immagini pronte per la produzione per k8s

Questi agenti sono programmi di contenimento che mantengono i contenitori in uno stato tale da non rompersi a vicenda. Gli agenti sono standardizzati e ciò consente un approccio standardizzato alla manutenzione dei container. 

Nel nostro caso, gli agenti devono fornire i log in un formato standard, contrassegnati e limitati. Dovrebbero anche fornirci metriche standardizzate estensibili dal punto di vista delle applicazioni aziendali.

Per agenti si intendono anche utilità per il funzionamento e la manutenzione che possono funzionare in diversi sistemi di orchestrazione che supportano immagini diverse (Debian, Alpine, Centos, ecc.).

Infine, gli agenti devono supportare CI/CD semplici che includano file Docker. Altrimenti, la nave andrà in pezzi, perché i container inizieranno a essere consegnati lungo rotaie “storte”.

Processo di creazione e dispositivo immagine di destinazione

Per mantenere tutto standardizzato e gestibile, è necessario seguire una sorta di processo di creazione standard. Pertanto, abbiamo deciso di raccogliere contenitori per contenitori: questa è ricorsione.

Immagini pronte per la produzione per k8s

Qui i contenitori sono rappresentati da contorni solidi. Allo stesso tempo, hanno deciso di inserirvi dei kit di distribuzione in modo che “la vita non sembri una pernacchia”. Perché ciò è stato fatto, lo spiegheremo di seguito.
 
Il risultato è uno strumento di compilazione, un contenitore specifico per la versione che fa riferimento a versioni di distribuzione specifiche e versioni di script specifiche.

Come lo usiamo? Abbiamo un Docker Hub che contiene un contenitore. Lo rispecchiamo all'interno del nostro sistema per eliminare le dipendenze esterne. Il risultato è un contenitore contrassegnato in giallo. Creiamo un modello per installare tutte le distribuzioni e gli script di cui abbiamo bisogno nel contenitore. Successivamente, assembliamo un'immagine pronta per l'uso: gli sviluppatori vi inseriscono il codice e alcune delle loro dipendenze speciali. 

Cosa c'è di buono in questo approccio? 

  • Innanzitutto, il controllo completo della versione degli strumenti di creazione: versioni di contenitori, script e distribuzioni. 
  • In secondo luogo, abbiamo raggiunto la standardizzazione: creiamo modelli, immagini intermedie e pronte all'uso allo stesso modo. 
  • In terzo luogo, i container ci garantiscono la portabilità. Oggi utilizziamo Gitlab e domani passeremo a TeamCity o Jenkins e saremo in grado di eseguire i nostri container allo stesso modo. 
  • Quarto, ridurre al minimo le dipendenze. Non è un caso che abbiamo messo i kit di distribuzione nel contenitore, perché questo ci permette di evitare di scaricarli ogni volta da Internet. 
  • In quinto luogo, la velocità di creazione è aumentata: la presenza di copie locali delle immagini consente di evitare perdite di tempo durante il download, poiché esiste un'immagine locale. 

In altre parole, abbiamo ottenuto un processo di assemblaggio controllato e flessibile. Utilizziamo gli stessi strumenti per creare contenitori con versione completa. 

Come funziona la nostra procedura di costruzione

Immagini pronte per la produzione per k8s

L'assemblaggio viene avviato con un comando, il processo viene eseguito nell'immagine (evidenziata in rosso). Lo sviluppatore ha un file Docker (evidenziato in giallo), lo renderizziamo sostituendo le variabili con valori. E lungo il percorso aggiungiamo intestazioni e piè di pagina: questi sono i nostri agenti. 

L'intestazione aggiunge le distribuzioni dalle immagini corrispondenti. E footer installa i nostri servizi all'interno, configura l'avvio del carico di lavoro, della registrazione e di altri agenti, sostituisce l'entrypoint, ecc. 

Immagini pronte per la produzione per k8s

Abbiamo pensato a lungo se installare un supervisore. Alla fine, abbiamo deciso che avevamo bisogno di lui. Abbiamo scelto S6. Il supervisore prevede la gestione del contenitore: permette di connettersi ad esso in caso di crash del processo principale e prevede la gestione manuale del contenitore senza ricrearlo. Log e parametri sono processi in esecuzione all'interno del contenitore. Devono anche essere controllati in qualche modo e lo facciamo con l'aiuto di un supervisore. Infine, l'S6 si occupa della pulizia, dell'elaborazione del segnale e di altri compiti.

Poiché utilizziamo diversi sistemi di orchestrazione, dopo la creazione e l'esecuzione, il contenitore deve capire in quale ambiente si trova e agire in base alla situazione. Per esempio:
Ciò ci consente di creare un'immagine ed eseguirla in diversi sistemi di orchestrazione e verrà lanciata tenendo conto delle specificità di questo sistema di orchestrazione.

 Immagini pronte per la produzione per k8s

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

Immagini pronte per la produzione per k8s

Il carico utile viene eseguito sotto la supervisione di S6. Presta attenzione al raccoglitore e agli eventi: questi sono i nostri agenti responsabili di registri e metriche. Kubernetes non li ha, ma Docker sì. Perché? 

Se osserviamo le specifiche del “pod” (di seguito – pod Kubernetes), vedremo che il contenitore degli eventi viene eseguito in un pod, che ha un contenitore di raccolta separato che svolge la funzione di raccolta di parametri e log. Possiamo utilizzare le funzionalità di Kubernetes: eseguire contenitori in un pod, in un singolo processo e/o spazio di rete. Presenta effettivamente i tuoi agenti ed esegui alcune funzioni. E se lo stesso contenitore viene avviato in Docker, riceverà in output tutte le stesse funzionalità, ovvero sarà in grado di fornire log e metriche, poiché gli agenti verranno avviati internamente. 

Metriche e log

Fornire parametri e log è un compito complesso. Ci sono diversi aspetti nella sua decisione.
L'infrastruttura è creata per l'esecuzione del carico utile e non per la consegna di massa dei log. Cioè, questo processo deve essere eseguito con requisiti minimi di risorse del contenitore. Ci impegniamo ad aiutare i nostri sviluppatori: "Prendi un contenitore Docker Hub, eseguilo e possiamo fornire i log". 

Il secondo aspetto è limitare il volume dei log. Se si verifica un aumento del volume dei log in diversi contenitori (l'applicazione genera un'analisi dello stack in un ciclo), il carico sulla CPU, sui canali di comunicazione e sul sistema di elaborazione dei log aumenta e ciò influisce sul funzionamento dell'host come un contenitori interi e altri sull'host, a volte ciò porta alla "caduta" dell'host. 

Il terzo aspetto è che è necessario supportare il maggior numero possibile di metodi di raccolta delle metriche pronti all'uso. Dalla lettura di file e il polling dell'endpoint Prometheus all'utilizzo di protocolli specifici dell'applicazione.

E l’ultimo aspetto è ridurre al minimo il consumo di risorse.

Abbiamo scelto una soluzione Go open source chiamata Telegraf. Si tratta di un connettore universale che supporta più di 140 tipi di canali di ingresso (plugin di ingresso) e 30 tipi di canali di uscita (plugin di uscita). Lo abbiamo finalizzato e ora ti racconteremo come lo utilizziamo prendendo come esempio Kubernetes. 

Immagini pronte per la produzione per k8s

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

  • Per fare ciò, utilizza le annotazioni dei pod e, a seconda del suo contenuto, crea, ad esempio, un punto finale Prometheus; 
  • In base alle specifiche del pod e alle impostazioni specifiche del contenitore, decide come fornire i log.

Raccogliamo i log tramite l'API Docker: gli sviluppatori devono solo inserirli in stdout o stderr e Collector risolverà il problema. I log vengono raccolti in blocchi con un certo ritardo per evitare un possibile sovraccarico dell'host. 

I parametri vengono raccolti tra le istanze del carico di lavoro (processi) in contenitori. Tutto viene taggato: namespace, under e così via, quindi convertito nel formato Prometheus e diventa disponibile per la raccolta (ad eccezione dei log). Inviamo inoltre log, metriche ed eventi a Kafka e oltre:

  • I log sono disponibili in Graylog (per l'analisi visiva);
  • Registri, metriche ed eventi vengono inviati a Clickhouse per l'archiviazione a lungo termine.

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

Se non disponiamo di Kubernetes con pod, lo schema è un po’ più complicato, ma funziona secondo gli stessi principi.

Immagini pronte per la produzione per k8s

Gli stessi processi vengono eseguiti all'interno del contenitore, sono orchestrati utilizzando S6. Tutti gli stessi processi vengono eseguiti all'interno dello stesso contenitore.

Come risultato,

Abbiamo creato una soluzione completa per la creazione e il lancio di immagini, con opzioni per la raccolta e la distribuzione di log e metriche:

  • Abbiamo sviluppato un approccio standardizzato per l'assemblaggio delle immagini e, sulla base di esso, abbiamo sviluppato modelli CI;
  • Gli agenti di raccolta dati sono le nostre estensioni Telegraf. Li abbiamo testati bene in produzione;
  • Utilizziamo il webhook di mutazione per implementare contenitori con agenti in pod; 
  • Integrato nell'ecosistema Kubernetes/Rancher;
  • Possiamo eseguire gli stessi contenitori in diversi sistemi di orchestrazione e ottenere il risultato che ci aspettiamo;
  • Creata una configurazione di gestione dei contenitori completamente dinamica. 

Coautore: Ilya Prudnikov

Fonte: habr.com

Aggiungi un commento