Skip to main content

Gestionando Redes de Contenedores

Cuando Docker se ejecuta, crea un bridge virtual llamado "docker0", para que pueda gestionar la comunicación interna entre el container y el host y también entre los containers.

Vamos a conocer algunos parámetros del comando "Docker container run" que nos ayudarán con la red en la que los containers se comunicarán.

  • --dns -- Indica el servidor DNS.

  • --hostname -- Indica un hostname.

  • --link -- Crea un link entre los containers, sin necesidad de conocer la IP uno del otro.

  • --net -- Permite configurar el modo de red que utilizarás con el container. Tenemos cuatro opciones, pero la más conocida y utilizada es "--net=host", que permite que el container utilice la red del host para comunicarse y no cree un stack de red para el container.

  • --expose -- Expone el puerto del container únicamente.

  • --publish -- Expone el puerto del container y del host.

  • --default-gateway -- Determina la ruta predeterminada.

  • --mac-address -- Determina una dirección MAC.

Cuando el container se inicia, la red pasa por algunas etapas hasta su inicialización completa:

  1. Se crea un par de interfaces virtuales.

  2. Se crea una interfaz con nombre único, como "veth1234", y luego se enlaza con el bridge de Docker, el "docker0".

  3. Con esto, se pone a disposición la interfaz "eth0" dentro del container, en un network namespace único.

  4. Se configura la dirección MAC de la interfaz virtual del container.

  5. Se asigna una IP en el "eth0" del container. Esta IP debe pertenecer al range del bridge "docker0".

Con esto, el container ya posee una interfaz de red y ya está listo para comunicarse con otros containers o con el host. :D

10.1. ¿Puedo hacer que el puerto del container responda en el puerto del host?

Sí, esto es posible y bastante utilizado.

Vamos a conocer un poco más sobre esto con un ejemplo utilizando aquella nuestra imagen "linuxtips/apache".

Lo primero que tenemos que saber es el puerto por el cual Apache2 se comunica. Esto es fácil, ¿verdad? Si está con las configuraciones predeterminadas de puerto de un web server, el Apache2 del container estará respondiendo en el puerto 80/TCP, ¿correcto?

Ahora vamos a hacer que el puerto 8080 de nuestro host responda por el puerto 80 de nuestro container, es decir, siempre que alguien llame al puerto 8080 de nuestro host, la solicitud será redirigida al puerto 80 del container. Simple, ¿verdad?

Para lograr hacer esta redirección, necesitamos utilizar el parámetro "-p" del comando "docker container run", como haremos en el siguiente ejemplo:

root@linuxtips:~# # docker container run -ti -p 8080:80 linuxtips/apache:1.0 /bin/bash
root@4a0645de6d94:/# ps -ef
UID PID PPID C STIME TTY TIME CMD
root 1 0 1 18:18 ? 00:00:00 /bin/bash
root 6 1 0 18:18 ? 00:00:00 ps -ef

root@4a0645de6d94:/# /etc/init.d/apache2 start
[....] Starting web server: apache2AH00558: apache2: Could not reliably determine the server's fully qualified domain name, using 172.17.0.3. Set the 'ServerName' directive globally to suppress this message
. ok

root@4a0645de6d94:/# ps -ef
UID PID PPID C STIME TTY TIME CMD
root 1 0 0 18:18 ? 00:00:00 /bin/bash
root 30 1 0 18:19 ? 00:00:00 /usr/sbin/apache2 -k start
www-data 33 30 0 18:19 ? 00:00:00 /usr/sbin/apache2 -k start
www-data 34 30 0 18:19 ? 00:00:00 /usr/sbin/apache2 -k start
root 109 1 0 18:19 ? 00:00:00 ps -ef

root@4a0645de6d94:/# ip addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host
valid_lft forever preferred_lft forever
74: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
link/ether 02:42:ac:11:00:03 brd ff:ff:ff:ff:ff:ff
inet 172.17.0.3/16 scope global eth0
valid_lft forever preferred_lft forever
inet6 fe80::42:acff:fe11:3/64 scope link
valid_lft forever preferred_lft forever

root@4a0645de6d94:/#

Observa que pasamos el parámetro "-p" de la siguiente forma:

  • -p 8080:80 -- Donde "8080" es el puerto del host y "80" el del container.

