Construir la imagen del contenedor de la aplicación usando Buildpacks

Escrito por el .
java planeta-codigo programacion
Enlace permanente Comentarios

Los sistemas y servicios son significativamente complejos por sí mismos, además en sistemas que se componen de varios de ellos cada uno con sus diferencias añade más complejidad al sistema. Eliminar toda la complejidad posible y simplificar el sistema es algo deseable. La herramienta Buildpacks aplica a la construcción de las aplicaciones lo que los contenedores aplican en tiempo de ejecución de las mismas, uniformizando las aplicaciones independientemente del lenguaje y plataforma que usen.

Buildpacks

Paketo

Los contenedores son una gran tecnología ya que hace muy simple ejecutar servicios independientemente de cómo están implementados y en qué lenguaje, uniformizan los procesos a ejecutar y tratarlos a todos de la misma forma y con la misma herramienta.

Al igual que en los barcos los contenedores facilitan transportar mercancías, en el ámbito de la tecnología los contenedores simplifican mucho la infraestructura en tiempo de ejecución permitiendo tratar los servicios de forma uniforme.

Los contendores se basan en una imagen y una herramienta que los ejecuta, una de las herramientas más conocidas para ejecutar contenedores es Docker y el archivo con las instrucciones para construir las imágenes son los archivos Dockerfile.

Es muy útil poder ejecutar procesos de forma uniforme independientemente del lenguaje y de forma aislada con todas las dependencias del entorno dentro de la imagen lo que permite ejecutar tantos servicios como se deseen sin que las dependencias de estos entre en conflicto.

La misma utilidad y propiedades de los contenedores en tiempo de ejecución es deseable en el momento de construcción de las imágenes de los servicios.

La herramienta Buildpacks

La herramienta Buildpacks analiza el código fuente y permite construir imágenes de contenedores compatible con OCI con la misma herramienta independientemente del lenguaje o plataforma que utilice y sin utilizar archivos Dockerfile. Además, permite reutilizar las instrucciones de construcción sin tener que implementarlas.

Los buildpacks son los módulos que añaden el soporte e implementan las instrucciones para construir las imágenes. Hay un buildpack específico según una necesidad de construcción, una aplicación puede necesitar varios buildpacks al mismo tiempo.

Por ejemplo, una aplicación Java necesita una versión de la JVM para ejecutarse que es proporcionado por un buildpack, si la aplicación utiliza Gradle como herramienta de construcción hay un nuildpack para construir aplicaciones Gradle. Y hay buildpacks para Java, Node, Python y otros lenguajes.

Los conceptos que usa Buildpacks son:

  • Builder: es la imagen del contenedor con la que se realiza la construcción, contiene todos los componentes necesarios junto con los buildpacks para ejecutar la construcción.
  • Buildpack: es el ejecutable que construye y ejecuta la aplicación. Al realizar la construcción contribuye los archivos necesarios a la imagen del contenedor resultante.
  • Lifecycle: orquesta la construcción con los buildpacks y ensambla los artefactos resultantes en la imagen del contenedor.
  • Stack: se compone de dos imágenes de contenedor, la imagen builder que proporciona la imagen para realizar la construcción y la imagen run que proporciona la imagen para la ejecución.

Buildpacks es extensible y es posible crear y usar builders y buildpaks propios ajustados a las necesidades de la organización o aplicación si los existentes no son suficientes.

Buildpack Buildpack builder

Buildpack

El proyecto Paketo

Paketo es un proyecto de código abierto que proporciona numerosos buildpacks para diferentes lenguajes de las aplicaciones más populares entre los que por supuesto están Java, Node, Python y Go entre otros.

En el proyecto de GitHub de Paketo están los diferentes repositorios de los buildpacks junto con sus opciones de configuración y código fuente, además proporciona varios builder basados en diferentes versiones de Ubuntu.

Instalación de Buildpacks

Hay varias formas de instalación de Buildpacks según el sistema operativo que se use, junto a varias formas de instalación usando el gestor de paquetes nativo del sistema operativo o una instalación manual en el sistema.

Builpacks es una herramienta programada en el lenguaje Go que una de sus ventajas es que generan un único binario que incluye todas las dependencias con lo que su instalación manual es muy sencilla, basta con descargar y copiar un único archivo.

