En una configuración de varios contenedores, los servicios que se ejecutan en los contenedores se comunican entre sí a través de una red común. En la misma configuración, algunos contenedores también interactúan con el mundo exterior.
Esta comunicación interna y externa se maneja con puertos expuestos y publicados en Docker respectivamente.
En este tutorial, hablaré sobre el manejo de puertos en Docker. Continuaré con la diferencia entre exponer y publicar puertos, por qué se usan y cómo usarlos.
Exponer vs publicar un puerto en Docker
Hay dos formas de manejar los puertos en Docker:exponer los puertos y publicar los puertos.
Exponer un puerto simplemente significa informar a los demás en qué puerto escuchará la aplicación en contenedor o aceptará conexiones. Esto es para comunicarse con otros contenedores, no con el mundo exterior.
Publicar un puerto es más como mapear los puertos de un contenedor con los puertos del host. De esta manera, el contenedor puede comunicarse con sistemas externos, el mundo real, Internet.
Estos gráficos te ayudarán a entender.
Verá, ¿cómo se asigna el puerto 80 del contenedor SERVIDOR al puerto 80 del sistema host? De esta forma, el contenedor puede comunicarse con el mundo exterior utilizando la dirección IP pública del sistema host.
A los puertos expuestos, por otro lado, no se puede acceder directamente desde fuera del mundo del contenedor.
Para recordar:
- Los puertos expuestos se utilizan para la comunicación interna del contenedor, dentro del mundo del contenedor.
- Los puertos publicados se utilizan para comunicarse con sistemas fuera del mundo de los contenedores.
Exponiendo puertos en Docker
En primer lugar, exponer un puerto no es estrictamente necesario. ¿Por qué? Porque la mayoría de las imágenes acoplables que usa en su configuración ya tienen un puerto predeterminado expuesto en su configuración.
Por ejemplo, una aplicación front-end en contenedor puede comunicarse con una base de datos MariaDB simplemente especificando la IP del contenedor y el puerto en el que MariaDB acepta conexiones (predeterminado 3306).
La exposición ayuda en una situación en la que no se utilizan los valores predeterminados, como en este caso si MariaDB no aceptara conexiones en el puerto 3306, se debe mencionar un puerto alternativo exponiéndolo.
Hay dos formas de exponer un puerto:
- Uso de
EXPOSE
Instrucción de Dockerfile. - Usando
--expose
con docker CLI oexpose
clave en docker-compose.
Es hora de profundizar en ambos.
Método 1:exponer puertos a través de Dockerfile
Puede agregar una instrucción simple en su Dockerfile para que otros sepan en qué puerto aceptará conexiones su aplicación.
Sobre esta instrucción, lo que debe saber es lo siguiente:-
EXPOSE
no agregue capas adicionales a la imagen acoplable resultante. Solo agrega metadatos.EXPOSE
es una forma de documentar su puerto de aplicación. El único efecto que tiene es en términos de legibilidad o comprensión de la aplicación.
Puede ver cómo funciona la exposición con una imagen de contenedor simple que he creado solo para este propósito. Esta imagen no hace nada .
Tira de la imagen.
docker pull debdutdeb/expose-demo:v1
Esta imagen expone un total de cuatro puertos, liste la imagen usando el siguiente comando.
docker image ls --filter=reference=debdutdeb/expose-demo:v1
Eche un vistazo al SIZE
columna, dirá 0 bytes.
➟ docker image ls --filter=reference=debdutdeb/expose-demo:v1
REPOSITORY TAG IMAGE ID CREATED SIZE
debdutdeb/expose-demo v1 ad3d8ffa9bfe N/A 0B
La razón es simple, no hay capas en esta imagen, todas las instrucciones de exposición agregadas eran metadatos, no hay capas reales.
También puede obtener el número de capas disponibles usando el siguiente comando:-
docker image inspect -f '{{len .RootFS.Layers}}' debdutdeb/expose-demo:v1
Debería ver una salida como esta:-
➟ docker image inspect -f '{{len .RootFS.Layers}}' debdutdeb/expose-demo:v1
0
Como dije anteriormente, los puertos expuestos se agregan como metadatos de imagen, para que sepamos qué puertos está usando la aplicación.
¿Cómo ves esa información sin mirar un Dockerfile? Necesitas inspeccionar la imagen. Para ser más específico, vea el comando a continuación, puede usar un comando similar en cualquier imagen para enumerar los puertos expuestos.
Estoy usando mi imagen de muestra para la demostración.
docker image inspect -f \
'{{range $exposed, $_ := .Config.ExposedPorts}}{{printf "%s\n" $exposed}}{{end}}' \
debdutdeb/expose-demo:v1
Salida de muestra:
➟ docker image inspect -f '{{range $exposed, $_ := .Config.ExposedPorts}}{{printf "%s\n" $exposed}}{{end}}' debdutdeb/expose-demo:v1
443/tcp
80/tcp
8080/tcp
9090/tcp
También puede comparar esta imagen, que expone un par de puertos, con debdutdeb/noexpose-demo:v1
imagen, eso no está exponiendo ningún puerto. Ejecute el mismo conjunto de comandos en esa imagen y observe la diferencia.
Método 2:exponer puertos a través de CLI o docker-compose
A veces, los desarrolladores de aplicaciones evitan incluir un EXPOSE
extra instrucción en su Dockerfile.
En tal caso, para asegurarse de que otros contenedores (a través de la API de la ventana acoplable) puedan detectar fácilmente el puerto en uso, puede exponer varios puertos después de la compilación, como parte del proceso de implementación.
Elija el método imperativo, es decir, la CLI, o el método declarativo, es decir, componer archivos.
Método CLI
En este método, al crear un contenedor, todo lo que tiene que hacer es usar --expose
opción (tantas veces como sea necesario) con el número de puerto y, opcionalmente, el protocolo con un /
. Aquí hay un ejemplo:-
docker container run \
--expose 80 \
--expose 90 \
--expose 70/udp \
-d --name port-expose busybox:latest sleep 1d
Estoy usando el busybox
imagen que no expone ningún puerto por defecto.
Método de composición de archivos
Si está utilizando un archivo de redacción, puede agregar una matriz expose
en la definición del servicio. Puede convertir la implementación anterior en un archivo compuesto así:-
version: "3.7"
services:
PortExpose:
image: busybox
command: sleep 1d
container_name: port-expose
expose:
- 80
- 90
- 70/udp
Una vez que tenga el contenedor en ejecución, al igual que antes, puede inspeccionarlo para saber qué puertos están expuestos. El comando se ve similar.
docker container inspect -f \
'{{range $exposed, $_ := .NetworkSettings.Ports}}
{{printf "%s\n" $exposed}}{{end}}' \
port-expose
Salida de muestra:-
➟ docker container inspect -f '{{range $exposed, $_ := .NetworkSettings.Ports}}{{printf "%s\n" $exposed}}{{end}}' port-expose
70/udp
80/tcp
90/tcp
Publicar un puerto en Docker
La publicación de puertos es sinónimo de reenvío de puertos, donde las solicitudes de una conexión entrante en un puerto público se reenvían al puerto del contenedor.
De manera similar, las respuestas enviadas desde el contenedor a través de su puerto se envían al cliente reenviando el tráfico al puerto especificado en el espacio del puerto del host.
Hay dos formas de publicar un puerto, una a través de la CLI y otra usando un archivo de composición. Ambos métodos también tienen una sintaxis larga y una sintaxis corta.
Método 1:publicar puertos mediante el comando Docker
Las dos sintaxis son las siguientes:
-p [optional_host_ip]:[host_port]:[container_port]/[optional_protocol]
--publish target=[container_port],published=[optional_host_ip]:[host_port],protocol=[optional_protocol]
Para la IP de host opcional, puede usar cualquier dirección IP asociada con cualquiera de las NIC. Si se omite la IP, Docker enlazará el puerto con todas las direcciones IP disponibles.
Vas a usar más el primero. El segundo es más legible. Veamos un ejemplo usando un contenedor nginx. Ejecute el siguiente comando:-
docker container run --rm --name nginx \
--publish target=80,published=127.0.0.1:8081,protocol=tcp \
-d nginx
Con este comando, simplemente estoy vinculando el puerto 80 del contenedor al puerto 8081 de mi host en localhost. Ahora, si se dirige a http://localhost:8081, verá nginx ejecutándose.
El comando anterior se puede convertir fácilmente a la forma más corta así
docker container run --rm --name nginx \
-p 80:127.0.0.1:8081/tcp -d nginx
Aunque es más corto, es más difícil de leer.
También puedes usar -p
o --publish
varias veces para publicar varios puertos.
Método 2:publicar un puerto a través de un archivo de composición
Para publicar un puerto usando un archivo de redacción, necesitará una matriz llamada ports
en la definición del servicio. Esta matriz puede ser una lista de cadenas que se parece a la sintaxis corta de la CLI, o puede usar una lista de objetos que es similar a la sintaxis larga.
Si tuviera que convertir la implementación anterior de nginx usando un archivo de redacción con una matriz de cadenas para la sección de puertos, se vería así:-
version: "3.7"
services:
Nginx:
image: nginx
container_name: nginx
ports:
- 80:127.0.0.1:8081/tcp
Permítame mostrarle también cómo usar la sintaxis de matriz de objetos.
version: "3.7"
services:
Nginx:
image: nginx
container_name: nginx
ports:
- target: 80
published: 127.0.0.1:8081
protocol: tcp
Para ver la lista de todos los puertos publicados, puede inspeccionar el contenedor así:
docker container inspect -f '{{range $container, $host := .NetworkSettings.Ports}}{{printf "%s -> %s\n" $container $host}}{{end}}' nginx
Si lo ejecuta, verá el siguiente resultado:-
➟ docker container inspect -f '{{range $container, $host := .NetworkSettings.Ports}}{{printf "%s -> %s\n" $container $host}}{{end}}' nginx
80/tcp -> [{127.0.0.1 8081}]
Hay otra forma más fácil de listar los puertos publicados, usando el docker container port
comando.
docker container port nginx
Salida de ejemplo:-
➟ docker container port nginx
80/tcp -> 127.0.0.1:8081
¿Cuándo exponer un puerto y cuándo publicarlo?
Esta es una pregunta justa. Se supone que exponer y publicar no son competidores. Cada uno tiene un propósito diferente. Si es el desarrollador de una imagen, expondrá los puertos para que el usuario pueda estar más seguro de dónde intentar una conexión. Por otro lado, si está utilizando una imagen y necesita ponerla a disposición del mundo exterior, publicará los puertos necesarios.
Espero que este artículo te haya sido útil. Si tiene alguna pregunta, hágamelo saber en los comentarios a continuación.