Spring Native in Google Cloud

1. Übersicht

In diesem Codelab lernen Sie das Spring Native-Projekt kennen, erstellen eine App, die es verwendet, und stellen sie in Google Cloud bereit.

Wir sehen uns die Komponenten, den jüngsten Projektverlauf, einige Anwendungsfälle und natürlich die Schritte an, die für die Verwendung in Ihren Projekten erforderlich sind.

Das Spring Native-Projekt befindet sich derzeit in der Testphase. Daher ist eine bestimmte Konfiguration erforderlich. Wie bei SpringOne 2021 angekündigt, ist Spring Native jedoch für die Integration in Spring Framework 6.0 und Spring Boot 3.0 mit erstklassigem Support vorgesehen. Daher ist dies der perfekte Zeitpunkt, um sich ein paar Monate vor der Veröffentlichung genauer mit dem Projekt zu befassen.

Die Just-in-Time-Kompilierung wurde zwar gut für Aspekte wie lang andauernde Prozesse optimiert, es gibt aber auch bestimmte Anwendungsfälle, in denen vorab kompilierte Anwendungen noch besser funktionieren, auf die wir im Codelab eingehen werden.

Sie werden lernen,

  • Cloud Shell verwenden
  • Cloud Run API aktivieren
  • Native Spring-Anwendung erstellen und bereitstellen
  • Anwendung in Cloud Run bereitstellen

Voraussetzungen

Umfrage

Wie möchten Sie diese Anleitung nutzen?

<ph type="x-smartling-placeholder"></ph> Nur bis zum Ende lesen Lies sie dir durch und absolviere die Übungen

Wie würden Sie Ihre Erfahrung mit Java bewerten?

<ph type="x-smartling-placeholder"></ph> Neuling Leicht fortgeschritten Kompetent

Wie würden Sie Ihre Erfahrungen im Umgang mit Google Cloud-Diensten bewerten?

<ph type="x-smartling-placeholder"></ph> Neuling Leicht fortgeschritten Kompetent

2. Hintergrund

Das Spring Native-Projekt nutzt verschiedene Technologien, um Entwicklern native Anwendungsleistungen bereitzustellen.

Zum besseren Verständnis von Spring Native ist es hilfreich, einige dieser Komponententechnologien zu verstehen, was sie für uns ermöglichen und wie sie hier zusammenwirken.

AOT-Kompilierung

Wenn Entwickler javac normal zum Kompilierungszeitpunkt ausführen, wird unser .java-Quellcode in .class-Dateien kompiliert, die in Bytecode geschrieben werden. Dieser Bytecode ist nur für die Java Virtual Machine gedacht, sodass die JVM diesen Code auf anderen Rechnern interpretieren muss, damit wir unseren Code ausführen können.

Dieser Prozess verleiht uns die Signaturportabilität von Java: „Einmal schreiben und überall ausführen“, ist aber im Vergleich zur Ausführung von nativem Code teuer.

Glücklicherweise verwenden die meisten Implementierungen der JVM die Just-in-Time-Kompilierung, um diese Interpretationskosten zu minimieren. Dies wird erreicht, indem die Aufrufe für eine Funktion gezählt werden. Wenn sie häufig genug aufgerufen wird, um einen Schwellenwert ( standardmäßig 10.000) zu überschreiten, wird sie zur Laufzeit in nativen Code kompiliert, um eine weitere teure Interpretation zu vermeiden.

Bei der Kompilierung im Voraus erfolgt der entgegengesetzte Ansatz, bei dem der gesamte erreichbare Code zum Zeitpunkt der Kompilierung in eine native ausführbare Datei kompiliert wird. Dabei wird die Portabilität gegen Arbeitsspeichereffizienz und andere Leistungssteigerungen während der Laufzeit geeinigt.

5042e8e62a05a27.png

Das ist natürlich ein Kompromiss und nicht immer lohnenswert. Die AOT-Kompilierung kann jedoch in bestimmten Anwendungsfällen besonders hervortreten, z. B.:

  • Kurzlebige Anwendungen, bei denen die Startzeit wichtig ist
  • Umgebungen mit starkem Arbeitsspeicherbedarf, in denen JIT zu teuer sein kann

Übrigens wurde die AOT-Kompilierung als experimentelle Funktion in JDK 9 eingeführt. Diese Implementierung war jedoch teuer in der Wartung und setzte sich nie ganz durch. Daher wurde sie in Java 17 stumm entfernt, zugunsten von Entwicklern, die nur GraalVM nutzten.

GraalVM

GraalVM ist eine stark optimierte Open-Source-JDK-Distribution, die extrem schnelle Startzeiten, native AOT-Bildkompilierung und Polyglot-Funktionen bietet, mit denen Entwickler mehrere Sprachen in einer einzigen Anwendung mischen können.

GraalVM befindet sich in der aktiven Entwicklung, indem es ständig neue Funktionen entwickelt und vorhandene verbessert. Daher empfehle ich Entwicklern, sich auf dem Laufenden zu halten.

Einige aktuelle Meilensteine:

  • Neue, nutzerfreundliche native Image-Build-Ausgabe ( 18.01.2021)
  • Java 17-Unterstützung ( 18.01.2022)
  • Standardmäßig wird die mehrstufige Kompilierung aktiviert, um die polyglotten Kompilierungszeiten zu verbessern ( 20.04.2021).

Spring nativ

Einfach ausgedrückt: Spring Native ermöglicht die Verwendung des nativen Bild-Compilers von GraalVM, um Spring-Anwendungen in native ausführbare Dateien zu verwandeln.

Dieser Prozess beinhaltet die Durchführung einer statischen Analyse Ihrer Anwendung während der Kompilierung, um alle Methoden in Ihrer Anwendung zu finden, die vom Einstiegspunkt aus erreichbar sind.

So entsteht im Grunde eine „geschlossene Welt“, Konzept Ihrer Anwendung, wobei davon ausgegangen wird, dass der gesamte Code zum Kompilierungszeitpunkt bekannt ist und während der Laufzeit kein neuer Code geladen werden darf.

Die native Bildgenerierung ist ein speicherintensiver Prozess, der länger dauert als das Kompilieren einer regulären Anwendung und bestimmte Einschränkungen für bestimmte Aspekte von Java auferlegt.

In einigen Fällen sind keine Codeänderungen erforderlich, damit eine Anwendung mit Spring Native funktioniert. In einigen Situationen ist jedoch eine spezielle native Konfiguration erforderlich, um richtig zu funktionieren. In diesen Fällen bietet Spring Native häufig native Hinweise, um diesen Vorgang zu vereinfachen.

3. Einrichtung/Vorarbeit

Bevor wir mit der Implementierung von Spring Native beginnen, müssen wir unsere App erstellen und bereitstellen, um eine Leistungs-Baseline festzulegen, die wir später mit der nativen Version vergleichen können.

1. Projekt erstellen

Zuerst laden wir unsere App von start.spring.io herunter:

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

Diese Einstiegs-App verwendet Spring Boot 2.6.4. Dies ist die aktuellste Version, die zum Zeitpunkt der Erstellung dieses nativen Frühlingsprojekts unterstützt wird.

Beachten Sie, dass Sie für dieses Beispiel auch Java 17 verwenden können, seit der Veröffentlichung von GraalVM 21.0.3. Wir verwenden auch für dieses Tutorial weiterhin Java 11, um die erforderliche Konfiguration zu minimieren.

Sobald wir die ZIP-Datei in der Befehlszeile haben, können wir ein Unterverzeichnis für unser Projekt erstellen und den Ordner darin entpacken:

mkdir spring-native

cd spring-native

unzip ../io-native-starter.zip

2. Codeänderungen

Sobald das Projekt eröffnet ist, fügen wir schnell ein Lebenszeichen hinzu und präsentieren die Performance Natives von Spring Native, sobald wir es durchführen.

Bearbeiten Sie die Datei DemoApplication.java entsprechend:

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();
    }
}

Zu diesem Zeitpunkt ist unsere grundlegende App einsatzbereit. Erstellen Sie also gerne ein Image und führen Sie es lokal aus, um eine Vorstellung von der Startzeit zu bekommen, bevor wir sie in eine native Anwendung konvertieren.

So erstellen Sie unser Image:

mvn spring-boot:build-image

Sie können auch docker images demo verwenden, um eine Vorstellung von der Größe des Basisbilds zu erhalten: 6ecb403e9af1475e.png

So führen Sie die Anwendung aus:

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

3. Referenzanwendung bereitstellen

Nachdem die App nun vorliegt, stellen wir sie bereit und notieren uns die Zeiten, die wir später mit den Startzeiten unserer nativen App vergleichen.

Je nach Art der Anwendung, die Sie erstellen, werden Ihre Inhalte auf unterschiedliche Weise gehostet.

Da unser Beispiel jedoch eine sehr einfache, unkomplizierte Webanwendung ist, können wir es einfach halten und uns auf Cloud Run verlassen.

Wenn Sie auf Ihrem eigenen Computer mitarbeiten, muss das gcloud CLI-Tool installiert und aktualisiert sein.

Wenn Sie sich in Cloud Shell befinden, können Sie einfach Folgendes im Quellverzeichnis ausführen:

gcloud run deploy

4. Anwendungskonfiguration

1. Maven-Repositories konfigurieren

Da sich dieses Projekt noch in der Testphase befindet, müssen wir unsere App so konfigurieren, dass sie experimentelle Artefakte finden kann, die nicht im zentralen Repository von Maven verfügbar sind.

Dazu müssen Sie die folgenden Elemente zu unserer pom.xml-Datei hinzufügen, was Sie im Editor Ihrer Wahl tun können.

Fügen Sie der pom-Datei die folgenden Repositories und PluginRepositories-Abschnitte hinzu:

<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. Abhängigkeiten hinzufügen

Fügen Sie als Nächstes die Spring-native Abhängigkeit hinzu, die zum Ausführen einer Spring-Anwendung als natives Image erforderlich ist. Hinweis: Dieser Schritt ist nicht erforderlich, wenn Sie Gradle verwenden.

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

3. Plug-ins hinzufügen/aktivieren

Fügen Sie jetzt das AOT-Plug-in hinzu, um die Kompatibilität und den Speicherbedarf nativer Images zu verbessern ( Weitere Informationen):

<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>

Jetzt aktualisieren wir das Spring-boot-maven-plugin, um die Unterstützung nativer Images zu aktivieren, und verwenden den Paketo-Builder, um das native Image zu erstellen:

<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>

Das kleine Builder-Bild ist nur eine von mehreren Optionen. Sie ist eine gute Wahl für unseren Anwendungsfall, da sie nur sehr wenige zusätzliche Bibliotheken und Dienstprogramme enthält, wodurch unsere Angriffsfläche minimiert wird.

Wenn Sie beispielsweise eine App erstellen, die Zugriff auf einige gängige C-Bibliotheken benötigt, oder Sie sich noch nicht sicher waren, welche Anforderungen Ihre App erfüllt, ist der Full-Builder möglicherweise die bessere Wahl.

5. Native Anwendung erstellen und ausführen

Sobald alles eingerichtet ist, sollten wir in der Lage sein, unser Image zu erstellen und unsere native, kompilierte App auszuführen.

Bevor Sie den Build ausführen, sollten Sie Folgendes beachten:

  • Dies dauert länger als ein regulärer Build (einige Minuten) d420322893640701.png
  • Dieser Build-Prozess benötigt viel Arbeitsspeicher (einige Gigabyte) cda24e1eb11fdbea.png
  • Für diesen Build-Prozess muss der Docker-Daemon erreichbar sein
  • Sie können Ihre Build-Phasen auch so konfigurieren, dass automatisch ein natives Build-Profil ausgelöst wird.

So erstellen Sie unser Image:

mvn spring-boot:build-image

Anschließend können wir die native App in Aktion sehen.

So führen Sie die Anwendung aus:

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

An diesem Punkt sind wir in einer guten Position, um beide Seiten der nativen Anwendungsgleichung zu sehen.

Wir haben bei der Kompilierung ein wenig Zeit und zusätzliche Arbeitsspeichernutzung aufgewendet, aber im Gegenzug erhalten wir eine Anwendung, die viel schneller startet und je nach Arbeitslast deutlich weniger Arbeitsspeicher verbraucht.

Wenn wir docker images demo ausführen, um die Größe des nativen Bildes mit dem Original zu vergleichen, stellen Sie eine deutliche Reduzierung fest:

e667f65a011c1328.png

Bei komplexeren Anwendungsfällen sind zusätzliche Änderungen erforderlich, um den AOT-Compiler darüber zu informieren, was Ihre App während der Laufzeit tun wird. Daher können bestimmte vorhersehbare Arbeitslasten (wie Batchjobs) dafür sehr gut geeignet sein, während andere einen größeren Anstieg bewirken können.

6. Native Anwendung bereitstellen

Um unsere Anwendung in Cloud Run bereitzustellen, müssen Sie das native Image in einen Paketmanager wie Artifact Registry übertragen.

1. Docker-Repository vorbereiten

Wir können diesen Prozess starten, indem wir ein Repository erstellen:

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

Als Nächstes sollten Sie sicherstellen, dass wir für die Übertragung in die neue Registry authentifiziert sind.

Die gcloud-Befehlszeile kann diesen Vorgang erheblich vereinfachen:

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

2. Image per Push an Artifact Registry übertragen

Als Nächstes taggen wir unser Bild:

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

Anschließend können wir sie mit docker push an Artifact Registry senden:

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

3. In Cloud Run bereitstellen

Jetzt können Sie das in Artifact Registry gespeicherte Image in Cloud Run bereitstellen:

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

Da wir unsere Anwendung als natives Image erstellt und bereitgestellt haben, können wir sicher sein, dass unsere Anwendung unsere Infrastrukturkosten während der Ausführung hervorragend nutzt.

Sie können die Startzeiten unserer Referenz-App mit dieser neuen nativen App vergleichen.

6dde63d35959b1bb.png

7. Zusammenfassung/Bereinigung

Herzlichen Glückwunsch zum Erstellen und Bereitstellen einer Spring Native-Anwendung in Google Cloud!

Diese Anleitung soll Sie hoffentlich motivieren, sich mit dem Spring Native-Projekt vertraut zu machen und es im Hinterkopf zu behalten, falls es in Zukunft Ihren Anforderungen entspricht.

Optional: Speicherplatz bereinigen und/oder Dienst deaktivieren

Unabhängig davon, ob Sie ein Google Cloud-Projekt für dieses Codelab erstellt oder ein vorhandenes wiederverwenden, sollten Sie darauf achten, unnötige Kosten durch die von uns genutzten Ressourcen zu vermeiden.

Sie können die von uns erstellten Cloud Run-Dienste löschen oder deaktivieren, das gehostete Image löschen oder das gesamte Projekt herunterfahren.

8. Zusätzliche Ressourcen

Obwohl es sich bei dem Spring Native-Projekt derzeit um ein neues und experimentelles Projekt handelt, gibt es bereits eine Fülle guter Ressourcen, die ersten Nutzern helfen, Probleme zu beheben und sich zu beteiligen:

Zusätzliche Ressourcen

Nachfolgend finden Sie Onlineressourcen, die für diese Anleitung relevant sein könnten:

Lizenz

Dieser Text ist mit einer Creative Commons Attribution 2.0 Generic License lizenziert.