La mayoría de aplicaciones estos día no corren de forma aislada y necesitan comunicarse con otros sistemas a través de la red. Si quisiéramos que un sitio web, un webservice, una base de datos o un servidor de caché en un contenedor de docker entonces necesitamos entender al menos los conceptos básicos de configuración de redes en contenedores docker.

Comencemos con un ejemplo simple, y obtengamos un servidor tomcat directo de DockerHub

docker run -d tomcat

Tomcat es un servidor de aplicaciones web cuya interfaz de usuario puede ser accedida a través del puerto 8080. Entonces, si instalamos Tomcat en nuestra computadora podríamos acceder en: http://localhost:8080

Pero en el caso de que tomcat este corriendo dentro de un contenedor docker, y lo iniciamos del mismo modo que en los ejemplos de post anteriores, podríamos comprobar que esta corriendo:

$ docker ps
> CONTAINER ID IMAGE   COMMAND          STATUS            PORTS    NAMES
> f51sd8434fec tomcat "catalina.sh run" Up About a minute 8080/tcp naive_einstein

Como la aplicación esta corriendo en modo daemon (con la opción -d) no vemos los logs en la consola de inmediato, pero de cualquier forma podemos verlos con el siguiente comando:

$ docker logs f51sd8434fec

Si no hubo errores, lo esperado sería ver algunos logs del inicio de tomcat concluyendo en que el servidor inicio exitosamente y es ahora accesible en el puerto 8080. No obstante si en nuestro navegador vamos a http://localhost:8080, no vamos a ser capaces de acceder a la interfaz de tomcat, la razón es que estamos tratando de acceder desde fuera del contenedor. En otras palabras solo podemos acceder a 8080 si nos conectamos con un comando en la consola y revisar adentro.

Para lograr que la instancia de tomcat sea accesible desde fuera del contenedor necesitamos agregar un par de cosas al a forma en como corremos el contenedor.

Primero necesitamos iniciarlo especificando el mapeo de puertos que queremos publicar con la bandera -p (–publish)

-p <puerto_host>:<puerto_contenedor>

Ademas docker también permite especificar la interfaz de red a utilizar con esta misma bandera

-p <ip>:<puerto_anfitrión>:<puerto_contenedor>

Entonces hay que detener el contenedor y reconstruirlo con los puertos indicados:

$ docker stop d51ad8634fac
$ docker run -d -p 8080:8080 tomcat

Después de esperar algunos segundos ya podemos acceder a http://localhost:8080 desde un navegador en nuestra maquina local.

Con este sencillo mapeo de puertos es suficiente para los casos de uso mas comunes en Docker. Ahora seremos capaces de instalar servicios o microservicios como contenedores docker y exponer sus puertos para habilitar la comunicación. De cualquier modo es bueno que profundicemos un poco mas sobre lo que sucede para que esto sea posible.

Redes de contenedores

Nos hemos conectado a una aplicación corriendo dentro de un contenedor. De hecho la conexión es de dos vías, por que anteriormente ejecutamos el comando apt-get para instalar algunos paquetes descargados de internet desde dentro del contenedor. Pero: ¿cómo es eso posible?

Si revisamos las interfaces de red en nuestra computadora podemos ver que hay una interfaz llamada docker0

$ ifconfig docker0
   docker0 Link encap:Ethernet HWaddr 12:31:db:d0:48:db
   inet addr:172.17.0.1 Bcast:0.0.0.0 Mask:255.255.0.0
   ...

La interfaz docker0 es creada por el docker daemon para poder conectarse con el contenedor. Ahora bien, podemos revisar las configuraciones de red de nuestro contenedor con el comando inspect.

$ docker inspect a480ebe42571

que imprime todas las configuraciones del contenedor en formato json. Para las configuraciones de red hay que fijarse en el objeto que pone “NetworkSettings”

{
...
   "NetworkSettings":{
      "Bridge":"",
      "Ports":{
         "8080/tcp":[
            {
               "HostIp":"0.0.0.0",
               "HostPort":"8080"
            }
         ]
      },
      "Gateway": "172.19.0.1",
      "IPAddress": "172.19.0.7",
      "IPPrefixLen":16
   }
...
}

Si quisiéramos filtrar la salida de las configuraciones para que nos muestre solo la dirección IP asignada al contenedor podemos usar la bandera –format para lograrlo así:

docker inspect --format '{{ .NetworkSettings.IPAddress }}' <container_id>

De esa salida podemos observar que el contendor tiene una direccion IP 172.19.0.7 y que se comunica con docker a través de la dirección 172.19.0.1. Esto significa que en el ejemplo anterior podíamos acceder al servidor tomcat aun sin configurar los puertos usando la dirección http://172.19.0.7 :8080, pero en la mayoría de los casos corremos el contenedor en un servidor y si queremos exponer los puertos con la dirección adecuada, por lo que necesitamos usar la bandera -p.

Por defecto, los contenedores están protegidos por el firewall del anfitrión y no abren ninguna ruta a sistemas externos. Esto puede cambiarse manipulando un poco con la bandera –network con las siguientes opciones:

  • bridge : Red a través del bridge por defecto de docker
  • none : No hay red
  • container : red unida con otro contenedor especificado
  • host : red de anfitrión (sin firewall)

Todas estas opciones pueden ser gestionadas con el comando docker network

$ docker network ls
NETWORK ID          NAME                           DRIVER              SCOPE
86f4403c1e05        bridge                         bridge              local
d030ab158e04        host                           host                local
2765cfb12659        none                           null                local

Si especificamos none como la red entonces no seremos capaces de conectarnos al contenedor y vice versa; El contenedor no tiene acceso al mundo exterior. la opción host hace que las interfaces del contenedor sean idénticas a las del anfitrión, comparten la misma IP así que todo lo que corre en el contenedor es visible desde afuera del mismo. Y finalmente la opción mas usada es la que viene por defecto: bridge, ya que nos permite controlar que vamos a exponer y que no, lo cual es mas seguro y accesible.

Mas sobre puertos de aplicación

Cuando echamos un vistazo al docker file de tomcat, vemos que a través del archivo también es posible exponer un puerto por defecto, usando la instrucción EXPOSE

EXPOSE 8080

Que le informa a los usuarios que puerto deberían exponer.

Pero ¿qué pasa si quisiéramos desentendernos del numero de puerto?. En el caso de que estemos usando un service discovery el número de puerto se vuelve irrelevante. Bien, pues docker tiene una bandera para asignarle un puerto automáticamente a nuestra aplicación:

docker run -d -P tomcat

Lo adivinaste, -P asigna un puerto libre a la aplicación que luego podemos averiguar corriendo:

$ docker port <container_id>
> 8080/tcp -> 0.0.0.0:32882

En donde podemos ver que el contenedor ha sido asignado al puerto 32882.

Y así, sin mas hemos repasado todos los conceptos básicos de redes usando contenedores docker.

Categorized in: