Native Spring di Google Cloud

1. Ringkasan

Dalam codelab ini, kita akan mempelajari project Spring Native, membangun aplikasi yang menggunakannya, dan men-deploy-nya di Google Cloud.

Kita akan membahas komponennya, histori terbaru project, beberapa kasus penggunaan, dan tentu saja langkah-langkah yang diperlukan agar Anda dapat menggunakannya dalam project Anda.

Project Spring Native saat ini dalam fase eksperimental, sehingga memerlukan beberapa konfigurasi khusus untuk memulai. Namun, seperti yang diumumkan di SpringOne 2021, Spring Native akan diintegrasikan ke dalam Spring Framework 6.0 dan Spring Boot 3.0 dengan dukungan kelas satu, sehingga ini adalah waktu yang tepat untuk melihat lebih dekat project ini beberapa bulan sebelum rilisnya.

Meskipun kompilasi just-in-time telah dioptimalkan dengan sangat baik untuk hal-hal seperti proses yang berjalan lama, ada kasus penggunaan tertentu yang membuat aplikasi yang dikompilasi ahead-of-time berperforma lebih baik, yang akan kita bahas selama codelab.

Anda akan mempelajari cara

  • Menggunakan Cloud Shell
  • Mengaktifkan Cloud Run API
  • Membuat dan men-deploy aplikasi Spring Native
  • Men-deploy aplikasi tersebut ke Cloud Run

Yang akan Anda butuhkan

Survei

Bagaimana Anda akan menggunakan tutorial ini?

Hanya membacanya Membacanya dan menyelesaikan latihan

Bagaimana penilaian Anda terhadap pengalaman dengan Java?

Pemula Menengah Mahir

Bagaimana penilaian Anda terhadap pengalaman menggunakan layanan Google Cloud?

Pemula Menengah Mahir

2. Latar belakang

Project Spring Native menggunakan beberapa teknologi untuk memberikan performa aplikasi native kepada developer.

Untuk memahami Spring Native sepenuhnya, sebaiknya pahami beberapa teknologi komponen ini, apa yang dapat dilakukan untuk kita, dan bagaimana teknologi ini bekerja sama di sini.

Kompilasi AOT

Saat developer menjalankan javac secara normal pada waktu kompilasi, kode sumber .java kita dikompilasi menjadi file .class yang ditulis dalam bytecode. Bytecode ini hanya dimaksudkan untuk dipahami oleh Java Virtual Machine, sehingga JVM harus menafsirkan kode ini di mesin lain agar kita dapat menjalankan kode kita.

Proses ini memberikan portabilitas tanda tangan Java kepada kita, yang memungkinkan kita untuk "menulis sekali dan menjalankan di mana saja", tetapi mahal jika dibandingkan dengan menjalankan kode native.

Untungnya, sebagian besar implementasi JVM menggunakan kompilasi just-in-time untuk mengurangi biaya interpretasi ini. Hal ini dicapai dengan menghitung pemanggilan untuk suatu fungsi, dan jika sering dipanggil untuk melewati nilai minimum ( 10.000 secara default), fungsi tersebut akan dikompilasi ke kode native saat runtime untuk mencegah interpretasi mahal lebih lanjut.

Kompilasi ahead-of-time mengambil pendekatan yang berlawanan, dengan mengompilasi semua kode yang dapat dijangkau ke dalam executable native pada waktu kompilasi. Hal ini menukar portabilitas dengan efisiensi memori dan peningkatan performa lainnya saat runtime.

5042e8e62a05a27.png

Tentu saja ini adalah pertukaran, dan tidak selalu layak untuk dilakukan. Namun, kompilasi AOT dapat bersinar dalam kasus penggunaan tertentu seperti:

  • Aplikasi yang berumur pendek dan waktu startupnya penting
  • Lingkungan yang sangat terbatas memori dan JIT mungkin terlalu mahal

Sebagai fakta menarik, kompilasi AOT diperkenalkan sebagai fitur eksperimental di JDK 9, meskipun implementasi ini mahal untuk dipertahankan, dan tidak pernah cukup populer, sehingga dihapus secara diam-diam di Java 17 untuk mendukung developer yang hanya menggunakan GraalVM.

GraalVM

GraalVM adalah distribusi JDK open source yang sangat dioptimalkan dan memiliki waktu startup yang sangat cepat, kompilasi image native AOT, dan kemampuan polyglot yang memungkinkan developer menggabungkan beberapa bahasa ke dalam satu aplikasi.

GraalVM dalam pengembangan aktif, mendapatkan kemampuan baru dan meningkatkan kemampuan yang ada setiap saat, jadi saya mendorong developer untuk terus memantau.

Beberapa pencapaian terbaru adalah:

  • Output build image native baru yang mudah digunakan ( 2021-01-18)
  • Dukungan Java 17 ( 2022-01-18)
  • Mengaktifkan kompilasi multi-tingkat secara default untuk meningkatkan waktu kompilasi polyglot ( 2021-04-20)

