Home / Blog / -

Docker: cos’è, a cosa serve e come funziona?

di Giuseppe Maggi

Mentre guardi video su Netflix o ascolti musica su Spotify, ti sei mi chiesto quanti altri nel mondo stiano facendo la stessa cosa in quel preciso istante? Sarebbe un pensiero stimolante non solo da utente ma anche da programmatore! Ti farebbe riflettere sulla mole di dati che le applicazioni gestiscono, sull’intensità dei loro flussi real-time e sul carico di lavoro che schizza alle stelle nei momenti di picco. Ciò, al giorno d’oggi, significa una sola cosa: la progettazione di sistemi software ha bisogno di flessibilità e scalabilità.

In pratica, ha bisogno di tutto ciò per cui le tecnologie a container sono nate. In questa guida parliamo proprio di questo e soprattutto di Docker, la soluzione leader in questo ambito.

La crisi del monolite

Nei decenni che hanno reso lo sviluppo software un bisogno impellente per la società – parliamo degli anni Ottanta e Novanta – si sono affermate due pratiche assolutamente necessarie:

  • La programmazione orientata agli oggetti, lanciata in grande stile dal C++ ma adottata poi praticamente da ogni linguaggio;
  • La realizzazione di applicazioni sempre più grandi con una struttura che oggi viene chiamata – in senso un po’ dispregiativo – monolitica.

La programmazione orientata agli oggetti permetteva di organizzare al meglio la rete di informazioni gestita dall’applicazione. In maniera, potremmo dire, piramidale i dati costituivano oggetti, questi venivano collegati in strutture dinamiche le quali a loro volta entravano a far parte di aggregazioni più grandi. Ciò permetteva di inglobare in maniera organizzata funzionalità che via via venivano richieste. Un approccio di questo tipo, oltre a tutti i risultati che l’Ingegneria del Software aveva portato, offriva la possibilità di realizzare grandi piattaforme di lavoro che con l’avvento di Internet potevano essere raggiunte attraverso la Rete offrendo servizi ovunque.

Da un punto di vista progettuale però la struttura a monolite regnava. Si finiva per creare software a corpo unico che accedevano all’esterno, per lo più, solo per attingere a fonti dati quali database, file, archivi di vario genere ma per il resto tendevano a conservare al proprio interno ogni funzionalità.

Il monolite venne messo in crisi dai tempi e dalle necessità che la crescente informatizzazione e diffusione della connettività portavano. Tra i principali svantaggi possiamo individuarne alcuni fortemente accentuati dalle dimensioni crescenti del software:

  • Difficoltà di manutenzione ed espansione. La modifica o integrazione di una piccola parte del software deve essere applicata ad un programma molto grande garantendo di raggiungere i propri scopi senza intaccare il suo funzionamento complessivo;
  • Ulteriore problema, legato a quello citato al punto precedente, verte sulle modalità da seguire per mandare in produzione queste modifiche. Se il programma costituisce un servizio on-line, costantemente utilizzato dagli utenti, per poter attivare la modifica generalmente è necessario spegnerlo e riavviarlo. Ciò spesso causa quelle fastidiose interruzioni di servizio che vengono notificate con banner o finestre di dialogo (“Si avvisa che per interventi tecnici il servizio non sarà disponibile dalle ore…”) ed in molti altri casi ciò non è affatto possibile semplicemente perché il servizio non può essere mai arrestato: non pochi committenti si tutelano di fronte a tale evenienza anche a livello contrattuale;
  • Difficoltà di attuazione di strategie di scalabilità. Si intende per scalabilità la capacità di un sistema di aumentare le proprie risorse nei momenti di picco e, altrettanto importante, ridurle nei momenti di minore carico. Con un monolite, la scalabilità può essere applicata solo a livello di intera applicazione replicandola su più server. Non è possibile infatti “scalare” solo certe funzionalità. Ad esempio, un sistema moderno esposto sul web potrebbe aver bisogno di scalare interfacce utente e gestione di alcune interazioni su database: con il monolite non è possibile, o lo replichi tutto o niente;
  • I monoliti sono generalmente scritti in un unico linguaggio perché compongono un solo progetto. Sarebbe utile ma problematico, ad esempio, sviluppare alcune parti in Node.js ed altre in Python, qualcosa in Java e – perché no? – un po’ in Microsoft .NET;
  • Un problema in qualsiasi parte del sistema rischierebbe di bloccarne l’intero funzionamento in quanto un errore nel programma, a seconda del punto in cui si verifica, potrebbe risultare fatale;
  • La gestione delle risorse non può essere elastica in quanto il monolite funziona come blocco unico e ciò non permette l’attuazione di risparmi in fatto di energia elettrica ma anche di gestione di RAM, connessioni a database, etc.;
  • Anche l’organizzazione dei team di lavoro non ne risulta agevolata in quanto le varie componenti del progetto collaborano strettamente e alcuni sviluppatori potrebbero aver bisogno di attendere il completamento del lavoro di altri per poter iniziare.

Insomma, in generale, i problemi delle applicazioni monolitiche sono ravvisabili nelle loro dimensioni e unicità che impediscono la gestione autonoma delle singole componenti.

Tutto ciò trova soluzioni efficacissime nei container e nelle architetture a microservizi.

» VUOI SAPERNE DI PIU’? VAI AL NOSTRO CORSO DOCKER «

Cosa sono i container

Un container è un ambiente che crea un contesto completo per l’esecuzione di un’applicazione. Può essere considerato una forma di virtualizzazione, ma a differenza delle macchine virtuali non ha bisogno dell’installazione di un sistema operativo, occupa il minimo spazio indispensabile e non richiede l’allocazione fissa di risorse come la RAM.

Un container è basato su un’immagine che rappresenta tutto il suo contenuto e definisce quale applicativo sarà eseguito al suo interno. L’immagine, una volta chiusa, continua a mantenere la sua composizione fissa e da essa potranno essere animati molteplici container: una buona progettazione delle immagini è al centro di qualsiasi strategia di containerizzazione.

Facciamo qualche esempio. Ti serve usare dei database MySQL, ma non hai voglia di installare il servizio sul computer? Con una tecnologia a container puoi predisporre o scaricare un’immagine con all’interno tale software ed eseguirla in un container. Vuoi provare un nuovo linguaggio? Sperimentare Node.js, Go, Rust o qualsiasi altro? Idem: scarichi un’immagine e la usi. E così via per qualsiasi altro ambiente ti possa interessare.

Allo stesso modo, quando avrai finito di completare la tua applicazione potrai predisporre un’immagine in cui definirai ciò che le occorre per funzionare bene e le risorse di cui avrà bisogno per poi impacchettare il tutto. Da quel momento in poi potrai avviare quanti container vuoi con la tua applicazione in esecuzione.

Questo è il funzionamento in generale ma ora andiamo a vedere come questi concetti vengano applicati in Docker.

Docker: iniziamo a lavorare

Docker (https://www.docker.com/) è un prodotto open source, realizzato in linguaggio Go, disponibile in due versioni principali:

  • Docker CE (Community Edition): gratuito, utilizzabile pienamente come supporto alla containerizzazione;
  • Docker EE (Enterprise Edition): a pagamento, offre certificazioni e supporto nonché elementi architetturali ideali per soluzioni di grande respiro.

Noi ci riferiremo da ora a Docker CE, il più utilizzato dagli sviluppatori per varie ragioni: primo, perché gratuito; secondo, perché piattaforma ricca e completissima per soluzioni professionali anche di livello notevole; terzo, perché contiene elementi fondamentali presenti nella versione EE ed in ogni altra piattaforma basata su Docker.

Docker è disponibile in ogni piattaforma. Per Windows e MacOS possiamo installare Docker DesktopDocker Desktop seppur ponendo attenzione alle caratteristiche hardware e di sistema operativo che richiede.

Per Linux, ambiente nativo e adattissimo a Docker, si può procedere con l’installazione del Docker Engine, il motore fondamentale sia mediante pacchetti precompilati disponibili nei repository sia attraverso scaricamento del prodotto.

Una volta installato Docker si può iniziare con la creazione di container partendo da una delle tante immagini già esistenti e disponibili nel principale servizio on-line dedicato alla loro distribuzione: Docker HubDocker Hub.

» VUOI SAPERNE DI PIU’? VAI AL NOSTRO CORSO DOCKER «

Il primo strumento per l’invio di comandi a Docker è il suo tool da riga di comando. E’ necessario prenderci confidenza sin da subito e Docker Hub mette a disposizione un modo velocissimo per farlo: l’immagine hello-world (https://hub.docker.com/_/hello-world) che già dal nome presuppone il suo ruolo di “esempio zero” di questa tecnologia.

Basta aprire l’interfaccia da riga di comando del proprio sistema (terminale, Prompt dei comandi, shell o in qualsiasi modo si chiami nella macchina che stiamo usando) e digitare:

$ docker run hello-world

Il risultato sarà una serie di righe contenenti, tra l’altro, un saluto di benvenuto, alcune indicazioni di massima nonché indicazioni per ulteriori prove: questa è la dimostrazione che Docker è stato installato correttamente.

Alla prima riga di output, probabilmente Docker riporterà il messaggio “Unable to find image ‘hello-world:latest’ locally” la quale suona un po’ come un errore ma in realtà sta dicendo semplicemente che, visto che non possediamo ancora in locale l’immagine hello-world con tag “latest”, Docker l’ha dovuta scaricare da Docker Hub per poterla eseguire.

Il comando “docker” visto in precedenza è il client da riga di comando di cui dicevamo tramite il quale potremo veicolare tutte le direttive per la gestione delle immagini e dei container. Viene seguito in genere da una parola chiave che specifica un’azione – in questo caso run ma ne esistono molte altre –, dal nome dell’oggetto sui cu si applica (qui l’immagine hello-world) e da una serie di opzioni a seconda dei casi.

Altro esempio super-rapido è l’avvio di un’immagine che contenga un sistema di programmazione. Proviamo a farci avviare Node.js senza averlo installato nel sistema:

$  docker run -it node

Viene in questo modo scaricata ed eseguita l’immagine node, ufficiale per Node.js, e lanciata in modalità interattiva ovvero in ascolto per ricevere i comandi dell’utente.

Avremo così una console in cui poter digitare direttive Javascript:

$ docker run -it node
> console.log(‘Ciao Node.js!’)
Ciao Node.js!
> a = 5
5
> b = 6
6
> somma=a+b
11
> .exit

Tutte le righe che iniziano con il simbolo > compongono la sessione di lavoro in Node.js all’interno del container e, al termine, con .exit abbiamo chiuso l’interazione con questo ambiente: ciò pone anche fine all’esecuzione del container stesso riportandoci alla console del sistema operativo.

» VUOI SAPERNE DI PIU’? VAI AL NOSTRO CORSO DOCKER «

Applicazioni multi-container, orchestrazione e microservizi

I container nascono per lavorare insieme e collaborare tra loro. L’abbandono del monolite porta inesorabilmente alla creazione di architetture a microservizi dove ogni porzione del software viene sostituito da un container che la interpreta. Con un approccio simile, l’applicazione diventa la composizione di una moltitudine di nodi che dialogano tra loro portando al superamento di tutti i limiti che il monolite presentava:

  • Si può modificare e integrare codice frequentemente, anche a distanza di pochi giorni, modificando il singolo container e ricaricandolo;
  • Il riavvio di un container non richiede l’interruzione del servizio;
  • La scalabilità è applicata a livello di container pertanto possiamo ridondare in caso di necessità le porzioni applicative più nevralgiche in una data situazione. I moderni sistemi in Cloud devono saper fare tutto ciò in maniera automatica rendendosi conto delle evoluzioni del carico di lavoro. Si parla in tali casi di autoscaling;
  • Ogni container può essere sviluppato in un linguaggio diverso infatti il collante che li unirà sarà un dialogo secondo protocolli standard di rete, molto spesso API REST, neutre rispetto alle singole tecnologie.

Il primo step per rendere un’applicazione multi-container sarà imparare la sintassi di Docker Compose, un tool che con un semplice file in formato YAML sarà in grado avviare insieme e secondo determinate politiche una serie di servizi interpretati da vari container.

Per installazioni di dimensioni molto estese, si passa poi all’orchestrazione dove più nodi Docker mettono a disposizione i propri container per creare reti sempre più articolate di componenti applicative. Le principali soluzioni di orchestrazione sono Swarm, nativo e sempre disponibile in Docker, e Kubernetes, ideato da Google e affermatosi con forza in ogni scenario. Nomi di questo genere sono ricorrenti in tutte le piattaforme Cloud che offrono – ormai è doveroso – la possibilità di realizzare applicazioni distribuite su più container.

Conclusioni

Docker ormai è un passaggio obbligato nelle situazioni applicative ed è uno strumento che può arricchire molto il bagaglio culturale di un programmatore. E’ un mondo a parte, ricco di concetti particolari, ma non bisogna farsi spaventare perché è perfettamente abbordabile, interessantissimo, gratuito e si rende utile sin dai primi momenti di utilizzo. Una volta conosciutolo, non si installerà mai più un database, un linguaggio o un tool solo per provarlo: si cercherà un’immagine su Docker Hub e la si avvierà. Rotto il ghiaccio poi, poche righe di Docker Compose permetteranno di avviare ambienti di lavoro più articolati composti da più elementi. Si avranno così conoscenze importanti, acquisite in pochissimo tempo: basterà qualche ora di studio dei nostri videocorsi.

» VUOI SAPERNE DI PIU’? VAI AL NOSTRO CORSO DOCKER «