Con esto, estamos indicando que toda solicitud que llegue al puerto 8080 de mi host deberá ser redirigida al puerto 80 del container.

Ya en el container, levantamos Apache2 y verificamos la IP del container, ¿correcto?

Ahora vamos a salir del container con el atajo "Ctrl + p + q". :)

Desde el host, vamos a realizar un "curl" con destino a la IP del container en el puerto 80, después con destino al puerto 8080 del host y luego analizar las salidas:

root@linuxtips:~# curl <IPCONTAINER>:80

Si todo salió bien hasta aquí, verás el código de la página de bienvenida de Apache2.

Lo mismo ocurre cuando ejecutamos el "curl" nuevamente, pero llamando a la IP del host. Mira:

root@linuxtips:~# curl <IPHOST>:8080

¡Muy fácil, hasta llega a emocionar! \o/

10.2. ¿Y cómo hace eso? ¿Magia?

¡No, no es magia! En realidad, el comando solo utiliza un módulo bastante antiguo del kernel de Linux llamado netfilter, que proporciona la herramienta iptables, que todos nosotros ya nos cansamos de usar.

Vamos a echar un vistazo a las reglas de iptables referentes a nuestro container. Primero la tabla filter:

root@linuxtips:~# iptables -L -n
Chain INPUT (policy ACCEPT)
target prot opt source destination

Chain FORWARD (policy ACCEPT)
target prot opt source destination
DOCKER-ISOLATION all -- 0.0.0.0/0 0.0.0.0/0
DOCKER all -- 0.0.0.0/0 0.0.0.0/0
ACCEPT all -- 0.0.0.0/0 0.0.0.0/0 ctstate RELATED,ESTABLISHED
ACCEPT all -- 0.0.0.0/0 0.0.0.0/0
ACCEPT all -- 0.0.0.0/0 0.0.0.0/0

Chain OUTPUT (policy ACCEPT)
target prot opt source destination

Chain DOCKER (1 references)
target prot opt source destination
ACCEPT tcp -- 0.0.0.0/0 172.17.0.2 tcp dpt:5000
ACCEPT tcp -- 0.0.0.0/0 172.17.0.3 tcp dpt:80

Chain DOCKER-ISOLATION (1 references)
target prot opt source destination
RETURN all -- 0.0.0.0/0 0.0.0.0/0

root@linuxtips:~#

Ahora la tabla NAT:

root@linuxtips:~# iptables -L -n -t nat

Chain PREROUTING (policy ACCEPT)
target prot opt source destination
DOCKER all -- 0.0.0.0/0 0.0.0.0/0 ADDRTYPE match dst-type LOCAL

Chain INPUT (policy ACCEPT)
target prot opt source destination

Chain OUTPUT (policy ACCEPT)
target prot opt source destination
DOCKER all -- 0.0.0.0/0 !127.0.0.0/8 ADDRTYPE match dst-type LOCAL

Chain POSTROUTING (policy ACCEPT)
target prot opt source destination
MASQUERADE all -- 172.17.0.0/16 0.0.0.0/0
MASQUERADE tcp -- 172.17.0.2 172.17.0.2 tcp dpt:5000
MASQUERADE tcp -- 172.17.0.3 172.17.0.3 tcp dpt:80

Chain DOCKER (2 references)
target prot opt source destination
RETURN all -- 0.0.0.0/0 0.0.0.0/0
DNAT tcp -- 0.0.0.0/0 0.0.0.0/0 tcp dpt:5000 to:172.17.0.2:5000
DNAT tcp -- 0.0.0.0/0 0.0.0.0/0 tcp dpt:8080 to:172.17.0.3:80

root@linuxtips:~#

Como podemos notar, tenemos reglas de NAT configuradas que permiten el DNAT del puerto 8080 del host al 80 del container. Mira a continuación:

MASQUERADE tcp -- 172.17.0.3  172.17.0.3  tcp dpt:80
DNAT tcp -- 0.0.0.0/0 0.0.0.0/0 tcp dpt:8080 to:172.17.0.3:80

Todo esto hecho "automágicamente" por Docker, sin necesidad de tener que configurar diversas reglas de iptables. <3