Spring Native

Sederhananya, Spring Native memungkinkan penggunaan compiler image native GraalVM untuk mengubah aplikasi Spring menjadi executable native.

Proses ini melibatkan pelaksanaan analisis statis aplikasi Anda pada waktu kompilasi untuk menemukan semua metode dalam aplikasi Anda yang dapat dijangkau dari titik entri.

Hal ini pada dasarnya membuat konsepsi "dunia tertutup" aplikasi Anda, yang mengasumsikan bahwa semua kode diketahui pada waktu kompilasi, dan tidak ada kode baru yang diizinkan untuk dimuat saat runtime.

Penting untuk diperhatikan bahwa pembuatan image native adalah proses yang menggunakan banyak memori dan memerlukan waktu lebih lama daripada mengompilasi aplikasi biasa, serta memberlakukan batasan pada aspek tertentu dari Java.

Dalam beberapa kasus, tidak ada perubahan kode yang diperlukan agar aplikasi dapat berfungsi dengan Spring Native. Namun, beberapa situasi memerlukan konfigurasi native tertentu agar berfungsi dengan baik. Dalam situasi tersebut, Spring Native sering kali memberikan Native Hints untuk menyederhanakan proses ini.

3. Penyiapan/Prakerja

Sebelum mulai menerapkan Spring Native, kita harus membuat dan men-deploy aplikasi untuk menetapkan dasar pengukuran performa yang dapat kita bandingkan dengan versi native nanti.

1. Membuat project

Kita akan mulai dengan mendapatkan aplikasi dari 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

Aplikasi starter ini menggunakan Spring Boot 2.6.4, yang merupakan versi terbaru yang didukung oleh project spring-native pada saat penulisan.

Perhatikan bahwa sejak rilis GraalVM 21.0.3, Anda juga dapat menggunakan Java 17 untuk sampel ini. Kita akan tetap menggunakan Java 11 untuk tutorial ini guna meminimalkan konfigurasi yang terlibat.

Setelah memiliki zip di command line, kita dapat membuat subdirektori untuk project kita dan mengekstrak folder di dalamnya:

mkdir spring-native

cd spring-native

unzip ../io-native-starter.zip

2. Perubahan kode

Setelah project terbuka, kita akan dengan cepat menambahkan tanda kehidupan dan menampilkan performa Spring Native setelah kita menjalankannya.

Edit DemoApplication.java agar sesuai dengan ini:

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

Pada tahap ini, aplikasi dasar pengukuran kita siap digunakan, jadi jangan ragu untuk mem-build image dan menjalankannya secara lokal untuk mendapatkan gambaran tentang waktu startup sebelum kita mengonversinya ke aplikasi native.

Untuk mem-build image kita:

mvn spring-boot:build-image

Anda juga dapat menggunakan docker images demo untuk mendapatkan gambaran tentang ukuran image dasar pengukuran: 6ecb403e9af1475e.png

Untuk menjalankan aplikasi kita:

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

3. Men-deploy aplikasi dasar pengukuran

Setelah memiliki aplikasi, kita akan men-deploy-nya dan mencatat waktu, yang akan kita bandingkan dengan waktu startup aplikasi native kita nanti.

Bergantung pada jenis aplikasi yang Anda buat, ada beberapa cara untuk menghosting aplikasi Anda.

Namun, karena contoh kita adalah aplikasi web yang sangat sederhana dan mudah, kita dapat menyederhanakan dan mengandalkan Cloud Run.

Jika Anda mengikuti di mesin Anda sendiri, pastikan untuk menginstal dan mengupdate alat gcloud CLI.

Jika Anda menggunakan Cloud Shell, semuanya akan diurus dan Anda cukup menjalankan perintah berikut di direktori sumber:

gcloud run deploy

4. Konfigurasi Aplikasi

1. Mengonfigurasi repositori Maven kita

Karena project ini masih dalam fase eksperimental, kita harus mengonfigurasi aplikasi kita agar dapat menemukan artefak eksperimental, yang tidak tersedia di repositori pusat maven.

Hal ini akan melibatkan penambahan elemen berikut ke pom.xml kita, yang dapat Anda lakukan di editor pilihan Anda.

Tambahkan bagian repositories dan pluginRepositories berikut ke pom kita:

<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. Menambahkan dependensi kita

Selanjutnya, tambahkan dependensi spring-native, yang diperlukan untuk menjalankan aplikasi Spring sebagai image native. Catatan: langkah ini tidak diperlukan jika Anda menggunakan Gradle

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

3. Menambahkan/mengaktifkan plugin kita

Sekarang tambahkan plugin AOT untuk meningkatkan kompatibilitas dan footprint image native ( Baca selengkapnya):

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

Sekarang kita akan mengupdate spring-boot-maven-plugin untuk mengaktifkan dukungan image native dan menggunakan builder paketo untuk mem-build image native kita:

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

Perhatikan bahwa image builder kecil hanyalah salah satu dari beberapa opsi. Ini adalah pilihan yang baik untuk kasus penggunaan kita karena memiliki sangat sedikit library dan utilitas tambahan, yang membantu meminimalkan permukaan serangan kita.

Jika, misalnya, Anda membuat aplikasi yang memerlukan akses ke beberapa library C umum, atau Anda belum yakin dengan persyaratan aplikasi Anda, builder lengkap mungkin lebih cocok.

5. Mem-build dan Menjalankan aplikasi native

Setelah semuanya siap, kita akan dapat mem-build image dan menjalankan aplikasi native yang dikompilasi.

Sebelum menjalankan build, berikut beberapa hal yang perlu diingat:

  • Proses ini akan memerlukan waktu lebih lama daripada build biasa (beberapa menit) d420322893640701.png
  • Proses build ini dapat menggunakan banyak memori (beberapa gigabyte) cda24e1eb11fdbea.png
  • Proses build ini memerlukan daemon Docker agar dapat dijangkau
  • Meskipun dalam contoh ini kita menjalani proses secara manual, Anda juga dapat mengonfigurasi fase build untuk otomatis memicu profil build native.

Untuk mem-build image kita:

mvn spring-boot:build-image

Setelah di-build, kita siap melihat aplikasi native beraksi.

Untuk menjalankan aplikasi kita:

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

Pada tahap ini, kita berada dalam posisi yang tepat untuk melihat kedua sisi persamaan aplikasi native.

Kita telah mengorbankan sedikit waktu dan penggunaan memori tambahan pada waktu kompilasi, tetapi sebagai gantinya kita mendapatkan aplikasi yang dapat dimulai jauh lebih cepat, dan menggunakan memori yang jauh lebih sedikit (bergantung pada beban kerja).

Jika kita menjalankan docker images demo untuk membandingkan ukuran image native dengan yang asli, kita dapat melihat pengurangan yang dramatis:

e667f65a011c1328.png

Kita juga harus mencatat bahwa dalam kasus penggunaan yang lebih kompleks, ada modifikasi tambahan yang diperlukan untuk memberi tahu compiler AOT tentang apa yang akan dilakukan aplikasi Anda saat runtime. Oleh karena itu, beban kerja tertentu yang dapat diprediksi (seperti tugas batch) mungkin sangat cocok untuk hal ini, sementara yang lain mungkin lebih sulit.

6. Men-deploy aplikasi native kita

Untuk men-deploy aplikasi kita ke Cloud Run, kita harus memasukkan image native kita ke dalam pengelola paket seperti Artifact Registry.

1. Menyiapkan repositori Docker kita

Kita dapat memulai proses ini dengan membuat repositori:

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

Selanjutnya, kita harus memastikan bahwa kita diautentikasi untuk mengirim ke registry baru kita.

gcloud CLI dapat menyederhanakan proses tersebut:

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

2. Mengirim image kita ke Artifact Registry

Selanjutnya, kita akan memberi tag pada image kita:

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

Kemudian, kita dapat menggunakan docker push untuk mengirimkannya ke Artifact Registry:

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

3. Men-deploy ke Cloud Run

Kita sekarang siap men-deploy image yang telah kita simpan di Artifact Registry ke Cloud Run:

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

Karena kita telah mem-build dan men-deploy aplikasi kita sebagai image native, kita dapat yakin bahwa aplikasi kita menggunakan biaya infrastruktur kita dengan sangat baik saat dijalankan.

Jangan ragu untuk membandingkan waktu startup aplikasi dasar pengukuran kita dengan aplikasi native baru ini untuk Anda sendiri.

6dde63d35959b1bb.png

7. Ringkasan/Pembersihan

Selamat telah mem-build dan men-deploy aplikasi Spring Native di Google Cloud.

Semoga tutorial ini mendorong Anda untuk lebih memahami project Spring Native dan mengingatnya jika memenuhi kebutuhan Anda di masa mendatang.

Opsional: Membersihkan dan/atau menonaktifkan layanan

Baik Anda membuat project Google Cloud untuk codelab ini atau menggunakan kembali project yang sudah ada, berhati-hatilah untuk menghindari biaya yang tidak perlu dari resource yang kita gunakan.

Anda dapat menghapus atau menonaktifkan layanan Cloud Run yang kita buat, menghapus image yang kita hosting, atau mematikan seluruh project.

8. Referensi lainnya

Meskipun project Spring Native saat ini merupakan project baru dan eksperimental, sudah ada banyak resource yang bagus untuk membantu pengguna awal memecahkan masalah dan terlibat:

Referensi lainnya

Berikut adalah resource online yang mungkin relevan untuk tutorial ini:

Lisensi

Karya ini dilisensikan berdasarkan Lisensi Umum Creative Commons Attribution 2.0.