Membangun Gambar Docker yang Dioptimalkan untuk Aplikasi Spring Boot

Kontainer telah menjadi cara pilihan untuk mengemas aplikasi dengan semua ketergantungan perangkat lunak dan sistem operasinya dan kemudian mengirimkannya ke lingkungan yang berbeda.

Artikel ini membahas berbagai cara untuk memasukkan aplikasi Spring Boot ke dalam container:

  • membuat gambar Docker menggunakan file Docker,
  • membuat image OCI dari sumber menggunakan Cloud-Native Buildpack,
  • dan pengoptimalan gambar run-time dengan memisahkan bagian-bagian JAR ke dalam lapisan yang berbeda menggunakan alat multi-tingkat.

 Contoh Kode

Artikel ini disertai dengan contoh kode kerja di GitHub .

Terminologi kontainer

Kita akan mulai dengan terminologi container yang digunakan dalam artikel:

  • Gambar kontainer: file dengan format tertentu. Kami akan mengubah aplikasi kami menjadi gambar kontainer dengan menjalankan alat build.
  • Wadah: Contoh gambar kontainer yang dapat dieksekusi.
  • Mesin kontainer: Proses daemon yang bertanggung jawab untuk menjalankan container.
  • Tuan rumah kontainer: Komputer host tempat mesin kontainer dijalankan.
  • Registri kontainer: Lokasi umum yang digunakan untuk mempublikasikan dan mendistribusikan gambar container.
  • standar OCIInisiatif Kontainer Terbuka (OCI) adalah struktur tata kelola yang ringan dan terbuka yang dibentuk dalam Linux Foundation. Spesifikasi Gambar OCI mendefinisikan standar industri untuk gambar kontainer dan format runtime untuk memastikan bahwa semua mesin kontainer dapat menjalankan gambar kontainer yang dibuat oleh alat pembangunan apa pun.

Untuk memasukkan aplikasi ke dalam container, kami membungkus aplikasi kami dalam gambar container dan mempublikasikan gambar tersebut ke registri bersama. Runtime kontainer mengambil gambar ini dari registri, membongkarnya, dan menjalankan aplikasi di dalamnya.

Spring Boot versi 2.3 menyediakan plugin untuk membuat image OCI.

Buruh pelabuhan adalah implementasi container yang paling umum digunakan, dan kami menggunakan Docker dalam contoh kami, jadi semua referensi container berikutnya dalam artikel ini akan merujuk ke Docker.

Membangun citra kontainer dengan cara tradisional

Membuat image Docker untuk aplikasi Spring Boot sangat mudah dengan menambahkan beberapa instruksi ke file Docker.

Kami pertama-tama membuat file JAR yang dapat dieksekusi dan, sebagai bagian dari instruksi file Docker, menyalin file JAR yang dapat dieksekusi di atas gambar JRE dasar setelah menerapkan pengaturan yang diperlukan.

Mari kita buat aplikasi Spring kita Inisialisasi Musim Semi dengan ketergantungan weblombokΠΈ actuator. Kami juga menambahkan pengontrol lainnya untuk menyediakan API GETmetode.

Membuat Dockerfile

Kami kemudian memasukkan aplikasi ini ke dalam container dengan menambahkan Dockerfile:

