Observação:
- Este tutorial está disponível em um ambiente de laboratório gratuito fornecido pela Oracle.
- Ele usa valores de exemplo para credenciais, tenancy e compartimentos do Oracle Cloud Infrastructure. Ao concluir seu laboratório, substitua esses valores por valores específicos do seu ambiente de nuvem.
GraalVM Native Image, Spring e Containeralization
Introdução
Este laboratório destina-se a desenvolvedores que desejam entender mais sobre como conteinerizar aplicativos GraalVM Native Image.
A tecnologia do GraalVM Native Image compila um código Java antecipadamente em um arquivo executável nativo. Somente o código que é necessário no tempo de execução pelo aplicativo é incluído no arquivo executável.
Um arquivo executável produzido por Native Image tem várias vantagens importantes, pois ele:
- Usa uma fração dos recursos exigidos pela JVM, portanto, é mais barato executar
- Começa em milissegundos
- Oferece desempenho de pico imediatamente, sem aquecimento
- Pode ser empacotado em uma imagem de contêiner leve para uma implantação mais rápida e eficiente
- Apresenta uma superfície de ataque reduzida (mais sobre isso em laboratórios futuros)
Muitas das principais estruturas de microsserviços oferecem suporte à compilação antecipada com o GraalVM Native Image, incluindo Micronaut, Spring, Helidon e Quarkus.
Além disso, existem plug-ins Maven e Gradle para Native Image, para que você possa facilmente criar, testar e executar aplicativos Java como arquivos executáveis.
Observação: O Oracle Cloud Infrastructure (OCI) fornece o GraalVM Enterprise sem custo adicional.
Tempo estimado de laboratório: 90 minutos
Objetivos do laboratório
Neste laboratório, você vai:
- Adicione um aplicativo Spring Boot básico a uma Imagem do Docker e execute-o
- Crie um executável nativo a partir deste aplicativo usando o GraalVM Native Image
- Adicionar o executável nativo a uma Imagem do Docker
- Diminua o tamanho das imagens do docker do aplicativo com contêineres GraalVM Native Image e Distintos
- Veja como usar as ferramentas de criação nativa do GraalVM, Plug-in Maven
OBSERVAÇÃO: se você vir o ícone de laptop no laboratório, isso significa que precisará fazer algo como digitar um comando. Fique de olho nisso.

# This is where you will need to do something
ETAPA 1: Conectar-se a um Host Virtual e Verificar o Ambiente de Desenvolvimento
Seu ambiente de desenvolvimento é fornecido por um host remoto: uma Instância de Computação do OCI com Oracle Linux 8, 4 núcleos e 32 GB de memória. O ambiente de desktop do Luna Labs será exibido antes que o host remoto esteja pronto, o que pode levar até dois minutos.
Conecte-se ao host remoto executando um script de configuração no ambiente do Luna Desktop. Esse script está disponível por meio da guia de recursos.
-
Clique duas vezes no ícone do Luna Lab na área de trabalho para abrir o navegador.

-
A Guia Recursos será exibida. Observe que a engrenagem mostrada ao lado do título Recursos girará enquanto a instância de computação está sendo provisionada na nuvem.

-
Quando a instância for provisionada (isso pode levar até 2 minutos), você verá o seguinte exibido na guia Recursos.

-
Copie o script de configuração que configura o ambiente do VS Code na guia de recursos. Clique no link Exibir Detalhes para revelar a configuração. Copie isso conforme mostrado na captura de tela abaixo:

-
Abra um Terminal, conforme mostrado na captura de tela abaixo:

-
Cole o código de configuração no terminal, que abrirá o VS Code para você.


-
Uma janela de Código VS será aberta e conectada automaticamente à instância de VM que foi provisionada para você. Clique em Continuar para aceitar a impressão digital da máquina.

