Vert.x obraz natywny GraalVM 19.3.0+

Autor
Damian
Terlecki
6 minut
Java

Vert.x to całkiem przyjemny framework do budowania reaktywnych aplikacji sieciowych nie tylko na platformie Java, ale również dla takich języków jak JavaScript czy Ruby. Charakteryzuje się zaskakująco małym rozmiarem – pomijając serwer Netty ~ 3 MB – sam rdzeń, jak można znaleźć na głównej stronie, zajmuje jedynie 650 kB. Dzięki stosunkowo niewielkiej liczbie obiektów potrzebnych do startu aplikacji Vert.x świetnie nadaje się do budowy mikroserwisów.

Obraz natywny

Jeśli chcemy jeszcze bardziej dopieścić naszą aplikację zbudowaną na bazie Vert.x, możemy stworzyć jej obraz natywny przy użyciu GraalVM (kompilacja AOT – Ahead of Time). Takie podejście pozwoli nam jeszcze bardziej obniżyć ilość czasu potrzebnego na uruchomienie aplikacji, a zarazem zmniejszyć rozmiar wykorzystywanej pamięci. Do tego procesu musimy jednak znać przede wszystkim:

  • platformę docelową, na której będzie uruchamiana aplikacja;
  • które klasy powinny zostać zainicjalizowane podczas procesu budowania;
  • które klasy wykorzystują mechanizm refleksji.
Zrzut ekranu z logami z budowania obrazu natywnego

SubstrateVM (wewnętrzna nazwa projektu narzędzia native-image), wspiera użytkownika w przygotowaniu poprawnego obrazu natywnego. Część problemów rozwiązywana jest przez samo narzędzie za pomocą analizy statycznej. Pozostałe elementy użytkownik musi skonfigurować ręcznie za pomocą parametrów podanych do narzędzia bądź plików konfiguracyjnych umieszczanych w src/main/resources/META-INF/native-image/<nazwa_grupy_paczki>/<nazwa_artefaktu>/*.

Proces

Generalnie proces przygotowania obrazu natywnego wygląda następująco:

  1. Tworzymy aplikację w postaci tzw. fat jara (ze wszystkimi potrzebnymi zależnościami).
  2. Pobieramy GraalVM:
  1. Instalujemy narzędzie native-image: gu install native-image.
  2. Budujemy obraz z naszej paczki z punktu 1.
  3. Jeśli wszystko pójdzie bez problemu, to powinniśmy otrzymać natywny plik binarny do uruchomienia.

Kroki 2-5 (ale również i krok 1), możemy wrzucić sobie do dockera:

FROM oracle/graalvm-ce:20.2.0-java11 AS buildEnv
RUN gu install native-image

WORKDIR /workdir
COPY <fat_jar_zbudowany_na_hoście>.jar .
RUN native-image -cp <fat_jar_zbudowany_na_hoście>.jar \
    --no-server \
    --no-fallback \
    --enable-all-security-services \
    --allow-incomplete-classpath \
    -H:Name="<nazwa_wynikowego_pliku>" \
    <twoja.klasa.main>

# Tutaj możemy skorzystać z budowania multi-stage i wystartować z odchudzonego obrazu.
# W zależności jak bardzo odchudzony obraz wybierzemy, możemy potrzebować kilku nieobecnych bibliotek.
# Wymagane biblioteki możesz znaleźć za pomocą ldd.
# RUN ldd <nazwa_wynikowego_pliku>
# FROM gcr.io/distroless/base
# COPY --from=buildEnv /usr/lib64/libz.so.1 /lib/x86_64-linux-gnu/libz.so.1
# COPY --from=buildEnv "/usr/lib64/libstdc++.so.6" "/lib/x86_64-linux-gnu/libstdc++.so.6"
# COPY --from=buildEnv "/usr/lib64/libgcc_s.so.1" "/lib/x86_64-linux-gnu/libgcc_s.so.1"

EXPOSE 8080
CMD ["./<nazwa_wynikowego_pliku>"]

Dodatkowe materiały

Do zbudowania paczki z wszystkimi niezbędnymi zależnościami polecam użycie pluginów.

Gradle

Jeśli do budowania projektu preferujesz użycie Gradle, to vertx-gradle-plugin powinien ułatwić Ci budowanie gradle shadowJar, uruchamianie gradle vertxRun i debugowanie gradle vertxDebug aplikacji:

plugins {
    id "io.vertx.vertx-plugin" version "1.1.1"
}

vertx {
    mainVerticle = '<twoja.klasa.main>'
}

Opcjonalnie, możemy budować obraz natywny z poziomu Gradle przy pomocy pluginów graalvm-native-image-plugin bądź gradle-graal.

Maven

Mavenowców zainteresować może plugin vertx-maven-plugin.

<project>
  ...
  <build>
    <plugins>
        ...
        <plugin>
            <groupId>io.reactiverse</groupId>
            <artifactId>vertx-maven-plugin</artifactId>
            <version>1.0.22</version>
            <executions>
                <execution>
                    <id>vmp</id>
                    <goals>
                        <goal>initialize</goal>
                        <goal>package</goal>
                    </goals>
                </execution>
            </executions>
            <configuration>
                <redeploy>true</redeploy>
            </configuration>
        </plugin>
        ...
    </plugins>
  </build>
  ...
</project>

Konfigurację budowania obrazu możemy również przenieść do specjalnego pluginu native-image-maven-plugin.

Konfiguracja

Pełną konfigurację potrzebną do przygotowania obrazu natywnego (GraalVM 19.2.1) aplikacji Vert.x (3.8.2) znajdziesz w repozytorium graal-native-image-howto.

Uwaga: W przypadku wersji GraalVM 19.3.0+ i wyżej, do parametrów uruchomieniowych native-image należy dodać io.netty.resolver.dns.DnsServerAddressStreamProviders$DefaultProviderHolder (źródło).