En GNU/Linux basta descargar el paquete de distribución para este sistema operativo, descomprimirlo e instalar el binario en el path del sistema de modo que al ejecutar el comando el intérprete del shell lo encuentre. En GNU/Linux el directorio /usr/local/bin permite añadir al sistema comandos adicionales sin que entren en conflicto con los que se instalan a través de paquetes de la distribución.

1
2
$ ls -lh /usr/local/bin/
-rwxr-xr-x 1 picodotdev picodotdev  23M ene 10 11:31 pack
ls-bin.sh

Construcción de la imagen del contenedor

Utilizando una aplicación escrita con Java con Gradle como herramienta de construcción y que usa Spring Boot, el comando para construir la imagen OCI del contenedor es el siguiente.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
$ pack build spring-injection-point \
    --env "BP_JVM_TYPE=JDK" \
    --env "BP_JVM_VERSION=11" \
    --env "BP_GRADLE_BUILT_ARTIFACT=app/build/distributions/app.zip" \
    --builder paketobuildpacks/builder-jammy-base \
    --buildpack gcr.io/paketo-buildpacks/ca-certificates \
    --buildpack gcr.io/paketo-buildpacks/syft \
    --buildpack gcr.io/paketo-buildpacks/procfile \
    --buildpack gcr.io/paketo-buildpacks/adoptium \
    --buildpack gcr.io/paketo-buildpacks/gradle \
    --buildpack gcr.io/paketo-buildpacks/executable-jar \
    --buildpack gcr.io/paketo-buildpacks/spring-boot \
    --default-process app \
    --path .
pack-build.sh

Los builpacks permiten ser configurados a través de las variables de entorno, en el comando es posible configurar el buildpack de adoptioum para que use la versión de Java que se desee o el tipo de máquina virtual, distribución JDK o JRE. En el archivo README.md de cada uno de los buildpacks están documentados las variables de entorno con su descripción y en algunos casos sus valores.

El comando especifica los buildpacks ya que el builder utilizado no incluye ninguno. En el parámetro path se indica la ruta al código fuente de la aplicación.

El buildpack paketo-buildpacks/procfile permite especificar diferentes comandos de ejecución en un archivo. El entrypoint que añade el builder en la imagen permite especificar el proceso a iniciar en el comando de ejecución del contenedor.

Ejecución del contenedor con Docker

Docker permite almacenar en local y ejecutar las imágenes construidas con Buildpacks como un contenedor normal. El comando para iniciar la aplicación es el siguiente que inicia el proceso por defecto de la imagen.

1
2
$ docker run -it --rm -p 8080:8080 spring-injection-point

docker-run.sh
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
Setting Active Processor Count to 8
Calculating JVM memory based on 13813724K available memory
For more information on this calculation, see https://paketo.io/docs/reference/java-reference/#memory-calculator
Calculated JVM Memory Configuration: -XX:MaxDirectMemorySize=10M -Xmx13215722K -XX:MaxMetaspaceSize=86001K -XX:ReservedCodeCacheSize=240M -Xss1M (Total Memory: 13813724K, Thread Count: 250, Loaded Class Count: 12770, Headroom: 0%)
Enabling Java Native Memory Tracking
Adding 124 container CA certificates to JVM truststore
Picked up JAVA_TOOL_OPTIONS: -Djava.security.properties=/layers/paketo-buildpacks_adoptium/java-security-properties/java-security.properties -XX:+ExitOnOutOfMemoryError -XX:ActiveProcessorCount=8 -XX:MaxDirectMemorySize=10M -Xmx13215722K -XX:MaxMetaspaceSize=86001K -XX:ReservedCodeCacheSize=240M -Xss1M -XX:+UnlockDiagnosticVMOptions -XX:NativeMemoryTracking=summary -XX:+PrintNMTStatistics
Property (app.property): value

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::                (v2.7.6)

