Curso Jakarta EE 9 (10). Docker (3): Desarrollo con contenedores.

logo Jakarta EE

En los dos capítulos anteriores hemos realizado un breve repaso al funcionamiento de Docker. En el contenido restante de este bloque aplicaremos lo aprendido en nuestro día a día como desarrolladores de aplicaciones Jakarta EE. Empecemos aprendiendo cómo desplegar proyectos en un WildFly “dockerizado” desde IntelliJ y Eclipse.

>>>> ÍNDICE <<<<

“Dockerizando” WildFly

Ya he incidido con anterioridad en la importancia de realizar el desarrollo y las pruebas automáticas en un entorno de ejecución lo más parecido posible al entorno de explotación final de la aplicación. Esto incluye las mismas versiones de Java, WildFly y cualquier otro servicio o librería externa que necesitemos. Docker elimina el dolor que supone la configuración y mantenimiento de este entorno en múltiples equipos distintos (el equipo de cada desarrollador, el servidor de integración continúa, el servidor para pruebas por parte de QA…), mediante la definición de imágenes portables y la configuración de entornos de contenedores con Docker Compose.

En este punto del curso, solo necesitamos un servidor WildFly compatible con Jakarta EE 9. En Docker Hub encontraremos las imágenes oficiales proporcionadas por JBoss, pero solo están disponibles las versiones finales y  además suelen publicarse con cierto retraso. No tenemos las betas y, lo que es peor, tampoco las ediciones preview que en estos momentos son las únicas compatibles con Jakarta EE 9 -recordemos, el namespace jakarta en lugar de javax-. Toca remangarse y crear una imagen adecuada a nuestras necesidades con un fichero Dockerfile.

Voy a tomar como referencia la imagen oficial de WildFly, disponible en GitHub, y su imagen base, jboss/base. A continuación, defino el Dockerfile paso a paso.

La imagen base es la oficial para la última versión de la distribución Linux CentOS de Red Hat.

FROM centos:centos8 

USER root

RUN echo "[AdoptOpenJDK]" > /etc/yum.repos.d/adoptopenjdk.repo \
 && echo "name=AdoptOpenJDK" >> /etc/yum.repos.d/adoptopenjdk.repo \
 && echo "baseurl=https://adoptopenjdk.jfrog.io/adoptopenjdk/rpm/rhel/7/$(uname -m)" >> /etc/yum.repos.d/adoptopenjdk.repo \
 && echo "enabled=1" >> /etc/yum.repos.d/adoptopenjdk.repo \
 && echo "gpgcheck=1" >> /etc/yum.repos.d/adoptopenjdk.repo \
 && echo "gpgkey=https://adoptopenjdk.jfrog.io/adoptopenjdk/api/gpg/key/public" >> /etc/yum.repos.d/adoptopenjdk.repo

RUN yum update -y && yum -y install adoptopenjdk-11-openj9-jre

Empezamos instalando como ROOT la misma versión y empaquetado de OpenJDK que venimos usando desde el inicio del curso. Para ello, añadimos el repositorio oficial en el que se encuentra el correspondiente paquete en formato rpm, y procedemos a su instalación con yum. Solo necesitamos la JRE y no la JDK completa, así que instalamos la primera para ahorrar tiempo y espacio porque su tamaño es menor.

RUN groupadd -r jboss -g 1000 && useradd -u 1000 -r -g jboss -m -d /opt/jboss -s /sbin/nologin -c "JBoss user" jboss && \
    chmod 755 /opt/jboss 

Se crea el usuario jboss para lanzar WildFly (el comando es el mismo que el usado en la imagen jboss/base). Por motivos de seguridad, es una práctica más que recomendable que en Linux los servicios se ejecuten usando usuarios específicos con los permisos mínimos imprescindibles. Y, desde luego, nunca debemos usar ROOT, recomendación que también tendremos en cuenta al ejecutar cualquier contenedor.

ENV WILDFLY_VERSION 22.0.0.Final
ENV WILDFLY_NAME  wildfly-preview
ENV JBOSS_HOME /opt/jboss/wildfly

RUN cd $HOME \
    && curl -O https://download.jboss.org/wildfly/$WILDFLY_VERSION/$WILDFLY_NAME-$WILDFLY_VERSION.tar.gz \
    && tar xf $WILDFLY_NAME-$WILDFLY_VERSION.tar.gz \
    && mv $HOME/$WILDFLY_NAME-$WILDFLY_VERSION $JBOSS_HOME \
    && rm $WILDFLY_NAME-$WILDFLY_VERSION.tar.gz \
    && chown -R jboss:0 ${JBOSS_HOME} \
    && chmod -R g+rw ${JBOSS_HOME}

Este bloque instala WildFly, tarea que en realidad consiste en descargar el fichero con los binarios, descomprimirlo en /opt/jboss/wildfly y configurar los permisos adecuados. Se han definido variables para los parámetros que pueden cambiar en la url de descarga con la finalidad de facilitar la actualización del Dockerfile si queremos crear una imagen para otra versión de WildFly. Asimismo, se establece la variable de entorno JBOSS_HOME.

#Arquillian Cube uses ss command 
RUN yum -y install iproute 

Esta orden parece un poco extraña. Arquillian Cube, extensión de Arquillian para Docker que veremos en el próximo capítulo, recurre al comando ss para comprobar el arranque de los servicios. De nuevo, usamos yum e instalamos el paquete que lo incluye.

# Ensure signals are forwarded to the JVM process correctly for graceful shutdown 
ENV LAUNCH_JBOSS_IN_BACKGROUND true 

La anterior variable de entorno asegura la correcta detención de WildFly.

USER jboss

ENV ADMIN_USER admin
ENV ADMIN_PASS admin

RUN $JBOSS_HOME/bin/add-user.sh -u $ADMIN_USER -p $ADMIN_PASS -g admin --silent

Cambiamos al usuario regular jboss para ejecutar las instrucciones restantes. Tenemos que crear un usuario de la consola de administración para desplegar artefactos con la API de administración (lo requiere Arquillian).

EXPOSE 8080 9990 8787 

Se indican los puertos que vamos a usar desde fuera. No obstante, tendremos que declarar su redirección o “binding” cada vez que se cree un contenedor.

CMD ["/opt/jboss/wildfly/bin/standalone.sh", "-b", "0.0.0.0", "-bmanagement", "0.0.0.0", "--debug", "*:8787"] 

El comando que se lanzará al iniciar el contenedor. Ejecuta WildFly permitiendo la conexión remota desde cualquier dirección IP (“0.0.0.0”) tanto al servidor (-b) web en el que estarán disponibles las aplicaciones que despleguemos como a la API de administración (-bmanagement). También se activa el modo de depuración para conectar un debugger remoto. No hace falta utilizar -Djboss.socket.binding.port-offset para cambiar los puertos predeterminados, pues esto lo haremos cuando se cree el contenedor. Toda la sentencia para arrancar el servidor ya la estudiamos en el capítulo dedicado a Arquillian.

Construimos la imagen de la forma habitual. La primera vez tardará un buen rato porque tendrá que descargarse e instalar WildFly y OpenJDK. Ambos suman unos 350 mb.

$ docker image build -t personalBudget/wildfly-test-debug .

Ahora lanzamos un contenedor desechable. Atención a la redirección de los puertos.

$ docker run -d --rm -p 8989:8080 -p 10090:9990 -p 8888:8787  onbudget/wildfly-test-debug

Probemos nuestra creación lanzando las pruebas que implementamos en su momento con el perfil de Maven remoto (arq-wildfly-remote) utilizando la siguiente configuración. Todas las pruebas deberían ser exitosas, y podemos depurarlas utilizando una configuración remota al puerto 8888 que también vimos en su momento.

<container qualifier="wildfly_remote">
    <configuration>
        <property name="managementPort">10090</property>
        <property name="port">8989</property>
        <property name="username">admin</property>
        <property name="password">admin</property>
    </configuration>
</container>

Desarrollo

Como programadores, nos interesa utilizar la imagen como parte de nuestro proceso de desarrollo e ir probando y depurando en el servidor, directamente desde nuestro IDE, el proyecto a medida que vamos codificándolo.

Recuperemos el proyecto hello. Muestra una página web estática en la raíz de la url de la aplicación y tiene un servlet. Veamos cómo desplegarlo con IntelliJ y Eclipse usando un contenedor.

IntelliJ

Empecemos creamos una configuración de ejecución con la opción de menú Run->Edit Configurations… y abriendo con el botón “+” que aparece arriba a la izquierda un listado.

Tenemos el siguiente formulario. Algunas opciones permanecen ocultas y hay que mostrarlas pulsando “Modify”.

  • La ubicación del fichero Dockerfile (está en la raíz del proyecto).
  • La asignación o binding de puertos. En la captura, he usado los predeterminados, y he incluido el puerto para depurar.
  • Un volumen para enlazar la carpeta deployments de WildFly dentro del contenedor con una carpeta física en el equipo anfitrión. En ella debe encontrarse el artefacto .war generado por IntelliJ.

Por omisión, IntelliJ construye el artefacto en la carpeta target tal y como hace Maven. Prefiero evitar publicar todo el directorio completo en WildFly y configurar IntelliJ para usar otra carpeta, por ejemplo /target/docker, tal y como se puede ver en la anterior captura de pantalla. Esto requiere de una configuración adicional accesible desde la opción de menú File->Project Structure….

En la sección del menú de la izquierda llamada “Artifacts”, para el artefacto “hello:war” se ha indicado la carpeta en la que se generará con el campo “Output Directory”. El artefacto solo se crea\actualiza cuando se pulsa la opción Build->Build Artifacts…, aunque podemos incluir esta acción como parte del proceso de construcción del proyecto (“Build Project”) cuya ejecución es más accesible porque cuenta con el botón de icono “martillo” y el atajo de teclado “Control + F9”. Por este motivo, he marcado el checkbox “Include in project build”.

Ya lo tenemos todo listo y podemos lanzar el contenedor. Si fuera necesario, la imagen se construirá de forma automática.

Probamos que todo ha ido bien comprobando nuestra aplicación en la dirección habitual (http://localhost:8080/hello/).

La vista de servicios (Alt+8) nos permite usar Docker sin salir del IDE. Si seleccionamos nuestro contenedor, veremos la configuración y su log, incluso podemos navegar por su sistema de archivos.


Nota: Si cerramos IntelliJ, el contenedor seguirá en ejecución.

La imagen ya está configurada para ejecutar WildFly en modo depuración y en su configuración en IntelliJ se ha publicado el puerto correspondiente (8787), así que para depurar tan solo hay que crear una configuración de depuración estándar. Esto ya lo hicimos al final del capítulo seis.

Estamos asumiendo que el servidor está disponible en el momento en el que lanzamos la depuración desde IntelliJ. Pero podemos asegurarlo añadiendo el arranque del contenedor como una tarea a realizar en la sección “Before launch” del formulario anterior.

Si hacemos esto último, en la configuración basada en Dockerfile que he llamado “WildFly 22 Preview” no hay que exponer el puerto 8787 del contenedor.

Eclipse

En este IDE, primero tenemos que crear la misma configuración de ejecución que vimos en el capítulo 3. Cuando ya la tengamos, abrimos la configuración del servidor y hacemos lo siguiente.

  • Marcar en “Server behaviour” la opción “Server Lifecycle is externally managed” para que Eclipse no se encargue de arrancar y detener el servidor.
  • En “Deployment Scanners”, desmarcar los dos checkbox. El despliegue lo haremos bajo demanda generando el artefacto cuando queramos.
  • Como consecuencia de la indicación anterior, en “Publishing” seleccionamos la opción “Never publish automatically”.
  • En la sección llamada “Server Ports”, cambiamos los puertos por omisión por los que vamos a exponer hacia fuera del contenedor con WildFly. En el ejemplo no los he modificado.

Esta pantalla tiene una segunda pestaña en la que indicaremos la carpeta en la que Eclipse pone el artefacto war para que sea desplegado por el servidor. Debemos usar un directorio que enlazaremos mediante un volumen con la carpeta deployments del WildFly instalado dentro del contenedor. Marcamos la opción “Use a custom deploy folder” para habilitar los selectores de las carpetas. En el ejemplo, estoy usando el directorio /opt/CURSO/CODE/hello/target/docker .

Guardamos los cambios (Control+S). Ya podemos utilizarla para desplegar nuestra aplicación, siempre y cuando esté en ejecución un contenedor que verifique la configuración esperada. Creamos la imagen correspondiente y un contenedor siguiendo el procedimiento habitual. Prestemos especial atención al volumen: debe enlazar la carpeta de despliegue establecida en la pantalla anterior con el directorio /opt/jboss/wildfly/standalone/deployments del contenedor.

docker image build -t wildfly-22-eclipse .
docker run -d -p 8080:8080 -p 9990:9990 -p 8787:8787 -v /opt/CURSO/CODE/hello/target/docker:/opt/jboss/wildfly/standalone/deployments --name wildfly_22_eclipse wildfly-22-eclipse

El artefacto se generará cuando ejecutemos la configuración. Una vez en marcha, para actualizar la aplicación desplegada usaremos la acción “Full Publish”. Está en el menú contextual del proyecto dentro de la vista del servidor, tal y como se aprecia en la siguiente imagen.

También se puede utilizar el atajo de teclado “Control+Shift+y” y pulsando “f” inmediatamente después. Recomiendo personalizarlo con una combinación más sencilla.

La configuración realizada nos permite depurar.

Es posible que cuando la ejecución del hilo llegue al punto de ruptura, Eclipse no sea capaz de mostrar el fichero con el código. ¡Que no cunda el pánico! En el visor en el que debería aparecer, podemos añadir una “fuente” para que Eclipse pueda mostrarlo. El procedimiento a seguir, en el siguiente video.

Para trabajar con Docker desde Eclipse recomiendo usar el plugin “Eclipse Docker Tooling“, disponible en el Marketplace.

Entre otras características, podemos gestionar imágenes y contenedores a golpe de ratón con la vista “Docker Explorer”. Se activa con la opción de menú Window->Show View->Other….

Código de ejemplo

El código de ejemplo para este capítulo se encuentra en GitHub (todos los proyectos están en un único repositorio). Para más información sobre cómo utilizar GitHub, consultar este artículo.

>>>> ÍNDICE <<<<

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Salir /  Cambiar )

Google photo

Estás comentando usando tu cuenta de Google. Salir /  Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Salir /  Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Salir /  Cambiar )

Conectando a %s

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios .