Nuestro ambiente de testing con Docker y Wildfly

Docker es una plataforma de virtualización a nivel de sistema operativo que permite empaquetar ambientes para aplicaciones en los llamados contenedores.

Estos contenedores tienen un alto nivel de abstracción dado que son muy livianos y portables (fácilmente transferibles), y además cada uno contiene solo las dependencias necesarias para la aplicación a ejecutar.

Docker nos permite un nivel de escalabilidad mucho mayor al manejo de máquinas virtuales o servidores físicos dado que las imágenes se van compilando de forma incremental en capas, en distintas versiones y son fácilmente descartables para realizar una nueva instalación.

Requisitos para trabajar con Docker

  1. Descargar Docker Desktop.
  2. Registrarnos en Docker Hub.

Generación de contenedor docker

Comandos más comunes

docker pull alpine

Descarga la imagen de alpine en su última versión.

docker run alpine

Corre la imagen de alpine en su última versión, si no existe la descarga.

docker run alpine:3.14

Corre la imagen de alpine en la versión 3.14, si no existe la descarga.

docker images

Nos lista las imágenes que tenemos descargadas con su versión, tamaño y otros datos.

docker ps

Nos muestra la lista de contenedores en ejecución.

docker ps -a

Muestra todos los contenedores que se han ejecutado recientemente.

docker start [containerID]

Inicia un contenedor a partir de un containerID, manteniendo los datos que se tenían antes en el mismo. Este contenedor es iniciado como proceso en segundo plano.

docker stop [containerID]

Detiene el contenedor.

docker rm $(docker ps -a -f status=exited -q)

Este comando elimina todos los contenedores en estado terminado.

docker stop $(docker ps -a -q)

Este comando elimina todos los contenedores en ejecución.

docker volume prune --force
docker container prune --force
docker image prune --force
docker system prune -a –force

Estos 4 comandos eliminan todos los volúmenes, containers e imágenes en desuso.

docker run -d [containerID]

Inicia una nueva instancia de contenedor, en segundo plano.

docker image rm [name||image-id]

Pasándole el nombre o el image-id elimina una imagen generada.

docker exec -it [containerID] sh

Ejecuta un comando dentro de un contenedor que ya está corriendo, en este caso ejecutando el comando sh, y comenzando a interactuar con ese contenedor.

Logs en Docker

docker logs [containerID]

Nos muestra todos los logs de ese containerID, que está en ejecución.

docker logs -f [containerID]

Nos muestra todos los logs de ese containerID, que está en ejecución con la diferencia que se queda en espera y sigue mostrando los logs en tiempo real.

Importante es que nuestra aplicación nunca escriba logs en un archivo, solo hay que enviarlos a stdout, dado que docker se encarga de rotar los logs.

Distribución Linux recomendada para contenedores

Siempre para todo ambiente, ya sea de prueba o de producción, nos conviene que cada imagen sea lo más liviana posible, y las distribuciones como ubuntu, debian, centOS pueden resultar bastante pesadas.

Para esto existe la distribución alpine, que es extremadamente liviana (solo 5mb)..

https://alpinelinux.org/downloads/

Dockerfile

Dockerfile, sintaxis y como usarlo

Un Dockerfile es un archivo de texto que contiene una serie de instrucciones para crear una imagen. Dicho archivo de texto es una receta de cocina para lograr un determinado plato (que es nuestra imagen).

En este caso vamos a indicar un Dockerfile con las instrucciones necesarias para generar un contenedor Alpine 3.14.0 con Wildfly 26.1.0Final y OpenJDK 17.

FROM openjdk:17-jdk-alpine3.14

RUN echo 'https://dl-4.alpinelinux.org/alpine/v3.14/main/' >> /etc/apk/repositories
RUN echo 'https://dl-4.alpinelinux.org/alpine/v3.14/community/' >> /etc/apk/repositories
RUN echo 'https://dl-4.alpinelinux.org/alpine/edge/testing' >> /etc/apk/repositories

WORKDIR /opt/app

ADD wildfly-preview-26.1.0.Final.tar.gz .

RUN apk update && \
    addgroup -g 101 -S wildfly && \
    adduser -S -D -H -u 101 -h /opt/app/wildfly26 -s /sbin/nologin -G wildfly -g wildfly wildfly && \
    cd /opt/app && \
    mv wildfly-preview-26.1.0.Final wildfly26 && \
    chown -R wildfly:wildfly /opt && \
    rm -rf /tmp/* /var/cache/apk/* && \
    mkdir /etc/wildfly && \
    chmod -R 755 /opt && \
    rm -rf /tmp/* /var/cache/apk/*

USER wildfly

RUN /opt/app/wildfly26/bin/add-user.sh user password --silent
CMD ["/opt/app/wildfly26/bin/standalone.sh", "-c", "standalone-full-ha.xml" , "-Djboss.node.name=nodo1", "-b", "0.0.0.0", "-bmanagement", "0.0.0.0"]

  • En la línea FROM se especifica la imagen base a utilizar y su tag.
  • Todas las líneas RUN especifican comandos que se irán ejecutando como si nosotros mismos los ejecutáramos en un terminal, también podemos concatenar varios comandos en una sola línea RUN (recomendable)
  • La línea WORKDIR especifica el directorio donde empezaremos posicionados, si no existe se crea.
  • El comando ADD se encarga de copiar ficheros partiendo de la ruta relativa donde está el Dockerfile, al WORKDIR establecido (además si el fichero es un tar, lo copia descomprimido).
  • Finalmente, la línea CMD especifica el servicio que se ejecutará en el contenedor.

Crear y ejecutar el contenedor desde el Dockerfile

Luego de generar el archivo con las instrucciones y guardarlo, entramos en un CMD y nos posicionamos donde está nuestro Dockerfile almacenado.

Ejecutamos lo siguiente:

docker build --no-cache --tag dockerNode1:1.0 

Con ese comando especificamos que se creará una imagen con el nombre dockerNode1:1.0 usando el Dockerfile de ese mismo directorio.

Al final se especifica que el tag que tendrá nuestra imagen es 1.0.

Si queremos crear una versión nueva de una imagen ya existente podemos hacer lo siguiente:

docker build -t dockerNode1:2.0

Docker reutilizará todas las capas en común entre ambas versiones.

Para ejecutar una instancia de este contenedor usamos el siguiente comando:

docker run --network customNetwork --ip 192.168.10.2 -d dockerNode1:1.0

Este docker run invoca una instancia de la imagen dockerNode1:1.0, especificando la red que lo contendrá y su ip.

Para borrar una imagen creada debemos usar la siguiente línea:

docker image rm --force [name||image-id]

Compartir nuestras imágenes con otras personas

Para compartir nuestras imágenes Docker tenemos dos opciones, una de ellas es usar el repositorio oficial de Docker Hub, y otra es exportarla y pasarla por una memoria extraíble.

Exportar imagen a archivo

Nosotros podemos exportar imágenes con una etiqueta asignada a un fichero comprimido .tar para luego ser importada en otro equipo.

docker save [image-id:tag] --output archivo.tar

Luego en el pc de quien recibirá el contenedor, debemos tener instalado docker y también ejecutar:

docker load -i archivo.tar

De esa forma importa la imagen y la tenemos disponible para ejecución.

Docker Hub

Esta plataforma nos permite usar el servicio gratuitamente mientras las imágenes sean de acceso público.

Una vez que hicimos el build debemos ponerle un nuevo tag a la imagen, que se ajuste al formato de Docker Hub. Para esto ejecutamos lo siguiente:

docker tag [image-id] usuarioDockerHub/dockerNode1:1.0

Esto nos crea una referencia al mismo [image-id], pero con un tag distinto.

Para hacer un push al registro de docker es necesario estar logueado en la plataforma, para lo cual ejecutamos el siguiente comando:

docker push usuarioDockerHub/dockerNode1:1.0

Ahora cualquier persona puede ejecutar lo siguiente (para obtener una copia de la imagen):

docker pull usuarioDockerHub/dockerNode1:1.0

Creación de redes de contenedores Docker

Docker nos permite conectar nuestros contenedores a una red, la cual manejará un determinado “driver” de red, puede ser uno de los siguientes:

  • Bridge
    • Es el driver por defecto (si no se especifica cual usar se usará este).
    • Genera una red manejando automáticamente las ip de los contenedores que se unen, dichos contenedores se comunican entre sí, pero no con el equipo anfitrión. También es posible publicar puertos de estos nodos con red Bridge, para que el host los vea, y pueda acceder a ellos.
  • Host
    • Remueve el aislamiento entre contenedores y el equipo anfitrión, usa la ip del host directamente, sin necesidad de mapeo de puertos.
  • None
    • Deshabilita toda conectividad para el contenedor
  • Custom
    • Podemos crear redes gestionadas por nosotros y nos brinda otras posibilidades como, por ejemplo:

Si creamos una red Custom que use el driver Bridge, podemos manejar nosotros la subred a utilizar y las ip de cada contenedor.

Para crear una red debemos usar este comando, especificando en el la subred a utilizar y el driver que se manejará:

docker network create testNetwork --driver bridge --subnet 192.168.10.0/24

Para ver las redes existentes tenemos la siguiente instrucción:

docker network ls

Para invocar una instancia de la imagen dockerNode1:1.0 acoplándonos a los parámetros anteriores, debemos usar la siguiente instrucción:

docker run --net testNetwork --ip 192.168.10.2 -d dockerNode1:1.0

Acceso a servicios de los contenedores desde el host

Si bien los contenedores acceden a internet a través del host, no pueden ser accedidos ni por el host ni por otro equipo de la red del host, para poder acceder a los servicios que proporcionen nuestros nodos es necesario mapear puertos.

Los puertos se mapean desde el comando docker run al invocar un nuevo contenedor, la forma es la siguiente:

docker run -itd –network testNetwork --ip 192.168.10.2 --publish 8080:8080 --publish 9990:9990 -d dockerNode1:1.0

Ahí estamos comunicando los puertos 8080 y 9990 del container con los 8080 y 9990 del host.

Si necesitamos mapear a otros puertos del host debemos tener en cuenta la sintaxis del publish:

–publish HOST:CONTAINER

Por ejemplo, si hacemos un –publish 8080:80 estamos mapeando el puerto 80 del contenedor al 8080 del anfitrión.

Ambiente de pruebas – Quick Start

En base a lo documentado, se desarrolló un ambiente de pruebas con 4 nodos Wildfly 26.1.1Final/JDK17 + 1 nodo Alpine FTP, como infraestructura de testing para nuestros proyectos con Java EE y Wildfly.

Este ambiente de pruebas sigue el siguiente esquema:

A los 4 nodos Wildfly se les agrega 1 nodo FTP con Alpine, con el fin de centralizar el acceso a ficheros de todos los nodos Wildfly.

Esto se logró con una estructura de montajes entre el equipo anfitrión, el nodo FTP y los 4 nodos Wildfly.

Si accedemos al nodo FTP desde filezilla podremos trabajar con las 4 carpetas de deployment de Wildfly y los 4 ficheros standalone.xml.

¿Por qué un ambiente de pruebas dockerizado?

Proporciona muchas ventajas con respecto a las máquinas virtuales en virtualbox o un ambiente de pruebas distinto en la pc de cada desarrollador porque:

  • Permite mayor portabilidad
    • Las imágenes de los contenedores son mucho más livianas (ej.: una distribución Alpine con Wildfly 26 y JDK17 instalado pesa aprox 800mb).
    • Son mucho más simples de implementar en otros equipos que un conjunto de máquinas virtuales.
  • Si automatizamos el despliegue de este ambiente de pruebas, podemos tener en nuestro equipo de trabajo un ambiente de testing unificado.
  • Podemos armar una subred de nodos con una ip asignada a cada uno, que está aislada del host, a excepción de los puertos que podamos mapear para ser accedidos por el anfitrión.

Automatización de despliegue de nodos

Se generó un repositorio github que es accesible para ustedes, en el cual se automatiza con Shell scripting la generación y puesta en marcha de todos los contenedores vistos en el diagrama anterior.

Repositorio: https://github.com/nbent1996/testingEnvironmentBlog

Script genDockerContainers.sh

Este script resuelve los siguientes puntos:

  • Genera 1 imagen de alpine con vsftpd (servicio ftp) correspondiente al gráfico.
  • Genera 4 imágenes de Wildfly correspondientes al gráfico (usando un mismo dockerfile).
  • Crear subred testNetwork de tipo Bridge en el rango de ip 192.168.10.0/24
  • Ejecutar los 5 contenedores en la subred testNetwork, dando acceso a los puertos en el host con las 5 imágenes generadas anteriormente 
  • Estructura de montajes que permite
    • Hacer deploy a través de ftp en el nodo ftp Alpine
    • Acceder a ficheros standalone, script de entrypoint de cada nodo, cacerts de java y carpeta de deployments desde el nodo ftp.

IMPORTANTE: Este script está adaptado para tener los ficheros del repositorio github en c:/testingEnvironmentBlog y ser ejecutado con CMDER (https://www.filehorse.com/es/descargar-cmder/)

Credenciales de acceso a FTP

User: ftpUser | Pass: ftp1234

Accedemos por Filezilla para realizar deployment o cambios en la config de standalone en la dirección localhost:8021.

Si se cambian configuraciones en standalone.xml, será necesario bajar el contenedor y volverlo a subir, para lo cual se recomienda extraer el comando Docker run del script genDockerContainers.sh y ejecutarlo tal cual sin cambios.

Credenciales de acceso a Wildfly

APP User: APPpcf | Pass:pcf1234

Management User: pcf | Pass: pcf1234

Estas credenciales son válidas en cualquiera de los 4 nodos.

Dockernode1

http://localhost:8087

http://localhost:9997

Dockernode2

http://localhost:8187

http://localhost:10097

Dockernode3

http://localhost:8287

http://localhost:10197

Dockernode4

http://localhost:8387

http://localhost:10297

Estructura de montajes

A continuación, se detalla la estructura de montajes, que permite mandar deployments a través de ftp, y modificar fácilmente ficheros de configuración de wildfly, cacerts de java, o los scripts de entrypoint de cada contenedor.

ANFITRIÓN (PC) ———->FTP———————————->  DockerNode
C:/testingEnvironmentBlog/home/testingEnvironmentBlog/standalonedockernode[1/2/3/4]/opt/jboss/wildfly/standalone
C:/testingEnvironmentBlog/home/testingEnvironmentBlog/scriptsServicioWildfly/levantardockernode[1/2/3/4].sh/opt/jboss/wildfly/levantarWildfly.sh
C:/testingEnvironmentBlog/home/testingEnvironmentBlog/cacerts/opt/java/openjdk/lib/security/cacerts
Estructura de montajes

Leave a Reply

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *