Spring Native sur Google Cloud

1. Aperçu

Dans cet atelier de programmation, vous allez découvrir le projet Spring Native, créer une application qui l'utilise et la déployer sur Google Cloud.

Nous parlerons des composants, de l'historique récent du projet, de certains cas d'utilisation et, bien sûr, des étapes à suivre pour l'utiliser dans vos projets.

Le projet Spring Native est en phase expérimentale. Par conséquent, il vous faudra une configuration spécifique pour commencer. Cependant, comme annoncé à SpringOne 2021, Spring Native est intégré à Spring Framework 6.0 et à Spring Boot 3.0 avec une assistance de première classe. C'est donc le moment idéal pour examiner de plus près le projet. quelques mois avant sa sortie.

Bien que la compilation "juste à temps" ait été très bien optimisée pour des processus de longue durée, par exemple, certains cas d'utilisation compilés à l'avance sont encore plus performants. Nous en parlerons au cours de l'atelier de programmation.

Vous apprendrez à effectuer les tâches suivantes :

  • Utiliser Cloud Shell
  • Activer l'API Cloud Run
  • Créer et déployer une application Spring Native
  • Déployer une telle application sur Cloud Run

Prérequis

Enquête

Comment allez-vous utiliser ce tutoriel ?

Je vais le lire uniquement Je vais le lire et effectuer les exercices

Comment évalueriez-vous votre expérience avec Java ?

Débutant Intermédiaire Expert

Quel est votre niveau d'expérience avec les services Google Cloud ?

Débutant Intermédiaire Expert

2. Expérience

Le projet Spring Native utilise plusieurs technologies pour fournir les performances des applications natives aux développeurs.

Pour bien comprendre Spring Native, il est utile de comprendre quelques-unes de ces technologies composants, ce qu'elles font pour nous et comment elles fonctionnent ensemble.

Compilation anticipée

Lorsque les développeurs exécutent javac au moment de la compilation, notre code source .java est compilé dans des fichiers .class écrits en bytecode. Ce bytecode n'est destiné qu'à la machine virtuelle Java. La JVM devra donc l'interpréter sur d'autres machines pour pouvoir exécuter le code.

Ce processus nous procure la portabilité de la signature Java, ce qui nous permet de l'écrire une seule fois et de l'exécuter partout. Mais son utilisation est plus coûteuse que l'exécution du code natif.

Heureusement, la plupart des implémentations de JVM utilisent la compilation "juste à temps" pour limiter ces coûts d'interprétation. Pour ce faire, elle comptabilise les appels d'une fonction et, si elle est appelée assez souvent pour dépasser un seuil ( 10 000 par défaut), elle est compilée dans le code natif au moment de l'exécution, afin d'éviter toute interprétation plus coûteuse.

La compilation anticipée prend l'approche opposée, en compilant tout le code accessible dans un exécutable natif au moment de la compilation. Cela facilite la portabilité de l'efficacité de la mémoire et d'autres gains de performances lors de l'exécution.

5042e8e62a05a27.png

C'est un compromis, mais cela ne vaut pas toujours la peine. Toutefois, cette compilation peut être utile dans certains cas d'utilisation, par exemple:

  • Applications de courte durée où le temps de démarrage est important
  • Environnements à mémoire très élevée où le JIT peut s'avérer trop coûteux

Anecdote amusante : la compilation AOT a été introduite en tant que fonctionnalité expérimentale dans le JDK 9. Sa mise en œuvre étant coûteuse, elle n'a jamais été totalement interceptée et a donc été très discrètement supprimée. en Java 17 en faveur des développeurs qui utilisent simplement GraalVM.

GraalVM

GraalVM est une distribution JDK hautement optimisée et Open Source qui offre des temps de démarrage extrêmement rapides, une compilation d'images natives AOT et des fonctionnalités polyglottes qui permettent aux développeurs de combiner plusieurs langages dans une seule application.

GraalVM est en développement actif. Il acquiert de nouvelles capacités et améliore constamment celles existantes. J'encourage donc les développeurs à rester informés.