FROM adoptopenjdk:11-jre-hotspot
ARG JAR_FILE=target/*.jar
COPY ${JAR_FILE} application.jar
EXPOSE 8080
ENTRYPOINT ["java","-jar","/application.jar"]

File Docker kami berisi gambar dasar dari adoptopenjdk, di atasnya kita salin file JAR kita dan kemudian buka portnya, 8080yang akan mendengarkan permintaan.

Membangun aplikasi

Pertama, Anda perlu membuat aplikasi menggunakan Maven atau Gradle. Di sini kami menggunakan Maven:

mvn clean package

Ini menciptakan file JAR yang dapat dieksekusi untuk aplikasi tersebut. Kita perlu mengubah JAR yang dapat dieksekusi ini menjadi image Docker agar dapat dijalankan di mesin Docker.

Membuat gambar kontainer

Kami kemudian memasukkan file JAR yang dapat dieksekusi ini ke dalam image Docker dengan menjalankan perintah docker builddari direktori root proyek yang berisi Dockerfile yang dibuat sebelumnya:

docker build  -t usersignup:v1 .

Kita dapat melihat gambar kita di daftar menggunakan perintah:

docker images 

Output dari perintah di atas mencakup gambar kita usersignupbersama dengan gambar dasar, adoptopenjdkditentukan dalam file Docker kami.

REPOSITORY          TAG                 SIZE
usersignup          v1                  249MB
adoptopenjdk        11-jre-hotspot      229MB

Lihat lapisan di dalam gambar kontainer

Mari kita lihat tumpukan lapisan di dalam gambar. Kami akan menggunakan alat  menyelam, untuk melihat lapisan ini:

dive usersignup:v1

Berikut adalah bagian keluaran dari perintah Dive: 

Membangun Gambar Docker yang Dioptimalkan untuk Aplikasi Spring Boot

Seperti yang bisa kita lihat, lapisan aplikasi mengambil porsi besar dalam ukuran gambar. Kami ingin mengurangi ukuran lapisan ini di bagian berikut sebagai bagian dari pengoptimalan kami.

Membuat gambar kontainer menggunakan Buildpack

Paket perakitan (paket build) adalah istilah umum yang digunakan oleh berbagai penawaran Platform as a Service (PAAS) untuk membuat gambar kontainer dari kode sumber. Ini diluncurkan oleh Heroku pada tahun 2011 dan sejak itu diadopsi oleh Cloud Foundry, Google App Engine, Gitlab, Knative, dan beberapa lainnya.

Membangun Gambar Docker yang Dioptimalkan untuk Aplikasi Spring Boot

Keuntungan paket cloud build

Salah satu manfaat utama menggunakan Buildpack untuk membuat gambar adalah Perubahan konfigurasi image dapat dikelola secara terpusat (builder) dan disebarkan ke semua aplikasi menggunakan builder.

Paket build digabungkan erat ke platform. Cloud-Native Buildpacks memberikan standarisasi lintas platform dengan mendukung format image OCI, yang memastikan bahwa image dapat dijalankan oleh mesin Docker.

Menggunakan plugin Spring Boot

Plugin Spring Boot membuat image OCI dari sumber menggunakan Buildpack. Gambar dibuat menggunakan bootBuildImagetugas (Gradle) atau spring-boot:build-imagetarget (Maven) dan instalasi Docker lokal.

Kita dapat menyesuaikan nama gambar yang diperlukan untuk dikirim ke registri Docker dengan menentukan nama di dalamnya image tag:

<plugin>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-maven-plugin</artifactId>
  <configuration>
    <image>
      <name>docker.io/pratikdas/${project.artifactId}:v1</name>
    </image>
  </configuration>
</plugin>

Mari gunakan Maven untuk melakukannya build-imagetujuan untuk membuat aplikasi dan membuat gambar container. Kami tidak menggunakan Dockerfile apa pun saat ini.

mvn spring-boot:build-image

Hasilnya akan seperti ini:

[INFO] --- spring-boot-maven-plugin:2.3.3.RELEASE:build-image (default-cli) @ usersignup ---
[INFO] Building image 'docker.io/pratikdas/usersignup:v1'
[INFO] 
[INFO]  > Pulling builder image 'gcr.io/paketo-buildpacks/builder:base-platform-api-0.3' 0%
.
.
.. [creator]     Adding label 'org.springframework.boot.version'
.. [creator]     *** Images (c311fe74ec73):
.. [creator]           docker.io/pratikdas/usersignup:v1
[INFO] 
[INFO] Successfully built image 'docker.io/pratikdas/usersignup:v1'

Dari output kita melihat itu paketo Cloud-Native buildpackdigunakan untuk membuat gambar OCI yang berfungsi. Seperti sebelumnya, kita dapat melihat image terdaftar sebagai image Docker dengan menjalankan perintah:

docker images 

Kesimpulan:

REPOSITORY                             SIZE
paketobuildpacks/run                  84.3MB
gcr.io/paketo-buildpacks/builder      652MB
pratikdas/usersignup                  257MB

Membuat gambar container menggunakan Jib

Jib merupakan plugin pembuatan gambar dari Google yang menyediakan metode alternatif untuk membuat gambar container dari kode sumber.

Mengkonfigurasi jib-maven-plugindi pom.xml:

      <plugin>
        <groupId>com.google.cloud.tools</groupId>
        <artifactId>jib-maven-plugin</artifactId>
        <version>2.5.2</version>
      </plugin>

Selanjutnya, kita jalankan plugin Jib menggunakan perintah Maven untuk membangun aplikasi dan membuat image container. Seperti sebelumnya, kami tidak menggunakan file Docker apa pun di sini:

mvn compile jib:build -Dimage=<docker registry name>/usersignup:v1

Setelah menjalankan perintah Maven di atas, kita mendapatkan output berikut:

[INFO] Containerizing application to pratikdas/usersignup:v1...
.
.
[INFO] Container entrypoint set to [java, -cp, /app/resources:/app/classes:/app/libs/*, io.pratik.users.UsersignupApplication]
[INFO] 
[INFO] Built and pushed image as pratikdas/usersignup:v1
[INFO] Executing tasks:
[INFO] [==============================] 100.0% complete

Outputnya menunjukkan bahwa gambar kontainer telah dibuat dan ditempatkan di registri.

Motivasi dan teknik untuk menciptakan gambar yang dioptimalkan

Kami memiliki dua alasan utama untuk pengoptimalan:

  • Performa: Dalam sistem orkestrasi kontainer, gambar kontainer diambil dari registri gambar ke host yang menjalankan mesin kontainer. Proses ini disebut perencanaan. Menarik gambar besar dari registri menghasilkan waktu penjadwalan yang lama dalam sistem orkestrasi kontainer dan waktu pembangunan yang lama di saluran CI.
  • keamanan: Gambar yang lebih besar juga memiliki area kerentanan yang lebih besar.

Gambar Docker terdiri dari tumpukan lapisan, yang masing-masing mewakili instruksi di Dockerfile kami. Setiap lapisan mewakili delta perubahan pada lapisan di bawahnya. Saat kami menarik image Docker dari registri, image tersebut ditarik berlapis-lapis dan di-cache di host.

Penggunaan Boot Musim Semi "JAR gemuk" di sebagai format kemasan default. Saat kami melihat JAR yang tebal, kami melihat bahwa aplikasi tersebut merupakan bagian yang sangat kecil dari keseluruhan JAR. Ini adalah bagian yang paling sering berubah. Sisanya terdiri dari dependensi Spring Framework.

Rumus pengoptimalan berpusat pada isolasi aplikasi pada tingkat terpisah dari dependensi Spring Framework.

Lapisan ketergantungan, yang merupakan sebagian besar file JAR yang tebal, diunduh hanya sekali dan disimpan dalam cache di sistem host.

Hanya lapisan tipis aplikasi yang ditarik selama pembaruan aplikasi dan penjadwalan kontainer. seperti yang ditunjukkan dalam diagram ini:

Membangun Gambar Docker yang Dioptimalkan untuk Aplikasi Spring Boot

Di bagian berikut, kita akan melihat cara membuat gambar yang dioptimalkan ini untuk aplikasi Spring Boot.

Membuat Gambar Kontainer yang Dioptimalkan untuk Aplikasi Spring Boot Menggunakan Buildpack

Spring Boot 2.3 mendukung pelapisan dengan mengekstraksi bagian file JAR yang tebal ke dalam lapisan terpisah. Fitur pelapisan dinonaktifkan secara default dan harus diaktifkan secara eksplisit menggunakan plugin Spring Boot Maven:

<plugin>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-maven-plugin</artifactId>
  <configuration>
    <layers>
      <enabled>true</enabled>
    </layers>
  </configuration> 
</plugin>

Kami akan menggunakan konfigurasi ini untuk membangun image container kami terlebih dahulu dengan Buildpack dan kemudian dengan Docker di bagian berikut.

Mari kita luncurkan build-imageTarget Maven untuk membuat gambar kontainer:

mvn spring-boot:build-image

Jika kita menjalankan Dive untuk melihat lapisan pada gambar yang dihasilkan, kita dapat melihat bahwa lapisan aplikasi (yang diberi garis merah) jauh lebih kecil dalam kisaran kilobyte dibandingkan dengan apa yang kita dapatkan dengan menggunakan format JAR yang gemuk:

Membangun Gambar Docker yang Dioptimalkan untuk Aplikasi Spring Boot

Membuat Gambar Kontainer yang Dioptimalkan untuk Aplikasi Spring Boot Menggunakan Docker

Daripada menggunakan plugin Maven atau Gradle, kita juga bisa membuat image Docker JAR berlapis dengan file Docker.

Saat kita menggunakan Docker, kita perlu melakukan dua langkah tambahan untuk mengekstrak lapisan dan menyalinnya ke gambar akhir.

Isi JAR yang dihasilkan setelah dibuat menggunakan Maven dengan layering diaktifkan akan terlihat seperti ini:

META-INF/
.
BOOT-INF/lib/
.
BOOT-INF/lib/spring-boot-jarmode-layertools-2.3.3.RELEASE.jar
BOOT-INF/classpath.idx
BOOT-INF/layers.idx

Outputnya menunjukkan nama JAR tambahan spring-boot-jarmode-layertoolsΠΈ layersfle.idxmengajukan. File JAR tambahan ini memberikan kemampuan pemrosesan berlapis, seperti yang dijelaskan di bagian selanjutnya.

Mengekstraksi dependensi pada masing-masing lapisan

Untuk melihat dan mengekstrak lapisan dari JAR berlapis kami, kami menggunakan properti sistem -Djarmode=layertoolsuntuk memulai spring-boot-jarmode-layertoolsJAR bukannya aplikasi:

java -Djarmode=layertools -jar target/usersignup-0.0.1-SNAPSHOT.jar

Menjalankan perintah ini menghasilkan output yang berisi opsi perintah yang tersedia:

Usage:
  java -Djarmode=layertools -jar usersignup-0.0.1-SNAPSHOT.jar

Available commands:
  list     List layers from the jar that can be extracted
  extract  Extracts layers from the jar for image creation
  help     Help about any command

Outputnya menunjukkan perintah listextractΠΈ helpс helpmenjadi default. Mari jalankan perintah dengan listpilihan:

java -Djarmode=layertools -jar target/usersignup-0.0.1-SNAPSHOT.jar list
dependencies
spring-boot-loader
snapshot-dependencies
application

Kami melihat daftar dependensi yang dapat ditambahkan sebagai lapisan.

Lapisan bawaan:

Nama lapisan

kadar

dependencies

ketergantungan apa pun yang versinya tidak mengandung SNAPSHOT

spring-boot-loader

Kelas Pemuat JAR

snapshot-dependencies

ketergantungan apa pun yang versinya berisi SNAPSHOT

application

kelas aplikasi dan sumber daya

Lapisan didefinisikan dalam layers.idxfile sesuai urutan penambahannya ke image Docker. Lapisan ini disimpan dalam cache di host setelah pengambilan pertama karena tidak berubah. Hanya lapisan aplikasi yang diperbarui yang diunduh ke host, yang lebih cepat karena ukurannya yang diperkecil .

Membangun gambar dengan dependensi yang diekstraksi ke dalam lapisan terpisah

Kami akan membangun gambar akhir dalam dua tahap menggunakan metode yang disebut perakitan multi-tahap . Pada langkah pertama kita akan mengekstrak dependensi dan pada langkah kedua kita akan menyalin dependensi yang diekstraksi ke gambar akhir.

Mari kita modifikasi Dockerfile kita untuk build multi-tahap:

# the first stage of our build will extract the layers
FROM adoptopenjdk:14-jre-hotspot as builder
WORKDIR application
ARG JAR_FILE=target/*.jar
COPY ${JAR_FILE} application.jar
RUN java -Djarmode=layertools -jar application.jar extract

# the second stage of our build will copy the extracted layers
FROM adoptopenjdk:14-jre-hotspot
WORKDIR application
COPY --from=builder application/dependencies/ ./
COPY --from=builder application/spring-boot-loader/ ./
COPY --from=builder application/snapshot-dependencies/ ./
COPY --from=builder application/application/ ./
ENTRYPOINT ["java", "org.springframework.boot.loader.JarLauncher"]

Kami menyimpan konfigurasi ini dalam file terpisah - Dockerfile2.

Kami membangun image Docker menggunakan perintah:

docker build -f Dockerfile2 -t usersignup:v1 .

Setelah menjalankan perintah ini kita mendapatkan output berikut:

Sending build context to Docker daemon  20.41MB
Step 1/12 : FROM adoptopenjdk:14-jre-hotspot as builder
14-jre-hotspot: Pulling from library/adoptopenjdk
.
.
Successfully built a9ebf6970841
Successfully tagged userssignup:v1

Kita dapat melihat bahwa image Docker dibuat dengan ID image dan kemudian diberi tag.

Terakhir, kita jalankan perintah Dive seperti sebelumnya untuk memeriksa lapisan di dalam image Docker yang dihasilkan. Kita dapat memberikan ID gambar atau tag sebagai masukan pada perintah Dive:

dive userssignup:v1

Seperti yang Anda lihat di keluaran, lapisan yang berisi aplikasi sekarang hanya berukuran 11 KB, dan dependensi di-cache di lapisan terpisah. 

Membangun Gambar Docker yang Dioptimalkan untuk Aplikasi Spring Boot

Mengekstraksi dependensi internal pada masing-masing lapisan

Kami selanjutnya dapat mengurangi ukuran tingkat aplikasi dengan mengekstrak dependensi khusus kami ke dalam tingkat terpisah alih-alih mengemasnya bersama aplikasi dengan mendeklarasikannya dalam ymlfile serupa bernama layers.idx:

- "dependencies":
  - "BOOT-INF/lib/"
- "spring-boot-loader":
  - "org/"
- "snapshot-dependencies":
- "custom-dependencies":
  - "io/myorg/"
- "application":
  - "BOOT-INF/classes/"
  - "BOOT-INF/classpath.idx"
  - "BOOT-INF/layers.idx"
  - "META-INF/"

Dalam berkas ini layers.idxkami telah menambahkan ketergantungan khusus bernama, io.myorgberisi dependensi organisasi yang diambil dari repositori bersama.

Keluaran

Dalam artikel ini, kita membahas penggunaan Cloud-Native Buildpacks untuk membuat image container langsung dari kode sumber. Ini adalah alternatif untuk menggunakan Docker untuk membuat image container dengan cara biasa: pertama-tama buat file JAR tebal yang dapat dieksekusi dan kemudian mengemasnya ke dalam image container dengan menentukan instruksi di file Docker.

Kami juga melihat cara mengoptimalkan container kami dengan mengaktifkan fitur pelapisan yang menarik dependensi ke dalam lapisan terpisah yang di-cache pada host dan lapisan tipis aplikasi dimuat pada waktu penjadwalan di mesin eksekusi container.

Anda dapat menemukan semua kode sumber yang digunakan dalam artikel di Github .

Referensi perintah

Berikut ini ikhtisar singkat dari perintah yang kami gunakan dalam artikel ini.

Pembersihan konteks:

docker system prune -a

Membuat image container menggunakan file Docker:

docker build -f <Docker file name> -t <tag> .

Kami membuat image container dari kode sumber (tanpa Dockerfile):

mvn spring-boot:build-image

Lihat lapisan ketergantungan. Sebelum membuat file JAR aplikasi, pastikan fitur pelapisan diaktifkan di spring-boot-maven-plugin:

java -Djarmode=layertools -jar application.jar list

Mengekstraksi lapisan ketergantungan. Sebelum membuat file JAR aplikasi, pastikan fitur pelapisan diaktifkan di spring-boot-maven-plugin:

 java -Djarmode=layertools -jar application.jar extract

Lihat daftar gambar kontainer

docker images

Lihat gambar container di sebelah kiri (pastikan alat selam sudah terpasang):

dive <image ID or image tag>

Sumber: www.habr.com