miércoles, 26 de agosto de 2020

Optimizar MultiStage build en Docker y Maven

 En esta entrada les quiero presentar un truco pequeño sobre como optimizar sus builds en Docker y Maven con tan solo cambiar una linea de código. 

Ya es bien sabido que Docker se ha convertido en una herramienta básica para los desarrolladores al momento de distribuir sus aplicaciones. Los contenedores de docker pueden ser descargados y ejecutados en cualquier ambiente compatible y ademas es uno de los manéjadores de contenedores soportados por kubernetes que se ha vuelto el estándar para correr aplicaciones en la nube. 

No solo eso. Con el soporte de multi-stage builds en Docker es posible támbien simplificar la manera en que las aplicaciones son compiladas y empaquetadas antes de ser distribuidas en su contenedor final. En un build multi-stage docker prepara un contenedor donde se ejecutan algunas instrucciones de compilado / pruebas y el resultado final es copiado a un nuevo contenedor.

Con esto se simplifica la manera en la que nuevos desarrolladores o servidores CI compilan las aplicaciones. Solo es necesario tener docker y poder descargar un contenedor con todas las herramientas ya listas para la compilación. Es este articulo lo haremos con maven. 

Un ejemplo de un multi-stage build con maven y docker, y que ademas se encuentra mucho en proyectos de github, se puede estructurar como sigue.

FROM maven:3.6-jdk-11 as build
WORKDIR /src
ADD * /src
RUN mvn package -DskipTests

FROM adoptopenjdk:11-jre-hotspot
COPY --from=build /src/target/app.jar /app/app.jar
CMD ["java", "-jar", "/app/app.jar"]
EXPOSE 8080

Este método es muy común. Simplemente descargamos un contenedor docker que cuente con maven y java. Luego agregamos todo el proyecto completo al directorio src y empaquetamos el proyecto.

Luego tomamos un nuevo contenedor que sera encargado de correr nuestra aplicación. Copiamos el jar final desde el contenedor con alias build y lo colocamos en nuestro contenedor final con propiedades extra para poder correr nuestra aplicación. 


Como optimizar este build? 

El build se puede optimizar con solo agregar dos lineas de código en nuestro Dockerfile. Lo primero sera copiar el archivo pom.xml por sí solo y correr mvn package. 
Esto instruye a docker a correr un build de maven, descargar todas las dependencias y crear una capa en el cache de docker con todas las dependencias.
El archivo se ve como sigue.
FROM maven:3.6-jdk-11 as build

WORKDIR /src
ADD pom.xml .
RUN mvn package -DskipTests
# Este paso optimiza los builds hasta que el archivo pom o las dependencias cambien
ADD src src
RUN mvn package -DskipTests

FROM adoptopenjdk:11-jre-hotspot
COPY --from=build /src/target/app.jar /app/app.jar
CMD ["java", "-jar", "/app/app.jar"]
EXPOSE 8080

Una vez que esta capa este en el cache de docker. Cualquier build que siga y comparta él mismo cache ya no necesitara descargar todas las dependencias y solo se enfocara en compilar los archivos dentro del directorio src. 


Espero y este pequeño truco les sirva para optimizar sus proyectos que utilizan maven y docker

jueves, 18 de junio de 2020

De Regreso & Docker

Hola que tal a todos,

Pues como a muchos nos ha pasado en este 2020 y con toda la locura del corona virus (SARS COv2), los proyectos en el trabajo y en la vida personal han disminuido. A Varios meses ya de estar encerrado y con algo de tiempo libre. He decidido re tomar este blog y escribir un poco sobre lo he aprendido durante esta cuarentena. Durante el encierro he estado desarrollando algunos proyectos que me parecen interesantes y que quisiera compartir en una serie de posts. Empezaremos con algunas entradas básicas que serán fundamentales para poder avanzar y entender un poco más sobre el desarrollo en la nube (cloud computing).

Esta entrada se tratara sobre docker y algunas de sus características un poco avanzadas sobre el sistema. Ya llevo varios años trabajando con docker en varias de las compañías en las que he desarrollado proyectos y la verdad es que es una herramienta fácil de entender y una vez que nos acostumbremos a desarrollar con docker no nos podremos imaginar no instalarlo tan pronto obtengamos una nueva computadora.

Una explicación sencilla


