Si parla spesso di come Docker, e i container in particolare, diano la possibilità di creare in maniera semplice degli ambienti isolati per le nostre applicazioni. Sebbene questo offra da un lato grandi vantaggi, dall’altro pone delle restrizioni che dovremo conoscere e sapere gestire.
File system
A livello di filesystem, ogni container è completamente isolato dal sistema sottostante e da quello di tutti gli altri container. Facciamo una prova:
$ docker run -it ubuntu /bin/bash
root@9de56c415886:/# mkdir html_it
root@9de56c415886:/# ls
bin boot dev etc home html_it ... sys tmp usr var
root@9de56c415886:/# // CTRL + P + Q
$ docker run -it ubuntu /bin/bash
root@33a9ddca9af1:/# ls
bin boot dev etc home lib lib64 media mnt opt proc root run sbin srv sys tmp usr var
In questo caso abbiamo creato un container Ubuntu (id 9de56c415886) al cui interno abbiamo creato la directory html_it. Abbiamo quindi messo tale container in background, per poi creare una nuova istanza (id 33a9ddca9af1): come prevedibile, all’interno di quest’ultima istanza la directory precedentemente creata non esiste. Questo, vale la pena specificarlo ancora una volta, avviene in quanto ogni modifica al container viene memorizzata all’interno del layer container e non si riflette ai layer immagine sottostanti.
Ma cosa possiamo fare qualora questo tipo di isolamento diventasse un limite? Come posso, ad esempio, condividere un file tra l’host Windows e un container? La soluzione più semplice alla questione è l’utilizzo di un data volume. Grazie a questa funzionalità, è possibile creare un ponte tra il filesystem del container e quello del sistema operativo sottostante. Ad esempio, il nostro container potrebbe generare dei file di log che sarebbe comodo leggere come se fossero dei comuni file sul nostro sistema Windows. Per creare un data volume, è necessario passare il flag -v
al comando docker run
e specificare un path locale (opzionale ma consigliato) e un path sul container:
$ docker run -it -v /c/Users/mariano.calandra/Desktop/data_volume:/logs ubuntu /bin/bash
root@b4afd75514bd:/# touch html_it.log
Così facendo, ogni file che aggiungeremo al path /logs
all’interno del container (come html_it.log, nell'esempio precedente) sarà visibile sul nostro Windows al path C:\Users\mariano.calandra\Desktop\data_volume
, e viceversa.
Processi di sistema
Vale un discorso analogo anche per i processi di sistema. Ogni container è a conoscenza solo dei suoi processi di sistema e non sa assolutamente nulla riguardo ai processi degli altri container o quelli dell’OS sottostante. Per verificare, possiamo utilizzare il container precedente e lanciare al suo interno il comando ps
(nei sistemi GNU\Linux, ps
è un comando utile a mostrare i processi di sistema e ha poco a che vedere con docker ps
):
$ docker run -it ubuntu /bin/bash
root@33a9ddca9af1:/# ps
PID TTY TIME CMD
1 ? 00:00:00 bash
11 ? 00:00:00 ps
Come possiamo osservare con i nostri occhi, esistono solo due processi, il processo 1
ovvero /bin/bash
(la cui esecuzione è stata richiesta direttamente al comando docker run
) ed il processo 11
ovvero lo stesso comando ps
appena eseguito. Fintanto che il processo 1
sarà attivo, anche il container stesso risulterà tale.
Il processo con id 1
è detto anche padre ed è il processo che visualizziamo nell’output del comando docker ps
, in corrispondenza della colonna COMMAND
.
Per evitare di dover materialmente entrare in un container per vedere i processi in esso attivi, abbiamo a disposizione il comando docker top
, seguito dall’id del container.
$ docker top 33
PID USER COMMAND
15635 root /bin/bash
In virtù dell’isolamento (virtuale) perpetrato da Docker, quando siamo all’interno del container il comando /bin/bash
risulta essere il processo padre con PID 1
, mentre osservandolo dall'esterno grazie al comando docker top
, quello che ci viene mostrato è il PID reale, assegnato a tale processo all’interno della macchina virtuale.
Networking
Anche a livello di networking i container sono in un certo qual modo isolati. Per dimostrarlo, creeremo un semplice container che fungerà da webserver e proveremo ad effettuare una chiamata sulla porta 80 per verificarne il funzionamento. Tra i vari webserver disponibili abbiamo Apache con il classico demone HTTP, e proviamo ad avviarlo in background con la solita sintassi:
$ docker run -d httpd:2.4
A conclusione del comando abbiamo un webserver perfettamente funzionante e potenzialmente in grado di rispondere alle nostre chiamate... ma a quale indirizzo?
A questo punto si aprono due scenari diversi a seconda che sia il nostro sistema operativo a far funzionare i container o una macchina virtuale. Se stiamo utilizzando un sistema operativo Windows, non è il nostro sistema operativo ad eseguire i container, ma una macchina virtuale Linux come discusso nella lezione 2. Se, nel nostro host Windows, aprissimo un browser qualsiasi e lo facessimo puntare a localhost o 127.0.0.1, non avremo sicuramente fortuna (a meno che non ci sia già un server web sul computer locale): come più volte detto, infatti, i container non girano in locale su Windows ma nella macchina virtuale Linux creata al momento dell’istallazione di Docker, ed è a quell’indirizzo IP che il server ci risponderà. Per conoscere l’indirizzo IP della macchina virtuale, basta digitare il comando docker-machine ip
che risponderà con l’indirizzo corretto della macchina:
$ docker-machine ip
192.168.99.100
A questo punto, ci si aspettterebbe che, visitando l’indirizzo http://192.168.99.100 dovremmo vedere il demone in azione. Invece, l'unica cosa che troveremo sarà nient’altro che un messaggio di errore!
Il problema è molto semplice: il demone HTTP è stato sì configurato, ma è blindato all’interno del suo stesso container e non vi è modo di accedervi come mostrato nell’immagine in basso.
Uno dei modi per risolvere la questione consiste nel mappare ad una determinata porta della macchina virtuale Linux, la porta su cui risponde il demone HTTP (la classica 80, se non diversamente specificato). Per farlo modificheremo sensibilmente il comando docker run
lanciato poco fa:
$ docker run -d -p 8088:80 httpd:2.4
Come prima, stiamo chiedendo a Docker di avviare in modalità detached un container a partire dall’immagine sul repository ufficiale httpd
, avente tag 2.4
. Stavolta, però, abbiamo anche chiesto a Docker che tutte le chiamate fatte sulla porta 8088
della macchina virtuale (il solito 192.168.99.100) vengano mappate sulla porta 80 del container. A questo punto non ci resta che aprire un browser e farlo puntare all’indirizzo http://192.168.99.100:8088: il risultato sarà simile all'immagine seguente.