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.
Lo que aprenderá
Sección titulada «Lo que aprenderá»Al finalizar podrá
- distinguir la red
bridgepor defecto de una redbridgecreada 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.
Antes de comenzar
Sección titulada «Antes de comenzar»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.
git clone https://github.com/jpadillaa/grocerylistapp.gitcd grocerylistappdocker build -t grocerylistapp:1.0.0 .Parte 1 - Redes con comandos Docker
Sección titulada «Parte 1 - Redes con comandos Docker»Observar la red bridge por defecto
Sección titulada «Observar la red bridge por defecto»Docker crea varias redes durante la instalación. Consulte las disponibles.
docker network lsUna salida aproximada puede verse así.
NETWORK ID NAME DRIVER SCOPExxxxxxxxxxxx bridge bridge localxxxxxxxxxxxx host host localxxxxxxxxxxxx none null localLa red bridge es la utilizada por defecto cuando se crea un contenedor sin especificar otra red.
Ejecutar dos contenedores
Sección titulada «Ejecutar dos contenedores»Cree dos contenedores Alpine.
docker run -d --name contenedor-a alpine sleep 3600docker run -d --name contenedor-b alpine sleep 3600Compruebe que ambos pertenecen a bridge.
docker network inspect bridge --format '{{range .Containers}}{{.Name}} {{end}}'Deberían aparecer contenedor-a y contenedor-b.
Comprobar la comunicación mediante IP
Sección titulada «Comprobar la comunicación mediante IP»Obtenga la dirección IP de contenedor-b.
docker inspect contenedor-b --format '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}'Desde contenedor-a, envíe tráfico ICMP hacia esa dirección.
docker exec contenedor-a ping -c 2 172.17.0.XReemplace 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.
Comprobar la comunicación mediante nombre
Sección titulada «Comprobar la comunicación mediante nombre»Intente ahora utilizar el nombre del segundo contenedor.
docker exec contenedor-a ping -c 2 contenedor-bEl 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.
Limpiar los contenedores
Sección titulada «Limpiar los contenedores»docker rm -f contenedor-a contenedor-bCrear una red bridge personalizada
Sección titulada «Crear una red bridge personalizada»Las redes bridge creadas por el usuario permiten que los contenedores conectados a ellas utilicen nombres para encontrarse entre sí.
Cree una nueva red.
docker network create app-netCompruebe que existe.
docker network ls | grep app-netEjecutar GroceryListApp en la red
Sección titulada «Ejecutar GroceryListApp en la red»Inicie GroceryListApp y conecte el contenedor a app-net.
docker run -d \ --name app \ --network app-net \ -v grocerylist-data:/data \ grocerylistapp:1.0.0El comando no publica ningún puerto hacia el sistema anfitrión. El servicio queda disponible para otros contenedores que compartan la red app-net.
Agregar Nginx a la misma red
Sección titulada «Agregar Nginx a la misma red»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.
docker run -d \ --name web \ --network app-net \ -p 80:80 \ -v $(pwd)/default.conf:/etc/nginx/conf.d/default.conf:ro \ nginx:alpineSolo web publica un puerto hacia el sistema anfitrión.
La relación queda así.
Sistema anfitrión │ │ puerto 80 ▼ web Nginx │ │ app:8080 ▼ appGroceryListAppComprobar la resolución por nombre
Sección titulada «Comprobar la resolución por nombre»Solicite la ruta de salud.
curl http://localhost/healthLa 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.
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
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.
Inspeccionar la red
Sección titulada «Inspeccionar la red»docker network inspect app-net --format '{{range .Containers}}{{.Name}}: {{.IPv4Address}}{{"\n"}}{{end}}'Deberían aparecer app y web con las direcciones asignadas por Docker.
Limpiar los recursos
Sección titulada «Limpiar los recursos»docker rm -f app webdocker volume rm grocerylist-datadocker network rm app-netrm -f default.confAislar 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.
docker network create red-frontenddocker network create red-backendEjecute un contenedor en cada una.
docker run -d --name frontend --network red-frontend alpine sleep 3600docker run -d --name backend --network red-backend alpine sleep 3600Comprobar el aislamiento
Sección titulada «Comprobar el aislamiento»Desde frontend, intente alcanzar backend.
docker exec frontend ping -c 2 -W 2 backendLa prueba debería fallar porque ambos contenedores participan en redes diferentes.
La relación inicial es
red-frontend red-backend │ │ frontend backendNo existe una red compartida entre los dos.
Conectar un contenedor a una segunda red
Sección titulada «Conectar un contenedor a una segunda red»Un contenedor puede participar en varias redes simultáneamente.
Conecte frontend también a red-backend.
docker network connect red-backend frontendLa topología cambia.
red-frontend red-backend │ │ │ frontend ─────────────────┘ backendIntente nuevamente.
docker exec frontend ping -c 2 backendAhora la comunicación debería funcionar porque ambos contenedores comparten red-backend.
Compruebe las redes asociadas con frontend.
docker inspect frontend --format '{{range $k, $v := .NetworkSettings.Networks}}{{$k}} {{end}}'La salida esperada incluye
red-frontend red-backendLimpiar los recursos
Sección titulada «Limpiar los recursos»docker rm -f frontend backenddocker network rm red-frontend red-backendParte 2 - Redes con Docker Compose
Sección titulada «Parte 2 - Redes con Docker Compose»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.
cd grocerylistappmkdir -p nginxCree 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.
docker compose up -dIdentificar la red
Sección titulada «Identificar la red»docker network ls | grep grocerylistappEl laboratorio espera una red denominada
grocerylistapp_defaultCompruebe qué contenedores pertenecen a ella.
docker network inspect grocerylistapp_default --format '{{range .Containers}}{{.Name}} {{end}}'Los servicios app y web deberían aparecer conectados a la misma red.
Comprobar la comunicación por nombre
Sección titulada «Comprobar la comunicación por nombre»curl http://localhost/healthNginx 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.
Limpiar antes de continuar
Sección titulada «Limpiar antes de continuar»docker compose down -vDeclarar redes explícitas en Compose
Sección titulada «Declarar redes explícitas en Compose»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 monitorapp pertenece a ambas redes. web y monitor, en cambio, no comparten ninguna.
Inicie los servicios.
docker compose up -dComprobar la segmentación
Sección titulada «Comprobar la segmentación»web puede comunicarse con app
Sección titulada «web puede comunicarse con app»Ambos comparten frontend.
curl http://localhost/healthLa solicitud debería funcionar.
monitor puede comunicarse con app
Sección titulada «monitor puede comunicarse con app»Ambos comparten backend.
docker compose exec monitor ping -c 2 appLa prueba debería funcionar.
monitor no puede comunicarse con web
Sección titulada «monitor no puede comunicarse con web»No comparten ninguna red.
docker compose exec monitor ping -c 2 -W 2 webLa prueba debería fallar.
web no puede comunicarse con monitor
Sección titulada «web no puede comunicarse con monitor»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í.
Limpiar el proyecto
Sección titulada «Limpiar el proyecto»docker compose down -vCompruebe que las redes del proyecto ya no aparecen.
docker network ls | grep grocerylistappNo deberían aparecer resultados asociados con esas redes.
Lo que debe poder reconocer ahora
Sección titulada «Lo que debe poder reconocer ahora»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 |
Referencias
Sección titulada «Referencias»| 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