# Networking

## Concetti base

I container, per impostazione predefinita, sono completamente isolati dal sistema host e tra di loro. Per consentire la comunicazione tra i container e il sistema host, è necessario aprire esplicitamente una determinata porta, rendere disponibile una specifica risorsa, ecc.

Per esporre una porta, si utilizza l'opzione `-p` con la seguente sintassi:

```bash
docker run -d -p 8080:80 nginx
```

In questo esempio, la porta 80 del container viene mappata sulla porta 8080 del sistema host. Ciò significa che le richieste inviate alla porta 8080 dell'host verranno inoltrate alla porta 80 del container, dove il servizio nginx è in ascolto.

```bash
docker ps

CONTAINER ID   IMAGE     COMMAND                  CREATED          STATUS          PORTS                                     NAMES
f00ddc2a0b6a   nginx     "/docker-entrypoint.…"   13 seconds ago   Up 13 seconds   0.0.0.0:8080->80/tcp, [::]:8080->80/tcp   interesting_meninsky
```

Se si desidera servire contenuti web, è possibile utilizzare l'opzione `-v` (*volume*) per mappare un contenuto all'interno del container. La sintassi è simile alla precedente:

```bash
docker run -d -p 8080:80 -v /path/to/my/webcontent:/usr/share/nginx/html nginx
```

In questo caso, il contenuto presente nella directory `/path/to/my/webcontent` del sistema host sarà disponibile all'interno del container nella directory `/usr/share/nginx/html`.

## Tipologie

Esistono diverse tipologie di networking che possono essere utilizzate in Docker, denominate `driver`. I tre driver principali sono:

- `host`: in questa modalità, il container condivide l'interfaccia di rete con il sistema host. Non vi è alcun isolamento, pertanto qualsiasi processo eseguito all'interno del container ha accesso diretto alla rete dell'host. Questo tipo di accesso alla rete è analogo a quello di un browser web su un sistema operativo
- `bridge`: questo driver fornisce isolamento tra l'host e i container, consentendo la creazione di reti virtuali. Tali reti operano in uno spazio di indirizzamento separato rispetto al sistema host e alla rete fisica, permettendo ai container di comunicare tra loro in modo privato o semi-privato. Questa modalità è la più comune e preferita per la maggior parte delle applicazioni Docker
- `null`: in questa configurazione, il container è completamente isolato dalla rete, non avendo alcuna connettività di rete disponibile

Altri driver per configurazioni specifiche sono:

- `macvlan`: consente ai container di avere indirizzi MAC unici e di apparire come dispositivi fisici sulla rete. Richiede la creazione di una rete macvlan e l'assegnazione di un'interfaccia fisica
- `overlay`: utilizzata per collegare container su più host, ideale per ambienti di orchestrazione come Docker Swarm o Kubernetes

## Port mapping

Mappare una porta significa associare una porta specifica del sistema host a una porta di un container. Questo processo consente di rendere accessibili i servizi in esecuzione all'interno del container dall'esterno.

![port-mapping](asset/img/port-mapping.png)

Il seguente stack di esempio è composto da due servizi: *php* e *nginx*. In particolare, il servizio nginx espone due porte: la porta 80 del container sulla porta 8080 del sistema host e la porta 443 del container sulla porta 8443 dell'host.

```yml
services:
    php:
        image: 'php:8.1-fpm'
        container_name: ${APP_NAME:?err}-php
        volumes:
            - './app:/var/www/html'

    nginx:
        image: 'nginx:latest'
        container_name: ${APP_NAME:?err}-nginx
        ports:
            - '8081:80'
            - '8443:443'
        volumes:
            - './app:/var/www/html'
            - './config/nginx:/etc/nginx/conf.d'
```

Se si avvia lo stack con il comando `docker compose up -d`, il comando `docker ps` conferma che nginx è in esecuzione e in ascolto su tutte le interfacce di rete (`0.0.0.0`) alla porta 8080.

```bash
CONTAINER ID   IMAGE          COMMAND                  CREATED         STATUS         PORTS                                     NAMES
73a0991fb71b   nginx:latest   "/docker-entrypoint.…"   3 seconds ago   Up 3 seconds   0.0.0.0:8080->80/tcp, [::]:8080->80/tcp   eager_northcutt
```

