Els contenidors es poden connectar i comunicar-se entre ells o altres serveis mitjançant xarxes.

Introducció

Docker utilitza una interfície virtual que té el nom de docker0 que es va crear quan vas instal.lar docker.

$ ip --brief addr
lo               UNKNOWN        127.0.0.1/8 ::1/128 
enp1s0           UP             192.168.123.6/22 fe80::b4a1:54f4:7454:d2fd/64 
enp2s0           UP             10.2.76.37/16 fe80::d18f:22b8:b29b:935c/64 
docker0          DOWN           172.17.0.1/16 

Quan crees un contenidor aquest es connecta a aquesta xarxa:

$ docker run --rm -d --name apache httpd
d16aea391c3efe464b3defb1aaf64e6d3e6a8321f5f0d9984e2d6104588d5ece
$ docker exec apache cat /etc/hosts
127.0.0.1       localhost
...
172.17.0.2      5311e760a9b7

En aquest cas el contenidor té l'adreça 172.17.0.2 de la xarxa 172.17.0.1/16 que pertany a la interfície virtual docker0.

Si intentes conectar-te amb localhost no pots, però si et connectes directament a la IP del contenidor si que pots:

$ curl localhost
curl: (7) Failed to connect to localhost port 80 after 1 ms: S’ha refusat la connexió
$ curl 172.17.0.2
<html><body><h1>It works!</h1></body></html>

Quan crees un contenidor a aquest se li pot assignar qualsevol IP de la xarxa docker0.

És per aquest motiu que quan arrenquem un servidor web fem un port forward per poder accedir al servidor des de l'adreça localhost amb l'opció -p 80:80:

$ docker stop apache
apache
$ docker run --rm -d --name apache -p 80:80 httpd
3f74091860626a79951137c8734a38eb99f68337fbb8bc9f636b416e545d3e17

Ara podem accedir al servidor tant des de l'adreça 127.0.0.1 com l'adreça 172.17.0.2:

$ curl localhost
<html><body><h1>It works!</h1></body></html>
$ curl 172.17.0.2
<html><body><h1>It works!</h1></body></html>

Això és possible perqué docker ha definit una regla a la taula nat de Netfilter

$ sudo nft list table nat | grep 172.17.0.2:80
    iifname != "docker0" meta l4proto tcp tcp dport 80 counter packets 0 bytes 0 dnat to 172.17.0.2:80

La regla diu que si un paquet tcp destinat al port 80 va a una interfície que no sigui docker0, aquest serà redirigit a 172.17.0.2:80.

Si elimines el contenidor pots verificar que la regla desapareix:

$ docker stop apache
apache
$ sudo nft list table nat | grep 172.17.0.2.:80
$

A continuació crearem 3 apaches:

$ docker run --rm -d --name apache1 -p 81:80 httpd
$ docker run --rm -d --name apache2 -p 82:80 httpd
$ docker run --rm -d --name apache3 -p 83:80 httpd

Pots veure les adreçes i ports on estan escoltant els servidors a la taula nat:

$ sudo nft list table nat | grep "to 172.17.0."
    iifname != "docker0" meta l4proto tcp tcp dport 81 counter packets 0 bytes 0 dnat to 172.17.0.2:80
    iifname != "docker0" meta l4proto tcp tcp dport 82 counter packets 0 bytes 0 dnat to 172.17.0.3:80
    iifname != "docker0" meta l4proto tcp tcp dport 83 counter packets 0 bytes 0 dnat to 172.17.0.4:80

A més, com tots els contenidors comparteixen la mateixa xarxa es poden comunicar entre ells!

Xarxes predefinides

De manera predeterminada, Docker inclou tres xarxes i cadascuna és proporcionada per un "driver" diferent:

$ docker network ls
NETWORK ID     NAME      DRIVER    SCOPE
b3049ee1e177   bridge    bridge    local
df32681267cb   host      host      local
3b3d0e4e7244   none      null      local

Bridge