Voici quelques étapes récentes:

  • Une sortie de création d'images natives conviviale et nouvelle ( 2021-01-18)
  • Compatibilité avec Java 17 ( 18/01/2022)
  • Activer la compilation à plusieurs niveaux par défaut pour améliorer les temps de compilation des polyglottes ( 2021-04-20)

Natif du printemps

En d'autres termes, Spring Native permet d'utiliser le compilateur d'images natives de GraalVM pour transformer les applications Spring en exécutables natifs.

Ce processus implique l'analyse statique de votre application au moment de la compilation pour identifier toutes les méthodes de votre application accessibles depuis le point d'entrée.

Cela revient essentiellement à créer une application "univers fermé" pour votre application, dans laquelle tout le code est supposé être connu au moment de la compilation et où aucun nouveau code ne peut être chargé lors de l'exécution.

Il est important de noter que la génération d'images natives est un processus exigeant en mémoire qui prend plus de temps que la compilation d'une application standard et qui impose des restrictions sur certains aspects de Java.

Dans certains cas, aucune modification de code n'est requise pour qu'une application fonctionne avec Spring Native. Cependant, dans certaines situations, une configuration native spécifique doit fonctionner pour fonctionner correctement. Dans de tels cas, Spring Native fournit souvent des indicateurs natifs pour simplifier ce processus.

3. Configuration/Préparation

Avant de commencer à implémenter Spring Native, nous devons créer et déployer notre application pour établir une référence de performance que nous pourrons comparer ultérieurement à la version native.

1. Créer le projet

Nous allons commencer par télécharger l'application sur start.spring.io:

curl https://start.spring.io/starter.zip -d dependencies=web \
           -d javaVersion=11 \
           -d bootVersion=2.6.4 -o io-native-starter.zip

Cette application de démarrage utilise Spring Boot 2.6.4, la dernière version compatible avec le projet Spring Native au moment de la rédaction de cet article.

Notez que depuis la version GraalVM 21.0.3, vous pouvez également utiliser Java 17 pour cet exemple. Nous utiliserons toujours Java 11 pour ce tutoriel afin de minimiser la configuration impliquée.

Une fois que nous avons zippé la ligne de commande, nous pouvons créer un sous-répertoire pour notre projet et décompresser le dossier:

mkdir spring-native

cd spring-native

unzip ../io-native-starter.zip

2. Modifications apportées au code

Une fois le projet ouvert, nous ajouterons rapidement un signe de vie et présenterons les performances du Spring Native Native.

Modifiez le fichier DemoApplication.java en procédant comme suit:

package com.example.demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.event.EventListener;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.time.Duration;
import java.time.Instant;

@RestController
@SpringBootApplication
public class DemoApplication {
    private static Instant startTime;
    private static Instant readyTime;

    public static void main(String[] args) {
        startTime = Instant.now();
                SpringApplication.run(DemoApplication.class, args);
    }

    @GetMapping("/")
    public String index() {
        return "Time between start and ApplicationReadyEvent: "
                + Duration.between(startTime, readyTime).toMillis()
                + "ms";
    }

    @EventListener(ApplicationReadyEvent.class)
    public void ready() {
                readyTime = Instant.now();
    }
}

À ce stade, notre application de référence est prête. N'hésitez donc pas à créer une image et à l'exécuter localement pour avoir une idée du temps de démarrage avant de le convertir en application native.

Pour créer notre image:

mvn spring-boot:build-image

Vous pouvez également utiliser docker images demo pour avoir une idée de la taille de l'image de référence: 6ecb403e9af1365e.png.

Pour exécuter l'application:

docker run --rm -p 8080:8080 demo:0.0.1-SNAPSHOT

3. Déployer l'application de référence

Maintenant que nous avons notre application, nous la déployons et prenons note de l'heure que nous comparerons à l'heure de démarrage de l'application native plus tard.

Selon le type d'application que vous développez, il existe plusieurs possibilités d'hébergement.vos données dans le menu déroulant ;