Você terminou! Parabéns! Agora você foi conectado com sucesso a um host remoto no Oracle Cloud!
O script acima abrirá o VS Code, conectado à sua instância de computação remota, com o código-fonte aberto do laboratório.
Em seguida, você precisará abrir um terminal dentro do Código VS. Esse terminal permitirá que você interaja com o host remoto. Um terminal pode ser aberto no Código VS por meio do menu: Terminal > Novo Terminal.

Usaremos esse terminal no resto do laboratório.
Observação sobre o Ambiente de Desenvolvimento
Usaremos o GraalVM Enterprise 22 como o ambiente Java para este laboratório. O GraalVM é uma distribuição JDK de alto desempenho da Oracle baseada no Oracle Java SE confiável e seguro.
Seu ambiente de desenvolvimento vem pré-configurado com o GraalVM e as ferramentas Native Image necessárias para este laboratório.
Você pode verificar facilmente se executando estes comandos no terminal:

java -version

native-image --version
ETAPA 2: Conheça Nosso Aplicativo Java de Amostra
Neste laboratório, você criará um aplicativo simples com uma API baseada em REST muito mínima. Você em seguida colocará esse aplicativo em contêiner usando o Docker. Primeiro, dê uma olhada rápida em seu aplicativo simples.
Fornecemos o código-fonte e os scripts de criação para este aplicativo, e a pasta que contém o código-fonte será aberta no Código VS.
O aplicativo foi desenvolvido na parte superior da estrutura Spring Boot e faz uso do Projeto Nativo da Primavera (uma incubadora de Primavera para gerar executáveis nativos usando o GraalVM Native Image).
O aplicativo tem duas classes, que podem ser encontradas em src/main/java:
com.example.demo.DemoApplication: A classe principal do Spring Boot que também define o ponto final HTTP,/jibbercom.example.demo.Jabberwocky: Uma classe de utilitário que implementa a lógica do aplicativo
Então, o que o aplicativo faz? Se você chamar o ponto final REST /jibber, definido no aplicativo, ele retornará algum verso sem sentido gerado no estilo do Jabberwocky poem, por Lewis Carroll. O programa obtém isso usando uma Cadeia de Markov para modelar o poema original (isto é essencialmente um modelo estatístico). Esse modelo gera um novo texto.
No aplicativo de exemplo, fornecemos ao aplicativo o texto do poema e, em seguida, geramos um modelo do texto, que o aplicativo usa para gerar um novo texto semelhante ao texto original. Estamos usando a biblioteca RiTa para fazer o trabalho pesado para nós - ele suporta a criação e o uso de Cadeias de Markov.
Abaixo estão dois snippets da classe de utilitário com.example.demo.Jabberwocky que cria o modelo. A variável text contém o texto do poema original. Este trecho de código mostra como criamos o modelo e, em seguida, o preenchemos com text. Isso é chamado do construtor de classe e definimos a classe como um Singleton (assim, apenas uma instância da classe é criada).
this.r = new RiMarkov(3);
this.r.addText(text);
Aqui você pode ver o método para gerar novas linhas de verso a partir do modelo, com base no 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();
}
Reserve um pouco de tempo para exibir o código e familiarizá-lo.
Para criar o aplicativo, você usará o Maven. O arquivo pom.xml foi gerado usando Spring Initializr e contém suporte para usar as ferramentas do Spring Native. Essa é uma dependência que você adiciona aos projetos do Spring Boot se planeja direcionar o GraalVM Native Image. Se você estiver usando o Maven, adicionar suporte para Spring Native inserirá o plug-in a seguir na configuração de build padrão.
<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>
Agora crie seu aplicativo. No diretório raiz do repositório, execute os seguintes comandos no shell:

mvn clean package
Isso gerará um arquivo JAR "executável", um que contém todas as dependências do aplicativo e também um arquivo MANIFEST configurado corretamente. Você pode executar esse arquivo JAR e, em seguida, "ping" o ponto final do aplicativo para ver o que você obtém em retorno - coloque o comando em segundo plano usando & para que você obtenha o prompt de volta.

java -jar ./target/jibber-0.0.1-SNAPSHOT-exec.jar &
Chame o ponto final usando o comando curl na linha de comando.
Quando você postar o comando em seu terminal, o VS Code poderá solicitar que você abra o URL em um browser, basta fechar o diálogo, conforme mostrado abaixo.

Execute o seguinte para testar o ponto final HTTP:

curl http://localhost:8080/jibber
Conseguiu um versículo sem sentido de volta? Portanto, agora que você tiver criado um aplicativo de trabalho, encerre-o e vá para o contêiner. Traga o aplicativo para o primeiro plano para que você possa encerrá-lo.

fg
Digite <ctrl-c> para encerrar o aplicativo agora.

<ctrl-c>
ETAPA 3: Contêinerizando seu Aplicativo Java com o Docker
A conteinerização do seu aplicativo Java como um contêiner Docker é, agradecida, relativamente simples. Você pode criar uma nova imagem do Docker com base em uma que contenha uma distribuição do JDK. Portanto, para esse laboratório, você usará um contêiner que já contém um JDK, container-registry.oracle.com/java/openjdk:17-oraclelinux8 - essa é uma imagem do Oracle Linux 8 com OpenJDK.
Veja a seguir um detalhamento do Dockerfile, que descreve como criar a imagem do Docker. Consulte os comentários para explicar o conteúdo.
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
O Dockerfile para colocar em contêiner o seu aplicativo Java pode ser encontrado no diretório, 00-containerise.
Para criar uma imagem do Docker que contém seu aplicativo, execute os seguintes comandos no 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 o Docker para ver sua imagem recém-criada:

docker images | head -n2
Você verá uma nova imagem listada. Execute esta imagem da seguinte forma:

docker run --rm -d --name "jibber-java" -p 8080:8080 localhost/jibber:java.01
Em seguida, chame o ponto final como você fez antes de usar o comando curl:

curl http://localhost:8080/jibber
Viu o versículo sem sentido? Agora verifique quanto tempo levou seu aplicativo para inicializar. Você pode extrair isso dos logs, pois os aplicativos Spring Boot gravam o tempo de inicialização nos logs:

docker logs jibber-java
Por exemplo, o aplicativo foi iniciado em 3.896s. Aqui está a extração dos 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, encerre seu contêiner e continue:

docker kill jibber-java
Você também pode consultar o Docker para obter o tamanho da imagem. Fornecemos um script que faz isso para você. Execute o seguinte em seu terminal:

echo $((`docker inspect -f "" localhost/jibber:java.01`/1024/1024))
Isso imprime o tamanho da imagem em MBs, que é de 606 MB.
ETAPA 4: Criando um Executável Nativo
Recapte o que você tem até agora:
- Você criou um aplicativo Spring Boot com um ponto final HTTP,
/jibber - Você o colocou em contêiner com sucesso
Agora, você verá como criar um executável nativo com base no seu aplicativo usando o GraalVM Native Image. Esse executável nativo terá várias características interessantes, a saber:
- Vai começar muito rápido.
- Ela usará menos recursos do que seu aplicativo Java correspondente
Você pode usar as ferramentas Native Image instaladas com o GraalVM para criar um executável nativo de um aplicativo na linha de comando. Mas, à medida que já estiver usando o Maven, você aplicará as Ferramentas de Build Nativo do GraalVM para Maven, que permitirão convenientemente continuar usando o Maven para criar.
Uma forma de adicionar suporte para a criação de um executável nativo é usar um perfil Maven, que permitirá que você decida se deseja apenas criar o arquivo JAR ou um executável nativo.
No arquivo pom.xml do Maven fornecido, adicionamos um perfil que cria um executável nativo. Examine de perto:
Primeiro, você precisa declarar o perfil e dar um nome a ele.
<profiles>
<profile>
<id>native</id>
<!-- Rest of profile hidden, to highlight relevant parts -->
</profile>
</profiles>
Em seguida, dentro do perfil, incluímos o plug-in de ferramentas de build do GraalVM Native Image e o anexamos à fase package no Maven. Isso significa que ele será executado como parte da fase package. Observe que você pode especificar argumentos de configuração para a ferramenta de build do Native Image subjacente usando a seção <buildArgs>. Em tags buildArg individuais, você pode informar parâmetros exatamente da mesma forma que faz para a ferramenta native-image. Portanto, você pode usar todos os parâmetros que funcionam com a ferramenta 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>
Agora execute o build do Maven usando o perfil, conforme abaixo (observe que o nome do perfil é especificado com o flag -P):

