angle-uparrow-clockwisearrow-counterclockwisearrow-down-uparrow-leftatcalendarcard-listchatcheckenvelopefolderhouseinfo-circlepencilpeoplepersonperson-fillperson-plusphoneplusquestion-circlesearchtagtrashx

Conectarse a un servicio en un host Docker desde un contenedor Docker

Cuando se utiliza el modo de red por defecto bridge , debe hacer que un servicio en el host Docker escuche (también) a docker0.

11 agosto 2022
En Docker
post main image
https://www.pexels.com/@vladbagacian/

Si tienes algún problema con Docker y buscas en Internet, es casi seguro que te tropieces con la pregunta ¿Cómo puedo conectarme a localhost? Lo que la gente quiere decir es: Cómo puedo conectarme a un servicio en el host Docker desde un contenedor Docker . Cuando empecé a usar Docker también tuve problemas con esto.

Estoy usando Linux, Ubuntu, y cuando finalmente host.docker.internal estuvo disponible para Linux mucha gente pensó que todos sus problemas habían terminado. Pero nada ha cambiado. El problema sigue siendo el mismo.

Hay muchos posts en internet sobre esto, y este es otro. Voy a hablar sólo del modo de red por defecto bridge , y no del modo 'network=host'.

Herramientas utilizadas

En este post utilizo algunas herramientas de línea de comandos, que quizás quieras instalar. Para obtener la última versión, o añadirlas al repositorio de tu máquina:

sudo apt update

netcat

Se utiliza para crear un servicio de escucha.

sudo apt install netcat

netstat

Se utiliza para mostrar los oyentes en el host.

sudo install net-tools

nmap

Se utiliza para escanear (en busca de puertos abiertos).

sudo apt install nmap

Servicios de escucha y redes

Vamos a crear un servicio de escucha en una ventana de terminal, escuchando el puerto 2345 en localhost, donde localhost es 127.0.0.1. Añadimos la opción -k (keep open), para evitar que netcat termine cuando se desconecte:

netcat -k -l 127.0.0.1 2345

Comprobar que tenemos un servicio netcat escuchando:

sudo netstat -tunlp

O, si tiene una lista muy larga:

sudo netstat -tunlp | grep 2345

Resultado:

Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name    
...
tcp        0      0 127.0.0.1:2345          0.0.0.0:*               LISTEN      2961983/netcat
...

Esto significa que netcat está escuchando en IP address 127.0.0.1 al puerto 2345.

Ahora inicia un contenedor Busybox Docker en otra ventana de terminal:

docker run -it --add-host=host.docker.internal:host-gateway --rm busybox

Y luego en el contenedor intenta acceder a nuestro servicio de escucha:

telnet host.docker.internal 2345

Resultado:

telnet: can't connect to remote host (172.17.0.1): Connection refused

Si telnet no responde, comprueba el UFW

Lo que también puede ocurrir es que telnet se cuelgue, y después de mucho tiempo responda con:

telnet: can't connect to remote host (172.17.0.1): Connection timed out

En este caso, la petición de Docker probablemente está siendo bloqueada por UFW (Uncomplicated Firewall). Puede comprobar si UFW está bloqueando la solicitud mirando las entradas de UFW en /var/log/syslog. En otra ventana de terminal, escriba lo siguiente:

tail -f /var/log/syslog | grep UFW

En el contenedor Docker , reinicie el comando telnet . Si UFW bloquea su solicitud, verá mensajes como:

Aug 11 09:48:05 myra kernel: [247791.035929] [UFW BLOCK] IN=docker0 OUT= PHYSIN=vethe3b7837 MAC=02:42:80:b6:f4:ea:02:42:ac:11:00:02:08:00 SRC=172.17.0.2 DST=172.17.0.1 LEN=60 TOS=0x00 PREC=0x00 TTL=64 ID=9274 DF PROTO=TCP SPT=35690 DPT=2345 WINDOW=64240 RES=0x00 SYN URGP=0

La información importante aquí es:

SRC=172.17.0.2   <--- SOURCE
DST=172.17.0.1   <--- DESTINATION
SPT=35690        <--- SOURCE PORT
DPT=2345         <--- DESTINATION PORT

Observe que el puerto 2345 está bloqueado por UFW. Usted puede hacer una de dos cosas aquí:

  • Desactivar UFW durante estas pruebas, o
  • Añadir una regla a UFW para permitir el acceso al puerto 2345:
    sudo ufw allow from 172.17.0.0/16 to any port 2345.
    Esto permite a las redes 172.17.x.y acceder al puerto 2345.

Por el momento te sugiero que desactives UFW. Después de hacer esto, telnet debería responder como se mencionó anteriormente:

telnet: can't connect to remote host (172.17.0.1): Connection refused

Ok, entonces ¿por qué se rechaza la conexión?

Para esto necesitas entender algunos fundamentos de la red. El servicio de escucha dice que está escuchando a 'Local Address': 127.0.0.1:2345. Y telnet en el contenedor Docker decía: can't connect to remote host (172.17.0.1).

Tenga en cuenta que 127.0.0.1 y 172.17.0.1 están en redes diferentes. Esto significa que nunca se puede establecer una conexión.

Desde el post 'How to Connect to Localhost Within a Docker Container', ver enlaces más abajo:

Una de las dificultades de este enfoque es que usted no puede ser capaz de conectarse a los servicios que se unen directamente a localhost. Tendrás que asegurarte de que tus servicios están escuchando conexiones en tu IP Docker bridge , así como en localhost y 127.0.0.1. De lo contrario, verá la conexión rechazada o errores similares dentro de su contenedor.

Exactamente. Esto significa que nuestro servicio de escucha no sólo debe escuchar a 127.0.0.1 sino también a 172.17.0.1.
Reiniciemos nuestro servicio de escucha y hagamos que escuche a 172.17.0.1:

netcat -l 172.17.0.1 2345

Inicie telnet de nuevo en el contenedor Docker :

telnet host.docker.internal 2345

Resultado:

Connected to host.docker.internal

¡Bingo! Esto significa que el servicio en el host Docker también debe escuchar a docker0. Puedes escribir algún texto y éste será reproducido por netcat.

Lo que también hemos aprendido aquí es que host.docker.internal NO es equivalente a localhost

host.docker.internal es sólo un nombre para docker0. Docker0 es una interfaz virtual, un bridge, creado por Docker, y host.docker.internal es el nombre (de dominio) del docker0 bridge. Todos los contenedores Docker están conectados al docker0 bridge por defecto. Aunque el IP address de docker0 suele ser 172.17.0.1, puede ser cualquiera del rango privado definido por RFC 1918. Por eso no debemos utilizar el IP address sino referirnos al docker0 como host.docker.internal.

      +-------------+        +-------------+
      | container A |        | container B |
      +-------------+        +-------------+
             |                      |
             |                      |
         +------------------------------+ 
    +----|           docker0            |---+
    |    +------------------------------+   |
    |                                       |
    |                  Host                 |
    |                                       |
    +---------------------------------------+
                         |
                      network
                         

De nuevo, ¿cómo puedo conectarme a un servicio en localhost desde un contenedor Docker ?

Cuando el servicio de escucha estaba escuchando en 127.0.0.1 no podíamos alcanzarlo desde nuestro contenedor docker. Pero cuando el servicio de escucha estaba escuchando en 172.17.0.1 podíamos conectarnos desde nuestro contenedor docker.

En otras palabras, la causa del problema es que el servicio en el host NO está escuchando en 172.17.0.1.

En Linux, la mayoría de los servicios pueden ser hechos para escuchar todas las interfaces, redes. Hacemos esto usando la dirección '0.0.0.0'. Podemos iniciar netcat con o sin IP address '0.0.0.0', es lo mismo:

netcat -l 2345

o,

netcat -l 0.0.0.0 2345

Comprobación de los puertos de escucha

sudo netstat -tunlp | grep 2345

Resultado:

Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name    
...
tcp        0      0 0.0.0.0:2345            0.0.0.0:*               LISTEN      1606615/netcat
...

Local Address '0.0.0.0' significa que escuchará todas las redes incluyendo 127.0.0.1 y 172.17.0.1.

Inicie telnet en el contenedor:

telnet host.docker.internal 2345

Resultado:

Connected to host.docker.internal

Como se esperaba, la conexión tuvo éxito.

Bien, pero ¿qué debo hacer para conectarme a un servicio en localhost desde un contenedor Docker ?

Se trata de asegurarse de que el servicio en localhost está (también) escuchando a 172.17.0.1, o docker0. En Ubuntu, el IP address de docker0 es 172.17.0.1, pero Docker dice que también puede ser elegido al azar, ver también arriba.

Esto significa que reiniciar Docker puede dar un docker0 IP address diferente, tal vez no hoy, pero tal vez en unos meses, o el próximo año(?). Como no podemos confiar en el docker0 IP address, la única otra opción es hacer que su servicio en el host escuche a '0.0.0.0'. Es posible establecer el bridge IP address y subnet, pero eso está fuera del alcance de este post.

Para acceder a MySQL en el host Docker desde un contenedor, puedes cambiar una línea en /etc/mysql/my.cnf:
From:

bind-address = 127.0.0.1

Para:

bind-address = 0.0.0.0

Tenga en cuenta que hay soluciones en Internet que hacen que el servicio en el host Docker escuche en el docker0 IP address. Se basan en el hecho de que este IP address no va a cambiar. A menos que sepas lo que estás haciendo no puedo recomendar esto.

Crear una red Docker separada para el servicio en el host

Puedes aislar el servicio en el host creando una red Docker para él, por ejemplo:

docker network create --driver=bridge --subnet=172.17.33.0/24 --gateway=172.17.33.10 my_subnet

Inicie el servicio en el host:

netcat -k -l 172.17.33.10 2345

A continuación, inicie el contenedor Docker :

docker run -it --add-host=my_host:172.17.33.10 --network=my_subnet --rm busybox

Y acceder al servicio desde el contenedor Docker :

telnet my_host 2345

Acceder a un servicio en el host Docker utilizando un Unix socket

Por último, existe una forma totalmente diferente de acceder a un servicio en el host Docker , pero depende de si el servicio lo soporta. Algunos servicios utilizan un 'socket unix' un archivo. Por ejemplo, MySQL utiliza el archivo:

/run/mysqld/mysqld.sock

Esto significa que también podemos acceder a MySQL desde un contenedor usando el mapeo de volumen, en Docker-Compose:

    volumes:
      # connect to mysql via unix socket 
      - /var/run/mysqld:/var/run/mysqld

Esto funciona bien. Tengo aplicaciones en el host y aplicaciones en el contenedor Docker , todas usando MariaDb en el host.

Docker IP addresses y reglas UFW

Esto es importante porque probablemente estés usando UFW para controlar el acceso a y desde la máquina. Como se mencionó anteriormente, cada vez que se crea un contenedor, el IP address puede cambiar y algunas reglas UFW ya no funcionarán.

Por defecto, Docker asigna un IP address a un contenedor de uno de los siguientes rangos de red para redes bridge :

  • 172.17.0.0/16
  • 172.18.0.0/16
  • 172.19.0.0/16
  • 172.20.0.0/14
  • 172.24.0.0/14
  • 172.28.0.0/14
  • 192.168.0.0/16

Esto significa que si especificamos una regla UFW con '172.17.0.0/16', esta regla ya no funcionará si Docker asigna la próxima vez un IP address de la red '192.168.0.0/16'.

Una forma de solucionar esto es especificar nosotros mismos los pools de direcciones por defecto para las redes Docker , como describí en el post 'Docker containers suddenly using 192.168.0.0/16 instead of 172.17.0.0/16: services lost', ver enlaces más abajo.

Podemos especificar pools de direcciones por defecto para nuestras redes Docker creando el archivo (no estaba allí):

/etc/docker/daemon.json

Este ejemplo le dice a Docker que use sólo IP address de la red 172.17.0.0/16:

{
	"default-address-pools":
	[
		{"base":"172.17.0.0/16","size":24}
	]
}

Escanee siempre sus puertos después de hacer cambios.

Después de hacer cambios, escanea siempre los puertos de tu máquina y del servidor de producción. Especialmente cuando usamos Docker necesitamos asegurarnos de que ningún puerto ha sido abierto por error.

Si queremos hacer esto nosotros mismos podemos utilizar el comando nmap . El problema es que los resultados dependen de la forma de conectarse a Internet. Tienes que comprobar el firewall en tu máquina y el firewall en tu punto de acceso a Internet. Además, el firewall de su proveedor de Internet puede bloquear las conexiones. La única manera de escanear su servidor de producción en busca de puertos abiertos es utilizar otro servidor (VPS) en la misma red, o en Internet, que no tenga restricciones de salida.

sudo apt install nmap

Un ejemplo de este comando para escanear los puertos 80-10000:

nmap -p 80-10000 www.example.com

También puede usar el IP address de su máquina de desarrollo, y ejecutar el nmap en esta máquina, pero encontré que los resultados no son correctos.

nmap -p 80-10000 <your-development-machine-ip-address>

Se mostró que el puerto 0.0.0.0:2345 estaba abierto. El escaneo desde otro ordenador de mi red local no mostraba esto.

Resumen

En este post he hablado de cómo se puede conectar a un servicio en un host Docker desde un contenedor Docker . Hay limitaciones pero la mayoría de las veces es posible. Ten en cuenta los Docker asignados IP addresses, pueden cambiar cada vez que se crea un contenedor. Y compruebe siempre sus sistemas para ver si hay puertos abiertos después de hacer cambios.

Enlaces / créditos

Change default docker0 bridge ip address
https://stackoverflow.com/questions/52225493/change-default-docker0-bridge-ip-address

Connecting Docker to localhost PostgreSQL on an Ubuntu 18.04 Host
https://gregtczap.com/blog/docker-postgres-ubuntu-localhost

Docker containers suddenly using 192.168.0.0/16 instead of 172.17.0.0/16: services lost
https://www.peterspython.com/en/blog/docker-containers-suddenly-using-192-168-0-0-16-instead-of-172-17-0-0-16-services-lost

From inside of a Docker container, how do I connect to the localhost of the machine?
https://stackoverflow.com/questions/24319662/from-inside-of-a-docker-container-how-do-i-connect-to-the-localhost-of-the-mach

How to Connect to Localhost Within a Docker Container
https://www.howtogeek.com/devops/how-to-connect-to-localhost-within-a-docker-container

What's the `docker0` there?
https://stackoverflow.com/questions/63948879/whats-the-docker0-there

Leer más

Docker

Deje un comentario

Comente de forma anónima o inicie sesión para comentar.

Comentarios (2)

Deje una respuesta.

Responda de forma anónima o inicie sesión para responder.

avatar

Спасибо! Мне очень помогла статья

avatar

thanks, only solution that actually worked!