1. Omówienie
W tym ćwiczeniu dowiesz się więcej o projekcie Spring Native, zbudujesz aplikację, która go używa, i wdrożysz ją w Google Cloud.
Omówimy jego komponenty, historię projektu, kilka przypadków użycia i oczywiście czynności, które należy wykonać, aby używać go w swoich projektach.
Projekt Spring Native jest obecnie w fazie eksperymentalnej, więc aby rozpocząć pracę, będzie wymagać określonej konfiguracji. Jednak zgodnie z zapowiedzią na konferencji SpringOne 2021, Spring Native ma zostać zintegrowany z Spring Framework 6.0 i Spring Boot 3.0 z najlepszym wsparciem, więc jest to idealny moment, aby przyjrzeć się bliżej temu projektowi na kilka miesięcy przed jego wydaniem.
Kompilacja w czasie wykonywania została bardzo dobrze zoptymalizowana pod kątem takich procesów jak długotrwałe wykonywanie, ale w pewnych przypadkach aplikacje skompilowane z wyprzedzeniem działają jeszcze lepiej. Omówimy to w ramach warsztatu.
Dowiesz się, jak
- Korzystanie z Cloud Shell
- Włączanie interfejsu Cloud Run API
- Tworzenie i wdrażanie aplikacji natywnej Spring
- Wdrażanie takiej aplikacji w Cloud Run
Czego potrzebujesz
- projekt Google Cloud Platform z aktywnym kontem rozliczeniowym GCP.
- zainstalowany interfejs wiersza poleceń gcloud lub dostęp do Cloud Shell.
- Podstawowe umiejętności programowania w Javie i XML
- praktyczna znajomość typowych poleceń systemu Linux;
Ankieta
Jak będziesz korzystać z tego samouczka?
Jak oceniasz swoje wrażenia z korzystania z języka Java?
Jak oceniasz korzystanie z usług Google Cloud?
2. Tło
Projekt Spring Native korzysta z kilku technologii, aby zapewnić deweloperom wydajność natywnych aplikacji.
Aby w pełni zrozumieć Spring Native, warto poznać kilka technologii komponentów, ich możliwości i sposób współpracy.
Kompilacja AOT
Gdy deweloperzy uruchamiają javac normalnie w czasie kompilacji, nasz kod źródłowy w formacie .java jest kompilowany do plików .class, które są napisane w bajtowym kodzie. Ten kod bajtowy jest zrozumiały tylko dla maszyny wirtualnej Java, więc aby można było go uruchomić, JVM musi go interpretować na innych maszynach.
To właśnie zapewnia przenośność podpisu Java, która pozwala nam „pisać raz i uruchamiać wszędzie”. Jest to jednak kosztowne w porównaniu z uruchamianiem kodu natywnych.
Na szczęście większość implementacji JVM korzysta z kompilacji just-in-time, aby ograniczyć koszty interpretacji. Jest to możliwe dzięki zliczaniu wywołań funkcji. Jeśli jest ona wywoływana wystarczająco często, aby przekroczyć określony próg ( domyślnie 10 tys. razów), jest kompilowana na kod natywny w czasie wykonywania, aby zapobiec dalszej kosztownej interpretacji.
Kompilacja z wyprzedzeniem działa na odwrót: kompiluje cały dostępny kod do natywnego pliku wykonywalnego w momencie kompilacji. W zamian za przenośność uzyskuje się wydajność pamięci i inne korzyści w zakresie wydajności w czasie wykonywania.
To oczywiście kompromis, który nie zawsze jest opłacalny. Kompilacja AOT może jednak być przydatna w pewnych przypadkach, np.:
- aplikacje o krótkim czasie działania, w których czas uruchamiania jest ważny;
- Środowiska o bardzo ograniczonej ilości pamięci, w których kompilacja Just-In-Time może być zbyt kosztowna
Na marginesie: kompilacja AOT została wprowadzona jako funkcja eksperymentalna w JDK 9, ale jej utrzymanie było kosztowne i nigdy nie zyskała popularności, więc została po cichu usunięta w Java 17 na rzecz GraalVM.
GraalVM
GraalVM to zoptymalizowana dystrybucja JDK na licencji open source, która charakteryzuje się bardzo szybkim czasem uruchamiania, kompilacją natywnych obrazów AOT i możliwościami wielojęzycznymi, które umożliwiają deweloperom łączenie wielu języków w jednej aplikacji.
GraalVM jest w trakcie aktywnego rozwoju, stale zyskując nowe możliwości i ulepszając te istniejące, więc zachęcam deweloperów do śledzenia postępów.
Oto kilka ostatnich kamieni milowych:
- Nowy, przyjazny dla użytkownika format wyjściowy natywnych obrazów ( 18.01.2021)
- Obsługa Java 17 ( 18.01.2022)
- Włączanie kompilacji wielopoziomowej domyślnie w celu skrócenia czasu kompilacji wielojęzycznej ( 2021-04-20)
Spring Native
Krótko mówiąc, Spring Native umożliwia korzystanie z kompilatora natywnych obrazów GraalVM do przekształcania aplikacji Spring w natywne pliki wykonywalne.
Polega on na przeprowadzeniu statycznej analizy aplikacji w czasie kompilacji w celu znalezienia wszystkich metod w aplikacji, które są dostępne z punktu wejścia.
W podstawie tworzy to koncepcję „zamkniętego świata” aplikacji, w której zakłada się, że cały kod jest znany w czasie kompilacji, a w czasie wykonywania nie można wczytywać nowego kodu.
Pamiętaj, że generowanie natywnych obrazów to proces wymagający dużej ilości pamięci, który trwa dłużej niż kompilacja zwykłej aplikacji. Wymusza on też ograniczenia w niektórych aspektach Javy.
W niektórych przypadkach aplikacja nie wymaga żadnych zmian kodu, aby działać z Spring Native. W niektórych sytuacjach prawidłowe działanie wymaga jednak odpowiedniej konfiguracji natywnej. W takich sytuacjach Spring Native często udostępnia wskazówki dotyczące aplikacji natywnych, aby uprościć ten proces.
3. Konfiguracja/wstępne przygotowanie
Zanim zaczniemy wdrażać Spring Native, musimy utworzyć i wdrażać aplikację, aby ustalić bazowy poziom wydajności, który później można porównać z wersją natywnych.
1. Tworzenie projektu
Na początek pobierz aplikację ze strony 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
Ta aplikacja startowa korzysta z Spring Boot 2.6.4, czyli najnowszej wersji, którą obsługuje projekt spring-native w momencie pisania tego tekstu.
Pamiętaj, że od wersji GraalVM 21.0.3 w tym przykładzie możesz też używać wersji Java 17. W tym samouczku nadal użyjemy Java 11, aby zminimalizować wymaganą konfigurację.
Gdy plik ZIP znajdzie się w wierszu poleceń, możemy utworzyć podfolder dla projektu i rozpakować w nim folder:
mkdir spring-native cd spring-native unzip ../io-native-starter.zip
2. Zmiany kodu
Gdy otworzymy projekt, szybko dodamy do niego sygnał życia i po jego uruchomieniu pokażemy skuteczność kampanii Spring Native.
Zmień plik DemoApplication.java tak, aby odpowiadał temu:
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();
}
}
Na tym etapie nasza podstawowa aplikacja jest gotowa do użycia, więc możesz utworzyć obraz i uruchomić go lokalnie, aby uzyskać ogólne pojęcie o czasie uruchamiania, zanim przekształcimy ją w natywną aplikację.
Aby utworzyć obraz:
mvn spring-boot:build-image
Aby dowiedzieć się, jaki powinien być rozmiar obrazu bazowego, możesz też użyć docker images demo
:
Aby uruchomić aplikację:
docker run --rm -p 8080:8080 demo:0.0.1-SNAPSHOT
3. Wdrażanie aplikacji podstawowej
Teraz, gdy mamy już aplikację, wdrożymy ją i zanotujemy czasy, które później porównamy z czasem uruchamiania natywnej aplikacji.
W zależności od typu tworzonej aplikacji możesz skorzystać z różnych usług hostowania.
Ponieważ jednak nasz przykład to bardzo prosta aplikacja internetowa, możemy zachować prostotę i polecić Cloud Run.
Jeśli wykonujesz te czynności na swoim komputerze, upewnij się, że masz zainstalowane i zaktualizowane narzędzie gcloud CLI.
Jeśli korzystasz z Cloud Shell, nie musisz się o to martwić. Wystarczy, że w katalogu źródłowym uruchomisz to polecenie:
gcloud run deploy
4. Konfiguracja aplikacji
1. Konfigurowanie repozytoriów Maven
Ten projekt jest jeszcze w fazie eksperymentalnej, więc musimy skonfigurować naszą aplikację, aby mogła znajdować eksperymentalne artefakty, które nie są dostępne w centralnym repozytorium Maven.
W tym celu dodaj te elementy do pliku pom.xml w dowolnym edytorze.
Dodaj do pliku pom te sekcje repozytoriów i pluginRepositories:
<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. Dodawanie zależności
Następnie dodaj zależność spring-native, która jest wymagana do uruchamiania aplikacji Spring jako obrazu natywnego. Uwaga: jeśli używasz Gradle, ten krok nie jest konieczny
<dependencies>
<!-- ... -->
<dependency>
<groupId>org.springframework.experimental</groupId>
<artifactId>spring-native</artifactId>
<version>0.11.2</version>
</dependency>
</dependencies>
3. Dodawanie i włączanie naszych wtyczek
Teraz dodaj wtyczkę AOT, aby poprawić zgodność i ślad natywnych obrazów ( więcej informacji):
<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>
Teraz zaktualizujemy wtyczkę spring-boot-maven, aby umożliwić obsługę obrazu natywnego, i użyjemy pakietu kompilacyjnego paketo do utworzenia obrazu natywnego:
<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>
Pamiętaj, że obraz tiny builder to tylko jedna z wielu opcji. Jest to dobry wybór w naszym przypadku, ponieważ zawiera bardzo niewiele dodatkowych bibliotek i narzędzi, co pomaga zminimalizować powierzchnię ataku.
Jeśli na przykład tworzysz aplikację, która potrzebuje dostępu do niektórych popularnych bibliotek C, lub nie masz jeszcze pewności co do wymagań aplikacji, full-builder może być lepszym rozwiązaniem.
5. Kompilowanie i uruchamianie aplikacji natywnej
Gdy wszystko będzie gotowe, powinniśmy móc skompilować obraz i uruchomić natywny, skompilowany program.
Zanim uruchomisz wersję, pamiętaj o tych kwestiach:
- Może to zająć więcej czasu niż zwykła kompilacja (kilka minut)
- Proces kompilacji może zużyć dużo pamięci (kilka gigabajtów)
- Ten proces kompilacji wymaga dostępu do demona Dockera.
- W tym przykładzie proces jest przeprowadzany ręcznie, ale możesz też skonfigurować fazy kompilacji, aby profil kompilacji natywnej był uruchamiany automatycznie.
Aby utworzyć obraz:
mvn spring-boot:build-image
Gdy to zrobisz, możesz zobaczyć naszą natywną aplikację w działaniu.
Aby uruchomić aplikację:
docker run --rm -p 8080:8080 demo:0.0.1-SNAPSHOT
W tej chwili mamy doskonały wgląd w obie strony równania dotyczącego natywnych aplikacji.
Zrezygnowaliśmy z trochę czasu i dodatkowego wykorzystania pamięci podczas kompilacji, ale w zamian otrzymujemy aplikację, która uruchamia się znacznie szybciej i korzysta z znacznie mniejszej ilości pamięci (w zależności od obciążenia pracą).
Jeśli uruchomimy docker images demo
, aby porównać rozmiar natywnego obrazu z oryginałem, zauważymy znaczne zmniejszenie:
Należy też pamiętać, że w bardziej złożonych przypadkach użycia konieczne są dodatkowe modyfikacje, aby poinformować kompilator AOT o tym, co aplikacja będzie robić w czasie wykonywania. Z tego powodu niektóre przewidywalne obciążenia (np. zadania zbiorcze) mogą być bardzo odpowiednie do tego, podczas gdy inne mogą wymagać większego wysiłku.
6. Wdrażanie aplikacji natywnej
Aby wdrożyć aplikację w Cloud Run, musimy przesłać nasz natywny obraz do menedżera pakietów, takiego jak Artifact Registry.
1. Przygotowywanie repozytorium Dockera
Proces ten rozpoczynamy od utworzenia repozytorium:
gcloud artifacts repositories create native-image-docker-repo --repository-format=docker \
--location=us-central1 --description="Repository for our native images"
Następnie musimy się upewnić, że mamy uprawnienia do przesyłania do naszego nowego rejestru.
Narzędzie gcloud CLI może nieco uprościć ten proces:
gcloud auth configure-docker us-central1-docker.pkg.dev
2. Przesyłanie obrazu do Artifact Registry
Teraz oznaczymy obraz tagiem:
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
Następnie możemy użyć docker push
, aby wysłać go do Artifact Registry:
docker push us-central1-docker.pkg.dev/$PROJECT_ID/native-image-docker-repo/quickstart-image:tag2
3. Wdrażanie w Cloud Run
Możemy teraz wdrożyć obraz przechowywany w Artifact Registry w Cloud Run:
gcloud run deploy --image us-central1-docker.pkg.dev/$PROJECT_ID/native-image-docker-repo/quickstart-image:tag2
Ponieważ nasza aplikacja została skompilowana i wdrożona jako obraz natywny, możemy mieć pewność, że podczas działania korzysta z naszych kosztów infrastruktury w najlepszy sposób.
Porównaj czas uruchamiania naszej podstawowej aplikacji z tą nową natywnych.
7. Podsumowanie/czyszczenie
Gratulujemy utworzenia i wdrożenia aplikacji Spring Native w Google Cloud.
Mamy nadzieję, że ten samouczek zachęci Cię do zapoznania się z projektem Spring Native i pamiętania o nim, jeśli w przyszłości będzie on spełniać Twoje potrzeby.
Opcjonalnie: czyszczenie lub wyłączenie usługi
Niezależnie od tego, czy utworzysz projekt Google Cloud na potrzeby tego samouczka, czy użyjesz istniejącego, pamiętaj, aby uniknąć zbędnych opłat za zasoby, których użyliśmy.
Możesz usunąć lub wyłączyć utworzone przez nas usługi Cloud Run, usunąć obraz, który hostujemy, lub wyłączyć cały projekt.
8. Dodatkowe materiały
Chociaż projekt Spring Native jest obecnie nowy i eksperymentalny, istnieje już wiele przydatnych materiałów, które pomogą użytkownikom wczesnej wersji rozwiązywać problemy i uczestniczyć w programie:
Dodatkowe materiały
Poniżej znajdziesz zasoby online, które mogą być przydatne w tym samouczku:
- Więcej informacji o wskazówkach dotyczących reklam natywnych
- Więcej informacji o GraalVM
- Jak wziąć udział
- Błąd braku pamięci podczas kompilowania obrazów natywnych
- Błąd uruchomienia aplikacji
Licencja
To zadanie jest licencjonowane na podstawie ogólnej licencji Creative Commons Attribution 2.0.