mvn package -Pnative
Isso gerará um executável nativo para a plataforma no diretório target, chamado jibber. Examine o tamanho do arquivo:

ls -lh target/jibber
Execute este executável nativo e teste-o. Execute o seguinte comando em seu terminal para executar o executável nativo e colocá-lo em segundo plano, usando &:

./target/jibber &
Chame o ponto final usando o comando curl:

curl http://localhost:8080/jibber
Agora você tem um executável nativo do aplicativo que começa muito rápido!
Encerre o aplicativo antes de continuar. Traga o aplicativo para o primeiro plano:

fg
Encerre-o com <ctrl-c>:

<ctrl-c>
ETAPA 5: Contêinerizando seu Executável Nativo
Agora, como você tem uma versão executável nativa do seu aplicativo, e você a viu funcionando, conteinerize-o.
Fornecemos um Dockerfile simples para empacotar esse executável nativo: ele está no diretório native-image/containerisation/lab/01-native-image/Dockerfile. O conteúdo é mostrado abaixo, juntamente com comentários para explicar cada linha.
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 construir, execute o seguinte em seu terminal:

docker build -f ./01-native-image/Dockerfile \
--build-arg APP_FILE=./target/jibber \
-t localhost/jibber:native.01 .
Dê uma olhada na imagem recém-criada:

docker images | head -n2
Agora você pode executá-lo e testá-lo da seguinte forma no terminal:

docker run --rm -d --name "jibber-native" -p 8080:8080 localhost/jibber:native.01
Chame o ponto final no terminal usando curl:

curl http://localhost:8080/jibber
Mais uma vez, você deve ter visto mais verso absurdo no estilo do poema Jabberwocky. Você pode dar uma olhada no tempo que o aplicativo demorou para inicializar verificando os logs produzidos pelo aplicativo como você fez anteriormente. Em seu terminal, execute o seguinte e procure o tempo de inicialização:

docker logs jibber-native
Vimos o seguinte, que mostra que o aplicativo foi iniciado em 0.074s. Essa é uma grande melhoria em comparação com o 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)
Encerre seu contêiner e mova-o para a próxima etapa:

docker kill jibber-native
Mas antes de ir para a próxima etapa, observe o tamanho do contêiner produzido:

echo $((`docker inspect -f "" localhost/jibber:native.01`/1024/1024))
O tamanho da imagem do contêiner que vimos foi de 199 MB. Muito menor do que nosso contêiner Java original.
ETAPA 6: Criando um Executável Mais Estático e Empacotando-o em uma Imagem Descomplicada
Recap, mais uma vez, o que você fez até agora:
- Você criou um aplicativo Spring Boot com um ponto final HTTP,
/jibber - Você o colocou em contêiner com sucesso
- Você criou um arquivo executável nativo do seu aplicativo usando as Ferramentas de compilação Native Image para Maven
- Você conteinerizou seu executável nativo
Seria ótimo se pudéssemos reduzir ainda mais o tamanho do seu contêiner, porque contêineres menores são mais rápidos para fazer download e começar. Com o GraalVM Native Image, você pode vincular estaticamente bibliotecas do sistema ao executável nativo gerado. Se você criar um executável nativo vinculado estaticamente, poderá empacotar o executável nativo diretamente em uma imagem Docker vazia, também conhecida como contêiner scratch.
Outra opção é produzir o que é conhecido como um executável nativo vinculado principalmente estáticamente. Com isso, você vincula estaticamente todas as bibliotecas do sistema, exceto a biblioteca C padrão, glibc. Com um executável nativo, você pode usar um pequeno contêiner, como o Distroless do Google, que contém a biblioteca glibc, alguns arquivos padrão e certificados de segurança SSL. O contêiner Distroless padrão tem cerca de 20 MB.
Você criará um arquivo executável vinculado principalmente e o empacotará em um contêiner Distroless.
Adicionamos outro perfil Maven para criar esse arquivo executável nativo vinculado principalmente estáticamente. Esse perfil é chamado de distroless. A única diferença entre esse perfil e o que você usou antes, native, é que passamos um parâmetro, -H:+StaticExecutableWithDynamicLibC. Como você deve adivinhar, isso instrui native-image a criar um executável nativo vinculado principalmente estáticamente.
Você pode criar seu executável nativo vinculado principalmente estaticamente da seguinte forma:

mvn package -Pdistroless
É fácil o suficiente. O executável nativo gerado está no diretório de destino jibber-distroless.
Agora empacote-o em um contêiner ininterrupto. O Dockerfile para fazer isso pode ser encontrado no diretório native-image/containerisation/lab/02-smaller-containers/Dockerfile. Observe o conteúdo do Dockerfile, que tem comentários para explicar cada linha:
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 construir, execute o seguinte em seu terminal:

docker build -f ./02-smaller-containers/Dockerfile \
--build-arg APP_FILE=./target/jibber-distroless \
-t localhost/jibber:distroless.01 .
Dê uma olhada na imagem Distroless recém-criada:

docker images | head -n2
Agora você pode executá-lo e testá-lo da seguinte forma:

docker run --rm -d --name "jibber-distroless" -p 8080:8080 localhost/jibber:distroless.01

curl http://localhost:8080/jibber
Ótimo! Funcionou. Mas como é pequeno, ou grande, seu contêiner? Use o script para verificar o tamanho da imagem:

echo $((`docker inspect -f "" localhost/jibber:distroless.01`/1024/1024))
O tamanho é de cerca de 107 MB! Então nós encolhemos o recipiente por 92 MB. Um longo caminho para baixo do nosso tamanho inicial, para o contêiner Java, de cerca de 600 MB.
Conclusão
Esperamos que tenha gostado deste laboratório e aprendido algumas coisas ao longo do caminho. Pesquisamos como você pode conteinerizar um aplicativo Java. Vimos então como converter esse aplicativo Java em um executável nativo, que começa significativamente mais rápido do que o aplicativo Java. Em seguida, conteinerizamos o executável nativo e temos visto que o tamanho da imagem do Docker, com o executável nativo nele, é muito menor que a imagem do Java Docker.
Finalmente, analisamos como podemos criar, na maioria das vezes, executáveis nativos vinculados estaticamente com o Native Image. Elas podem ser empacotadas em contêineres menores, como no Distroless, e nós diminuímos ainda mais o tamanho da imagem do Docker.
Saiba Mais
- Assista a uma apresentação do arquiteto Native Image Christian Wimmer GraalVM Native Image: Análise estática em larga escala para Java
- Documentação de referência do GraalVM Native Image
Mais Recursos de Aprendizagem
Explore outros laboratórios em docs.oracle.com/learn ou acesse mais conteúdo de aprendizado gratuito no canal YouTube do Oracle Learning. Além disso, visite education.oracle.com/learning-explorer para se tornar um Oracle Learning Explorer.
Para obter a documentação do produto, visite o Oracle Help Center.
GraalVM Native Image, Spring and Containerisation
F54861-01
March 2022
Copyright © 2022, Oracle and/or its affiliates.