
Il primo passo per il deployment su Kubernetes è posizionare la tua applicazione in un contenitore. In questa serie, esamineremo come creare un'immagine di un piccolo e sicuro contenitore.
Con Docker, creare immagini di contenitori non è mai stato così semplice. Specifica l'immagine di base, aggiungi le tue modifiche e crea il contenitore.

Anche se questa tecnica è perfetta per iniziare, l'uso delle immagini di base predefinite può portare a operazioni insicure con immagini grandi e piene di vulnerabilità.
Inoltre, la maggior parte delle immagini Docker utilizza Debian o Ubuntu come immagine di base e, sebbene ciò garantisca grande compatibilità e semplice adattamento (un file Docker richiede solo due righe di codice), le immagini di base possono aggiungere centinaia di megabyte di sovraccarico al tuo contenitore. Ad esempio, un semplice file di un'applicazione Node.js 'hello-world' occupa circa 700 megabyte, mentre la dimensione effettiva della tua applicazione è solo di pochi megabyte.

Pertanto, tutto questo carico aggiuntivo rappresenta un'eccellente perdita di spazio digitale e un perfetto nascondiglio per vulnerabilità e errori nel sistema di sicurezza. Dunque, vediamo due modi per ridurre le dimensioni dell'immagine del contenitore.
Il primo modo riguarda l'uso di immagini di base di piccole dimensioni, il secondo è l'uso del pattern progettuale Builder Pattern. L'utilizzo di immagini di base più piccole è probabilmente il metodo più semplice per ridurre le dimensioni del tuo contenitore. È molto probabile che il linguaggio o lo stack che stai utilizzando fornisca un'immagine dell'applicazione originale significativamente più piccola rispetto all'immagine predefinita. Diamo un'occhiata al nostro contenitore node.js.

Per impostazione predefinita, la dimensione dell'immagine di base node:8 in Docker è di 670 MB, mentre la dimensione di node:8-alpine è di solo 65 MB, ossia dieci volte inferiore. Utilizzando un'immagine di base più piccola, come Alpine, ridurrai notevolmente la dimensione del tuo contenitore. Alpine è una distribuzione Linux piccola e leggera, molto popolare tra gli utenti di Docker perché è compatibile con molte applicazioni, mantenendo al contempo dimensioni ridotte. A differenza dell'immagine Docker standard "node", "node:alpine" rimuove numerosi file e programmi di servizio, mantenendo solo quelli necessari per eseguire la tua applicazione.
Per passare a un'immagine di base più piccola, basta aggiornare il file Docker per iniziare a lavorare con la nuova immagine di base:

Ora, a differenza della vecchia immagine onbuild, devi copiare il tuo codice nel contenitore e installare le dipendenze. Nel nuovo file Docker, il contenitore inizia con l'immagine node:alpine, quindi crea una directory per il codice, installa le dipendenze utilizzando il gestore di pacchetti NPM e, infine, esegue server.js.

Con questo aggiornamento, si ottiene un container di dimensioni dieci volte inferiori. Se il tuo linguaggio di programmazione o stack non offre funzioni per ridurre l'immagine di base, utilizza Alpine Linux. Questo ti darà anche la possibilità di gestire completamente il contenuto del container. L'utilizzo di immagini base di piccole dimensioni è un ottimo modo per creare rapidamente piccoli container. Ma si può ottenere un ulteriore riduzione utilizzando il Builder Pattern.

Nei linguaggi interpretati, il codice sorgente viene prima passato all'interprete e poi eseguito direttamente. Nei linguaggi compilati, il codice sorgente viene innanzitutto trasformato in codice compilato. Durante questo processo, la compilazione utilizza spesso strumenti che in realtà non sono necessari per eseguire il codice. Ciò significa che puoi rimuovere completamente questi strumenti dal container finale. Per questo, puoi utilizzare il Builder Pattern.

Il codice viene creato nel primo container e compilato. Successivamente, il codice compilato viene impacchettato nel container finale senza compilatori e strumenti necessari per compilare quel codice. Analizziamo questo processo con un'applicazione Go. Prima di tutto, passeremo dall'immagine onbuild a Alpine Linux.