Algunos de nosotros todavía nos acordaremos de los viejos tiempos del desarrollo de software (no soy tan antiguo como otros pero sí me han tocado varias transformaciones). Primero se obtenía el código fuente, se modificaba y se probaba localmente. Luego se enviaba a otro grupo para que estos compilaran, lo empaquetaran y lo instalaran en la maquina donde se ejecutaba. Pero qué pasa?, si el sistema operativo, las librerías,  las versiones de las herramientas de compilación, etc son diferentes en cada uno de los diferentes pasos. Esto podría provocar problemas en el desarrollo.

Docker intenta minimizar estos problemas al empaquetar la aplicación y las librerías que se necesitan en un solo contenedor que puede ser ejecutado en una gran variedad de plataformas. Algunas personas mencionan a las Maquinas Virtuales (virtualbox) como un paso intermedio entre maquinas físicas y docker para ejecutar programas. La diferencia es que las maquinas virtuales necesitan un sistema operativo base + librerías para poder ejecutar programas.

Docker agrupa las librerías y herramientas básicas para ejecutar el sistema y tu programa sobre la plataforma docker que corre encima de un el sistema operativo nativo.

Para mas información sobre como instalar docker y mas explicaciones visita https://docs.docker.com/get-docker/ o https://docs.docker.com/get-started/part2/


Un simple ejemplo de como construir una imagen (Dockerfile).


En la actualidad la mayoría de los proyectos utilizan alguna herramienta de construcción como Maven, Npm o Make. Estas herramientas simplifican la compilación, prueba y empaquetamiento del programa. Pero aun tenemos el problema de la distribución es por eso que muchos proyectos agregan el archivo Dockerfile al lado del archivo de proyecto como el pom.xml.

Todos los ejemplos los podemos encontrar en el repositorio: https://github.com/ti3r/demostracion-simple-java-docker

Una vez que los desarrolladores modifican el código es tan fácil como ejecutar para obtener la imagen final:

docker build -t proyecto:latest .

(este comando utiliza el archivo: https://github.com/ti3r/demostracion-simple-java-docker/blob/main/Dockerfile)

Con esta técnica, el proyecto debe de estar compilado, probado y empaquetado para que el resultado final sea agregado a la imagen de docker. Generalmente este paso es realizado en un servidor de Integración Continua (CI), como CircleCI, Jenkins, etc. el cual se encargara de construir la imagen de docker que sera ejecutada.



Un ejemplo un poco mas complejo (Dockerfile multi stage build)


Cómo vemos empaquetar aplicaciones con docker es muy sencillo. Es muy común también que nuevos desarrolladores no tengan las herramientas para construir la aplicación, o que algún servidor de CI tenga versiones diferentes o que haya librerías diferentes o que no tengamos acceso a instalar algunos requerimientos para poder compilar, probar y empaquetar la aplicación. Una manera de resolver estos problemas es utilizar Multi-Stage Docker Builds. Para este ejercicio utilizaremos el archivo:

Podemos ejecutar:

docker build -t proyecto:latest -f Dockerfile.multi .


Una explicación rápida de que es lo que pasa en este archivo.

  1. Definimos un stage utilizando una image base con nómbre "build"
  2. Definimos un directorio donde se copiaran los archivos del proyecto
  3. Copiamos todos los archivos fuente, el archivo pom y el wrapper de maven a ejecutar (para saber mas sobre mvn wrapper puedes leerlo aquí: https://github.com/takari/maven-wrapper)
  4. Ejecutamos maven para que cree el programa final
  5. Definimos la imagen base del programa de resultado
  6. Definimos las etiquetas y directorios de la imagen final 
  7. Copiamos el resultado de la compilación del stage "build" a nuestra imagen final

Utilizar multi-stage builds en docker resuelve varios problemas sobre cómo el preparar y compilar nuestra aplicación final. De esta manera podemos:
  • Estandarizar todas las herramientas necesarias para obtener nuestra aplicación final dentro de nuestro repositorio. 
  • Utilizar diferentes imágenes para compilar y correr nuestro programa reduciendo el tamaño de la imagen final ya que no necesitamos
  • Acelerar el desarrollo del proyecto ya que nuevos desarrolladores no necesitaran instalar nada en el sistema antes de aportar. Solo tener docker, clonar el repositorio y ejecutar los comandos necesarios