Nota:
- Este tutorial está disponible en un entorno de prácticas gratuitas proporcionado por Oracle.
- Utiliza valores de ejemplo para credenciales, arrendamiento y compartimentos de Oracle Cloud Infrastructure. Al finalizar el laboratorio, sustituya estos valores por valores específicos de su entorno en la nube.
GraalVM Native Image, Spring y Containerisation
Introducción
Este laboratorio está destinado a desarrolladores que deseen saber más sobre cómo crear contenedores de aplicaciones de GraalVM Native Image.
La tecnología de GraalVM Native Image compila código Java de forma anticipada en un archivo ejecutable nativo. Solo el código que necesita la aplicación en tiempo de ejecución se incluye en el archivo ejecutable.
Un archivo ejecutable producido por Native Image tiene varias ventajas importantes, ya que:
- Utiliza una fracción de los recursos que necesita JVM, por lo que es más barato para ejecutarse
- Comienza en milisegundos
- Ofrece un rendimiento máximo inmediatamente, sin calentamiento
- Se puede empaquetar en una imagen de contenedor ligera para un despliegue más rápido y eficaz
- Presenta una superficie de ataque reducida (más sobre esto en futuros laboratorios)
Muchos de los marcos de microservicios líderes admiten la compilación anticipada con GraalVM Native Image, incluidos Micronaut, Spring, Helidon y Quarkus.
Además, hay plugins de Maven y Gradle para Native Image para que pueda crear, probar y ejecutar fácilmente aplicaciones Java como archivos ejecutables.
Nota: Oracle Cloud Infrastructure (OCI) proporciona GraalVM Enterprise sin costo adicional.
Tiempo de laboratorio estimado: 90 minutos
Objetivos del laboratorio
En este laboratorio:
- Agregar una aplicación de Spring Boot básica a una imagen de Docker y ejecutarla
- Cree un ejecutable nativo a partir de esta aplicación, mediante GraalVM Native Image
- Agregar el ejecutable nativo a una imagen de Docker
- Reducción del tamaño de la imagen del docker de la aplicación con GraalVM Native Image y contenedores distintos
- Descubra cómo utilizar las herramientas de creación nativa de GraalVM, Maven Plugin
NOTA: si ve el icono de portátil en el laboratorio, esto significa que debe hacer algo como introducir un comando. Manténgase atento a ello.
# This is where you will need to do something
PASO 1: conexión a un host virtual y comprobación del entorno de desarrollo
Un host remoto proporciona su entorno de desarrollo: una instancia informática de OCI con Oracle Linux 8, 4 núcleos y 32 GB de memoria. El entorno de escritorio Luna Labs se mostrará antes de que el host remoto esté listo, lo que puede tardar hasta dos minutos.
Para conectarse al host remoto, ejecute un script de configuración en el entorno de Luna Desktop. Este script está disponible en el separador Recursos.
-
Haga doble clic en el icono Luna Lab del escritorio para abrir el explorador.
-
Se mostrará el separador Recursos. Tenga en cuenta que el engranaje que se muestra junto al título Recursos girará mientras la instancia informática se aprovisiona en la nube.
-
Cuando se aprovisione la instancia (esto puede tardar hasta 2 minutos), verá lo siguiente en el separador Recursos.
-
Copie la secuencia de comandos de configuración que configura el entorno de código VS desde la ficha Recursos. Haga clic en el enlace Ver detalles para mostrar la configuración. Copie esto como se muestra en la captura de pantalla siguiente:
-
Abra un terminal, como se muestra en la captura de pantalla siguiente:
-
Pegue el código de configuración en el terminal, que le abrirá VS Code.
-
Se abrirá una ventana de código VS y se conectará automáticamente a la instancia de VM que se le haya aprovisionado. Haga clic en Continuar para aceptar la huella del equipo.
Ha finalizado. Enhorabuena, ahora se ha conectado correctamente a un host remoto en Oracle Cloud.
El script anterior abrirá VS Code, conectado a su instancia informática remota con el código de origen del laboratorio abierto.
A continuación, deberá abrir un terminal dentro de VS Code. Este terminal le permitirá interactuar con el host remoto. Se puede abrir un terminal en VS Code a través del menú: Terminal > New Terminal.
Utilizaremos este terminal en el resto del laboratorio.
Nota sobre el entorno de desarrollo
Utilizaremos GraalVM Enterprise 22, como entorno Java para este laboratorio. GraalVM es una distribución de JDK de alto rendimiento de Oracle basada en la solución segura y de confianza de Oracle Java SE.
Su entorno de desarrollo viene preconfigurado con GraalVM y las herramientas de Native Image necesarias para este laboratorio.
Puede comprobar fácilmente si ejecuta estos comandos en el terminal:
java -version
native-image --version
PASO 2: Conozca nuestra aplicación Java de ejemplo
En este laboratorio, va a crear una aplicación sencilla con una API basada en REST muy mínima. A continuación, pondrá en contenedores esta aplicación con Docker. En primer lugar, eche un vistazo rápido a su aplicación sencilla.
Hemos proporcionado el código fuente y los scripts de creación para esta aplicación, y la carpeta que contiene el código fuente se abrirá en el código VS.
La aplicación se basa en la estructura de inicio principal y utiliza el proyecto nativo principal (incubador de Spring para generar ejecutables nativos mediante GraalVM Native Image).
La aplicación tiene dos clases, que se pueden encontrar en src/main/java
:
com.example.demo.DemoApplication
: clase de Spring Boot principal que también define el punto final HTTP,/jibber
com.example.demo.Jabberwocky
: clase de utilidad que implanta la lógica de la aplicación
Por lo tanto, ¿qué hace la aplicación? Si llama al punto final REST /jibber
, definido dentro de la aplicación, devolverá algunos versos sin sentido generados al estilo del poema jabberwocky, por Lewis Carroll. El programa lo consigue utilizando una cadena Markov para modelar el poema original (esencialmente un modelo estadístico). Este modelo genera un nuevo texto.
En la aplicación de ejemplo, proporcionamos a la aplicación el texto del poema y, a continuación, generamos un modelo del texto que la aplicación utiliza para generar un nuevo texto similar al texto original. Estamos utilizando la biblioteca RiTa para hacer el trabajo duro para nosotros, ya que admite la creación y el uso de cadenas de Markov.
A continuación, se muestran dos fragmentos de la clase de utilidad com.example.demo.Jabberwocky
que crea el modelo. La variable text
contiene el texto del poema original. Este fragmento muestra cómo se crea el modelo y, a continuación, se rellena con text
. Esto se llama desde el constructor de clases y definimos la clase como Singleton (por lo tanto, solo se crea una instancia de la clase).
this.r = new RiMarkov(3);
this.r.addText(text);
Aquí puedes ver el método para generar nuevas líneas de verso a partir del modelo, basado en el texto original.
public String generate() {
String[] lines = this.r.generate(10);
StringBuffer b = new StringBuffer();
for (int i=0; i< lines.length; i++) {
b.append(lines[i]);
b.append("<br/>\n");
}
return b.toString();
}
Tómese un poco tiempo para ver el código y familiarizarse con él.
Para crear la aplicación, va a utilizar Maven. El archivo pom.xml
se generó mediante Spring Initializr y contiene soporte para utilizar las herramientas de Spring Native. Esta es una dependencia que ha agregado a los proyectos de Spring Boot si tiene previsto utilizar como destino GraalVM Native Image. Si utiliza Maven, al agregar soporte para Spring Native, se insertará el siguiente plugin en la configuración de compilación por defecto.
<plugin>
<groupId>org.springframework.experimental</groupId>
<artifactId>spring-aot-maven-plugin</artifactId>
<version>${spring-native.version}</version>
<executions>
<execution>
<id>test-generate</id>
<goals>
<goal>test-generate</goal>
</goals>
</execution>
<execution>
<id>generate</id>
<goals>
<goal>generate</goal>
</goals>
</execution>
</executions>
</plugin>
Ahora cree la aplicación. Desde el directorio raíz del repositorio, ejecute los siguientes comandos en el shell:
mvn clean package
Esto generará un archivo JAR "ejecutable", que contiene todas las dependencias de la aplicación y también un archivo MANIFEST
configurado correctamente. Puede ejecutar este archivo JAR y, a continuación, "ping" el punto final de la aplicación para ver lo que devuelve: coloque el comando en segundo plano con &
para que vuelva a recibir la petición de datos.
java -jar ./target/jibber-0.0.1-SNAPSHOT-exec.jar &
Llame al punto final con el comando curl
desde la línea de comandos.
Cuando publique el comando en su terminal, el código de VS puede solicitarle que abra la URL en un explorador, simplemente cierre el diálogo, como se muestra a continuación.
Ejecute lo siguiente para probar el punto final HTTP:
curl http://localhost:8080/jibber
¿Recibió el verso no sentido? Por lo tanto, ahora que ha creado una aplicación en funcionamiento, finalícela y continúe con la contenedorización. Lleve la aplicación al primer plano para poder finalizarla.
fg
Introduzca <ctrl-c>
para terminar ahora la aplicación.
<ctrl-c>
PASO 3: contenedores de la aplicación Java con Docker
La contenedorización de la aplicación Java como contenedor Docker es, gracias a ello, relativamente sencilla. Puede crear una nueva imagen de Docker basada en una que contenga una distribución de JDK. Por lo tanto, en esta práctica utilizará un contenedor que ya contenga un JDK, container-registry.oracle.com/java/openjdk:17-oraclelinux8
: se trata de una imagen de Oracle Linux 8 con OpenJDK.
A continuación, se muestra un desglose de Dockerfile, que describe cómo crear la imagen de Docker. Consulte los comentarios para explicar el contenido.
FROM container-registry.oracle.com/java/openjdk:17-oraclelinux8 # Base Image
ARG JAR_FILE # Pass in the JAR file as an argument to the image build
EXPOSE 8080 # This image will need to expose TCP port 8080, as this is the port on which your app will listen
COPY ${JAR_FILE} app.jar # Copy the JAR file from the `target` directory into the root of the image
ENTRYPOINT ["java"] # Run Java when starting the container
CMD ["-jar","app.jar"] # Pass in the parameters to the Java command that make it load and run your executable JAR file
El archivo Dockerfile para crear en contenedores la aplicación Java se puede encontrar en el directorio, 00-containerise
.
Para crear una imagen de Docker que contenga la aplicación, ejecute los siguientes comandos desde el terminal:
docker build -f ./00-containerise/Dockerfile \
--build-arg JAR_FILE=./target/jibber-0.0.1-SNAPSHOT-exec.jar \
-t localhost/jibber:java.01 .
Consulte Docker para ver la imagen recién creada:
docker images | head -n2
Debe ver una nueva imagen en la lista. Ejecute esta imagen de la siguiente manera:
docker run --rm -d --name "jibber-java" -p 8080:8080 localhost/jibber:java.01
A continuación, llame al punto final como hizo antes de utilizar el comando curl
:
curl http://localhost:8080/jibber
¿Viste el verso sin sentido? Ahora compruebe cuánto tiempo ha tardado la aplicación en iniciarse. Puede extraer esto de los logs, ya que las aplicaciones Spring Boot escriben la hora de inicio en los logs:
docker logs jibber-java
Por ejemplo, la aplicación se ha iniciado en 3.896s. A continuación, se muestra la extracción de los logs:
2022-03-09 19:48:09.511 INFO 1 --- [ main] com.example.demo.DemoApplication : Started DemoApplication in 3.896 seconds (JVM running for 4.583)
OK, finalice el contenedor y continúe:
docker kill jibber-java
También puede consultar Docker para obtener el tamaño de la imagen. Hemos proporcionado un script que hace esto por usted. Ejecute lo siguiente en el terminal:
echo $((`docker inspect -f "" localhost/jibber:java.01`/1024/1024))
Esto imprime el tamaño de la imagen en MB, que es de 606 MB.
PASO 4: creación de un ejecutable nativo
Recuerde lo que hasta ahora tiene:
- Ha creado una aplicación Spring Boot con un punto final HTTP,
/jibber
- Lo ha contenedorizado correctamente
Ahora verá cómo puede crear un ejecutable nativo desde su aplicación mediante GraalVM Native Image. Este ejecutable nativo va a tener una serie de características interesantes, a saber:
- Va a empezar muy rápido
- Utilizará menos recursos que su aplicación Java correspondiente.
Puede utilizar las herramientas de Native Image instaladas con GraalVM para crear un ejecutable nativo de una aplicación desde la línea de comandos. Pero, como ya está utilizando Maven, va a aplicar las Herramientas de compilación nativas de GraalVM para Maven, lo que le permitirá realizar una práctica cómoda utilizando Maven para crear.
Una forma de agregar soporte para crear un ejecutable nativo es utilizar un perfil de Maven, que le permitirá decidir si desea crear el archivo JAR o un ejecutable nativo.
En el archivo pom.xml
de Maven proporcionado, hemos agregado un perfil que crea un ejecutable nativo. Eche un vistazo más detallado:
Primero, debe declarar el perfil y asignarle un nombre.
<profiles>
<profile>
<id>native</id>
<!-- Rest of profile hidden, to highlight relevant parts -->
</profile>
</profiles>
A continuación, en el perfil, se incluye el plugin de herramientas de creación de GraalVM Native Image y se lo asocia a la fase package
en Maven. Esto significa que se ejecutará como parte de la fase package
. Tenga en cuenta que puede transferir argumentos de configuración a la herramienta de creación de Native Image subyacente mediante la sección <buildArgs>
. En las etiquetas buildArg
individuales, puede transferir parámetros exactamente de la misma forma que lo hace con la herramienta native-image
. Por lo tanto, puede utilizar todos los parámetros que funcionan con la herramienta native-image
:
<build>
<plugins>
<plugin>
<groupId>org.graalvm.buildtools</groupId>
<artifactId>native-maven-plugin</artifactId>
<version>${native-buildtools.version}</version>
<extensions>true</extensions>
<executions>
<execution>
<id>build-native</id>
<phase>package</phase>
<goals>
<goal>build</goal>
</goals>
</execution>
</executions>
<configuration>
<imageName>jibber</imageName>
<buildArgs>
<buildArg>-H:+ReportExceptionStackTraces</buildArg>
</buildArgs>
</configuration>
</plugin>
<!-- Rest of profile hidden, to high-light relevant parts -->
</plugins>
</build>
Ahora ejecute la creación de Maven utilizando el perfil, como se indica a continuación (tenga en cuenta que el nombre del perfil se especifica con el indicador -P
):
mvn package -Pnative
Esto generará un ejecutable nativo para la plataforma en el directorio target
, denominado jibber
. Observe el tamaño del archivo:
ls -lh target/jibber
Ejecute este ejecutable nativo y pruébelo. Ejecute el siguiente comando en el terminal para ejecutar el ejecutable nativo y ponerlo en segundo plano, mediante &
:
./target/jibber &
Llame al punto final con el comando curl
:
curl http://localhost:8080/jibber
Ahora tiene un ejecutable nativo de la aplicación que comienza muy rápido.
Termine la aplicación antes de continuar. Lleve la aplicación a primer plano:
fg
Céntelo con <ctrl-c>
:
<ctrl-c>
PASO 5: contenedorización del ejecutable nativo
Ahora, puesto que tiene una versión ejecutable nativa de la aplicación, y la ha visto funcionando, créela.
Hemos proporcionado un archivo Dockerfile simple para empaquetar este ejecutable nativo: está en el directorio native-image/containerisation/lab/01-native-image/Dockerfile
. El contenido se muestra a continuación, junto con comentarios para explicar cada línea.
FROM container-registry.oracle.com/os/oraclelinux:8-slim
ARG APP_FILE # Pass in the native executable
EXPOSE 8080 # This image will need to expose TCP port 8080, as this is port your app will listen on
COPY ${APP_FILE} app # Copy the native executable into the root directory and call it "app"
ENTRYPOINT ["/app"] # Just run the native executable :)
Para crearlo, ejecute lo siguiente desde el terminal:
docker build -f ./01-native-image/Dockerfile \
--build-arg APP_FILE=./target/jibber \
-t localhost/jibber:native.01 .
Observe la imagen recién creada:
docker images | head -n2
Ahora puede ejecutar esto y probarlo de la siguiente manera desde el terminal:
docker run --rm -d --name "jibber-native" -p 8080:8080 localhost/jibber:native.01
Llame al punto final desde el terminal mediante curl
:
curl http://localhost:8080/jibber
De nuevo, debería haber visto más verso sin sentido en el estilo del poema Jabberwocky. Puede comprobar el tiempo que la aplicación tardó en iniciarse consultando los logs producidos por la aplicación como lo hizo anteriormente. Desde el terminal, ejecute lo siguiente y busque el tiempo de inicio:
docker logs jibber-native
Hemos visto lo siguiente que muestra que la aplicación se ha iniciado en 0.074s. ¡Es una gran mejora en comparación con el original de 3.896s!
2022-03-09 19:44:12.642 INFO 1 --- [ main] com.example.demo.DemoApplication : Started DemoApplication in 0.074 seconds (JVM running for 0.081)
Termine el contenedor y muévalo al siguiente paso:
docker kill jibber-native
Pero antes de ir al siguiente paso, eche un vistazo al tamaño del contenedor producido:
echo $((`docker inspect -f "" localhost/jibber:native.01`/1024/1024))
El tamaño de la imagen de contenedor que vimos fue de 199
MB. Es mucho menor que nuestro contenedor de Java original.
PASO 6: creación de un ejecutable más estático y empaquetado en una imagen sin roles
Vuelva a capturar lo que ha hecho hasta ahora:
- Ha creado una aplicación Spring Boot con un punto final HTTP,
/jibber
- Lo ha contenedorizado correctamente
- Ha creado un ejecutable nativo de su aplicación mediante las herramientas de creación de Native Image para Maven
- Ha contenedorizado su ejecutable nativo
Sería genial si pudiéramos reducir aún más el tamaño del contenedor, ya que los contenedores más pequeños son más rápidos de descargar e iniciar. Con GraalVM Native Image, puede enlazar de forma estática bibliotecas del sistema al ejecutable nativo que genera. Si crea un ejecutable nativo enlazado estáticamente, puede empaquetar el ejecutable nativo directamente en una imagen de Docker vacía, también conocida como contenedor scratch
.
Otra opción es producir lo que se conoce como ejecutable nativo enlazado en su mayoría estáticamente. Con esto, se enlaza de manera estática en todas las bibliotecas del sistema, excepto en la biblioteca C estándar, glibc
. Con un ejecutable nativo de este tipo, puede utilizar un contenedor pequeño, como Distroless de Google, que contiene la biblioteca glibc
, algunos archivos estándar y certificados de seguridad SSL. El contenedor estándar Distroless tiene un tamaño aproximado de 20 MB.
Creará un ejecutable enlazado principalmente de forma estática y, a continuación, lo empaquetará en un contenedor sin roles.
Hemos agregado otro perfil de Maven para crear este ejecutable nativo, que está totalmente enlazado. Este perfil se denomina distroless
. La única diferencia entre este perfil y el que utilizó antes, native
, es que transferimos un parámetro, -H:+StaticExecutableWithDynamicLibC
. Como puede suponer, le indica a native-image
que cree un ejecutable nativo enlazado en su mayoría estáticamente.
Puede crear su ejecutable nativo mayormente enlazado estáticamente de la siguiente manera:
mvn package -Pdistroless
Es lo suficientemente fácil. El ejecutable nativo generado está en el directorio de destino jibber-distroless
.
Ahora lo empaqueta en un contenedor sin distribución. El archivo Dockerfile para ello se puede encontrar en el directorio native-image/containerisation/lab/02-smaller-containers/Dockerfile
. Observe el contenido del Dockerfile, que tiene comentarios para explicar cada línea:
FROM gcr.io/distroless/base # Our base image, which is Distroless
ARG APP_FILE # Everything else is the same :)
EXPOSE 8080
COPY ${APP_FILE} app
ENTRYPOINT ["/app"]
Para crearlo, ejecute lo siguiente desde el terminal:
docker build -f ./02-smaller-containers/Dockerfile \
--build-arg APP_FILE=./target/jibber-distroless \
-t localhost/jibber:distroless.01 .
Observe la imagen Distroless recién construida:
docker images | head -n2
Ahora puede ejecutarla y probarla de la siguiente manera:
docker run --rm -d --name "jibber-distroless" -p 8080:8080 localhost/jibber:distroless.01
curl http://localhost:8080/jibber
¡Bien! Trabajó. Pero, ¿cuán pequeño o grande es su contenedor? Utilice el script para comprobar el tamaño de la imagen:
echo $((`docker inspect -f "" localhost/jibber:distroless.01`/1024/1024))
El tamaño es de alrededor de 107 MB. Por lo tanto, hemos reducido el contenedor en 92 MB. Un largo camino hacia abajo desde nuestro tamaño de inicio, para el contenedor Java, de unos 600 MB.
Conclusión
Esperamos que haya disfrutado de este laboratorio y aprendido algunas cosas a lo largo del camino. Hemos visto cómo puede en contenedores de una aplicación Java. A continuación, hemos visto cómo convertir esa aplicación Java en un ejecutable nativo, lo que se inicia significativamente más rápido que la aplicación Java. A continuación, compartimos el ejecutable nativo y hemos visto que el tamaño de la imagen de Docker, con el ejecutable nativo en él, es mucho más pequeño que la imagen de Java Docker.
Por último, estudiamos cómo podemos construir ejecutables nativos enlazados en su mayoría estáticamente con Native Image. Se pueden empaquetar en contenedores más pequeños, como Distroless y esto nos permite reducir aún más el tamaño de la imagen de Docker.
Más información
- Vea una presentación del arquitecto de Native Image, Christian Wimmer GraalVM Native Image: análisis estático a gran escala para Java
- Documentación de referencia de GraalVM Native Image
Más recursos de aprendizaje
Explore otras prácticas en docs.oracle.com/learn o acceda a contenido de aprendizaje más gratuito en el canal YouTube de Oracle Learning. Además, visite education.oracle.com/learning-explorer para convertirse en un explorador de formación de Oracle.
Para obtener documentación sobre los productos, visite Oracle Help Center.
GraalVM Native Image, Spring and Containerisation
F54862-01
March 2022
Copyright © 2022, Oracle and/or its affiliates.