Nel nuovo file Docker, il container inizia con l'immagine golang:alpine. Quindi crea una directory per il codice, copia il codice sorgente, compila questo codice e avvia l'applicazione. Questo container è molto più piccolo rispetto al container onbuild, ma contiene ancora il compilatore e altri strumenti Go che in realtà non ci servono. Pertanto, estraiamo semplicemente il programma compilato e mettiamolo nel nostro container.

Potresti notare qualcosa di strano in questo file Docker: contiene due righe FROM. La prima sezione, composta da 4 righe, appare identica al precedente file Docker, tranne per il fatto che utilizza la parola chiave AS per dare un nome a questo passaggio. Nella sezione successiva c'è una nuova riga FROM che consente di avviare una nuova immagine, utilizzando Raw alpine come base invece dell'immagine golang:alpine.
Raw Alpine Linux non ha certificati SSL installati, il che porterà al fallimento della maggior parte delle chiamate API tramite HTTPS. Quindi, procediamo ad installare alcuni certificati root CA.
E ora la parte più interessante: per copiare il codice compilato dal primo contenitore al secondo, puoi semplicemente usare il comando COPY situato nella 5ª riga della seconda sezione. Questo copierà solo un file dell'applicazione, senza toccare gli strumenti ausiliari di Go. Il nuovo file Docker multi-stadio avrà un'immagine del contenitore di soli 12 megabyte, rispetto ai 700 megabyte dell'immagine di origine, il che fa una grande differenza!
Pertanto, utilizzare immagini di base di dimensioni ridotte e il Builder Pattern è un ottimo modo per creare container di dimensioni significativamente inferiori senza un grande volume di lavoro.
È possibile che, a seconda dello stack applicativo, ci siano ulteriori modi per ridurre le dimensioni dell'immagine e del container, ma è davvero vero che container più piccoli offrono un vantaggio misurabile? Esaminiamo due aspetti in cui i container piccoli sono estremamente efficaci: prestazioni e sicurezza.
Per valutare l'aumento delle prestazioni, consideriamo la durata del processo di creazione del container, il suo push nel registro e il successivo pull da esso. È evidente che un container di dimensioni minori ha un vantaggio indiscutibile rispetto a uno di dimensioni maggiori.

Docker memorizzerà i livelli, quindi le build successive verranno eseguite molto rapidamente. Tuttavia, in molti sistemi CI utilizzati per il build e il test dei contenitori, i livelli non vengono memorizzati, quindi c'è un notevole risparmio di tempo. Come si può vedere, il tempo di costruzione di un contenitore di grandi dimensioni, a seconda della potenza della tua macchina, varia da 34 a 54 secondi, mentre utilizzando un contenitore ridotto attraverso il Builder Pattern, si va da 23 a 28 secondi. Per operazioni di questo tipo, il guadagno di prestazioni sarà del 40-50%. Quindi, pensa solo a quante volte crei e testi il tuo codice.
Dopo che il contenitore è stato creato, devi caricare la sua immagine (push container image) nel registro dei contenitori per poterla utilizzare nel tuo cluster Kubernetes. Ti consiglio di utilizzare il registro dei contenitori Google.

Utilizzando Google Container Registry (GCR), paghi solo per lo storage e il traffico «grezzi», senza costi aggiuntivi per la gestione dei container. È una soluzione riservata, sicura e molto veloce. GCR utilizza molte tecniche per accelerare l'operazione di pull. Come puoi vedere, l'inserimento dell'immagine del container Docker Container Image usando go:onbuild, a seconda delle prestazioni del computer, richiederà da 15 a 48 secondi, mentre la stessa operazione con un container più piccolo richiederà da 14 a 16 secondi, con un incremento della velocità delle operazioni che arriva fino a 3 volte per macchine meno performanti. Per macchine più grandi, i tempi sono simili, poiché GCR utilizza una cache globale per la base di immagini condivisa, quindi non è necessario caricarle affatto. Nei computer a bassa potenza, la CPU è il collo di bottiglia, quindi il vantaggio dell'uso di container più piccoli è molto più evidente.
Se utilizzi GCR, ti consiglio vivamente di impiegare Google Container Builder (GCB) come parte del tuo sistema di build.

Come potete vedere, il suo utilizzo consente di ottenere risultati significativamente migliori nella riduzione della durata dell'operazione Build+Push, rispetto anche a una macchina performante: in questo caso, il processo di costruzione e invio dei contenitori si accelera quasi di due volte. Inoltre, ogni giorno ricevete 120 minuti di compilazione gratuita, che soddisfa la maggior parte delle esigenze di creazione dei contenitori.
La metrica di performance più importante è la velocità di estrazione o download dei contenitori Pull. Se non vi preoccupa molto il tempo impiegato per l'operazione push, la durata del processo pull influisce seriamente sulle prestazioni complessive del sistema. Supponiamo di avere un cluster di tre nodi e che uno di essi fallisca. Se utilizzate un sistema di gestione come Google Kubernetes Engine, esso sostituirà automaticamente il nodo non funzionante con uno nuovo. Tuttavia, questo nuovo nodo sarà completamente vuoto e dovrete trasferire tutti i vostri contenitori su di esso affinché inizi a funzionare. Se l'operazione pull richiede troppo tempo, il vostro cluster funzionerà con prestazioni ridotte per tutto quel tempo.
Ci sono molte situazioni in cui ciò può verificarsi: l'aggiunta di un nuovo nodo al cluster, l'aggiornamento dei nodi o persino il passaggio a un nuovo container per il deployment. Pertanto, minimizzare il tempo di estrazione del pull diventa un fattore chiave. È indiscutibile che un container di piccole dimensioni si scarica molto più velocemente di uno grande. Se utilizzi più container in un cluster Kubernetes, il risparmio di tempo può essere molto significativo.

Guarda il confronto fornito: l'operazione di pull con container piccoli richiede da 4 a 9 volte meno tempo, a seconda della potenza della macchina, rispetto alla stessa operazione utilizzando go:onbuild. L'uso di immagini di base comuni di piccole dimensioni accelera notevolmente il tempo e la velocità con cui nuovi nodi Kubernetes possono essere implementati e andare online.
Affrontiamo la questione della sicurezza. Si ritiene che i container più piccoli siano molto più sicuri rispetto a quelli grandi, poiché hanno una superficie di attacco minore. È davvero così? Una delle funzionalità più utili di Google Container Registry è la possibilità di scansionare automaticamente i tuoi container per vulnerabilità. Alcuni mesi fa ho creato container sia onbuild che multilivello, quindi vediamo se ci sono punti vulnerabili.

Il risultato è straordinario: nel piccolo container sono state trovate solo 3 vulnerabilità medie, mentre nel grande ce ne sono 16 critiche e 376 altre vulnerabilità. Esaminando il contenuto del grande container, si può vedere che la maggior parte dei problemi di sicurezza non ha nulla a che fare con la nostra applicazione, ma è legata a programmi che non utilizziamo affatto. Pertanto, quando si parla di una grande superficie di attacco, si intende proprio questo.

La conclusione è chiara: crea container piccoli, poiché offrono reali vantaggi in termini di prestazioni e sicurezza per il tuo sistema.

Un po' di pubblicità 🙂
Grazie per essere con noi. Ti piacciono i nostri articoli? Vuoi vedere più contenuti interessanti? Supportaci effettuando un ordine o raccomandandoci ai tuoi conoscenti, , un'alternativa unica ai server entry-level, che abbiamo creato per te: (disponibili opzioni con RAID1 e RAID10, fino a 24 core e fino a 40GB DDR4).
Dell R730xd a metà prezzo nel data center Equinix Tier IV ad Amsterdam? Solo da noi nei Paesi Bassi! Dell R420 — 2x E5-2430 2.2GHz 6C 128GB DDR3 2x960GB SSD 1Gbps 100TB — a partire da $99! Scopri di più su
Fonte: habr.com