2023-01-11 19:53:33,542 INFO  [main]     io.github.picodotdev.blogbitix.springinjectionpoint.Main Starting Main using Java 11.0.17 on d5713708092f with PID 1 (/workspace/app/lib/app.jar started by cnb in /workspace)
2023-01-11 19:53:33,550 INFO  [main]     io.github.picodotdev.blogbitix.springinjectionpoint.Main No active profile set, falling back to 1 default profile: "default"
2023-01-11 19:53:34,402 INFO  [main] org.springframework.boot.web.embedded.tomcat.TomcatWebServer Tomcat initialized with port(s): 8080 (http)
2023-01-11 19:53:34,423 INFO  [main]                     org.apache.catalina.core.StandardService Starting service [Tomcat]
2023-01-11 19:53:34,424 INFO  [main]                      org.apache.catalina.core.StandardEngine Starting Servlet engine: [Apache Tomcat/9.0.69]
2023-01-11 19:53:34,524 INFO  [main] .apache.catalina.core.ContainerBase.[Tomcat].[localhost].[/] Initializing Spring embedded WebApplicationContext
2023-01-11 19:53:34,524 INFO  [main] .boot.web.servlet.context.ServletWebServerApplicationContext Root WebApplicationContext: initialization completed in 931 ms
2023-01-11 19:53:34,932 INFO  [main] org.springframework.boot.web.embedded.tomcat.TomcatWebServer Tomcat started on port(s): 8080 (http) with context path ''
2023-01-11 19:53:34,944 INFO  [main]     io.github.picodotdev.blogbitix.springinjectionpoint.Main Started Main in 1.858 seconds (JVM running for 2.534)
2023-01-11 19:53:34,946 INFO  [main]  io.github.picodotdev.blogbitix.springinjectionpoint.Service Hello World!
System.out

Los buildpacks añaden un launcher que permite varias formas de ejecutar el contenedor de la aplicación. También es posible especificar otro proceso de la imagen con la opción –entrypoint y el nombre del proceso.

1
2
$ docker run -it --rm --entrypoint app -p 8080:8080 spring-injection-point

docker-run-entrypoint.sh

A veces interesa iniciar una shell para inspeccionar el sistema de archivos del contenedor y ejecutar procesos manualmente. En este ejemplo se muestra la versión de Ubuntu que forma la imagen base del contenedor y se aprecia que está basada en la versión 22.04.

1
2
$ docker run -it --rm --entrypoint launcher -p 8080:8080 spring-injection-point bash

docker-run-entrypoint-bash.sh
1
2
$ cat /etc/issue
Ubuntu 22.04.1 LTS
issue.out

Finalmente, es posible inspeccionar la imagen del contenedor sin iniciarlo para obtener información acerca de cómo se ha construido.

1
2
$ pack inspect-image spring-injection-point

pack-inspect-image.sh
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
Inspecting image: spring-injection-point

REMOTE:
(not present)

LOCAL:

Stack: io.buildpacks.stacks.jammy

Base Image:
  Reference: 9460382ca7aa20a60d1dcb756dbeb1535fe3e2eeb3e0c01a85ad7a60eaa091ad
  Top Layer: sha256:5853f77d360cc55683c2896aa9fd14bf47430731ad6fec18d9511f56656c232f

Run Images:
  index.docker.io/paketobuildpacks/run-jammy-base:latest

Buildpacks:
  ID                                       VERSION        HOMEPAGE
  paketo-buildpacks/ca-certificates        3.5.1          https://github.com/paketo-buildpacks/ca-certificates
  paketo-buildpacks/syft                   1.23.0         https://github.com/paketo-buildpacks/syft
  paketo-buildpacks/procfile               5.5.0          https://github.com/paketo-buildpacks/procfile
  paketo-buildpacks/adoptium               10.8.0         https://github.com/paketo-buildpacks/adoptium
  paketo-buildpacks/gradle                 6.10.0         https://github.com/paketo-buildpacks/gradle
  paketo-buildpacks/executable-jar         6.5.0          https://github.com/paketo-buildpacks/executable-jar
  paketo-buildpacks/spring-boot            5.22.0         https://github.com/paketo-buildpacks/spring-boot

Processes:
  TYPE                 SHELL        COMMAND                                                             ARGS        WORK DIR
  app (default)        bash         JAVA_HOME=/layers/paketo-buildpacks_adoptium/jre app/bin/app                    /workspace
pack-inspect-image.out
Terminal

El código fuente completo del ejemplo puedes descargarlo del repositorio de ejemplos de Blog Bitix alojado en GitHub y probarlo en tu equipo ejecutando siguiente comando:
./pack-build.sh


Comparte el artículo: