Di default i container Docker possono comunicare col mondo esterno, ma non viceversa.
Per esporre la porta di un container col mondo esterno, possiamo effettuare un mapping tramite la flag -p quando
si crea un container. Ad esempio col comando: docker run --name nginx -d -p 8080:80 nginx
stiamo creando un container nginx e stiamo mappando la porta 8080 del Docker host con la porta 80 del container.
Ma come funziona la comunicazione tra container?
Il comando network e le reti di default di Docker
Il comando docker network
permette di gestire le reti in Docker. In particolare, eseguendo il comando
docker network --help
possiamo vedere quali comandi sono messi a disposizione:
I comandi a disposizione sono pochi e chiari.
Proviamo a visualizzare la lista di reti con il comando docker network ls
Di default, la lista contiene tre reti:
- la rete bridge, chiamata docker0, è la rete che i container utilizzano di default. I container sono collegati quindi alla stessa rete e pertanto possono comunicare tra loro. Inoltre questo offre anche un isolamento verso il mondo esterno e verso tutti i container che non fanno parte di questa rete
- la rete host, non è una vera e propria rete; un container che fa parte di questa rete, si collega in pratica alla rete del Docker Host (funzionalità non disponibile su Docker Desktop)
- la rete none anch'essa non è una vera e propria rete; non collega il container a nessuna rete.
Se creiamo quindi due container NGINX, ad esempio eseguendo i comandi: docker run --name nginx1 -d nginx
docker run --name nginx2 -d nginx
possiamo notare, col comando docker inspect nginx1
e docker inspect nginx2
che fanno parte della stessa rete.
Infatti entrando nel container nginx2, col comando docker exec -it nginx2 bash
, possiamo eseguire con successo
una cURL sull'Ip del container nginx1:
Anche se i due container fanno parte della stessa rete e quindi possono "pingarsi" a vicenda, questo non risolve un
caso d'uso comune: ad esempio un container di un'applicazione web che deve comunicare con un container che fa da database.
Essendo i container effimeri, ed essendo che quando riavviamo un container questo può cambiare IP, non possiamo far
comunicare l'applicazione web col database tramite l'indirizzo IP di quest'ultimo, non essendo fisso.
Nel paragrafo seguente, vedremo come risolvere questo problema.
Linking tra container: la flag --link (deprecato)
Nel paragrafo
Persistenza nei container Docker: volumi e bind mount,
abbiamo creato due container per vedere la funzionalità dei volumi: un container postgres e un container pgAdmin.
Il container postgres è stato creato col seguente comando: docker run --name postgres -e POSTGRES_USER=user -e POSTGRES_PASSWORD=password -e POSTGRES_DB=mydb -p 5432:5432 -d postgres:13.5
La flag -p permette di mappare una porta del Docker Host con una porta del container. Perché l'abbiamo utilizzata?
Per far sì che il container pgAdmin possa comunicare col database.
Il container pgAdmin è stato creato col seguente comando: docker run --name=pgadmin -e PGADMIN_DEFAULT_PASSWORD=user -e PGADMIN_DEFAULT_EMAIL=admin@admin.com -p 5050:80 -d dpage/pgadmin4
Anche qui creiamo una mappatura di porte: mappiamo la porta 5050 del nostro host con la porta 80 del container.
Così facendo, possiamo accedere a pgAdmin da localhost:5050
.
In realtà noi accediamo al database di postgres utilizzando esclusivamente pgAdmin: l'ideale sarebbe evitare la mappatura
-p 5432:5432
ed inserire direttamente l'hostname del container di postgres. Inoltre, se eliminassimo quella mappatura,
eviteremmo di esporre il database di postgres verso l'esterno, aggiungendo un grado di sicurezza in più. Vediamo come fare!
Una feature legacy di Docker è quella di collegare i container tramite la flag --link
: --link <name or id>:alias
dove name è il nome di un container e alias un alias per il nome del collegamento, oppure: --link <name or id>
in questo caso l'alias sarà uguale al nome del container.
Questa flag permette, durante la creazione di un container col comando docker run
, di collegare quest'ultimo ad
un determinato container. Ma cosa si intende per "collegare"? Vediamolo con un esempio!
Distruggiamo e ricreiamo il container nginx2: docker stop nginx2 && docker rm nginx2 && docker run --name nginx2 -d --link nginx1 nginx
.
Ora proviamo nuovamente ad entrare nel container nginx2 e questa volta eseguiamo una cURL in questo modo:
Stavolta il container può effettuare un cURL utilizzando direttamente il nome del container linkato, cioè nginx1.
Ed è proprio questo quello che fa la flag --link. Se eseguiamo il comando cat sul file hosts del container nginx2,
possiamo vedere che è stato inserito da Docker la mappatura ip_nginx1->nginx1:
Inoltre, sono state aggiunte anche diverse variabili di ambiente che indicano varie informazioni sul container nginx1:
Notiamo che l'IP del container nginx1 è 172.17.0.2. Ma cosa succederebbe se spegnessimo il container nginx1, creassimo un container nginx3 e poi avviassimo nuovamente il container nginx1? Vediamo!
Il container nginx1 ha cambiato IP! Sarà un problema ora per il container nginx2?
Ovviamente nessun problema! Docker ha aggiornato anche l'IP di nginx1 su nginx2. Altrimenti non ci sarebbe alcun vantaggio a utilizzare questa flag.
Allo stesso modo quindi, possiamo collegare il container di postgres con quello di pgAdmin, senza avere la necessità di esporre il database verso l'esterno.
Tuttavia, con questa flag vengono condivise anche variabili di ambiente tra i container, che non è una buona cosa.
Comunque, questa flag è stata deprecata a favore delle network custom, quindi il suo uso è da evitare.
Creazione di network custom
È possibile create network custom col comando docker network create <network_name>
.
Due container facenti parte della stessa rete custom, saranno in grado di comunicare tra loro e saranno isolati
dalle altre reti Docker, compresa quella di default, la docker0.
La figura sopra, presa dalla documentazione ufficiale di Docker, mostra come la rete docker0 abbia un indirizzo di rete diverso dalla rete custom my_bridge.
Alcune differenze tra la rete bridge di default, docker0 e una rete bridge custom:
- I container che fanno parte della rete docker0 possono accedere a ogni altro container della stessa rete esclusivamente
tramite indirizzo IP (a meno che non si utilizzi --link).
Un container in una rete custom pùo comunicare con altri container della stessa rete custom tramite i loro nomi o alias. - Se non specificata la flag --network durante la creazione dei container, questi faranno parte della rete docker0.
Di default quindi i container non sono isolati tra loro.
Specificando una rete custom, solo i container della stessa rete custom possono comunicare tra loro.
Creiamo una rete chiamata nginx_net:
docker network create nginx_net
.
Eseguiamo un inspect su di essa:
docker network inspect nginx_net
[
{
"Name": "nginx_net",
"Id": "0165d657ff0af159642158c5adf0c4f171eeae00376dd69fcae2b22e4ec4ffb4",
"Created": "2022-01-15T14:52:11.414646094Z",
"Scope": "local",
"Driver": "bridge",
"EnableIPv6": false,
"IPAM": {
"Driver": "default",
"Options": {},
"Config": [
{
"Subnet": "172.18.0.0/16",
"Gateway": "172.18.0.1"
}
]
},
...
Nel campo subnet vediamo subito che la rete è 172.18.0.0 che è diversa da quella di docker0, che nel mio caso è
172.17.0.0.
Volendo, possiamo anche impostare la subnet durante creazione della rete:
docker network rm nginx_net
docker network create --subnet 10.100.0.0/16 nginx_net
Creiamo ora nuovamente i container nginx1 e nginx2, questa volta utilizzando per entrambi la rete nginx_net,
con la flag --network <network_name>
In questo modo, i container creati sono sulla stessa rete ed inoltre sono isolati dagli altri container che non fanno
parte di quest'ultima.
Se proviamo nuovamente a eseguire il comando curl nginx1
dal container nginx2, possiamo verificare che funziona senza problemi.
Quindi il modo giusto per far comunicare il container di postgres con quello di pgAdmin, è:
- creare una rete custom
- creare il container postgres utilizzando la rete custom
- create il container pgADmin utilizzando la rete custom.
A quel punto, nella schermata di pgAdmin dove si setta la connessione al db, possiamo inserire come hostname il nome del container di postgres.
Accenno sul networking avanzato di Docker
Quando abbiamo visualizzato la lista di reti disponibili, oltre alla colonna NAME, c'era anche una colonna chiamata DRIVER.
Il networking di Docker viene creato utilizzando i driver. Come abbiamo già visto, Docker crea di default tre reti
che utilizzano i driver bridge, host e none.
Il driver bridge permette di creare reti virtuali private all'interno dello stesso demone Docker.
Le reti custom create col comando docker network create
utilizzano di default il driver bridge.
È possibile però far comunicare container anche di differenti demoni Docker, utilizzando il driver overlay.
È possibile anche creare reti gestendo direttamente indirizzi MAC dei container tramite il driver macvlan
.
Se vuoi approfindire questi due driver, questi sono i link della docs ufficiale:
https://docs.docker.com/network/overlay/ e https://docs.docker.com/network/macvlan/
Conclusioni
In questo articolo abbiamo visto come funziona il networking in Docker e come far comunicare due (o più) container.
Abbiamo visto l'uso della flag --link, che è però deprecata ed è da evitare. Abbiamo visto come creare una rete custom
e come collegare i container a quest'ultima con la flag --network. Questo è l'approccio consigliato da utilizzare.
Docs ufficiale su --link: https://docs.docker.com/network/links/.
Docs ufficiale sul networking in Docker: https://docs.docker.com/network/.
Miei articoli su Docker: Docker
Libri consigliati su Docker e Kubernetes:
- Docker: Sviluppare e rilasciare software tramite container: https://amzn.to/3AZEGDI
- Kubernetes TakeAway: Implementa i cluster K8s come un professionista: https://amzn.to/3dVxxuP