Guida a Docker: volumi e reti

27/07/2021

Queste due caratteristiche sono fondamentali, in primis per favorire l’archiviazione permanente, nel secondo caso per favorire la creazione di reti con container collegati tutti tra loro. In un sistema distribuito, l’idea è infatti quella di avere tanti container, ognuno dei quali esegue un’applicazione (potrebbe essere un CMS, un database, ecc.) in grado di comunicare con altri applicativi all’interno della stessa rete.

Volumi

Cos’è un volume?

Si definisce volume un meccanismo per l’archiviazione persistente, generato per i container Docker e utilizzabile da questi. In particolare, si fa distinzione tra:

  • Volume bind: sono volumi dipendenti dalla struttura del file system del sistema operativo host, e può essere messo a disposizione qualsiasi percorso per il montaggio, ad esempio /home/<utente>/RobaVaria. Il loro ciclo di vita coincide con quello del container. All’eliminazione di quest’ultimo, infatti, i file salvati rimarranno comunque, ma Docker revocherà la specifica di volume fino al prossimo montaggio.
  • Volume named: sono volumi gestiti direttamente da Docker, indipendenti dalla struttura del file system del sistema operativo host e gestibili dalla shell di Docker. Sono indipendenti dal ciclo di vita del container.

Quale usare dipende molto dagli scopi finali. Senza ombra di dubbio, i volumi bind sono ottimi per condividere roba tra il container e l’host, tenendo conto però della portabilità limitata da una piattaforma all’altra. Se decidete di trasportare un volume bind da Linux a Windows, la modalità di montaggio è chiaramente diversa.

Il montaggio di un volume su un container Docker può avvenire solo durante la creazione di un container. Per montare un volume su un container esistente, è opportuno eseguire un commit del container, eliminarlo e crearne uno nuovo dall’immagine generata in cui si effettua il montaggio. Per ulteriori info su come eseguire i commit, vi invito a consultare la prima guida.

Volume bind

Per montare un volume bind, si utilizza il comando docker run, con l’aggiunta dell’opzione -v.

docker run -ti -v <percorsoHost>:<percorsoContainer> <immagine>

Questo comando andrà a creare un container interattivo, col volume già montato. Sia il percorso dell’host, che il percorso del container devono essere assoluti.

Inoltre, aggiungendo :ro in seguito <percorsoContainer>, il volume viene montato in sola lettura.

docker run -ti -v <percorsoHost>:<percorsoContainer>:ro <immagine>

Volumi named

La gestione verrà eseguita mediante il comando docker volume.

Creazione

Per creare un volume named, si utilizza il comando docker volume, ad esempio:

docker volume create <nomeVolume>

Consultare elenco volumi

Un elenco dei volumi creati è consultabile mediante il comando:

docker volume list

La lista contiene:

  • Gli identificativi univoci dei volumi, ovvero i nomi;
  • I driver di ciascun volume, che di default è local. Il driver local sta ad indicare che il nostro volume si trova nel medesimo host in cui è presente il container. Altri driver sono specificabili, argomento che tuttavia non approfondiremo in questa sede.

Montare un volume named

Analogamente ai volumi bind, i volumi named devono essere montati al momento della creazione del container, specificando inoltre il percorso di montaggio dal punto di vista del sistema operativo del container stesso.

docker run -ti -v <nomeVolume>:<percorsoContainer> <immagine>

Ispezionare un volume

Esattamente come si fa con i container, è possibile ispezionare i volumi usando il comando:

docker volume inspect <nomeVolume>

In tal contesto è possibile verificare qual è la posizione di tale volume rispetto al file system dell’host, la quale poi è raggiungibile tramite il file manager oppure il terminale stesso.

Si noti tuttavia che, al contrario dei volumi bind, non è possibile apportare modifiche come utente normale direttamente dall’host, mentre è possibile leggere.

Eliminare un volume

Per eliminare un volume, usare il comando:

docker volume rm <nomeVolume>

I volumi da eliminare non devono essere impiegati da nessun container, per cui è opportuno prima eliminare i container che lo utilizzano e in seguito procedere con l’eliminazione del volume.

Reti

Con Docker è possibile definire delle reti per interconnettere i container. Di default, a ciascun container è associata la rete bridge, costituita dalla subnet 172.17.0.0/16 (può variare), alla quale è possibile associarvi fino a un massimo di 216 container.

Cercando di essere più sintetici possibili, una subnet non è altro che una rete di calcolatori, coordinata da un router, il quale assegna a ciascuno di essi un indirizzo IP, logicamente visibile solo all’interno della stessa. Questo aspetto lo gestisce in automatico Docker per noi, quindi ci interessa soprattutto operare con comandi per ad esempio creare reti personalizzate.

Creare reti personalizzate

È possibile creare delle reti personalizzate, utilizzando il comando:

docker network create <nomeRete>

Di default, quando si crea una rete, muta rispetto alla rete bridge, o rispetto alla rete custom creata precedentemente. Questo è constatabile dal comando di ispezione:

docker network inspect <nomeRete>

Eliminare una rete

Per eliminare una rete, usare il comando:

docker network rm <nomeRete>

Creare subnet personalizzate

È possibile inoltre specificare l’IP della subnet e la maschera di rete usando il comando:

docker network create --subnet <IPSubnet>/<mascheraDiRete> <nomeRete>

Esempio

docker network create --subnet 172.30.0.0/24 pippoNet

Scelta della maschera di rete

La maschera di rete, in parole povere, indica quanti nodi/container possono esserci al massimo interconnessi in questa rete. Il numero di nodi massimi che si possono connettere sono espressi in potenze di 2, fino a un massimo di 232, poichè un ottetto è composto da otto bit, essendo quattro in un IPv4, si ha un totale di 32 bit. Ad esempio, 28 = 256 indirizzi IP e quindi nodi associabili. La maschera di rete si calcola sottraendo 32 all’esponente scelto, nel caso specifico: 32-8 = 24.

La rete Docker in questione avrà quindi:

  • I primi tre ottetti, 172.30.0, che identificano la rete, per qualunque indirizzo IP;
  • 172.30.0.255 l’IP di broadcast;
  • 172.30.0.1 l’IP di gateway;
  • Il range che va da 172.30.0.2 a 172.30.0.254 per i container.

Connettere un container a una rete

L’interfaccia di rete può essere associata al momento della creazione del container mediante il comando:

docker run -ti --net <nomeRete> <immagine>

Inoltre, è possibile specificare quale IP statico utilizzare:

docker run -ti --net <nomeRete> --ip <indirizzoIP> <immagine>

Vedere a quali reti è connesso un container

Per vedere a quali reti è connesso il container, si può usare il comando di ispezione:

docker inspect <container>

E consultare la voce Networks.

Connettere/disconnettere rete a container già esistente

Per connettere un container a una rete già esistente, si può usare il comando:

docker network connect <nomeRete> <container>

Anche in questo caso, è possibile specificare l’indirizzo IP statico:

docker network connect --ip <indirizzoIP> <nomeRete> <container>

Analogamente, è possibile disconnettere:

docker network disconnect <nomeRete> <container>

Tutte le configurazioni di rete vengono mantenute anche dopo aver arrestato e riavviato il container.

Visualizzare elenco reti

Per visualizzare l’elenco delle reti è possibile usare il comando:

docker network ls

Come nei volumi, anche qui abbiamo gli identificatori univoci e il driver. In questo caso, il driver predefinito è bridge, il quale crea un vero e proprio isolamento tra la rete host e la rete del container, avrà una sua subnet con gateway e quant’altro. Anche qui è possibile specificare driver alternativi. Non approfondiremo altro in questa sede.

ESERCIZIO: Interconnessione di più container su subnet differenti

Tutte le subnet sono isolate tra loro, per principio. Ad ogni modo è possibile interconnettere più subnet collegando un container ad esse. Ci si attenga alla seguente procedura:

Creare due reti:

docker network create pippoNet
docker network create plutoNet

Creare un container per ciascuna rete, aggiungendo inoltre la clausola —cap-add=NET_ADMIN per avere i massimi privilegi forniti dal kernel Linux per quanto concerne la gestione della rete:

docker run -ti --net pippoNet --cap-add=NET_ADMIN --name pippo ubuntu
docker run -ti --net plutoNet --cap-add=NET_ADMIN --name pluto ubuntu

Creare un terzo container da connettere ad ambo le reti:

docker run -ti --net pippoNet --cap-add=NET_ADMIN --name paperino ubuntu
docker network connect plutoNet paperino

Assicurarsi che tutti i container siano in esecuzione:

docker start paperino
docker start pippo
docker start pluto

A questo punto apriamo tre finestre del terminale ed entriamo in ciascun container con:

docker exec -ti <container> bash

Eseguire su tutti i container il seguente comando:

apt update; apt install iproute2 iputils-ping

Da una nuova scheda verifichiamo l’inspect di paperino e appuntiamo quali sono gli indirizzi IP di paperino su plutoNet e pippoNet:

docker inspect paperino

Inoltre, tramite il comando:

docker network inspect <nomeRete>

Appuntiamoci gli IP e la maschera di rete delle subnet pippoNet e plutoNet.

Aggiungere alla tabella di routing dei container pippo e pluto il container paperino come gateway per le reti reciproche:

ip route add <subnet plutoNet> via <ip paperino su pippoNet> # container pippo  
ip route add <subnet pippoNet> via <ip paperino su plutoNet> # container pluto

Provare a pingare i container pippo e pluto a vicenda, ma inutile dire che se funziona il primo comando, allora certamente funzionerà il secondo:

ping <ip pluto> # pippo
ping <ip pippo> # pluto

Port forwarding

È possbile reindirizzare le connessioni di una porta in ascolto sull’host verso una porta locale di un container aggiungendo in un comando docker run l’opzione -p <portaHost>:<portaContainer>.

Esempio

docker run -d -p <8080>:<80> <nginx>

Questo comando permette di avviare un container in modalità detached (così da non avviare una shell), mettendo in ascolto la porta 8080 dell’host sulla porta 80 del container

Conclusione

Con questo termina la seconda parte su Docker, guida che trovate inoltre sul canale YouTube con tutte le dimostrazioni visive.