Immagini pronte per la produzione per k8s

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

Immagini pronte per la produzione per k8s

Siamo di Exness, un'azienda fintech che sviluppa servizi per il trading online e prodotti fintech per B2B e B2C. Nel nostro R&D ci sono diverse squadre, con oltre 100 dipendenti nel reparto sviluppo.

Rappresentiamo il team responsabile della piattaforma per raccogliere e lanciare il codice dai nostri sviluppatori. In particolare, ci occupiamo della raccolta, conservazione e fornitura di metriche, log ed eventi dalle applicazioni. Attualmente gestiamo circa tremila container Docker in un ambiente di produzione, supportiamo il nostro magazzino big data da 50 Tb e forniamo soluzioni architetturali che ruotano attorno alla nostra infrastruttura: Kubernetes, Rancher e vari fornitori di cloud pubblici. 

La nostra motivazione

Cosa brucia? Nessuno può rispondere. Dove è il focolaio? È difficile capire. Quando è scoppiato? Si può scoprire, ma non subito. 

Immagini pronte per la produzione per k8s

Perché alcuni contenitori si fermano mentre altri crollano? Quale contenitore ne è la causa? Esternamente, i contenitori sembrano identici, ma all'interno ognuno ha il proprio Neo.

Immagini pronte per la produzione per k8s

I nostri sviluppatori sono professionisti capaci. Creano buoni servizi che portano profitto all'azienda. Tuttavia, ci sono anche inconvenienti, quando i contenitori delle applicazioni si comportano in modo anomalo. Un contenitore consuma troppa CPU, un altro la rete, un terzo si occupa di operazioni di input/output, mentre un quarto non si capisce affatto cosa faccia con i socket. Tutto questo causa problemi e la nave affonda. 

Agenti

Per capire cosa succede all'interno, abbiamo deciso di inserire agenti direttamente nei contenitori.

Immagini pronte per la produzione per k8s

Questi agenti sono programmi di supporto che mantengono i contenitori in uno stato tale da non danneggiarsi a vicenda. Gli agenti sono standardizzati, il che consente di uniformare l'approccio alla gestione dei contenitori. 

Nel nostro caso, gli agenti devono fornire log in un formato standard, etichettati e con throttling. Devono inoltre fornire metriche standardizzate, espandibili per quanto riguarda le applicazioni aziendali.

Con gli agenti si intendono anche strumenti per l'implementazione e la manutenzione, capaci di operare in diversi sistemi di orchestrazione e supportare diverse immagini (Debian, Alpine, CentOS, ecc.).

Infine, gli agenti devono supportare una semplice CI/CD, che comprenda file Docker. Altrimenti, la nave si disintegrerà, poiché i contenitori cominceranno a essere forniti su binari "storti".

Il processo di build e la realizzazione dell'immagine di destinazione

Per garantire che tutto sia standardizzato e gestibile, è necessario seguire un processo di build standard. Pertanto, abbiamo deciso di costruire contenitori con contenitori - una sorta di ricorsione.

Immagini pronte per la produzione per k8s

Qui i contenitori sono presentati come contorni solidi. Abbiamo anche deciso di includere distribuzioni in essi, per rendere la "vita più dolce". Spiegheremo più avanti il motivo di questa scelta.
 
Il risultato è stato uno strumento per la costruzione: un contenitore di una certa versione che fa riferimento a specifiche versioni di distribuzioni e a specifiche versioni di script.

Come lo utilizziamo? Abbiamo un Docker Hub in cui si trova il contenitore. Lo specchiamo all'interno del nostro sistema per eliminare le dipendenze esterne. È stato creato un contenitore contrassegnato in giallo. Creiamo un template per installare nel contenitore tutte le distribuzioni e gli script necessari. Dopo di che, assemblamo un'immagine pronta per l'uso: gli sviluppatori vi inseriscono il codice e le loro specifiche dipendenze. 

Quali sono i vantaggi di questo approccio? 

  • Innanzitutto, il completo controllo delle versioni degli strumenti di assemblaggio – contenitore di assemblaggio, versioni degli script e delle distribuzioni. 
  • In secondo luogo, abbiamo raggiunto la standardizzazione: creiamo template, immagini intermedie e pronte all'uso in modo uniforme. 
  • In terzo luogo, i contenitori ci assicurano la portabilità. Oggi utilizziamo Gitlab, e domani possiamo passare a TeamCity o Jenkins e mantenere la stessa capacità di eseguire i nostri contenitori. 
  • In quarto luogo, minimizzazione delle dipendenze. Non abbiamo inserito per caso le distribuzioni nel contenitore, poiché ciò consente di non doverle scaricare ogni volta da Internet. 
  • In quinto luogo, è aumentata la velocità di assemblaggio: la presenza di copie locali delle immagini consente di risparmiare tempo nelle scaricamenti, dato che è disponibile un'immagine locale. 

In altre parole, abbiamo raggiunto un processo di assemblaggio controllato e flessibile. Utilizziamo gli stessi strumenti per assemblare qualsiasi contenitore con versioning completo. 

Come funziona la nostra procedura di assemblaggio

Immagini pronte per la produzione per k8s

L'assemblaggio inizia con un solo comando, e il processo si svolge all'interno di un'immagine (evidenziato in rosso). Lo sviluppatore ha un file Docker (evidenziato in giallo), lo renderizziamo sostituendo le variabili con i valori. Nel frattempo, aggiungiamo header e footer: questi sono i nostri agenti. 

L'header aggiunge i pacchetti dai relativi immagini. Il footer installa i nostri servizi, configura l'avvio del carico di lavoro, della registrazione e altri agenti, e sostituisce l'entrypoint, ecc. 

Immagini pronte per la produzione per k8s

Ci abbiamo pensato a lungo se implementare un supervisore. Alla fine, abbiamo deciso che ci serviva. Abbiamo scelto S6. Il supervisore gestisce il contenitore: permette di collegarsi ad esso in caso di crash del processo principale e offre una gestione manuale del contenitore senza doverlo ricreare. 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 della gestione della manutenzione, dell'elaborazione dei segnali e di altre attività.

Poiché utilizziamo diversi sistemi di orchestrazione, dopo la costruzione e l'avvio, il contenitore deve capire in quale ambiente si trova e agire di conseguenza. Ad esempio:
Questo ci consente di creare un'unica immagine e di eseguirla in diversi sistemi di orchestrazione, tenendo conto delle 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 events: sono i nostri agenti che si occupano di log e metriche. In Kubernetes non ci sono, mentre in Docker ci sono. Perché? 

Se si guarda la specifica del "pod" (qui e oltre – pod di Kubernetes), si vedrà che il contenitore events viene eseguito in un pod, che ha un contenitore separato collector, incaricato di raccogliere metriche e log. Possiamo sfruttare le capacità di Kubernetes: eseguire contenitori all'interno dello stesso pod, nello stesso spazio di processo e/o di rete. Infatti, è possibile implementare i propri agenti e svolgere determinate funzioni. E se lo stesso contenitore fosse avviato in Docker, avrebbe le stesse capacità, vale a dire, sarebbe in grado di inviare log e metriche, poiché gli agenti saranno eseguiti all'interno. 

Metriche e log

La consegna di metriche e log è un compito complesso. La sua soluzione comporta diversi aspetti.
L'infrastruttura è creata per eseguire carichi di lavoro utili, non per la massiccia consegna di log. Questo processo deve dunque essere eseguito con il minimo impatto sulle risorse dei contenitori. Ci impegniamo ad aiutare i nostri sviluppatori: "Prendete un contenitore da Docker Hub, avviatelo e saremo in grado di consegnare i log." 

Il secondo aspetto è la limitazione del volume dei log. Se in più container si verifica una situazione di picco nel volume dei log (l'applicazione genera continuamente stack-trace), aumenta il carico sulla CPU, sui canali di comunicazione e sul sistema di elaborazione dei log, e ciò influisce sul funzionamento dell'host nel suo complesso e su altri container nello stesso host, portando talvolta a un "crash" dell'host. 

Il terzo aspetto è la necessità di supportare out-of-the-box il maggior numero possibile di metodologie per la raccolta delle metriche. Dalla lettura dei file e l'interrogazione del Prometheus-endpoint fino all'utilizzo di protocolli specifici delle applicazioni.

E l'ultimo aspetto è la necessità di minimizzare il consumo delle risorse.

Abbiamo scelto una soluzione open-source basata su Go chiamata Telegraf. Questo è un connettore versatile che supporta oltre 140 tipi di canali di input (input plugins) e 30 tipi di output (output plugins). Lo abbiamo modificato e ora vi mostreremo come viene utilizzato da noi attraverso l'esempio di Kubernetes. 

Immagini pronte per la produzione per k8s

Supponiamo che uno sviluppatore distribuisca un carico e Kubernetes riceva una richiesta per creare un pod. In quel momento, 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 loro contenuto, crea, ad esempio, un endpoint di Prometheus; 
  • Sulla base della specifica del pod e delle impostazioni specifiche dei contenitori, decide come inviare i log.

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

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

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

Allo stesso modo, tutto funziona in AWS, solo che sostituiamo Graylog con Kafka con Cloudwatch. Inviamo i log lì, e risulta molto conveniente: è immediatamente 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. Tutti gli stessi processi sono avviati all'interno di un unico contenitore.

In conclusione

Abbiamo creato una soluzione integrata per la costruzione e il rilascio delle immagini in produzione, con opzioni di raccolta e consegna di log e metriche:

  • Abbiamo sviluppato un approccio standardizzato per la costruzione delle immagini, basato sul quale abbiamo creato modelli CI;
  • Gli agenti per la raccolta dei dati sono le nostre estensioni Telegraf. Le abbiamo testate con successo in produzione;
  • Utilizziamo un webhook di mutazione per l'inserimento dei contenitori con agenti nei pod; 
  • Ci siamo integrati nell'ecosistema Kubernetes/Rancher;
  • Possiamo eseguire contenitori identici in diversi sistemi di orchestrazione e ottenere i risultati attesi;
  • Abbiamo creato una configurazione di gestione dei container completamente dinamica. 

Coautore: Il'ja Prudnikov

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