Ir al contenido

Laboratorio 08 - Networking en Docker

Cada contenedor Docker dispone de su propio entorno de red. Para que dos contenedores puedan comunicarse, o para que un servicio sea accesible desde el sistema anfitrión, es necesario definir cómo participan esos contenedores en una o más redes.

Este laboratorio se divide en dos partes. Primero trabajará con redes y contenedores creados mediante comandos docker. Después utilizará Docker Compose para observar cómo esas mismas relaciones pueden declararse como parte de una aplicación multicontenedor.

GroceryListApp servirá como aplicación de referencia para conectar los conceptos de red con un escenario ya conocido.

Al finalizar podrá

  • distinguir la red bridge por defecto de una red bridge creada por el usuario;
  • comprobar la comunicación entre contenedores mediante dirección IP;
  • utilizar resolución de nombres en redes personalizadas;
  • conectar un contenedor a más de una red;
  • aislar servicios que no necesitan comunicarse entre sí;
  • reconocer la red que Docker Compose crea automáticamente para un proyecto;
  • declarar redes explícitas en Compose para controlar qué servicios comparten conectividad.

Necesitará

  • Docker y Docker Compose instalados y en ejecución;
  • la imagen de GroceryListApp construida localmente o acceso al repositorio para construirla;
  • una terminal con acceso a Docker.

Si todavía no dispone de la imagen, prepare el proyecto.

Ventana de terminal
git clone https://github.com/jpadillaa/grocerylistapp.git
cd grocerylistapp
docker build -t grocerylistapp:1.0.0 .

Docker crea varias redes durante la instalación. Consulte las disponibles.

Ventana de terminal
docker network ls

Una salida aproximada puede verse así.

NETWORK ID NAME DRIVER SCOPE
xxxxxxxxxxxx bridge bridge local
xxxxxxxxxxxx host host local
xxxxxxxxxxxx none null local

La red bridge es la utilizada por defecto cuando se crea un contenedor sin especificar otra red.

Cree dos contenedores Alpine.

Ventana de terminal
docker run -d --name contenedor-a alpine sleep 3600
docker run -d --name contenedor-b alpine sleep 3600

Compruebe que ambos pertenecen a bridge.

Ventana de terminal
docker network inspect bridge --format '{{range .Containers}}{{.Name}} {{end}}'

Deberían aparecer contenedor-a y contenedor-b.

Obtenga la dirección IP de contenedor-b.

Ventana de terminal
docker inspect contenedor-b --format '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}'

Desde contenedor-a, envíe tráfico ICMP hacia esa dirección.

Ventana de terminal
docker exec contenedor-a ping -c 2 172.17.0.X

Reemplace 172.17.0.X por la dirección obtenida en el paso anterior.

La prueba permite comprobar que dos contenedores conectados a la misma red bridge pueden comunicarse mediante sus direcciones IP.

Intente ahora utilizar el nombre del segundo contenedor.

Ventana de terminal
docker exec contenedor-a ping -c 2 contenedor-b

El laboratorio espera un error similar a

ping: bad address 'contenedor-b'

La red bridge por defecto utilizada en este ejercicio no proporciona la resolución automática por nombre que Docker ofrece en las redes bridge creadas por el usuario.

Esto introduce una distinción importante. La conectividad por IP y la resolución de nombres son capacidades relacionadas, pero no equivalentes.

Depender de una dirección IP obtenida dinámicamente también hace que la configuración sea frágil frente a la recreación de contenedores.

Ventana de terminal
docker rm -f contenedor-a contenedor-b

Las redes bridge creadas por el usuario permiten que los contenedores conectados a ellas utilicen nombres para encontrarse entre sí.

Cree una nueva red.

Ventana de terminal
docker network create app-net

Compruebe que existe.

Ventana de terminal
docker network ls | grep app-net

Inicie GroceryListApp y conecte el contenedor a app-net.

Ventana de terminal
docker run -d \
--name app \
--network app-net \
-v grocerylist-data:/data \
grocerylistapp:1.0.0

El comando no publica ningún puerto hacia el sistema anfitrión. El servicio queda disponible para otros contenedores que compartan la red app-net.

Cree default.conf en el directorio actual.

server {
listen 80;
location / {
proxy_pass http://app:8080;
proxy_set_header Host $host;
}
}

La instrucción relevante es

proxy_pass http://app:8080;

app corresponde al nombre del contenedor de GroceryListApp. No es necesario conocer previamente su dirección IP.

Inicie Nginx en la misma red.

Ventana de terminal
docker run -d \
--name web \
--network app-net \
-p 80:80 \
-v $(pwd)/default.conf:/etc/nginx/conf.d/default.conf:ro \
nginx:alpine

Solo web publica un puerto hacia el sistema anfitrión.

La relación queda así.

Sistema anfitrión
│
│ puerto 80
▼
web
Nginx
│
│ app:8080
▼
app
GroceryListApp

Solicite la ruta de salud.

Ventana de terminal
curl http://localhost/health

La respuesta esperada es

{"status":"ok"}

Nginx pudo reenviar la solicitud porque resolvió el nombre app dentro de la red app-net.

Puede comprobar esa resolución directamente.

Ventana de terminal
docker exec web sh -c "nslookup app"

La salida debería incluir la dirección interna asignada al contenedor app.

Comprobar que app no está publicado hacia el host

Sección titulada «Comprobar que app no está publicado hacia el host»

Ejecute

Ventana de terminal
curl -s --max-time 3 http://localhost:8080 || echo "No accesible desde el host (esperado)"

La ausencia de respuesta directa es el comportamiento esperado. app participa en app-net, pero no publica su puerto 8080 hacia el sistema anfitrión.

Este diseño permite que un servicio sea accesible para otros contenedores sin quedar expuesto directamente mediante un puerto del host.

Ventana de terminal
docker network inspect app-net --format '{{range .Containers}}{{.Name}}: {{.IPv4Address}}{{"\n"}}{{end}}'

Deberían aparecer app y web con las direcciones asignadas por Docker.

Ventana de terminal
docker rm -f app web
docker volume rm grocerylist-data
docker network rm app-net
rm -f default.conf

Aislar contenedores mediante redes diferentes

Sección titulada «Aislar contenedores mediante redes diferentes»

Una red también define un límite de conectividad. Dos contenedores que no comparten una red no disponen de una ruta directa entre ellos mediante esas redes.

Cree dos redes.

Ventana de terminal
docker network create red-frontend
docker network create red-backend

Ejecute un contenedor en cada una.

Ventana de terminal
docker run -d --name frontend --network red-frontend alpine sleep 3600
docker run -d --name backend --network red-backend alpine sleep 3600

Desde frontend, intente alcanzar backend.

Ventana de terminal
docker exec frontend ping -c 2 -W 2 backend

La prueba debería fallar porque ambos contenedores participan en redes diferentes.

La relación inicial es

red-frontend red-backend
│ │
frontend backend

No existe una red compartida entre los dos.

Un contenedor puede participar en varias redes simultáneamente.

Conecte frontend también a red-backend.

Ventana de terminal
docker network connect red-backend frontend

La topología cambia.

red-frontend red-backend
│ │ │
frontend ─────────────────┘ backend

Intente nuevamente.

Ventana de terminal
docker exec frontend ping -c 2 backend

Ahora la comunicación debería funcionar porque ambos contenedores comparten red-backend.

Compruebe las redes asociadas con frontend.

Ventana de terminal
docker inspect frontend --format '{{range $k, $v := .NetworkSettings.Networks}}{{$k}} {{end}}'

La salida esperada incluye

red-frontend red-backend
Ventana de terminal
docker rm -f frontend backend
docker network rm red-frontend red-backend

Utilizar la red creada automáticamente por Compose

Sección titulada «Utilizar la red creada automáticamente por Compose»

Docker Compose crea una red para el proyecto cuando los servicios no declaran una configuración de redes explícita.

Ubíquese en el repositorio y prepare el directorio de Nginx.

Ventana de terminal
cd grocerylistapp
mkdir -p nginx

Cree nginx/default.conf.

server {
listen 80;
location / {
proxy_pass http://app:8080;
proxy_set_header Host $host;
}
}

Cree docker-compose.yml.

services:
app:
build: .
volumes:
- grocerylist-data:/data
web:
image: nginx:alpine
ports:
- "80:80"
volumes:
- ./nginx/default.conf:/etc/nginx/conf.d/default.conf:ro
depends_on:
- app
volumes:
grocerylist-data:

No aparece una sección networks. Compose utilizará la red que crea para el proyecto.

Inicie la aplicación.

Ventana de terminal
docker compose up -d
Ventana de terminal
docker network ls | grep grocerylistapp

El laboratorio espera una red denominada

grocerylistapp_default

Compruebe qué contenedores pertenecen a ella.

Ventana de terminal
docker network inspect grocerylistapp_default --format '{{range .Containers}}{{.Name}} {{end}}'

Los servicios app y web deberían aparecer conectados a la misma red.

Ventana de terminal
curl http://localhost/health

Nginx utiliza nuevamente app como destino.

En Compose, el nombre del servicio definido en docker-compose.yml funciona como referencia dentro de la red del proyecto.

Ventana de terminal
docker compose down -v

La red por defecto es suficiente cuando todos los servicios deben compartir conectividad. Cuando la aplicación necesita separar grupos de servicios, Compose permite declarar varias redes y decidir a cuáles pertenece cada servicio.

Reemplace docker-compose.yml por

services:
app:
build: .
volumes:
- grocerylist-data:/data
networks:
- backend
- frontend
web:
image: nginx:alpine
ports:
- "80:80"
volumes:
- ./nginx/default.conf:/etc/nginx/conf.d/default.conf:ro
depends_on:
- app
networks:
- frontend
monitor:
image: alpine
command: sleep 3600
networks:
- backend
volumes:
grocerylist-data:
networks:
frontend:
backend:

La configuración define dos redes y tres servicios.

Servicio frontend backend
web Sí No
app Sí Sí
monitor No Sí

La topología puede representarse así.

app
/ \
/ \
frontend backend
│ │
web monitor

app pertenece a ambas redes. web y monitor, en cambio, no comparten ninguna.

Inicie los servicios.

Ventana de terminal
docker compose up -d

Ambos comparten frontend.

Ventana de terminal
curl http://localhost/health

La solicitud debería funcionar.

Ambos comparten backend.

Ventana de terminal
docker compose exec monitor ping -c 2 app

La prueba debería funcionar.

No comparten ninguna red.

Ventana de terminal
docker compose exec monitor ping -c 2 -W 2 web

La prueba debería fallar.

Ventana de terminal
docker compose exec web sh -c "ping -c 2 -W 2 monitor || echo 'No accesible (esperado)'"

La prueba también debería fallar.

app puede comunicarse con ambos grupos porque participa en las dos redes. Esto no convierte automáticamente al contenedor en un router entre ellas. Lo que demuestra el ejercicio es que app dispone de conectividad propia hacia servicios de frontend y de backend, mientras que web y monitor continúan sin compartir una red entre sí.

Ventana de terminal
docker compose down -v

Compruebe que las redes del proyecto ya no aparecen.

Ventana de terminal
docker network ls | grep grocerylistapp

No deberían aparecer resultados asociados con esas redes.

El laboratorio muestra que la red define tanto posibilidades de comunicación como límites entre servicios.

La primera diferencia importante aparece entre la red bridge por defecto y una red bridge creada por el usuario. En el ejercicio, ambas permiten conectividad por IP entre contenedores que comparten red, pero la red personalizada añade resolución automática por nombre.

La segunda diferencia aparece al trabajar con varias redes. Dos servicios que no comparten una red permanecen separados dentro de la topología planteada. Un contenedor que participa en dos redes puede comunicarse con servicios de ambas, sin que eso implique que los demás servicios adquieran conectividad directa entre sí.

Docker Compose conserva esos mismos principios y los expresa de forma declarativa. Si no se especifican redes, crea una para el proyecto. Si se declaran varias, cada servicio puede asociarse únicamente con aquellas que necesita.

Concepto Comportamiento observado
Red bridge por defecto Permite comunicación por IP entre contenedores conectados a ella
Red bridge personalizada Añade resolución automática por nombre entre sus contenedores
docker network connect Conecta un contenedor existente a una red adicional
Red automática de Compose Conecta los servicios del proyecto cuando no se declaran redes explícitas
Redes explícitas en Compose Permiten definir qué grupos de servicios comparten conectividad
Recurso Enlace
Docker networking overview Documentación de redes de Docker
Bridge network driver Documentación del controlador bridge
Networking in Compose Documentación de redes en Compose
GroceryListApp Repositorio en GitHub

Fuente original

Laboratorio 08 - Networking en Docker