Tạo hình ảnh Docker được tối ưu hóa cho ứng dụng Spring Boot

Các thùng chứa đã trở thành phương tiện ưa thích để đóng gói một ứng dụng với tất cả các phần phụ thuộc vào phần mềm và hệ điều hành của nó, sau đó phân phối chúng đến các môi trường khác nhau.

Bài viết này trình bày các cách khác nhau để chứa ứng dụng Spring Boot:

  • tạo hình ảnh Docker bằng tệp Docker,
  • tạo hình ảnh OCI từ nguồn bằng Cloud-Native Buildpack,
  • và tối ưu hóa hình ảnh trong thời gian chạy bằng cách tách các phần của JAR thành các lớp khác nhau bằng các công cụ nhiều tầng.

 Ví dụ về mã

Bài viết này có kèm theo ví dụ về mã làm việc trên GitHub .

Thuật ngữ vùng chứa

Chúng ta sẽ bắt đầu với thuật ngữ container được sử dụng trong bài viết:

  • Hình ảnh vùng chứa: tập tin có định dạng cụ thể. Chúng tôi sẽ chuyển đổi ứng dụng của mình thành hình ảnh vùng chứa bằng cách chạy công cụ xây dựng.
  • thùng chứa: Một phiên bản thực thi của hình ảnh vùng chứa.
  • Động cơ container: Quá trình daemon chịu trách nhiệm chạy container.
  • Máy chủ vùng chứa: Máy tính chủ nơi công cụ container chạy.
  • Đăng ký vùng chứa: Vị trí chung dùng để xuất bản và phân phối hình ảnh vùng chứa.
  • tiêu chuẩn OCISáng kiến ​​vùng chứa mở (OCI) là một cấu trúc quản trị mở, gọn nhẹ được hình thành trong Linux Foundation. Đặc tả hình ảnh OCI xác định các tiêu chuẩn ngành cho hình ảnh vùng chứa và định dạng thời gian chạy để đảm bảo rằng tất cả các công cụ vùng chứa có thể chạy hình ảnh vùng chứa được tạo bởi bất kỳ công cụ xây dựng nào.

Để chứa một ứng dụng, chúng tôi gói ứng dụng của mình trong một hình ảnh vùng chứa và xuất bản hình ảnh đó lên sổ đăng ký dùng chung. Thời gian chạy vùng chứa lấy hình ảnh này từ sổ đăng ký, giải nén nó và chạy ứng dụng bên trong nó.

Phiên bản 2.3 của Spring Boot cung cấp các plugin để tạo image OCI.

phu bến tàu là cách triển khai vùng chứa được sử dụng phổ biến nhất và chúng tôi sử dụng Docker trong các ví dụ của mình, vì vậy tất cả các tài liệu tham khảo về vùng chứa tiếp theo trong bài viết này sẽ đề cập đến Docker.

Xây dựng hình ảnh container theo cách truyền thống

Tạo Docker image cho ứng dụng Spring Boot rất dễ dàng bằng cách thêm một vài hướng dẫn vào tệp Docker.

Trước tiên, chúng tôi tạo một tệp JAR thực thi và, như một phần của hướng dẫn tệp Docker, sao chép tệp JAR thực thi lên trên hình ảnh JRE cơ sở sau khi áp dụng các cài đặt cần thiết.

Hãy tạo ứng dụng Spring của chúng ta trên Khởi đầu mùa xuân với sự phụ thuộc weblombokи actuator. Chúng tôi cũng đang thêm bộ điều khiển nghỉ ngơi để cung cấp API với GETphương pháp.

Tạo một Dockerfile

Sau đó chúng tôi chứa ứng dụng này bằng cách thêm Dockerfile:

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

Tệp Docker của chúng tôi chứa hình ảnh cơ sở từ adoptopenjdk, trên đó chúng tôi sao chép tệp JAR của mình rồi mở cổng, 8080sẽ lắng nghe yêu cầu.

Xây dựng ứng dụng

Đầu tiên bạn cần tạo một ứng dụng bằng Maven hoặc Gradle. Ở đây chúng tôi sử dụng Maven:

mvn clean package

Điều này tạo ra một tệp JAR thực thi cho ứng dụng. Chúng ta cần chuyển đổi JAR thực thi này thành hình ảnh Docker để chạy trên công cụ Docker.

Tạo hình ảnh vùng chứa

Sau đó, chúng tôi đặt tệp JAR thực thi này vào hình ảnh Docker bằng cách chạy lệnh docker buildtừ thư mục gốc của dự án chứa Dockerfile được tạo trước đó:

docker build  -t usersignup:v1 .

Chúng ta có thể thấy hình ảnh của mình trong danh sách bằng lệnh:

docker images 

Đầu ra của lệnh trên bao gồm hình ảnh của chúng tôi usersignupcùng với hình ảnh cơ sở, adoptopenjdkđược chỉ định trong tệp Docker của chúng tôi.

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

Xem các lớp bên trong hình ảnh vùng chứa

Chúng ta hãy nhìn vào chồng các lớp bên trong hình ảnh. Chúng tôi sẽ sử dụng dụng cụ  lặn, để xem các lớp này:

dive usersignup:v1

Đây là một phần đầu ra từ lệnh Dive: 

Tạo hình ảnh Docker được tối ưu hóa cho ứng dụng Spring Boot

Như chúng ta có thể thấy, lớp ứng dụng chiếm một phần đáng kể trong kích thước hình ảnh. Chúng tôi muốn giảm kích thước của lớp này trong các phần sau như một phần của quá trình tối ưu hóa của chúng tôi.

Tạo hình ảnh vùng chứa bằng Buildpack

Gói lắp ráp (gói xây dựng) là một thuật ngữ chung được sử dụng bởi nhiều dịch vụ Nền tảng dưới dạng Dịch vụ (PAAS) khác nhau để tạo hình ảnh vùng chứa từ mã nguồn. Nó được Heroku ra mắt vào năm 2011 và kể từ đó đã được Cloud Foundry, Google App Engine, Gitlab, Knative và một số công ty khác áp dụng.

Tạo hình ảnh Docker được tối ưu hóa cho ứng dụng Spring Boot

Ưu điểm của gói xây dựng đám mây

Một trong những lợi ích chính của việc sử dụng Buildpack để tạo hình ảnh là Các thay đổi về cấu hình hình ảnh có thể được quản lý tập trung (trình tạo) và được truyền tới tất cả các ứng dụng bằng trình tạo.

Các gói xây dựng được liên kết chặt chẽ với nền tảng. Cloud-Native Buildpacks cung cấp tiêu chuẩn hóa trên các nền tảng bằng cách hỗ trợ định dạng hình ảnh OCI, đảm bảo rằng hình ảnh có thể được chạy bởi công cụ Docker.

Sử dụng plugin Spring Boot

Plugin Spring Boot xây dựng hình ảnh OCI từ nguồn bằng Buildpack. Hình ảnh được tạo bằng cách sử dụng bootBuildImagenhiệm vụ (Gradle) hoặc spring-boot:build-imagemục tiêu (Maven) và cài đặt Docker cục bộ.

Chúng ta có thể tùy chỉnh tên của image cần thiết để đẩy vào sổ đăng ký Docker bằng cách chỉ định tên trong 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>

Hãy sử dụng Maven để làm điều đó build-imagemục tiêu để tạo một ứng dụng và tạo hình ảnh vùng chứa. Chúng tôi không sử dụng bất kỳ Dockerfiles nào vào thời điểm này.

mvn spring-boot:build-image

Kết quả sẽ là một cái gì đó như thế này:

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

Từ đầu ra chúng ta thấy rằng paketo Cloud-Native buildpackđược sử dụng để tạo hình ảnh OCI hoạt động. Như trước đây, chúng ta có thể thấy image được liệt kê dưới dạng Docker image bằng cách chạy lệnh:

docker images 

Kết luận:

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

Tạo hình ảnh vùng chứa bằng Jib

Jib là một plugin tạo hình ảnh của Google cung cấp một phương pháp thay thế để tạo hình ảnh vùng chứa từ mã nguồn.

Đang cài đặt jib-maven-plugintrong pom.xml:

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

Tiếp theo, chúng tôi chạy plugin Jib bằng lệnh Maven để xây dựng ứng dụng và tạo hình ảnh vùng chứa. Như trước đây, chúng tôi không sử dụng bất kỳ tệp Docker nào ở đây:

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

Sau khi thực hiện lệnh Maven ở trên, chúng ta nhận được kết quả đầu ra sau:

[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

Đầu ra cho thấy hình ảnh vùng chứa đã được tạo và đặt trong sổ đăng ký.

Động lực và kỹ thuật tạo hình ảnh được tối ưu hóa

Chúng tôi có hai lý do chính để tối ưu hóa:

  • Năng suất: Trong hệ thống điều phối vùng chứa, hình ảnh vùng chứa được truy xuất từ ​​sổ đăng ký hình ảnh đến máy chủ chạy công cụ vùng chứa. Quá trình này được gọi là lập kế hoạch. Việc kéo các hình ảnh lớn từ sổ đăng ký dẫn đến thời gian lập lịch lâu trong hệ thống điều phối vùng chứa và thời gian xây dựng lâu trong đường ống CI.
  • Безопасность: Hình ảnh lớn hơn cũng có diện tích lỗ hổng lớn hơn.

Hình ảnh Docker bao gồm một chồng các lớp, mỗi lớp đại diện cho một hướng dẫn trong Dockerfile của chúng tôi. Mỗi lớp đại diện cho một vùng đồng bằng của những thay đổi trong lớp bên dưới. Khi chúng tôi lấy hình ảnh Docker từ sổ đăng ký, nó sẽ được kéo theo từng lớp và được lưu vào bộ đệm trên máy chủ.

Sử dụng Spring Boot "JAR béo" trong làm định dạng đóng gói mặc định. Khi nhìn vào JAR dày, chúng ta thấy rằng ứng dụng chỉ chiếm một phần rất nhỏ trong toàn bộ JAR. Đây là phần thay đổi thường xuyên nhất. Phần còn lại bao gồm các phần phụ thuộc của Spring Framework.

Công thức tối ưu hóa tập trung vào việc tách ứng dụng ở một mức độ riêng biệt khỏi các phần phụ thuộc của Spring Framework.

Lớp phụ thuộc, tạo thành phần lớn tệp JAR dày, chỉ được tải xuống một lần và được lưu vào bộ đệm trên hệ thống máy chủ.

Chỉ một lớp mỏng của ứng dụng được kéo ra trong quá trình cập nhật ứng dụng và lập lịch vùng chứa. như thể hiện trong sơ đồ này:

Tạo hình ảnh Docker được tối ưu hóa cho ứng dụng Spring Boot

Trong các phần sau, chúng ta sẽ xem cách tạo những hình ảnh được tối ưu hóa này cho ứng dụng Spring Boot.

Tạo hình ảnh vùng chứa được tối ưu hóa cho ứng dụng Spring Boot bằng Buildpack

Spring Boot 2.3 hỗ trợ phân lớp bằng cách trích xuất các phần của tệp JAR dày thành các lớp riêng biệt. Tính năng phân lớp bị tắt theo mặc định và phải được bật rõ ràng bằng cách sử dụng plugin Spring Boot Maven:

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

Trước tiên, chúng tôi sẽ sử dụng cấu hình này để xây dựng hình ảnh vùng chứa bằng Buildpack và sau đó với Docker trong các phần sau.

Hãy khởi động build-imageMục tiêu Maven để tạo hình ảnh vùng chứa:

mvn spring-boot:build-image

Nếu chạy Dive để xem các lớp trong hình ảnh thu được, chúng ta có thể thấy rằng lớp ứng dụng (được viền màu đỏ) nhỏ hơn nhiều trong phạm vi kilobyte so với những gì chúng ta nhận được bằng cách sử dụng định dạng JAR béo:

Tạo hình ảnh Docker được tối ưu hóa cho ứng dụng Spring Boot

Tạo hình ảnh vùng chứa được tối ưu hóa cho ứng dụng Spring Boot bằng Docker

Thay vì sử dụng plugin Maven hoặc Gradle, chúng ta cũng có thể tạo hình ảnh Docker JAR nhiều lớp bằng tệp Docker.

Khi sử dụng Docker, chúng ta cần thực hiện thêm hai bước để trích xuất các lớp và sao chép chúng vào hình ảnh cuối cùng.

Nội dung của JAR kết quả sau khi xây dựng bằng Maven với tính năng phân lớp được bật sẽ trông như thế này:

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

Đầu ra hiển thị một JAR bổ sung có tên spring-boot-jarmode-layertoolsи layersfle.idxtài liệu. Tệp JAR bổ sung này cung cấp khả năng xử lý theo lớp, như được mô tả trong phần tiếp theo.

Trích xuất các phụ thuộc trên các lớp riêng lẻ

Để xem và trích xuất các lớp từ JAR lớp của chúng tôi, chúng tôi sử dụng thuộc tính hệ thống -Djarmode=layertoolsđể bắt đầu spring-boot-jarmode-layertoolsJAR thay vì ứng dụng:

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

Chạy lệnh này sẽ tạo ra đầu ra chứa các tùy chọn lệnh có sẵn:

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

Đầu ra hiển thị các lệnh listextractи helpс helplà mặc định. Hãy chạy lệnh với listlựa chọn:

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

Chúng tôi thấy một danh sách các phụ thuộc có thể được thêm dưới dạng lớp.

Các lớp mặc định:

Tên lớp

nội dung

dependencies

bất kỳ phần phụ thuộc nào có phiên bản không chứa SNAPSHOT

spring-boot-loader

Các lớp trình tải JAR

snapshot-dependencies

bất kỳ phần phụ thuộc nào có phiên bản chứa SNAPSHOT

application

các lớp ứng dụng và tài nguyên

Các lớp được xác định trong layers.idxtheo thứ tự chúng sẽ được thêm vào hình ảnh Docker. Các lớp này được lưu trữ trong máy chủ sau lần truy xuất đầu tiên vì chúng không thay đổi. Chỉ lớp ứng dụng đã cập nhật mới được tải xuống máy chủ, tốc độ này nhanh hơn do kích thước giảm .

Xây dựng hình ảnh với các phần phụ thuộc được trích xuất thành các lớp riêng biệt

Chúng ta sẽ xây dựng hình ảnh cuối cùng theo hai giai đoạn bằng phương pháp gọi là lắp ráp nhiều giai đoạn . Trong bước đầu tiên, chúng tôi sẽ trích xuất các phần phụ thuộc và trong bước thứ hai, chúng tôi sẽ sao chép các phần phụ thuộc đã được trích xuất vào hình ảnh cuối cùng.

Hãy sửa đổi Dockerfile của chúng tôi để xây dựng nhiều giai đoạn:

# 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"]

Chúng tôi lưu cấu hình này vào một tệp riêng - Dockerfile2.

Chúng tôi xây dựng hình ảnh Docker bằng lệnh:

docker build -f Dockerfile2 -t usersignup:v1 .

Sau khi chạy lệnh này, chúng ta nhận được kết quả đầu ra sau:

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

Chúng ta có thể thấy rằng hình ảnh Docker được tạo bằng ID hình ảnh và sau đó được gắn thẻ.

Cuối cùng, chúng tôi chạy lệnh Dive như trước để kiểm tra các lớp bên trong hình ảnh Docker được tạo. Chúng tôi có thể cung cấp ID hình ảnh hoặc thẻ làm đầu vào cho lệnh Dive:

dive userssignup:v1

Như bạn có thể thấy ở đầu ra, lớp chứa ứng dụng hiện chỉ có 11 KB và các phần phụ thuộc được lưu vào bộ đệm trong các lớp riêng biệt. 

Tạo hình ảnh Docker được tối ưu hóa cho ứng dụng Spring Boot

Trích xuất các phụ thuộc nội bộ trên các lớp riêng lẻ

Chúng tôi có thể giảm thêm kích thước của tầng ứng dụng bằng cách trích xuất bất kỳ phần phụ thuộc tùy chỉnh nào của chúng tôi vào một tầng riêng biệt thay vì đóng gói chúng cùng với ứng dụng bằng cách khai báo chúng trong ymltập tin tương tự có tên 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/"

Trong tập tin này layers.idxchúng tôi đã thêm một phụ thuộc tùy chỉnh có tên, io.myorgchứa các phụ thuộc của tổ chức được lấy từ kho lưu trữ chung.

Đầu ra

Trong bài viết này, chúng tôi đã xem xét việc sử dụng Cloud-Native Buildpacks để xây dựng hình ảnh vùng chứa trực tiếp từ mã nguồn. Đây là một giải pháp thay thế cho việc sử dụng Docker để tạo hình ảnh vùng chứa theo cách thông thường: đầu tiên tạo một tệp JAR có thể thực thi dày và sau đó đóng gói nó vào hình ảnh vùng chứa bằng cách chỉ định các hướng dẫn trong tệp Docker.

Chúng tôi cũng đã xem xét việc tối ưu hóa vùng chứa của mình bằng cách bật tính năng phân lớp giúp kéo các phần phụ thuộc vào các lớp riêng biệt được lưu vào bộ nhớ đệm trên máy chủ và một lớp mỏng của ứng dụng được tải vào thời điểm lập lịch trong công cụ thực thi của vùng chứa.

Bạn có thể tìm thấy toàn bộ mã nguồn được sử dụng trong bài viết tại Github .

Tham chiếu lệnh

Dưới đây là tóm tắt nhanh các lệnh chúng tôi đã sử dụng trong bài viết này.

Xóa bối cảnh:

docker system prune -a

Tạo hình ảnh vùng chứa bằng tệp Docker:

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

Chúng tôi xây dựng hình ảnh vùng chứa từ mã nguồn (không có Dockerfile):

mvn spring-boot:build-image

Xem các lớp phụ thuộc Trước khi xây dựng tệp JAR ứng dụng, hãy đảm bảo rằng tính năng phân lớp được bật trong spring-boot-maven-plugin:

java -Djarmode=layertools -jar application.jar list

Trích xuất các lớp phụ thuộc Trước khi xây dựng tệp JAR ứng dụng, hãy đảm bảo rằng tính năng phân lớp được bật trong spring-boot-maven-plugin:

 java -Djarmode=layertools -jar application.jar extract

Xem danh sách hình ảnh container

docker images

Xem bên trái bên trong hình ảnh vùng chứa (đảm bảo công cụ lặn đã được cài đặt):

dive <image ID or image tag>

Nguồn: www.habr.com