La xarxa bridge correspon a la intefície virtual docker0 que hem vist abans.

Host

Enlloc d’executar el contenidor en una xarxa privada pots executar-lo directament com si fos qualsevol altre procés amb l’opció --network host.

D'aquesta manera el contenidor té accés als serveis que s’executen a localhost i a qualsevol altre interfície del host.

Com pots veure a continuació un contenidor no té accés a les intefícies del host, només veu les seves:

$ docker run --rm alpine ip -f inet -4 -o addr
1: lo    inet 127.0.0.1/8 scope host lo\       valid_lft forever preferred_lft forever
29: eth0    inet 172.17.0.5/16 brd 172.17.255.255 scope global eth0\       valid_lft forever preferred_lft forever

Pero si fem servir l'opció --network host té accés a totes les interfícies de la màquina:

$ docker run --rm --network host alpine ip -f inet -4 -o addr
1: lo    inet 127.0.0.1/8 scope host lo\       valid_lft forever preferred_lft forever
2: enp1s0    inet 192.168.123.6/22 brd 192.168.123.255 scope global dynamic noprefixroute enp1s0\       valid_lft 2485sec preferred_lft 2485sec
3: enp2s0    inet 10.2.76.37/16 brd 10.2.255.255 scope global dynamic noprefixroute enp2s0\       valid_lft 2485sec preferred_lft 2485sec
4: docker0    inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0\       valid_lft forever preferred_lft forever

També pots executar un servidor apache sense necessitat de fer un "port forward":

$ docker run --rm -d --name apache --network host httpd
e3b505083672b87298989e4f9d9665b0b260054ff4d7a2d597a76f95db6acc2e
$ curl localhost
<html><body><h1>It works!</h1></body></html>

None

També pots executar un contenidor sense que tingui accés a xarxa:

La única interfície que té és localhost:

$ docker run --rm --network none alpine cat /etc/hosts
127.0.0.1       localhost

Pots verificar que el contenidor no es pot connectar a l’exterior:

$ docker run --rm --network none alpine ping -c 1 google.es
ping: bad address 'google.es'

A diferència d'un contenidor connectat a una xarxa bridgeo host:

$ docker run --rm alpine ping -c 1 google.es
PING google.es (142.250.185.227): 56 data bytes
64 bytes from 142.250.185.227: seq=0 ttl=117 time=1.258 ms

--- google.es ping statistics ---
1 packets transmitted, 1 packets received, 0% packet loss
round-trip min/avg/max = 1.258/1.258/1.258 ms

NodePort

Fins ara no haviem explicat res de la interficie bridge i només et deiem que afegixis l’opció -p 80:80.

Quan crees un contenidor pots configurar diversos "port forward" amb l’opció -p (o --publish), i no es poden canviar més endavant.

L’opció -p necessitat com argument la interfície de l'amfitrió, el port de l'amfitrió, el port de destinació i el protocol de port.

Per exemple, cadascuna d'aquestes opcions reenviarà el port TCP 8080 des de totes les interfícies d'amfitrió al port TCP 8080 del nou contenidor.

-p 0.0.0.0:8080:8080/tcp
-p 8080:8080/tcp
-p 8080:8080

El primer argument és la forma completa.

Exemple

En aquest exemple fem un "port forward2 del port 80 de interficie 0.0.0.0 al port 80 de la IP 172.17.0.x.

$ docker run --rm -d --name apache -p 80:80 httpd
735cea84eccd6d4829eefd776e28f8ad9f550ca8fa2b7d9af4ba01a0deb9eda9
$ curl localhost
<html><body><h1>It works!</h1></body></html>
$ curl localhost:80
<html><body><h1>It works!</h1></body></html>

Perquè diem a la interfície 172.17.0.x? Perquè el contenidor estarà connectat a la xarxa bridge, però no sabem quina serà la IP que se li assignarà i ens és indiferent.

Si vols saber quina és la IP exacta mira el fitxer /etc/hosts:

