Saltar al contenido principal

Redes en Docker

En Docker 0 conectamos el navegador del host con un contenedor mediante un puerto publicado:

host :8080 ── publicación ──→ contenedor :80

Ahora TasteMatch necesita hablar con otro servicio. El recorrido es distinto:

contenedor A ── red Docker ──→ contenedor B

Antes de escribir comandos, separa tres lugares:

  1. host: nuestro equipo;
  2. contenedor actual: donde se ejecuta el proceso que hace la conexión;
  3. otro contenedor: el servicio al que queremos llegar.

localhost siempre empieza por «aquí»​

Dentro de un contenedor, localhost se refiere a ese mismo contenedor.

TasteMatch
┌──────────────────────────┐
│ localhost → TasteMatch │
└──────────────────────────┘

Base de datos
┌──────────────────────────┐
│ otro contenedor │
└──────────────────────────┘

Por tanto, TasteMatch no llega a la base de datos usando localhost si la base está en otro contenedor. Tampoco significa automáticamente «el host». Primero debemos identificar desde qué entorno se realiza la conexión.

Qué aporta una red Docker​

Una red Docker permite conectar contenedores y controlar qué servicios pueden comunicarse. En una red bridge definida por el usuario, los contenedores pueden localizarse por nombre.

Consulta las redes existentes:

docker network ls

Docker suele disponer de una red llamada bridge. Para nuestras aplicaciones crearemos una red bridge propia: expresa la relación entre sus contenedores y proporciona resolución de nombres adecuada para la práctica.

docker network create tastematch-net
docker network ls
docker network inspect tastematch-net

inspect muestra configuración y, cuando existan, los contenedores conectados. No necesitamos estudiar interfaces Linux ni reglas internas de red.

Primer experimento: servidor y cliente​

Inicia Nginx dentro de la red, sin publicar su puerto al host:

docker run -d --name web-interna \
--network tastematch-net \
nginx:alpine

Utiliza una imagen cliente que ya contiene curl:

docker run --rm --name cliente \
--network tastematch-net \
curlimages/curl:8.12.1 \
http://web-interna

La respuesta HTML de Nginx demuestra la comunicación:

cliente
│ http://web-interna:80
▼
tastematch-net
▼
web-interna

No hemos instalado herramientas dentro de Nginx ni publicado -p. El cliente alcanza el puerto interno porque ambos contenedores comparten la red.

Comunicación por nombre y DNS interno​

En la red creada por el usuario, Docker proporciona resolución de nombres para los contenedores conectados. web-interna se resuelve a la dirección actual del contenedor correspondiente.

nombre estable para la aplicación
↓
resolución de Docker
↓
dirección del contenedor en esa red

No conviene escribir una IP interna obtenida durante una prueba. Puede cambiar cuando se recrea el contenedor. El nombre expresa mejor qué servicio buscamos.

Puedes comprobar la red desde ambos lados:

docker network inspect tastematch-net
docker inspect web-interna

Busca la sección de redes y confirma que web-interna está conectado a tastematch-net.

Red Docker y publicación resuelven problemas distintos​

NecesidadMecanismo
Un contenedor llega a otroRed Docker compartida y puerto del servicio.
El host llega a un contenedorPublicación con -p.

Publicar un puerto no es requisito para la comunicación interna. Solo publica aquello que realmente necesite una entrada desde el host.

navegador host ── -p 8080:3000 ──→ TasteMatch
│
red Docker interna
│
▼
servicio de datos

El segundo servicio puede permanecer sin puertos publicados si solo lo utiliza TasteMatch.

Aplicación y servicio​

Imagina estos contenedores:

tastematch-app
necesita DB_HOST=tastematch-db
│
▼
tastematch-net
│
▼
tastematch-db
escucha en su puerto interno

El hostname correcto es el nombre del servicio en la red, no localhost y no una IP copiada de inspect. La forma de proporcionar DB_HOST se estudiará en la siguiente unidad; aquí importa el recorrido de red.

Conectar un contenedor existente​

Crea un segundo servidor inicialmente fuera de nuestra red:

docker run -d --name web-conectable nginx:alpine

Al no indicar --network, el contenedor queda inicialmente en la red bridge predeterminada de Docker, no en tastematch-net.

Conéctalo:

docker network connect tastematch-net web-conectable
docker network inspect tastematch-net

Comprueba desde un cliente efímero:

docker run --rm --network tastematch-net \
curlimages/curl:8.12.1 \
http://web-conectable

Desconéctalo:

docker network disconnect tastematch-net web-conectable

Una nueva petición desde esa red ya no debería localizarlo por ese nombre. Desconectar no elimina el contenedor.

Un contenedor puede participar en varias redes​

Crea otra red y conecta web-interna:

docker network create observacion-net
docker network connect observacion-net web-interna
docker inspect web-interna

El contenedor pertenece ahora a dos redes. Eso puede permitirle comunicarse con grupos diferentes, pero no conecta automáticamente entre sí a todos los demás miembros de ambas redes.

Las redes también expresan aislamiento: un contenedor que solo está en observacion-net no obtiene por ello acceso por nombre a servicios exclusivos de tastematch-net.

Retira la conexión adicional:

docker network disconnect observacion-net web-interna
docker network rm observacion-net

Limpieza de la práctica​

Primero elimina los contenedores creados:

docker stop web-interna web-conectable
docker rm web-interna web-conectable

Después elimina la red cuando ya no tenga contenedores conectados:

docker network rm tastematch-net
docker network ls

Docker rechazará normalmente eliminar una red que continúa en uso. Inspecciona qué está conectado antes de actuar.

Errores habituales​

  • Usar localhost para otro contenedor: apunta al contenedor actual.
  • Publicar un puerto para comunicación interna: -p sirve para la entrada desde el host, no sustituye una red compartida.
  • Copiar una IP interna: usa el nombre del contenedor en una red definida por el usuario.
  • Separar los contenedores en redes distintas: comprueba ambos extremos con inspect.
  • Confundir el puerto del host y el del servicio: entre contenedores se usa el puerto interno del servicio.
  • Eliminar una red sin revisar miembros: inspecciona y desconecta o retira conscientemente los contenedores correspondientes.

Mini reto: servidor y cliente por nombre​

Consigue esta arquitectura sin publicar el servidor al host:

cliente-reto ── reto-net ──→ servidor-reto:80
  1. Crea reto-net.
  2. Inicia servidor-reto con Nginx en esa red.
  3. Usa un contenedor efímero curlimages/curl como cliente.
  4. Realiza una petición a http://servidor-reto y conserva la evidencia.
  5. Explica por qué http://localhost desde el cliente no representa al servidor.
  6. Demuestra con docker network inspect qué contenedores participan.
  7. Crea un segundo servidor Nginx llamado servidor-extra sin indicar --network: quedará en bridge, fuera de reto-net.
  8. Conecta servidor-extra a reto-net con docker network connect.
  9. Desde un nuevo cliente efímero en reto-net, realiza una petición a http://servidor-extra y comprueba que responde utilizando ese hostname.
  10. Desconecta servidor-extra de reto-net con docker network disconnect.
  11. Predice qué ocurrirá al repetir la petición desde otro cliente efímero en reto-net. Después ejecútala y compara el resultado con tu predicción.
  12. Detén y elimina únicamente servidor-reto y servidor-extra, y elimina reto-net. Crea los clientes efímeros con --rm para que se eliminen al terminar.

Explica finalmente por qué ningún puerto necesitó publicarse al host. En la siguiente unidad proporcionaremos a una aplicación valores como el hostname del servicio sin reconstruir su imagen.