5 minute read

docker

Hace unos año escribí una entrada en este blog titulada: Enlazando contenedores docker donde hacía una primera aproximación al mecanismo que nos ofrece docker de que varios contenedores sean accesibles entre ellos por medio de su nombre (resolución de nombres). Este mecanismo funciona de manera distinta según la red docker donde estén conectados los contenedores. En este artículo vamos a introducir los distintos tipos de redes que nos ofrece docker y los distintos métodos de asociación o enlazado entre contenedores que tenemos a nuestra disposición.

Introducción a las redes en docker

Cuando instalamos docker tenemos las siguientes redes predefinidas:

# docker network ls
NETWORK ID          NAME                DRIVER              SCOPE
ec77cfd20583        bridge              bridge              local
69bb21378df5        host                host                local
089cc966eaeb        none                null                local
  • Por defecto los contenedores que creamos se conectan a la red de tipo bridge llamada bridge (por defecto el direccionamiento de esta red es 172.17.0.0/16). Los contenedores conectados a esta red que quieren exponer algún puerto al exterior tienen que usar la opción -p para mapear puertos.
  • Si conecto un contenedor a la red host, el contenedor será accesible usando la misma IP que tu máquina. Por ejemplo:

      # docker run -d --name mi_servidor --network host josedom24/aplicacionweb:v1
        
      # docker ps
      CONTAINER ID        IMAGE                        COMMAND                  CREATED             STATUS              PORTS               NAMES
      135c742af1ff        josedom24/aplicacionweb:v1   "/usr/sbin/apache2ct…"   3 seconds ago       Up 2 seconds                                  mi_servidor
    

    Podemos acceder directamente al puerto 80 del servidor para ver la página web.

  • La red none no configurará ninguna IP para el contenedor y no tiene acceso a la red externa ni a otros contenedores. Tiene la dirección loopback y se puede usar para ejecutar trabajos por lotes.

Nosotros podemos crear nuevas redes (redes definidas por el usuario), por ejemplo para crear una red de tipo bridge:

# docker network create mired

Y para ver las características de esta nueva red, podemos ejecutar:

# docker network inspect mired

Para crear un contenedor en esta red, ejecutamos:

# docker run -d --name mi_servidor --network mired -p 80:80 josedom24/aplicacionweb:v1

Dependiendo de la red que estemos usando (la red puente por defecto o una red definida por el usuario) el mecanismo de enlace entre contenedores será distinto.

Enlazando contenedores conectados a la red bridge por defecto

Esta manera en enlazar contenedores no está recomendada y esta obsoleta. Además el uso de contenedores conectados a la red por defecto no está recomendado en entornos de producción. Para realizar este tipo de enlace vamos a usar el flag --link:

Veamos un ejemplo, primero creamos un contenedor de mariadb:

# docker run -d --name servidor_mysql -e MYSQL_DATABASE=bd_wp -e MYSQL_USER=user_wp -e MYSQL_PASSWORD=asdasd -e MYSQL_ROOT_PASSWORD=asdasd mariadb

A continuación vamos a crear un nuevo contenedor, enlazado con el contenedor anterior:

# docker run -d --name servidor --link servidor_mysql:mariadb josedom24/aplicacionweb:v1

Para realizar la asociación entre contenedores hemos utilizado el parámetro --link, donde se indica el nombre del contenedor enlazado y un alias por el que nos podemos referir a él.

En este tipo de enlace tenemos dos características:

  • Se comparten las variables de entorno

    Las variables de entorno del primer contenedor son accesibles desde el segundo contenedor. Por cada asociación de contenedores, docker crea una serie de variables de entorno, en este caso, en el contenedor servidor, se crearán las siguientes variables, donde se utiliza el nombre del alias indicada en el parámetro --link:

      # docker exec servidor env
    
      ...
      MARIADB_PORT=tcp://172.17.0.2:3306
      MARIADB_PORT_3306_TCP=tcp://172.17.0.2:3306
      MARIADB_PORT_3306_TCP_ADDR=172.17.0.2
      MARIADB_PORT_3306_TCP_PORT=3306
      MARIADB_PORT_3306_TCP_PROTO=tcp
      MARIADB_NAME=/servidor/mariadb
      MARIADB_ENV_MYSQL_USER=user_wp
      MARIADB_ENV_MYSQL_PASSWORD=asdasd
      MARIADB_ENV_MYSQL_ROOT_PASSWORD=asdasd
      MARIADB_ENV_MYSQL_DATABASE=bd_wp
      MARIADB_ENV_GOSU_VERSION=1.10
      MARIADB_ENV_GPG_KEYS=177F4010FE56CA3336300305F1656F24C74CD1D8
      MARIADB_ENV_MARIADB_MAJOR=10.4
      MARIADB_ENV_MARIADB_VERSION=1:10.4.11+maria~bionic
      ...
    
  • Los contenedores son conocido por resolución estática

    Otro mecanismo que se realiza para permitir la comunicación entre contenedores asociados es modificar el fichero /etc/hosts para que tengamos resolución estática entre ellos. Podemos comprobarlo:

      # docker exec servidor cat /etc/hosts
      ...
      172.17.0.2	mariadb c76089892798 servidor_mysql
    

Enlazando contenedores conectados a una red definida por el usuario

En este caso vamos a definir una red de tipo bridge:

# docker network create mired

Y creamos los contenedores conectados a dicha red:

#  docker run -d --name servidor_mysql --network mired -e MYSQL_DATABASE=bd_wp -e MYSQL_USER=user_wp -e MYSQL_PASSWORD=asdasd -e MYSQL_ROOT_PASSWORD=asdasd mariadb

# docker run -d --name servidor --network mired josedom24/aplicacionweb:v1

En este caso no se comparten las variables de entorno, y la resolución de nombres de los contenedores se hace mediante un servidor dns que se ha creado en el gateway de la red que hemos creado:

# docker  exec -it servidor bash
root@a86f6d758eba:/# apt update && apt install dnsutils -y
...
root@a86f6d758eba:/# dig servidor_mysql
...
; ANSWER SECTION:
servidor_mysql.		600	IN	A	172.20.0.2
...
;; SERVER: 127.0.0.11#53(127.0.0.11)

root@a86f6d758eba:/# dig servidor_mysql.mired
...
; ANSWER SECTION:
servidor_mysql.mired.		600	IN	A	172.20.0.2

root@a86f6d758eba:/# dig servidor
...
; ANSWER SECTION:
servidor.	    	600	IN	A	172.20.0.3

Como vemos desde un contenedor se pueden resolver tanto los nombres de los servidores, como el FHQN formado por el nombre del contenedor y como nombre de dominio el nombre de la red a la que están conectados.

Instalación de wordpress en docker

Veamos un ejemplo, vamos a instalar wordpress usando dos contenedores enlazados: uno con la base de datos mariadb y otro con la aplicación wordpress.

Creamos una red de tipo bridge:

# docker network create red_wp

Creamos un contenedor desde la imagen mariadb con el nombre servidor_mysql, conectada a la red creada:

docker run -d --name servidor_mysql --network red_wp -e MYSQL_DATABASE=bd_wp -e MYSQL_USER=user_wp -e MYSQL_PASSWORD=asdasd -e MYSQL_ROOT_PASSWORD=asdasd mariadb

A continuación vamos a crear un nuevo contenedor, con el nombre servidor_wp, con el servidor web a partir de la imagen wordpress, conectada a la misma red y con las variables de entorno necesarias:

docker run -d --name servidor_wp --network red_wp -e WORDPRESS_DB_HOST=servidor_mysql -e WORDPRESS_DB_USER=user_wp -e WORDPRESS_DB_PASSWORD=asdasd -e WORDPRESS_DB_NAME=bd_wp -p 80:80  wordpress

La variable de entorno del contenedor wordpress WORDPRESS_DB_HOST la hemos inicializado con el nombre del contenedor de la base de datos, ya que como hemos explicado anteriormente, al estar conectado a la misma red los dos contenedores, este nombre se podrá resolver. Podemos acceder a la ip del servidor docker y comprobar la instalación de wordpress.

docker

Enlazando contenedores con docker-compose

Cuando trabajamos con escenarios donde necesitamos correr varios contenedores podemos utilizar docker-compose para gestionarlos. En el fichero docker-compose.yml vamos a definir el escenario. El programa docker-compose se debe ejecutar en el directorio donde este ese fichero. Por ejemplo para la ejecución de wordpress persistente podríamos tener un fichero con el siguiente contenido:

version: '3.1'

services:

  wordpress:
    container_name: servidor_wp
    image: wordpress
    restart: always
    environment:
      WORDPRESS_DB_HOST: db
      WORDPRESS_DB_USER: user_wp
      WORDPRESS_DB_PASSWORD: asdasd
      WORDPRESS_DB_NAME: bd_wp
    ports:
      - 80:80
    volumes:
      - /opt/wordpress:/var/www/html/wp-content

  db:
    container_name: servidor_mysql
    image: mariadb
    restart: always
    environment:
      MYSQL_DATABASE: bd_wp
      MYSQL_USER: user_wp
      MYSQL_PASSWORD: asdasd
      MYSQL_ROOT_PASSWORD: asdasd
    volumes:
      - /opt/mysql_wp:/var/lib/mysql

Cuando creamos un escenario con docker-compose se crea una nueva red definida por el usuario docker donde se conectan los contenedores, por lo tanto están enlazados, pero no comparten las variables de entorno (por esta razón hemos creado las variables de entorno al definir el contenedor de wordpress). Además tenemos resolución por dns que resuelve tanto el nombre del contendor (por ejemplo, servidor_mysql) como el alias (por ejemplo, db).

Para crear el escenario:

# docker-compose up -d
Creating network "dc_default" with the default driver
Creating servidor_wp    ... done
Creating servidor_mysql ... done

La primera imagen de este artículo está tomada de la siguiente página web: https://www.nuagenetworks.net/blog/docker-networking-overview/

Comments

Andrés Gorostidi Pulgar

Tengo una duda. Imaginemos que yo tenga un Centos con varias IPs, y que estoy corriendo varios dockers. En principio, cuando haga el port mapping al levantar el docker acceder por la IP principal al servicio que me brinde el docker (imaginemos un apache, corriendo en un docker, en el puerto 80). Y si ahora quiere levantar otro docker, corriendo en el mismo puerto, usando otra de las IPs del host ? Como le digo que usa una IP diferente del host y no la misma ? El proposito es tener en la misma maquina ambos dockers, ambos escuchando en el mismo puerto, con IPs diferentes…

Gracias!

José Domingo Muñoz

Hola Andrés, podrías ejecutar los siguientes comandos para ejecutar dos servidores web (he usado nginx en la prueba) y que cada uno responda en el puerto 80 de cada una de las direcciones ip que tiene la máquina:

docker run -d --name web1 -p 192.168.121.65:80:80 nginx
docker run -d --name web2 -p 192.168.122.79:80:80 nginx

Y comprobamos que están corriendo los dos contenedores:

docker ps -a
CONTAINER ID   IMAGE     COMMAND                  CREATED          STATUS          PORTS                       NAMES
4b330d9bc911   nginx     "/docker-entrypoint.…"   4 seconds ago    Up 3 seconds    192.168.122.79:80->80/tcp   web2
658b74e65ffe   nginx     "/docker-entrypoint.…"   21 seconds ago   Up 20 seconds   192.168.121.65:80->80/tcp   web1

Prueba a entrar en las distintas ip y verás que tiene acceso a cada uno de los servidores web. Un saludo

Emil

Hola. Estoy recién empezando con Docker y tengo una duda. En una maquina virtual tengo dos SO, Ubuntu Desktop y Ubuntu Server. En los dos sistemas he configurado una red interna para que se puedan comunicar. En Ubuntu Server, he instalado docker y mediante docker-compose he creado una imagen mysql, que en el archivo docker-compose.yml he puesto “ports: 3306:3306”, con la idea desde Ubuntu Desktop acceder a información al servidor. Todo parece estar bien, incluso cuando hago “sudo docker ps”, me aparece mysql funcionando y con el port 3306 configurado. Pero, desde el host (Ubuntu Desktop) intento verificar el funcionamiento, escribiendo en la terminal “sudo nmap -p- –open -n " o "nc -z4nv 3306", no funciona. El primer comando me devuelve el port 22 en lugar de 3306, y el segundo comando si escribo 22, si me reconoce la ip_guest. Cualquier ayuda me seria útil.

Gracias

José Domingo Muñoz

Hola Emil,

Intenta acceder desde el ubuntu Desktop al servidor de base de datos. Para ello, instala el cliente de mysql y ejecuta:

mysql -u usuario -p -h <ip_ubuntu_server>

Un saludo, espero que te sirva.

Gleiver Fndez. Harria.

Hola José, leyendo tu artículo me surge una duda. Como puedo mediante una archivo docker-compose.yml especificar que un servicio dentro de un determinado contenedor se emita por una determinada interfaz de red determinada asumiendo que en el host anfitrión tenga 2.el escenario que quiero interpretar en un archivo .yml de docker-compose es el siguiente: docker run –detach –volume mi-data:/data –network host –name X –restart unless-stopped imagen-servicio etho. El network lo puedo modificar y declarar directamente en el yml como un bridge pero la interfaz eth0 del host anfitrión como lo declaró??

José Domingo Muñoz

Hola Gleiver, He estado buscando por internet y por la documentación de docker y yo no he encontrado ninguna solución a tu problema.
Entiendo que cuando usas el tipo de red host en docker el contenedor está ofreciendo el servicio en el mismo host, y por lo tanto ofrece el servicio en todas las interfaces de red de la máquina.
Una posible solución que se me ocurre, y que no se si es válida para tu caso es usar el cortafuego para restringir el acceso al servicio desde una determina interfaz.
Siento no poder ayudar más. Un saludo

Leave a Comment

Your email address will not be published. Required fields are marked *

Loading...