$ docker exec apache cat /etc/hosts | grep 172
172.17.0.2      fd2c0cd862d5
$ curl 172.17.0.2
<html><body><h1>It works!</h1></body></html>
$ docker stop apache
apache

Per què aquest exemple no funciona?

$ docker run --rm -d --name apache -p 80:8000 httpd
2a2c233c194d5f7c92a6f54213cdd952a692832fc0439fd571486a5d4b1a4192
$ curl localhost
curl: (56) Recv failure: La màquina remota ha reiniciat la connexió

Perquè apache està escoltant al port 80 de la IP 172.17.0.x i no al port 8000.

$ docker exec -it apache bash
... $ apt update && apt install -y nmap
... $ nmap localhost
    ...
    PORT   STATE SERVICE
    80/tcp open  http
    $ exit
    exit
$ docker stop apache
apache

Per què aquest exemple no funciona?

$ docker run --rm -d --name apache -p 80:80/udp httpd
899d341907378ea811ac519e47b87e9c6debbb7401b5bf2cf3c687415df77d60
$ curl localhost
curl: (7) Failed to connect to localhost port 80 after 0 ms: S’ha refusat la connexió
$ docker stop apache
apache

Perquè apache està escoltant al port 80/tcp de la IP 172.17.0.x i no al port 80/udp.

Recordeu:

0.0.0.0:8080:8080/tcp
8080:8080/tcp
8080:8080

Perquè aquest exemple si funciona en el port 80, 3000 i 8000 ?

$ docker run --rm -d --name apache -p 80:80 -p 3000:80 -p 8000:80 httpd
bf6cfaed3562bea6b5f35470c03210566dd657b5e19335b33b555c4678240813
$ curl localhost
<html><body><h1>It works!</h1></body></html>
$ curl localhost:3000
<html><body><h1>It works!</h1></body></html>
$ curl localhost:8000
<html><body><h1>It works!</h1></body></html>

Perquè hi ha 3 "port forwards": 80 → 80, 3000 → 80 i 8000 → 80

I això vol dir que hi ha tres regles a la taula nat de netfilter amb port forward a la IP del contenidor al port 80:

$ sudo nft list table nat | grep "dnat to"
  iifname != "docker0" meta l4proto tcp tcp dport 8000 counter packets 0 bytes 0 dnat to 172.17.0.2:80
  iifname != "docker0" meta l4proto tcp tcp dport 3000 counter packets 0 bytes 0 dnat to 172.17.0.2:80
  iifname != "docker0" meta l4proto tcp tcp dport 80 counter packets 0 bytes 0 dnat to 172.17.0.2:80

Per què aquest exemple funciona en totes les interfícies del host?

$ docker run --rm -d --name apache -p 80:80 httpd
49cbdb93cfd7b09f96bbcce972ec7b54e2b7429f8c45412091852406541ba86a
$ ip --brief addr
lo               UNKNOWN        127.0.0.1/8 ::1/128 
enp1s0           UP             192.168.123.6/22 fe80::b4a1:54f4:7454:d2fd/64 
enp2s0           UP             10.2.76.37/16 fe80::d18f:22b8:b29b:935c/64 
docker0          UP             172.17.0.1/16 fe80::42:89ff:fe45:9db0/64 
vethfdcf90a@if17 UP             fe80::4cef:f3ff:fe98:b5a4/64 
$ curl 127.0.0.1
<html><body><h1>It works!</h1></body></html>
$ curl 192.168.123.6
<html><body><h1>It works!</h1></body></html>
$ curl 10.2.76.37
<html><body><h1>It works!</h1></body></html>
$ curl 172.17.0.1
<html><body><h1>It works!</h1></body></html>

Perquè per defecte el "port forward" es fa amb la IP 0.0.0.0, que vol dir totes.

Per què aquest exemple només funciona amb la IP 192.168.123.6 ?

