Con el auge de la tecnología de contenedores liderado por Docker ha cambiado la forma en que empaquetamos y distribuimos nuestras aplicaciones. Por ejemplo, en el mundo Java hemos pasado de empaquetar aplicaciones JEE en los formatos establecidos por dicho estándar (ear, war...) y ejecutarlas en servidores de aplicaciones JEE a desarrollar con springboot y utilizar los contenedores para distribuir y ejecutar nuestras aplicaciones.
En el ámbito de los contenedores, el proceso de empaquetado ha experimentado una evolución desde la aparición de Docker hasta estos días. En los últimos tiempos han surgido herramientas que nos ayudan con esta tarea facilitándonos resultados de cierta calidad. Ahí es donde entra Paketo.
Paketo es una potente herramienta de generación de imágenes de contenedores a partir del código de nuestra aplicación, sin que necesitemos especificar instrucciones en un fichero Dockerfile.
En este artículo hablaremos de como generar imágenes que sigan la especificación OCI utilizando Paketo. Como ejemplo empaquetaremos una aplicación springboot.
Acerca de contenedores y formatos de imágen
OCI es una especificación impulsada por la iniciativa Opencontainers de la Linux Foundation.
Esta especificación define como debe ser el entorno de ejecución de un contenedor (Runtime) y también el formato de empaquetado de las imágenes que van a correr en ese entorno de ejecución.
Hay varias implementaciones de entornos de ejecución de contenedores entre los que cabe destacar runc que es la implementación de referencia y crun.
Lo que nos interesa para el propósito de este artículo es el formato de imagen del contenedor. Durante bastante tiempo la implementación de referencia para imágenes de contenedores ha sido la definida por Docker. Con el tiempo Docker cedió tanto su implementación del entorno de ejecución (runc) como la especificación del formato de imagen a OCI.
El formato de OCI se diseñó para ser compatible con Docker y en realidad hay muy pocas diferencias entre uno y otro.
Algunas de las diferencias entre OCI y Docker en cuanto al formato de la imagen se encuentran en los mediaType utilizados en el manifiesto de la imagen. El formato Docker contiene referencias a la marca docker. Al ser OCI una especificación independiente se han cambiado estas referencias por otras propias.
Vamos a ver utilizando docker qué pinta tiene un manifiesto en formato Docker:
Para ver que pinta tiene el manifiesto en formato OCI bajamos el fichero tar de la imagen, lo descomprimimos y echamos un vistazo al manifiesto. Para ello voy a utilizar skopeo que es una herramienta que permite trabajar con imágenes en formato OCI.
Como se puede apreciar se han reemplazado las referencias a Docker en los mediaType, pero la estructura es muy parecida, con las secciones config y layers que describen la imagen y cada una de las capas que la componen.
El formato OCI está siendo soportado progresivamente en los repositorios de imágenes más habituales (Container registry) como Nexus o Artifactory. Aunque es posible que nos encontremos con otros repositorios que aún no lo soporten.
Aunque el formato OCI es independiente del fabricante de un runtime o plataforma concretos, en la práctica vemos que no hay grandes diferencias entre ambos y en general los repositorios de imágenes y las plataformas que soportan el formato OCI también son compatibles con el formato Docker, al menos de momento...
Del Dockerfile a Paketo
Desde la aparición de Docker han ido apareciendo distintas formas y herramientas para generar imágenes en las que empaquetar las aplicaciones.
La más básica consiste en utilizar un Dockerfile directamente, escribiendo en el mismo las instrucciones necesarias para incluir nuestra aplicación.
Siguiendo a esta, tenemos herramientas como el plugin Maven de Spotify o fabric8 que nos facilitan el trabajo de generar la imagen utilizando un Dockerfile como base. Estas dos últimas son proyectos inactivos que han llegado al final de su ciclo de vida, aunque en el caso de fabric8 aún hay algunos subproyectos como el del plugin Maven que continúan activos.
Por otro lado, tenemos herramientas como Jib con la que podemos utilizar un Dockerfile como base, pero también podemos empaquetar nuestra aplicación sin necesidad de Dockerfile utilizando como base una imagen. Además, tiene la ventaja de no necesitar Docker daemon, lo cual facilita su utilización en procesos de CI/CD.
Por último, tenemos aquellas herramientas que nos permiten empaquetar nuestra aplicación especificando el tipo de aplicación que tenemos sin tener que especificar un Dockerfile o una imagen base concreta. Hay que comentar que la imagen base utilizada en última instancia va a variar en función del tipo de aplicación que queremos empaquetar, por ejemplo, va ser diferente para una aplicación Java con springboot que para una aplicación Node. Por ello el proceso empieza a ser algo más sofisticado y complejo.
En este grupo de herramientas tenemos a Paketo o s2i. En la página de buildpacks tenemos una pequeña comparativa donde se mezclan los distintos enfoques que hemos ido viendo.
Para poder crear una imagen adecuada a la aplicación que queremos empaquetar Paketo recurre al concepto de buildpacks. Cada buildpack engloba las herramientas necesarias para empaquetar el tipo de aplicación que necesitemos que a nivel muy básico consisten en una imagen Builder que se encargará de la construcción y una imagen base en la que empaquetaremos la aplicación.
Con Paketo no necesitamos mantener un Dockerfile, ni la imagen base. En el caso de paketo los problemas de seguridad de la imagen base que vayan apareciendo se van subsanando mediante una política de actualizaciones que asegura la compatibilidad con la imagen anterior.
La construcción de la imagen está enfocada a facilitar las actualizaciones y minimizar los tiempos de construcción. Para ello sigue unas prácticas concretas a la hora de organizar las capas de la imágen. La imagen generada por paketo tendrá formato OCI
Tenemos algunas otras características a las que se puede echar un vistazo en la página de buildpacks. Es de especial interés en los últimos tiempos la capacidad de generar un BOM (Bill of Materials) de la imagen, utilizando Cyclone.
Empaquetemos una aplicación springboot
Para poder seguir de forma práctica el siguiente ejemplo vamos a necesitar las siguientes cosas:
Descargamos aplicación de ejemplo y construimos el jar
Empezaremos clonando uno de los repositorios de ejemplo del proyecto springboot:
git clone https://github.com/spring-projects/spring-hateoas-examples.git
Nos vamos al directorio del ejemplo "spring-hateoas-examples-simplified":
cd spring-hateoas-examples/simplified/
Construimos la aplicación:
mvn clean package
Esto nos ha dejado en el directorio target un jar con el nombre spring-hateoas-examples-simplified-1.0.0.BUILD-SNAPSHOT.jar
Utilizando el CLI de Paketo
Ahora podemos hacer dos cosas, construir la imagen con Paketo a partir del jar o hacerlo a partir de la fuente del proyecto.
Alternativamente, como estamos utilizando una aplicación springboot de ejemplo podríamos hacer esto mismo utilizando el plugin de maven de springboot que ya se integra con buildpack. Pero veamos en este ejemplo como lo haríamos a partir del jar que acabamos de construir:
pack --env BP_JVM_VERSION=8 build spring-hateoas-examples-simplified --builder paketobuildpacks/builder:base --path ./target/spring-hateoas-examples-simplified-1.0.0.BUILD-SNAPSHOT.jar
Veremos una salida parecida a la de las siguientes imágenes:
Descarga la imagen builder:
Detecta el tipo de aplicación:
Genera la imagen estructurada en capas:
Al final vemos que nos ha generado nuestra imagen.
Un detalle curioso es que la imagen fue construida hace... ¡42 años! Esto se debe a que Paketo pretende que las construcciones de sus imágenes sean reproducibles. Es decir que teniendo el mismo input produzcamos el mismo output. En este caso las fechas de construcción como ocurre en otros sistemas similares se fijan en una fecha concreta para asegurar esta característica, ya que de otro modo la fecha introduciría un cambio en el output.
Echemos un vistazo por dentro a la imagen generada, utilizando dive que es una herramienta que permite visualizar las distintas capas de las que se compone la imagen:
dive spring-hateoas-examples-simplified
Podemos ver como en una de las últimas capas añade el código compilado de la aplicación. Esto lo hace así porque la ordenación de capas sigue el criterio de poner las capas que más cambian hacia el final para aprovechar la caché de construcción de aquellas capas que tienden a cambiar menos y mejorar la velocidad de construcción.
Conclusión
Paketo puede ser un gran aliado en el trabajo de empaquetar todo tipo de aplicaciones liberándonos del trabajo de mantener ficheros Dockerfile. Además, nos proporciona imágenes de calidad asegurando la compatibilidad y mejorando los tiempos de construcción.
Aunque no hay grandes diferencias entre el formato de imagen OCI y el formato Docker habrá que estar atentos a lo que ocurre en el futuro ya que OCI en principio viene a reemplazar al formato Docker y cada día más herramientas y plataformas se están adaptando a estas especificaciones.
Referencias
https://buildpacks.io/features/
https://opencontainers.org/faq/#what-has-docker-done-to-help-create-this-foundation