Introducción a Docker

Carlos Verdes
11 min readJun 9, 2015

El objetivo de este artículo es mostrar una visión global sobre qué ofrece Docker utilizando un ejemplo práctico: coger una aplicación JEE (.ear) y desplegarla dentro de un servidor Wildfly utilizando Docker.

Docker detrás de un proxy

En caso de estar detrás de un proxy es necesario configurar las variables de entorno Linux (http/s_proxy) en el fichero de perfil de usuario: /var/lib/boot2docker/profile.

sudo vi /var/lib/boot2docker/profileexport HTTP_PROXY=http://$your_proxy_host:$your_proxy_portexport HTTPS_PROXY=http://$your_proxy_host:$your_proxy_port

Una vez editado el fichero hay que reiniciar el servicio (a veces es necesario reiniciar la imagen entera):

sudo /etc/init.d/docker restart

Contenedores e imágenes

La idea de utilizar Docker es levantar un entorno donde poder lanzar nuestras aplicaciones. Cada instancia de dicho entorno es lo que se llama contenedor y se pueden tener levantados tantos como sean necesarios (cada contenedor en ejecución tiene una asignada ip dentro de Docker y puede publicar de 0 a n puertos).

Cuando tenemos un entorno estable que se quiera reutilizar en distintos escenarios es cuando se genera una imagen. Las imágenes son por tanto una captura estática de un contenedor. También podríamos decir que un contenedor es una instancia “viva” de una imagen.

Los contenedores se inician partiendo de una imagen como base de partida utilizando el comando “docker run”. Esto son los pasos que sigue Docker al iniciar un contenedor:

  1. Monta el sistema de ficheros de arranque: bootfs (lectura)
  2. Monta el sistema de ficheros de root: rootfs (lectura)
  3. Monta las n capas de imagen (lectura)
  4. Monta una capa por encima (lectura/escritura) → el contenedor

Todas las modificaciones de ficheros se realizan SOLO en la capa contenedor. Cuando se quiere guardar el estado de un contenedor para utilizarlo de base para otras aplicaciones se hace un “docker commit” → creando una imagen de ese contenedor.

Creando contenedor con Wildfly

En nuestro caso vamos a necesitar montar un contenedor que tenga Java y un servidor de aplicaciones, por lo tanto lo primero es bajarnos una imagen que cumpla con lo que necesitamos:

docker pull jboss/wildfly

Si nos fijamos en el Dockerfile de esta imagen veremos que monta las siguientes capas:

  1. fedora:20 → imagen base con distribución Linux Fedora
  2. jboss/base →crea un usuario/grupo jboss + otras utilidades
  3. jboss/base-jdk:7 → instala Java7
  4. jboss/wildfly → instala Wildfly

Una vez hecho el pull podemos comprobar que tenemos disponible nueva imagen en nuestro entorno:

docker images
...
REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE
jboss/wildfly latest 2ac466861ca1 11 weeks ago 951.3 MB

Ahora toca el turno de arrancar un contenedor con esta imagen y comprobar que tenemos disponible el servidor de aplicaciones:

docker run -d -p 5000:8080 --name "servidor-desa" jboss/wildfly
...
f63d52408a314df9c2b80840e8ad71bddd8cc2f0513b0f8f0914854f7911b915

Donde (ver diagrama abajo):

  • -d (daemon) → arranca el servidor en background
  • -p 5000:8080 → publica el puerto 8080 de Wildfly al puerto 5000 del host (en nuestro caso el host es nuestro equipo)
  • --name “servidor-desa” → define un alias para el contenedor
  • f63… → es el código del contenedor (el hostname dentro de Docker)

Con el comando “docker ps” podemos comprobar que efectivamente el servidor está en ejecución:

docker ps
...
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
f63d52408a31 jboss/wildfly:latest /opt/jboss/wildfly/ 2 days ago Up 3 seconds 0.0.0.0:5000->
8080/tcp servidor-desa

El contenedor se puede parar y levantar con los comandos “docker start” y “docker stop” (en nuestro ejemplo sería “docker start servidor-desa”).

Dado que tenemos el servidor levantado y publicado en el puerto 5000 podemos comprobar que efectivamente tenemos el servidor disponible desde un navegador:

http://localhost:5000

Nota importante: en entornos con boot2docker el contenedor de Docker corre en una maquina virtual (por tanto utiliza una ip diferente a localhost) por lo que es necesario lanzar el comando “boot2docker ip” desde consola para saber en que ip se está ejecutando Docker (en windows normalmente es la 192.168.59.103).

Personalizando el contenedor