$ docker run --rm -d --name apache -p 192.168.123.6:80:80 httpd
739fed667dd61fbc1e9f7b5e015bd946d28d70a6c50f266e57569f29bae64486
$ curl 127.0.0.1
curl: (7) Failed to connect to 127.0.0.1 port 80 after 0 ms: S’ha refusat la connexió
$ curl 192.168.123.6
<html><body><h1>It works!</h1></body></html>
$ curl 10.2.76.37
curl: (7) Failed to connect to 10.2.76.37 port 80 after 0 ms: S’ha refusat la connexió
$ curl 172.17.0.1
curl: (7) Failed to connect to 172.17.0.1 port 80 after 0 ms: S’ha refusat la connexió

Si mirem la taula nat ...

$ sudo nft list table nat | grep "dnat to"
  iifname != "docker0" meta l4proto tcp ip daddr 192.168.123.6 tcp dport 80 counter packets 1 bytes 60 dnat to 172.17.0.2:80

Port aleatori

A vegades quan tens molts contenidors funcionant només vols fer un "port forward" des de qualsevol port que estigui disponible.

En aquest cas només has de dir el port destí sobre el qual es farà el "port forward":

$ docker run --rm -d --name apache -p 80 httpd
8f962b1ebc2e338bced6e0a827a4c0d33f12479aa68128800da02c08c8b968df
$ curl localhost
curl: (7) Failed to connect to localhost port 80 after 0 ms: S’ha refusat la connexió

Podem fer un nmap per veure en quin port està escoltant:

$ nmap localhost
...
PORT      STATE SERVICE
631/tcp   open  ipp
3389/tcp  open  ms-wbt-server
32768/tcp open  filenet-tms

I provar ... o mirar la taula nat:

$ sudo nft list table nat | grep "dnat to"
  iifname != "docker0" meta l4proto tcp tcp dport 32768 counter packets 0 bytes 0 dnat to 172.17.0.2:80

Doncs és el port 32768, però si hi ha molts contenidors ...

Amb l’ordre docker port puc conèixer en quins ports s’està fent un "port forward" a un contenidor!

$ docker port apache
80/tcp -> 0.0.0.0:32768
80/tcp -> [::]:32768

Solucionat!

$ curl localhost:32768
<html><body><h1>It works!</h1></body></html>

Xarxes definides per l’usuari

Molts contenidors no funcionen sols sinó que depenen d'altres contenidors per funcionar, i la manera de connectar aquests contenidors és mitjançat xarxes virtuals.

Les xarxes definides per l’usuari, encara que facin servir el mateix driver que la xarxa predefinida bridge, tenen més funcions com per exemple la resolució DNS.

Crea una xarxa virtual específica amb el nom xarxa-1:

$ docker network create --driver bridge --attachable --subnet 10.10.41.0/24 xarxa-1
10db898153b0705a54c972fa5c8f513cd4be8e8563a02f07d76eb95496a2a836

$ docker network ls | grep xarxa-1
10db898153b0   xarxa-1   bridge    local

$ ip --brief addr | grep br
br-10db898153b0  UP             10.10.41.1/24 fe80::42:c7ff:fe3e:289/64 

L'opció --attachable permet connectar i desconnectar contenidors a la xarxa en qualsevol moment.

Amb l'opció --subnet pots definir la subxarxa.

Crea un contenidor amb name explorer que estigui connectat a la xarxa xarxa-1 i mira les interficies disponibles del contenidor:

$ docker run -d --name explorer --network xarxa-1 alpine sleep infinity
e79785bb7a564e7136258e8040b01dc24e6d67928f934ee8a30834c6766c56f0

$ docker exec explorer ip -f inet -4 -o addr
1: lo    inet 127.0.0.1/8 scope host lo\       valid_lft forever preferred_lft forever
24: eth0    inet 10.10.41.2/24 brd 10.10.41.255 scope global eth0\       valid_lft forever preferred_lft forever

Amb l'opció --network hem fet que el contenidor es vinculi a la xarxa xarxa-1 i no a docker0.

Crea una altre xarxa virtual amb el nom xarxa-2:

