Cómo funcionan las redes en Docker

Cuando decides aprender Docker uno de los temas que más suele costar es la parte de networking. Por eso hoy quiero contarte a modo de resumen cómo nos ayuda esta tecnología a cubrir esta parte.

Port mapping

Normalmente, cuando comienzas a jugar con Docker, lo primero que usas es lo que se conoce como port mapping. El objetivo de esta característica es poder acceder a la aplicación que está hospedada en un contenedor de Docker desde tu máquina local. Para ello, lo que hacemos es mapear un puerto de nuestra máquina con el puerto por el que está a la escucha el contenedor. Esto se consigue de la siguiente manera:

docker run -d --publish 9090:80 nginx

El comando –publish (normalmente acortado a -p) es el que nos permite crear esa asociación entre mi puerto local (9090) y el puerto que nos interese del contenedor (80). En este caso, para acceder simplemente necesitaríamos utilizar un navegador web y acudir a la dirección http://localhost:9090

Por otro lado, si la imagen que estamos utilizando para crear nuestro contenedor ha hecho un buen uso de las instrucciones EXPOSE, podemos usar el comando de esta otra manera, para que Docker se encargue de elegir por nosotros puertos aleatorios en nuestra máquina que mapear con aquellos especificados en el Dockerfile:

docker run -d --publish-all hello-world

Esto está bien cuando yo, Gisela, quiero hablar con uno de los contenedores que se está ejecutando en mi máquina a nivel de desarrollo pero ¿y si quiero que los contenedores hablen entre sí?

Docker y el networking

Si seguiste los artículos anteriores que escribí sobre Docker, ya sabes más que de sobra que las aplicaciones se ejecutan dentro de contenedores, de manera independiente unas de otras. Sin embargo, estas aplicaciones necesitan comunicarse entre ellas, en la mayoría de las ocasiones, como puede ser el front end con el back end, como bases de datos, APIs, etcétera. En este sentido, Docker tiene una forma súper sencilla de lidiar con las redes ¿Y si te dijera que has estado trabajando con redes en Docker desde el minuto uno sin tu saberlo? Pues así de fácil es.

A modo de culturilla, la parte de networking de Docker está basada en una arquitectura llamada Container Network Model (CNM). Esta está implementada en una librería llamada libnetwork, que forma parte de Docker Engine. Por otro lado, cada SO que soporta Docker Engine tiene un conjunto de drivers que permiten a libnetwork crear la configuración de la red que corresponda en cada sistema operativo por nosotros. De manera nativa, estos traen consigo cuatro tipos:

  • Bridge/NAT: Se utiliza cuando tienes un solo host de Docker y neesitas que tus contenedores hablen entre sí.
  • Overlay: está disponible cuando trabajamos en un formato clúster, es decir que tenemos más de un host de Docker trabajando como uno solo, por lo que necesito que los contenedores puedan hablar entre sí, independientemente de dónde les haya tocado ejecutarse dentro del clúster.
  • None: este driver es la forma de decir a Docker que para un contenedor/es en concreto no queremos que estén conectados a ninguna red. Lo que hace en este caso es no asignarle una tarjeta de red y listo 🙂
  • Macvlan: esta opción se utiliza en el caso de querer conectar los dos mundos: el dockerizado y el sin dockerizar. Se trataría de un entorno híbrido que hace que mis contenedores tengan asignadas una IP dentro de la red donde está mi host/s.

También existen plugins de terceros que extienden estas opciones, como por ejemplo Weave Net.

Por defecto, todas las instalaciones de Docker vienen con tres configuraciones creadas por defecto. Para comprobarlo puedes utilizar el siguiente comando:

docker network ls

Este te devolverá lo siguiente:

PS C:\dev> docker network ls
NETWORK ID     NAME      DRIVER    SCOPE
d4cd8b5ba890   bridge    bridge    local
b326b2ae67bd   host      host      local
179faac8beb7   none      null      local

Veamos cada una de ellas con detalle.

Bridge

Este tipo es el más simple que ofrece Docker y es el que has estado usando todo este tiempo, sin tú saberlo. Se utiliza cuando sólo existe un host y solo es necesario comunicar los contenedores que viven en él entre ellos. En Linux se crean con el driver llamado bridge mientras que en Windows se utiliza el llamado nat, pero el funcionamiento es el mismo. Esta red es la utilizada por defecto, salvo que utilices el parámetro –network, que veremos después. Para comprobarlo, vamos a crear dos contenedores:

docker run -dit --name container-1 alpine ash
docker run -dit --name container-2 alpine ash

Si inspeccionamos la red bridge, veremos que existe un apartado llamado Containers donde aparecen los dos que tenemos ejecutándose:

docker network inspect bridge
docker network inspect bridge

Como ves, ahí están tus dos contendores, con las IPs 172.17.0.3 y 172.17.0.2, lo cual nos indica que están dentro del mismo rango y podrían comunicarse entre ellos. Pruebas a hacer un ping desde el container-1 al container-2:

docker attach container-1
ping -c 2 172.17.0.3

y verás que el resultado es satisfactorio:

/ # ping -c 2 172.17.0.3
PING 172.17.0.3 (172.17.0.3): 56 data bytes
64 bytes from 172.17.0.3: seq=0 ttl=64 time=0.723 ms
64 bytes from 172.17.0.3: seq=1 ttl=64 time=0.069 ms
--- 172.17.0.3 ping statistics ---
2 packets transmitted, 2 packets received, 0% packet loss
round-trip min/avg/max = 0.069/0.396/0.723 ms

Sin embargo, cada vez que cree este contendor esta IP puede ser diferente, ya que de ninguna forma estas IPs se reservan a mis contenedores. Esto puede suponer un problema si varios contenedores se comunican entre sí, y quiero añadir esta información como parte de mi aplicación. Es por ello que lo que molaría es que pudieramos comunicarnos con los contenedores a través de su nombre, en lugar de la IP, y es otra de las características que Docker nos brinda. Sin embargo, en esta red bridge generada por defecto no es posible usar este mecanismo. De hecho, si lo intentas verás que los contenedores en esta red no se reconocen por su nombre:

/ # ping -c 2 container-2
ping: bad address 'container-2'

Para ello debemos crearnos nuestras propias redes.

Crear tu propia red

Para crea una red dentro de Docker es súper sencillo, solamente debes utilizar el siguiente comando:

docker network create returngis-net

Una vez creada, si vuelves a listar las redes disponibles en tu host verás que tienes una nueva, llamada returngis-net que utiliza el driver bridge.

PS C:\dev> docker network ls
NETWORK ID     NAME            DRIVER    SCOPE
d4cd8b5ba890   bridge          bridge    local
b326b2ae67bd   host            host      local
179faac8beb7   none            null      local
0a8a9486bff6   returngis-net   bridge    local

Para probar la comunicación a través del nombre vamos a crear dos contenedores dentro de la misma, utilizando el parámetro –network:

docker container run -dit --name container-a --network returngis-net alpine ash
docker container run -dit --name container-b --network returngis-net alpine ash

Si ahora intentamos hacer ping desde el contenedor b al contenedor a:

docker attach container-b
ping -c 3 container-a

Veremos que en esta ocasión el resultado es positivo:

PS C:\dev> docker attach container-b
/ # ping -c 3 container-a
PING container-a (172.18.0.2): 56 data bytes        
64 bytes from 172.18.0.2: seq=0 ttl=64 time=0.131 ms
64 bytes from 172.18.0.2: seq=1 ttl=64 time=0.106 ms
64 bytes from 172.18.0.2: seq=2 ttl=64 time=0.118 ms     
--- container-a ping statistics ---
3 packets transmitted, 3 packets received, 0% packet loss
round-trip min/avg/max = 0.106/0.118/0.131 ms
/ #

Esto es así porque en este escenario Docker utiliza un servidor DNS configurado por cada contenedor que les permite resolver el nombre del resto con los que comparta red.

Conectar un contenedor a dos redes

Una duda que puede surgirte a raíz de este ejemplo es ¿puede un contenedor conectarse a más de un red? Pues la respuesta es que sí 🙂 y también es muy fácil de hacerlo. Sin embargo, durante la creación solo es posible conectarse a una única red. Para conectarse a una segunda, tercera, etcétera necesitas hacerlo a posteriori utilizar el comando network connect:

docker network connect returngis-net container-1

Si has ido ejecutando todos los ejemplos, ahora el container-1, que originalmente solo estaba en la red por defecto, llamada bridge, ahora también pertenece a la red llamada returngis-net, por lo que podría comunicarse tanto con container-a como con container-b:

docker attach container-1
ping -c 2 container-a
ping -c 2 container-b

Host

Otra de las redes que aparece en el listado de tu máquina local era la llamada host. Lo que hace esta red es eliminar el aislamiento entre el host y los contenedores, por lo que un contenedor no recibe su propia IP, como vimos en los ejemplos anteriores, sino que utiliza la del host. Por lo tanto, si ejecutas un contenedor asociado a esta red escuchando por el puerto 80, la aplicación del contenedor estará disponible a través del puerto 80 de la IP del host. Este modo suele ser útil cuando necesitas mejorar el rendimiento de tu aplicación. Sólo funciona en Linux y no está soportado en Docker Desktop, debido a que estamos trabajando con una máquina virtual.

docker run --rm -d --network host --name my_nginx nginx

Para comprobar que funciona deberías de acceder a http://localhost:80.

None

En el caso de que no quisieramos que un contenedor tenga una red asignada, utilizaremos –network none como en el siguiente ejemplo:

#Disable the network for a container
docker run --rm -dit --network none --name no-net-alpine alpine ash

Si ahora revisas las tarjetas de red que tiene asignado este contenedor comprobarás que solo aparece la interfaz loopback, que es para que el contenedor pueda hablar consigo mismo, pero nada más.

PS C:\dev> docker exec no-net-alpine ip link show
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
2: sit0@NONE: <NOARP> mtu 1480 qdisc noop state DOWN qlen 1000
    link/sit 0.0.0.0 brd 0.0.0.0

Overlay

Para el tipo overlay, necesitas trabajar en modo clúster. Si intentas crear una red de este tipo, sin estar en este modo, obtendrás el siguiente mensaje:

PS C:\dev> docker network create --driver overlay multihost-net
Error response from daemon: This node is not a swarm manager. Use "docker swarm init" or "docker swarm join" to connect this node to swarm and try again.

Sin embargo, la experiencia para ti sería la misma: la comunicación entre los diferentes contenedores podrá hacerse tanto a través de su IP como de su nombre, actuando como una única red, aunque haya diferentes máquinas ejecutando nuestros contenedores.

Macvlan

Como decía al inicio, existe un cuarto modo llamado macvlan que nos permite asignar IPs de una red a cada uno de los contenedores que viven en nuestro host. Sin embargo, para que este tipo de redes funcione necesitamos que la tarjeta de red del host esté en modo promiscuo, lo cual significa que necesita estar a la escucha de todos los paquetes que viajan por dicha red, simplemente para poder reconocer aquellos que potencialmente pueden ser para uno de sus contendores. Este modo muchas veces no está permitido en muchas organizaciones. La forma de configurar una red de este tipo sería así:

 docker network create -d macvlan \
  --subnet=172.16.86.0/24 \
  --gateway=172.16.86.1 \
  -o parent=eth0 \
  my-macvlan-net

Con ello conseguiríamos que los contenedores que estén dentro de esta puedan ser alcanzados por otras aplicaciones que vivan fuera de Docker.

¡Saludos!

logo lemoncode

 

 

Bootcamp DevOps

Si tienes ganas de meterte en el área de DevOps, formo parte del
equipo de docentes del Bootcamp DevOps Lemoncode, ¿Te animas a
aprender con nosotros?

Más Info