Cependant, comme notre exemple est une application Web très simple, nous pouvons rester simples et nous appuyer sur Cloud Run.

Si vous suivez les instructions sur votre propre machine, assurez-vous que l'outil gcloud CLI est installé et mis à jour.

Si vous êtes dans Cloud Shell et que vous devrez le faire tous, vous pouvez simplement exécuter la commande suivante dans le répertoire source:

gcloud run deploy

4. Configuration de l'application

1. Configurer nos dépôts Maven

Ce projet étant encore en phase expérimentale, nous devons configurer notre application pour qu'elle puisse trouver les artefacts expérimentaux, qui ne sont pas disponibles dans le dépôt central de Maven.

Pour cela, vous devrez ajouter les éléments suivants à votre fichier pom.xml, ce que vous pouvez faire dans l'éditeur de votre choix.

Ajoutez les sections suivantes aux dépôts et plug-ins dépôts à votre pom:

<repositories>
    <repository>
        <id>spring-release</id>
        <name>Spring release</name>
        <url>https://repo.spring.io/release</url>
    </repository>
</repositories>

<pluginRepositories>
    <pluginRepository>
        <id>spring-release</id>
        <name>Spring release</name>
        <url>https://repo.spring.io/release</url>
    </pluginRepository>
</pluginRepositories>

2. Ajouter nos dépendances

Ensuite, ajoutez la dépendance Spring Native, requise pour exécuter une application Springer en tant qu'image native. Remarque: Cette étape n'est pas nécessaire si vous utilisez Gradle.

<dependencies>
    <!-- ... -->
    <dependency>
        <groupId>org.springframework.experimental</groupId>
        <artifactId>spring-native</artifactId>
        <version>0.11.2</version>
    </dependency>
</dependencies>

3. Ajouter et activer des plug-ins

Ajoutez maintenant le plug-in AOT pour améliorer la compatibilité et l'empreinte de l'image native ( en savoir plus):

<plugins>
    <!-- ... -->
    <plugin>
        <groupId>org.springframework.experimental</groupId>
        <artifactId>spring-aot-maven-plugin</artifactId>
        <version>0.11.2</version>
        <executions>
            <execution>
                <id>generate</id>
                <goals>
                    <goal>generate</goal>
                </goals>
            </execution>
        </executions>
    </plugin>
</plugins>

Nous allons à présent mettre à jour le plug-in spring-boot-maven-plugin pour activer la compatibilité avec les images natives et utiliser le compilateur Paketo pour créer notre image native:

<plugins>
    <!-- ... -->
    <plugin>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-maven-plugin</artifactId>
        <configuration>
            <image>
                <builder>paketobuildpacks/builder:tiny</builder>
                <env>
                    <BP_NATIVE_IMAGE>true</BP_NATIVE_IMAGE>
                </env>
            </image>
        </configuration>
    </plugin>
</plugins>

Notez que l'image du petit compilateur n'est que l'une des options disponibles. Elle convient parfaitement à notre cas d'utilisation, car elle contient très peu de bibliothèques et d'utilitaires supplémentaires, ce qui permet de minimiser la surface d'attaque.

Par exemple, si vous créez une application qui nécessite d'accéder à des bibliothèques C courantes ou si vous n'êtes pas encore certain des exigences concernant votre application, l'outil de création complète peut être mieux adapté.

5. Générer et exécuter une application native

Une fois ces opérations en place, nous pourrons créer notre image et exécuter notre application native compilée.

Avant d'exécuter la compilation, tenez compte des points suivants:

  • Cela prendra plus de temps qu'un build standard (quelques minutes). D420322893640701.png
  • Ce processus de compilation peut prendre beaucoup de mémoire (quelques gigaoctets) cda24e1eb11fdbea.png
  • Ce processus de compilation nécessite que le daemon Docker soit accessible
  • Dans cet exemple, nous allons traiter le processus manuellement, mais vous pouvez également configurer les phases de compilation pour qu'elles déclenchent automatiquement un profil de compilation natif.

Pour créer notre image:

mvn spring-boot:build-image

Une fois cette opération effectuée, nous pouvons commencer à voir l'application native en action.

Pour exécuter l'application:

docker run --rm -p 8080:8080 demo:0.0.1-SNAPSHOT

À ce stade, nous sommes en mesure de visualiser les deux côtés de l'équation de l'application native.

Nous avons accordé un peu de temps et de temps supplémentaire à l'utilisation de la mémoire lors de la compilation, mais en échange, nous obtenons une application qui démarre beaucoup plus rapidement et consomme beaucoup moins de mémoire (en fonction de la charge de travail).

Si nous exécutons docker images demo pour comparer la taille de l'image native à celle d'origine, nous constatons une réduction spectaculaire:

E667F65A011C1328.png

Notez également que dans des cas d'utilisation plus complexes, des modifications supplémentaires sont nécessaires pour informer le compilateur AOT des actions à effectuer au moment de l'exécution de votre application. C'est pourquoi certaines charges de travail prévisibles (telles que les tâches par lot) peuvent parfaitement convenir à ce type de processus, tandis que d'autres peuvent avoir un impact plus important.

6. Déployer notre application native

Pour déployer notre application sur Cloud Run, nous devons intégrer notre image native à un gestionnaire de packages comme Artifact Registry.

1. Préparer notre dépôt Docker

Pour lancer ce processus, nous pouvons créer un dépôt:

gcloud artifacts repositories create native-image-docker-repo --repository-format=docker \
--location=us-central1 --description="Repository for our native images"

Ensuite, nous devons nous assurer que nous sommes authentifiés à déployer dans notre nouveau registre.

La CLI gcloud peut simplifier complètement ce processus:

gcloud auth configure-docker us-central1-docker.pkg.dev

2. Transférer notre image vers Artifact Registry

Ensuite, nous allons ajouter un tag à notre image:

export PROJECT_ID=$(gcloud config list --format 'value(core.project)')

docker tag  demo:0.0.1-SNAPSHOT \
us-central1-docker.pkg.dev/$PROJECT_ID/native-image-docker-repo/quickstart-image:tag2

Nous pouvons ensuite utiliser docker push pour l'envoyer à Artifact Registry:

docker push us-central1-docker.pkg.dev/$PROJECT_ID/native-image-docker-repo/quickstart-image:tag2

3. Déployer sur Cloud Run

Nous sommes maintenant prêts à déployer l'image que nous avons stockée dans Artifact Registry sur Cloud Run:

gcloud run deploy --image us-central1-docker.pkg.dev/$PROJECT_ID/native-image-docker-repo/quickstart-image:tag2

Puisque nous avons créé et déployé notre application en tant qu'image native, nous pouvons être sûrs qu'elle utilise efficacement les coûts d'infrastructure liés à son exécution.

N'hésitez pas à comparer le temps de démarrage de notre application de référence à celui de la nouvelle application native.

6dde63d35959b1bb.png

7. Résumé/Nettoyage

Félicitations pour la création et le déploiement d'une application Spring Native sur Google Cloud.

Nous espérons que ce tutoriel vous incitera à vous familiariser avec le projet Spring Native et à en tenir compte pour répondre à vos besoins à l'avenir.

Facultatif: Nettoyer et/ou désactiver le service

Que vous ayez créé un projet Google Cloud pour cet atelier de programmation ou que vous en réutilisiez un, veillez à éviter les frais inutiles liés aux ressources que nous avons utilisées.

Vous pouvez supprimer ou désactiver les services Cloud Run que nous avons créés, supprimer l'image que nous avons hébergée ou arrêter le projet dans son intégralité.

8. Autres ressources

Il s'agit d'un nouveau projet expérimental, mais il existe déjà une multitude de ressources utiles pour aider les utilisateurs de la première heure à résoudre les problèmes et à s'impliquer:

Autres ressources

Vous trouverez ci-dessous des ressources en ligne qui peuvent être pertinentes pour ce tutoriel:

Licence

Ce document est publié sous une licence Creative Commons Attribution 2.0 Generic.