Ahora que tenemos un servidor de Wildfly corriendo lo que vamos a hacer es personalizarlo añadiendo la consola de administración. Para ello es necesario añadir un argumento al lanzar el servidor y editar un fichero de usuarios para configurar el usuario administrador. En este paso vamos a aprender como crear una imagen nueva que cumpla con estos requisitos para después poder reutilizarla.

Cuando hacemos un “docker run” por defecto se lanza el comando que esté configurado en su Dockerfile, en el caso que nos ocupa el comando es:

/opt/jboss/wildfly/bin/standalone.sh -b 0.0.0.0

Para que Wildfly active la administración es necesario añadir un argumento más (-bmanagement 0.0.0.0), por lo que vamos a borrar nuestro contenedor y volver a lanzarlo activando la administración:

docker stop servidor-desa 
... --> contendor parado, ahora se puede borrar
docker rm servidor-desa
... --> una vez borrado levantamos nueva instancia
docker run -d --name “servidor-desa” -p 5001:9990 -p 5000:8080 jboss/wildfly /opt/jboss/wildfly/bin/standalone.sh -b 0.0.0.0 -bmanagement 0.0.0.0
260ce7024c7679bedeab4826716368d7e405788d1396e3ba2d8b1dedf307b2a5

Como se puede comprobar se ha publicado el puerto 9990 (el de admin) en el puerto 5001 del host (en este caso nuestra máquina) por lo que podemos comprobar que tenemos disponible la consola en nuestro navegador.

Sin embargo el servidor nos indica que no tenemos configurado el usuario de administración, por lo que tenemos dos opciones:

  1. Ejecutar una consola bash y lanzar a mano el script que indica (add-user.sh)
docker exec -t -i servidor-desa /bin/bash
...
./wildfly/bin/add-user.sh

2. Editar el fichero de usuarios en la creación del contenedor. Este paso se verá en otra sección: Creando una imagen desde un Dockerfile.

Con cualquiera de las dos opciones deberíamos tener ya la consola de administración funcionando con el usuario de administración configurado, se puede comprobar en la misma url:

Creando una imagen

Ahora que ya tenemos un contenedor basado en jboss/wildfly personalizado con la consola de administración llega el momento de hacer una imagen para que nuestros compañeros sólo tengan que lanzarla (evitando tener que hacer todos los pasos de configuración previos).

Lo primero es hacer un commit sobre el contenedor:

docker commit -a "Tu usuario" -m "Comentario" servidor-desa tu_usuario/wildfly-desa
...
4e99b7dcdf5465236b81afa5f04430a073543344addac2fd1e7df4ea94ae49f5

Donde:

  • -a → define el autor, aquí debes poner tu nombre
  • -m → es el comentario del commit (similar a git)
  • servidor-desa → es el identificador del contenedor (recuerda que es lo que pasamos en la creación del contenedor con el parámetro --name, sino puedes lanzar “docker ps” y comprobar el id del contenedor)
  • tu_usuario → es el usuario que hayas dado de alta en docker
  • wildfly-desa → es el nombre que quieras dar a tu imagen

Una vez terminado el commit puedes comprobar que tienes en local una nueva imagen lista para ser lanzada (y crear un contenedor nuevo con wildfly + administración):

docker images
REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE
tu_usuario/wildfly-desa latest 4e99b7dcdf54 4 minutes ago 951.4 MB

Como último paso para que tus compañeros puedan utilizar esta imagen hacemos un push al servidor:

docker push tu_usuario/wildfly-desa

Una vez termine el proceso puedes pedir a un compañero que lance el siguiente comando docker (debería bajarse tu imagen y lanzar el servidor).

Puedes comprobar con el siguiente ejemplo una imagen subida a Docker (usuario admin de Wildfly → admin/admin123):

docker run -d --name "servidor-desa" -p 5000:8080 -p 5001:9990 nosolojava/wildfly-desa /opt/jboss/wildfly/bin/standalone.sh -b 0.0.0.0 -bmanagement 0.0.0.0

Desplegando mi aplicación

Una vez que ya tenemos corriendo nuestro contenedor con Wildfly y publicados los servicios http y admin podemos lanzar nuestras aplicaciones JEE (.ear).