$ docker network create --driver bridge --attachable --subnet 10.10.42.0/24 xarxa-2
8afdb8106b838d541846c04cf890865ebd6e872a9a7c7ea2ed0a270c14a45fa3

$ docker network ls | grep xarxa
10db898153b0   xarxa-1   bridge    local
8afdb8106b83   xarxa-2   bridge    local

$ ip --brief addr | grep br
br-10db898153b0  UP             10.10.41.1/24 fe80::42:c7ff:fe3e:289/64 
br-8afdb8106b83  DOWN           10.10.42.1/24 

Pots veure que:

  • Al host ara hi ha dos xarxes virtuals més
  • Que la interfície br-10db898153b està UP (té un contenidor) i la interfície br-8afdb8106b83 està DOWN (no té cap contenidor).

Connecta el contenidor explorer a la xarxa xarxa-2 i verifica que ara té dos interfícies eth:

$ docker network connect xarxa-2 explorer

$ docker exec explorer ip -f inet -4 -o addr
1: lo    inet 127.0.0.1/8 scope host lo\       valid_lft forever preferred_lft forever
7: eth0    inet 10.10.41.2/24 brd 10.10.41.255 scope global eth0\       valid_lft forever preferred_lft forever
9: eth1    inet 10.10.42.2/24 brd 10.10.42.255 scope global eth1\       valid_lft forever preferred_lft forever

Una xarxa només té sentit si hi ha més d’un participant.

Com podem saber si hi ha algú més? Utilitzant Nmap

Entra dins el contenidor explorer i instal.la nmap:

$ docker exec -it explorer sh
... $ apk update && apk add nmap

Escaneja les xarxes a les que està connectat el contenidor:

... $ more /etc/hosts | grep 10.10
    10.10.41.2      4cbb41838ee0
    10.10.42.2      4cbb41838ee0
    
... $nmap -sn 10.10.41.* -sn 10.10.42.* -oG /dev/stdout | grep Status
    Host: 10.10.41.1 (ubuntu)       Status: Up
    Host: 10.10.41.2 (4cbb41838ee0) Status: Up
    Host: 10.10.42.1 (ubuntu)       Status: Up
    Host: 10.10.42.2 (4cbb41838ee0) Status: Up

Pots veure que en les dos xarxes només està el contenidor i el host.

Crea un altre contenidor connecta’t a la xarxa xarxa-2 amb el nom jupyter:

$ docker run -d --name jupyter --network xarxa-2 alpine sleep infinity
7cc41611065226f144840578d5cf0088a257482ec6fbbb14eac693eeff9f995e

Torna a executar nmap des del contenidor explorer:

$ docker exec explorer nmap -sn 10.10.42.* -oG /dev/stdout | grep Status
Host: 10.10.42.1 (ubuntu)       Status: Up
Host: 10.10.42.3 (jupyter.xarxa-2)      Status: Up
Host: 10.10.42.2 (4cbb41838ee0) Status: Up

Pots veure que hi ha un nou node amb IP 10.10.42.3 i nom jupyter.xarxa-2.

nmap pot resoldre aquesta IP perquè hi ha un servidor DNS que resolt IPs amb el nom de contenidor.

Verifica que pots resoldre el nom jupyter:

$ docker exec -it explorer sh

... $ nslookup jupyter
    ...
    Name:   jupyter
    Address: 10.10.42.3

... $ ping -c 1 jupyter
    PING jupyter (10.10.42.3): 56 data bytes
    64 bytes from 10.10.42.3: seq=0 ttl=64 time=0.121 ms
    ... 

DNS

El Sistema de noms de domini (DNS) és un protocol per assignar noms de hosts a adreces IP.

D’aquesta manera un host pot connectar-se a un altre host mitjançant un nom enlloc d’una IP.

hostname

Per defecte docker utilitza el nom del contenidor com a hostname:

$ docker run --rm --name hello --network xarxa-1 alpine nslookup hello
...
Name:   hello
Address: 10.10.41.3

Recorda que la xarxa per defecte (bridge) no té resolució DNS:

$ docker run --rm --name hello alpine nslookup hello
;; connection timed out; no servers could be reached

Amb l'opció --hostname pots especificar un altre nom alternatiu:

$ docker run --rm -it --name hello --network xarxa-2 --hostname bye alpine sh
... $ nslookup bye | grep 10
    Address: 10.10.42.4
... $ nslookup hello | grep 10
    Address: 10.10.42.4

bye és un nom alternatiu a hello !

Servidor DNS

Per defecte, el contenidor fa servir el servei DNS del host:

$ docker run --rm alpine nslookup xtec.dev
Server:         192.168.120.1
Address:        192.168.120.1:53
...

Però si vols pots crear un contenidor configurat amb un altre servidor dns També pots especificar un o més servidors DNS per utilitzar.

Per exemple, el servidor DNS 1.1.1.1 de Cloudflare:

$ docker run --rm --dns 1.1.1.1 alpine nslookup xtec.dev
Server:         1.1.1.1
Address:        1.1.1.1:53
...

Mira com és per defecte el fitxer /etc/resolv.conf d'un contenidor:

$ docker run --rm alpine cat /etc/resolv.conf
...
nameserver 192.168.120.1
search .

Mira com és el fitxer /etc/resolv.conf d'un contenidor amb l'opció dns:

$ docker run --rm --dns 1.1.1.1 alpine cat /etc/resolv.conf
nameserver 1.1.1.

Amb l'opció -dns modifiques el fitxer /etc/resolv.conf !

També pots utilitzar l’opció --dns-search per especificar un domini de cerca DNS.

Activitats

Postgres

PostgreSQL és una de les bases de dades relacionals més utilitzades.

Crea un contenidor postgres:

$ docker run -d --name postgres --network xarxa-1 -e POSTGRES_PASSWORD=password postgres

Executa un terminal interactiu en el contenidor postgres i crea una taula client amb dos entrades:

$ docker exec -it postgres bash
... $ psql -U postgres
    
    ... $ create table client(name text);
        CREATE TABLE
        $ insert into client values('Mary');
        INSERT 0 1
        $ insert into client values('Mike');
        INSERT 0 1
        $ select * from client;
        name 
        ------
        Mary
        Mike
        (2 rows)
        $ exit
    $ exit
    exit

Crea un contenidor postgres en la mateixa xarxa:

$ docker run --rm -it --network xarxa-1 postgres bash

Amb el flag -h postgres el client es conectarà al servidor postgres que estigui en la IP que resolgui el DNS pel nom postgres:

$ PGPASSWORD=password psql -h postgres -U postgres

... $ select * from client,
     name 
    ------
     Mary
     Mike
    (2 rows)

    $ exit

Ara enlloc de fer servir la resolució DNS utilitzarem directament la IP:

$ apt install -y dnsutils
$ nslookup postgres | grep 10.
Address: 10.10.41.2

$ PGPASSWORD=password psql -h 10.10.41.2 -U postgres

... $ select * from client,
     name 
    ------
     Mary
     Mike
    (2 rows)

    $ exit

Pots verificar que és el contenidor que hem creat abans perquè té una taula client amb els registres 'Mary' i 'Mike'.

Això és possible perquè els dos contenidors estan en la mateixa xarxa!

Crea un contenidor postgres en la xarxa-2 i verifica que no et pots conectar al contenidor postgres ni per nom ni per IP:

$ docker run --rm -it --network xarxa-2 postgres bash

... $ PGPASSWORD=password psql -h postgres -U postgres
    psql: error: could not translate host name "postgres" to address: Temporary failure in name resolution
    
    $ PGPASSWORD=password psql -h 10.10.41.2 -U postgres
    ^C

Wordpress

Si vols més informació ves a Wordpress

Hi ha moltes aplicacions que necessiten un base de dades, per exemple Wordpress:

Crea un contenidor mariadb pel wordpress:

$ docker run -d --name wordpress-database --network xarxa-2 -e MARIADB_ALLOW_EMPTY_ROOT_PASSWORD=true mariadb

Crea un contenidor Wordpress:

$ docker run -d --name wordpress --network xarxa-2 -p 80:80 wordpress

Crea una base de dades pel Wordpress:

$ docker exec -it wordpress-database bash
... $ mariadb
    ... Welcome to the MariaDB monitor.  Commands end with ; or \g.
        
        $ create database wordpress;
        $ grant all privileges on wordpress.* to 'wordpress' identified by 'password';
        $ flush privileges;
        $ exit

Ja pots obrir un navegador al Wordpress:

Amb el contenidor explorer i fes un nmap de les xarxes:

$ docker exec explorer nmap -sn 10.10.42.* -oG /dev/stdout | grep Status
...

Pots veure que la base de dades wordpress s'ha omplert de taules.

$ docker exec -it wordpress-database bash
... $ mariadb
    ... Welcome to the MariaDB monitor.  Commands end with ; or \g.
        
        $ show databases;
        ...
        $ use wordpress;

HAProxy

HAProxy és un balancejador de càrrega d’alta disponibilitat (i proxy invers) per TCP i aplicacions HTTP.

Crea un volum amb el nom apache ( a Emmagatzematge ja parlarem de volums):

$ docker volume create apache
apache

Crea una xarxa amb el nom apache :

$ docker network create --attachable --subnet 10.10.51.0/24 apache

Crea tres contenidor apache a la xarxa apache amb el volum apache montat a /usr/local/apache2/htdocs/:

$ docker run -d --name apache-1 --network apache -v apache:/usr/local/apache2/htdocs httpd
$ docker run -d --name apache-2 --network apache -v apache:/usr/local/apache2/htdocs httpd
$ docker run -d --name apache-3 --network apache -v apache:/usr/local/apache2/htdocs httpd

Pots veure que els tres servidor apache són accessibles des de la xarxa 10.10.51.0/24, però només des d'aquesta xarxa:

$ nmap 10.10.51.0/24
...
Nmap scan report for 10.10.51.2
...
80/tcp open  http

Nmap scan report for 10.10.51.3
...
80/tcp open  http

Nmap scan report for 10.10.51.4
...
PORT   STATE SERVICE
80/tcp open  http

Entra en el contenidor apache-1 i modifica el contingut del fitxer index.html:

$ docker exec -it apache-1 bash
... $ echo "Hello World!" > htdocs/index.html

Com que tots els apaches comparteixen el mateix volum, tenen el mateix fitxer index.html:

$ apt update && apt install -y curl
$ curl localhost
Hello World!
$ curl apache-2
Hello World!
$ curl apache-3
Hello World1

Crea un fitxer de configuració haproxy.cfg:

$ mkdir haproxy

$ echo "
frontend proxy
 bind 0.0.0.0:80
 default_backend webservers

backend webservers
 server s1 apache-1:80 check
 server s2 apache-2:80 check
 server s3 apache-3:80 check
" > haproxy/haproxy.cfg

Arrenca un contenidor haproxy en la xarxa apache amb aquest fitxer i un "port forward" 9000:80:

$ docker run -d --name proxy --network apache -v $(pwd)/haproxy:/usr/local/etc/haproxy:ro -p 9000:80 haproxy

Verifica que haproxy funciona amb tres, dos, un i cap apache actius.

$ curl localhost:9000
Hello World!
$ docker stop apache-1
$ curl localhost:9000
Hello World!
$ docker stop apache-2
$ curl localhost:9000
Hello World!
$ docker stop apache-3
$ curl localhost:9000
curl: (52) Empty reply from server

Quan el proxy no té cap apache que li respongui (estan tots parats) retorna una resposta buida: Empty reply from server.

Arrenca un apache i verifica que el proxy torna a funcionar (al cap d'uns segons)

$ docker start apache-2
$ curl localhost:9000
Hello World!

Referències