Accedendo a `localhost:8080`, è possibile raggiungere il sito web servito dallo stack. Tuttavia, questo consente anche a chiunque si trovi sulla stessa rete o, se presente il NAT sul router, a chiunque conosca l'indirizzo IP del sistema host di accedere al servizio.

Per **limitare l'accesso** al servizio nginx in modo che sia raggiungibile solo dal sistema host, è necessario modificare la configurazione dello stack affinché ascolti solo su localhost e non su tutte le interfacce di rete:

```yml
    nginx:
        image: 'nginx:latest'
        container_name: ${APP_NAME:?err}-nginx
        ports:
            - '127.0.0.1:8081:80'
            - '127.0.0.1:8443:443'
```

Se non è importante che le porte associate sul sistema host siano sempre le stesse, è possibile **allocarle dinamicamente**, in modo da sceglierne sempre una disponibile, evitando conflitti:

```yml
    nginx:
        image: 'nginx:latest'
        container_name: ${APP_NAME:?err}-nginx
        ports:
            - '80'
            - '443'
```

Le porte sono ora allocate in modo casuale:

```bash
docker ps

CONTAINER ID   IMAGE          COMMAND                  CREATED         STATUS         PORTS                                       NAMES
654f739ce935   nginx:latest   "/docker-entrypoint.…"   5 seconds ago   Up 4 seconds   0.0.0.0:32770->80/tcp, [::]:32770->80/tcp   crazy_jackson
```

Se si desidera che Docker assegni automaticamente una porta disponibile in `localhost`, è possibile utilizzare la seguente sintassi:

```yml
services:
    nginx:
        image: 'nginx:latest'
        container_name: ${APP_NAME:?err}-nginx
        ports:
            - '127.0.0.1:0:80'  # Assegna una porta casuale su localhost
```

In questo caso, `127.0.0.1:0` indica a Docker di scegliere automaticamente una porta disponibile sul sistema host, associandola alla porta 80 del container:

```bash
docker ps
CONTAINER ID   IMAGE          COMMAND                  CREATED         STATUS         PORTS                     NAMES
6559daa92656   nginx:latest   "/docker-entrypoint.…"   3 seconds ago   Up 2 seconds   127.0.0.1:32769->80/tcp   nginx
```

### Mapping Multiple Ports

Docker consente anche di mappare più porte contemporaneamente. Questo è utile quando il container deve esporre più servizi su porte diverse:

```bash
docker run -p 8080:80 -p 443:443 nginx
```

Oppure, per mappare un intervallo di porte: `docker run -p 5000-5100:5000-5100 nginx`.

## Reti virtuali

Per impostazione predefinita, i container Docker sono isolati l'uno dall'altro, il che impedisce la comunicazione diretta tra servizi che operano su stack diversi. Tuttavia, è possibile creare una rete virtuale condivisa tra container di due stack differenti, consentendo loro di comunicare.

La sintassi, per definire un nuovo network è la seguente:

```yml
services:
    nginx:
        image: 'nginx:latest'
        container_name: ${APP_NAME:?err}-nginx
        ports:
            - '8081:80'
            - '8443:443'
        volumes:
            - './app:/var/www/html'
            - './config/nginx:/etc/nginx/conf.d'
        networks:
            - default # Le reti sotto network non vengono aggiunte alla configurazione di default, ma sostituite. Pertanto, la rete default deve essere specificata esplicitamente
            - nginx # Il container nginx farà parte dei seguenti network
networks:
    nginx:
        name: mynetwork
```

Nel secondo stack, è necessario definire la stessa rete per consentire la comunicazione tra i container:

```yml
services:
    debug:
        image: ubuntu
        entrypoint: "bash -c 'while true; do sleep 1; done'"
        networks:
            - default
            - nginx

networks:
    nginx:
        name: mynetwork
        external: true  # La rete è definita esternamente. Se non specificato, verrebbe generato un errore, poiché la rete esiste già e non può essere creata una rete con nome identico

```

In questo modo, è possibile mettere in comunicazione due container di due stack diversi, semplicemente creando una rete virtuale condivisa.