Para ello hay varias opciones:

  1. Utilizar la consola web (http://localhost:5001/)
  2. Utilizar eclipse (configurando un servidor JbossAS remoto)
  3. Copiar en la carpeta deployments del servidor

Como el objetivo de este artículo es ver las funcionalidades que ofrece Docker vamos a desarrollar el punto 3 → vamos a montar una unidad.

Montando una carpeta de despliegues

El servidor Wildfly despliega automáticamente las aplicaciones que se copien en la siguiente carpeta:

/opt/jboss/wildfly/standalone/deployments

Pero… como accedemos a la carpeta de un contenedor que corre dentro de Docker? La respuesta es montando una unidad de disco que relacione una carpeta del host (tu equipo) con una carpeta de tu contenedor.

En el ejemplo se utiliza Windows donde se mapea por defecto la carpeta c:\Users del host como /c/Users dentro de Docker. Por tanto vamos a crear la siguiente carpeta en nuestro equipo:

c:\Users\tuUsuarioWindows\despliegues

Comprobamos que tenemos acceso desde Docker:

cd /c/Users/tuUsuarioWindows/despliegues/

Y por último creamos un mapeo entre esta carpeta y la del contenedor de despliegues con el argumento -v:

docker stop servidor-desa
...
docker rm servidor-desa
...
docker run -d --name "servidor-desa" -p 5000:8080 -p 5001:9990 -v /c/Users/tuUsuarioWindows/despliegues:/opt/jboss/wildfly/standalone/deployments nosolojava/wildfly-desa /opt/jboss/wildfly/bin/standalone.sh -b 0.0.0.0 -bmanagement 0.0.0.0

Cuando arrancamos un contenedor con una unidad local la carpeta destino (en nuestro caso la carpeta deployments del Wildfly) se oculta utilizando sólo la carpeta local (en nuestro caso c:\Users\…). Esto tiene dos ventajas:

  1. No se modifica el contenedor → si reiniciamos el contenedor (sin mapear la unidad local) el contenido de esa carpeta será el mismo que la imagen original
  2. Podemos mover ficheros entre host y contenedor fácilmente (por ejemplo se pueden cargar/guardar datos de una base de datos)

Para comprobar que todo funciona correctamente hemos creado un .ear con una aplicación web simple (TestDockerEar1.ear) y la hemos copiado en la carpeta local de nuestro equipo.

Por un lado vemos que en nuestra carpeta ha aparecido un fichero nuevo, TestDockerEar1.ear.deployed… lo cual nos indica que Wildfly ha desplegado la aplicación:

Para comprobar que efectivamente está corriendo probamos la siguiente url:

http://localhost:5000/context_root/

Esto permite tener a varios desarrolladores trabajando en local con sus despliegues sin afectar a la imagen original, o en otro escenario probar una versión nueva de una aplicación y en caso de fallo desmontar la unidad (cogiendo la aplicación de la imagen origen). Esto mismo se podría aplicar para bases de datos, carpetas de configuración, recursos web, etc.

Comunicando contenedores

Una de las cosas más interesantes de Docker es la comunicación entre contenedores, en nuestro ejemplo vamos a crear otro contenedor con MongoDB y ver como enlazarlo nuestro contenedor con Wildfly.

Lo primero lanzamos un contenedor MongoDB (“mongo-desa”):

docker run --name mongo-desa -d mongo

Lo siguiente es volver a crear el contenedor de Wildfly pero con un enlace al contenedor de Mongo:

docker stop servidor-desa
docker rm servidor-desa
docker run -d --name "servidor-desa" -p 5000:8080 -p 5001:9990 -v /c/Users/tuUsuarioWindows/despliegues:/opt/jboss/wildfly/standalone/deployments --link mongo-desa:mongo nosolojava/wildfly-desa /opt/jboss/wildfly/bin/standalone.sh -b 0.0.0.0 -bmanagement 0.0.0.0

Cuando se crea un link entre contenedores el contenedor cliente (en este caso servidor-desa) tiene acceso a la ip:puerto/s del otro contenedor (los expuestos con EXPOSE en el Dockerfile) mediante diferentes métodos:

  • Mirando el fichero de /etc/hosts (solo la ip)
docker exec -t servidor-desa cat /etc/hosts
...
172.17.0.2 mongo b2ea564c3dd4 mongo-desa
  • Utilizando variables de entorno
docker exec -t servidor-desa env
...
MONGO_PORT=tcp://172.17.0.2:27017
MONGO_PORT_27017_TCP=tcp://172.17.0.2:27017
MONGO_PORT_27017_TCP_ADDR=172.17.0.2
MONGO_PORT_27017_TCP_PORT=27017
MONGO_PORT_27017_TCP_PROTO=tcp
MONGO_NAME=/servidor-desa/mongo

NOTA: la mejor forma de obtener la ip es mirando /etc/hosts pues si se reinicia el contendor origen (mongo-desa en el ejemplo) puede que cambie la ip y Docker sólo actualiza este fichero (las variables de entorno se quedan con el valor antiguo)

Con esta información se puede configurar una conexión a la base de datos utilizando:

  • host: “mongo” (el contenedor resolverá en el ejemplo a 172.17.0.2)
  • port: “$MONGO_PORT_27017_TCP_PORT” (en el ejemplo: 27017)

Una de las ventajas de este modelo es que el puerto 27017 de la base de datos es sólo visible para el contenedor de Wildfly, siendo imposible acceder desde fuera del contenedor de Docker. Otra ventaja es que si el contenedor origen cambia la configuración de sus puertos el contenedor cliente podría adaptar su configuración automáticamente.

Creando una imagen desde un Dockerfile

Se ha visto como levantar un contenedor sobre una imagen, como manipular el contenedor y guardarlo para crear otra imagen. En esta sección se va a ver como generar una imagen utilizando un fichero Dockerfile.

Un Dockerfile se puede entender como un script que levanta un contenedor a partir de una imagen, ejecuta ciertos pasos en el contenedor (configuración, instalación de aplicaciones, etc.) y guarda los cambios en una imagen nueva (utilizando el comando docker build).

En nuestro caso vamos a crear el servidor wildfly-desa anterior pero utilizando un Dockerfile.

Lo primero que hacemos es crear un fichero Dockerfile dentro de una carpeta visible desde Docker (en Windows dentro de c:\Users\tuUsuario\…).

Contenido del fichero:

FROM jboss/wildfly:latestRUN /opt/jboss/wildfly/bin/add-user.sh admin Admin123 --silentCMD ["/opt/jboss/wildfly/bin/standalone.sh", "-b", "0.0.0.0", "-bmanagement", "0.0.0.0"]

Pasos realizados:

  1. Bajar imagen de jboss/wildfly → instrucción FROM
  2. Ejecutar un batch para añadir usuario admin → instrucción RUN
  3. Configurar el startup.sh para arrancar el admin → instrucción CMD

NOTA: Como hemos configurado el CMD con el parámetro -bmanagement no necesitaremos añadir “/opt/jboss/wildfly/bin/standalone.sh -b 0.0.0.0 -bmanagement 0.0.0.0” al lanzar la nueva imagen.

Una vez creado el fichero le indicamos a docker que genere una nueva imagen:

cd /c/Users/tuUsuarioWindows/carpetaConDockerfile
docker build -t tuUsuarioDocker/nombreImagen:version
docker images
...
REPOSITORY TAG IMAGE ID...
tuUsuarioDocker/nombreImagen version bb9a5a653574

Por ejemplo el siguiente comando genera sobre el usuario nosolojava una imagen wildfly-admin con versión v2:

docker build -t nosolojava/wildfly-admin:v2 .

Si hemos subido la imagen a Docker (con docker push) podemos decir a nuestros compañeros que lancen la nueva versión de la imagen… Siguiendo el ejemplo anterior:

docker run -d --name "servidor-desa" -p 5000:8080 -p 5001:9990 -v /c/Users/tuUsuarioWindows/despliegues:/opt/jboss/wildfly/standalone/deployments --link mongo-desa:mongo nosolojava/wildfly-admin:v2

Automatizando la creación de imágenes

Docker permite enlazar nuestros repositorios Github o Bitbucket y crear automáticamente una imagen nueva.

En nuestro ejemplo vamos a utilizar el repositorio github “nosolojava/docker-wildfly-test”.

Dentro del repositorio git creamos dos ficheros:

1- Dockerfile

FROM nosolojava/wildfly-desa:v2

ADD TestDockerEar1.ear /opt/jboss/wildfly/standalone/deployments/

2- el .ear a copiar (en nuestro ejemplo TestDockerEar1)

En este caso en lugar de hacer ejecutar el comando “docker build” le indicamos a Docker que enlace con el repositorio de github. Para ello hay que configurar la cuenta de github en el perfil de Docker y dar acceso desde Github.

Una vez hecho entramos en la siguiente url:

https://registry.hub.docker.com/builds/add/

Y seleccionamos el repositorio:

Y por último esperar a que Docker haga la “build” por nosotros:

A partir de este momento cada vez que actualicemos en Github Docker hará una “build” automáticamente. Por último se puede añadir un Webhook para que cuando haya una nueva versión de la imagen se notifique al resto de colaboradores (entre otras utilidades).

Después de unos minutos puedes comprobar en Docker que está disponible la nueva imagen (además si tienes un readme.me en Git también actualiza en Docker la pestaña de información):

Se puede comprobar que ha ido bien lanzando el siguiente Docker:

docker run -d -p 5010:8080 nosolojava/docker-wildfly-test 

… y ver como está en ejecución nuestro .ear en el puerto 5010:

--

--