多階段構建
解釋
在傳統構建中,所有構建指令按順序在單個構建容器中執行:下載依賴項、編譯程式碼和打包應用程式。所有這些層最終都會出現在您的最終映象中。這種方法有效,但會導致臃腫的映象,會帶來不必要的負擔並增加您的安全風險。這就是多階段構建發揮作用的地方。
多階段構建在您的 Dockerfile 中引入了多個階段,每個階段都有特定的目的。可以將其視為在多個不同環境中同時執行構建的不同部分的能力。透過將構建環境與最終執行時環境分離,您可以顯著減小映象大小並減少攻擊面。對於具有大量構建依賴項的應用程式來說,這尤其有利。
建議對所有型別的應用程式使用多階段構建。
- 對於解釋性語言(如 JavaScript、Ruby 或 Python),您可以在一個階段構建和壓縮程式碼,並將生產就緒的檔案複製到較小的執行時映象中。這會最佳化您的映象以進行部署。
- 對於編譯性語言(如 C、Go 或 Rust),多階段構建允許您在一個階段進行編譯,並將編譯後的二進位制檔案複製到最終的執行時映象中。無需在最終映象中捆綁整個編譯器。
以下是用虛擬碼展示的多階段構建結構的簡化示例。請注意,存在多個 FROM
語句以及一個新的 AS <stage-name>
。此外,第二階段中的 COPY
語句是從上一個階段 --from
複製的。
# Stage 1: Build Environment
FROM builder-image AS build-stage
# Install build tools (e.g., Maven, Gradle)
# Copy source code
# Build commands (e.g., compile, package)
# Stage 2: Runtime environment
FROM runtime-image AS final-stage
# Copy application artifacts from the build stage (e.g., JAR file)
COPY --from=build-stage /path/in/build/stage /path/to/place/in/final/stage
# Define runtime configuration (e.g., CMD, ENTRYPOINT)
此 Dockerfile 使用兩個階段
- 構建階段使用包含編譯應用程式所需的構建工具的基本映象。它包含安裝構建工具、複製原始碼和執行構建命令的命令。
- 最終階段使用更小的基本映象,適合執行您的應用程式。它從構建階段複製編譯後的工件(例如 JAR 檔案)。最後,它定義用於啟動應用程式的執行時配置(使用
CMD
或ENTRYPOINT
)。
試一試
在本實踐指南中,您將釋放多階段構建的強大功能,為示例 Java 應用程式建立精簡高效的 Docker 映象。您將使用基於 Maven 的簡單“Hello World”Spring Boot 應用程式作為您的示例。
下載並安裝 Docker Desktop。
開啟此 預初始化專案 生成一個 ZIP 檔案。以下是其外觀
Spring Initializr 是 Spring 專案的快速入門生成器。它提供可擴充套件的 API 來生成基於 JVM 的專案,其中包含對幾個常見概念的實現——例如 Java、Kotlin 和 Groovy 的基本語言生成。
選擇**生成**以建立並下載此專案的 zip 檔案。
對於此演示,您已將 Maven 構建自動化與 Java、Spring Web 依賴項和 Java 21 結合使用以獲取元資料。
瀏覽專案目錄。解壓縮檔案後,您將看到以下專案目錄結構
spring-boot-docker ├── Dockerfile ├── Dockerfile.multi ├── HELP.md ├── mvnw ├── mvnw.cmd ├── pom.xml └── src ├── main │ ├── java │ │ └── com │ │ └── example │ │ └── springbootdocker │ │ └── SpringBootDockerApplication.java │ └── resources │ ├── application.properties │ ├── static │ └── templates └── test └── java └── com └── example └── springbootdocker └── SpringBootDockerApplicationTests.java 15 directories, 9 files
src/main/java
目錄包含專案的原始碼,src/test/java
目錄
包含測試原始碼,pom.xml
檔案是專案的專案物件模型 (POM)。pom.xml
檔案是 Maven 專案配置的核心。它是一個單一的配置檔案,
包含構建自定義專案所需的大多數資訊。POM 非常龐大,看起來
很嚇人。幸運的是,您還沒有必要了解每個細節才能有效地使用它。建立一個 RESTful Web 服務來顯示“Hello World!”。
在
src/main/java/com/example/springbootdocker/
目錄下,您可以修改您的SpringBootDockerApplication.java
檔案,內容如下package com.example.springbootdocker; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController @SpringBootApplication public class SpringBootDockerApplication { @RequestMapping("/") public String home() { return "Hello World"; } public static void main(String[] args) { SpringApplication.run(SpringBootDockerApplication.class, args); } }
SpringbootDockerApplication.java
檔案首先宣告您的com.example.springbootdocker
包並匯入必要的 Spring 框架。此 Java 檔案建立一個簡單的 Spring Boot Web 應用程式,當用戶訪問其主頁時,它會以“Hello World”進行響應。
建立 Dockerfile
現在您有了專案,您可以建立 Dockerfile
了。
在包含所有其他資料夾和檔案(如 src、pom.xml 等)的同一資料夾中建立一個名為
Dockerfile
的檔案。在
Dockerfile
中,透過新增以下行定義基本映象FROM eclipse-temurin:21.0.2_13-jdk-jammy
現在,使用
WORKDIR
指令定義工作目錄。這將指定未來命令將在哪裡執行以及將複製到容器映象中的目錄檔案。WORKDIR /app
將 Maven 包裝器指令碼和專案的
pom.xml
檔案複製到容器中的當前工作目錄/app
中。COPY .mvn/ .mvn COPY mvnw pom.xml ./
在容器中執行一個命令。它執行
./mvnw dependency:go-offline
命令,該命令使用 Maven 包裝器 (./mvnw
) 下載專案的所有依賴項,而不會構建最終的 JAR 檔案(這對加速構建很有用)。RUN ./mvnw dependency:go-offline
將主機上的專案中的
src
目錄複製到容器中的/app
目錄。COPY src ./src
將容器啟動時執行的預設命令設定為。此命令指示容器執行 Maven 包裝器 (
./mvnw
) 以及spring-boot:run
目標,這將構建並執行 Spring Boot 應用程式。CMD ["./mvnw", "spring-boot:run"]
就這樣,您應該有以下 Dockerfile
FROM eclipse-temurin:21.0.2_13-jdk-jammy WORKDIR /app COPY .mvn/ .mvn COPY mvnw pom.xml ./ RUN ./mvnw dependency:go-offline COPY src ./src CMD ["./mvnw", "spring-boot:run"]
構建容器映象
執行以下命令來構建 Docker 映象
$ docker build -t spring-helloworld .
使用
docker images
命令檢視 Docker 映象的大小$ docker images
這樣做將產生如下輸出
REPOSITORY TAG IMAGE ID CREATED SIZE spring-helloworld latest ff708d5ee194 3 minutes ago 880MB
此輸出顯示映象大小為 880MB。它包含完整的 JDK、Maven 工具鏈等等。在生產環境中,您在最終映象中不需要這些內容。
執行 Spring Boot 應用程式
現在您已構建了一個映象,是時候執行容器了。
$ docker run -d -p 8080:8080 spring-helloworld
然後您將在容器日誌中看到類似於以下的輸出
[INFO] --- spring-boot:3.3.0-M3:run (default-cli) @ spring-boot-docker --- [INFO] Attaching agents: [] . ____ _ __ _ _ /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \ ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \ \\/ ___)| |_)| | | | | || (_| | ) ) ) ) ' |____| .__|_| |_|_| |_\__, | / / / / =========|_|==============|___/=/_/_/_/ :: Spring Boot :: (v3.3.0-M3) 2024-04-04T15:36:47.202Z INFO 42 --- [spring-boot-docker] [ main] c.e.s.SpringBootDockerApplication : Starting SpringBootDockerApplication using Java 21.0.2 with PID 42 (/app/target/classes started by root in /app) ….
透過 Web 瀏覽器訪問“Hello World”頁面,地址為 https://:8080,或者透過以下 curl 命令
$ curl localhost:8080 Hello World
使用多階段構建
考慮以下 Dockerfile
FROM eclipse-temurin:21.0.2_13-jdk-jammy AS builder WORKDIR /opt/app COPY .mvn/ .mvn COPY mvnw pom.xml ./ RUN ./mvnw dependency:go-offline COPY ./src ./src RUN ./mvnw clean install FROM eclipse-temurin:21.0.2_13-jre-jammy AS final WORKDIR /opt/app EXPOSE 8080 COPY --from=builder /opt/app/target/*.jar /opt/app/*.jar ENTRYPOINT ["java", "-jar", "/opt/app/*.jar"]
請注意,此 Dockerfile 已拆分為兩個階段。
第一階段與之前的 Dockerfile 相同,提供了一個用於構建應用程式的 Java 開發工具包 (JDK) 環境。此階段被稱為 builder。
第二階段是一個名為 `final` 的新階段。它使用一個更精簡的 `eclipse-temurin:21.0.2_13-jre-jammy` 映象,該映象僅包含執行應用程式所需的 Java 執行時環境 (JRE)。此映象提供了執行編譯後的應用程式(JAR 檔案)所需的 Java 執行時環境。
對於生產環境使用,強烈建議您使用 jlink 生成一個自定義的 JRE 類執行時。所有版本的 Eclipse Temurin 都提供 JRE 映象,但 `jlink` 可以讓您建立一個僅包含應用程式所需 Java 模組的最小執行時。這可以顯著減小最終映象的大小並提高安全性。 請參閱此頁面瞭解更多資訊。
使用多階段構建,Docker 構建使用一個基礎映象進行編譯、打包和單元測試,然後使用另一個單獨的映象作為應用程式執行時。因此,最終映象的大小會更小,因為它不包含任何開發或除錯工具。透過將構建環境與最終執行時環境分離,您可以顯著減小映象大小並提高最終映象的安全性。
現在,重新構建您的映象並執行您的生產環境就緒的構建。
$ docker build -t spring-helloworld-builder .
此命令使用 `Dockerfile` 檔案中位於當前目錄的最終階段構建名為 `spring-helloworld-builder` 的 Docker 映象。
注意
在您的多階段 Dockerfile 中,最終階段(final)是構建的預設目標。這意味著,如果您沒有使用 `docker build` 命令中的 `--target` 標誌顯式指定目標階段,Docker 會預設自動構建最後一個階段。您可以使用 `docker build -t spring-helloworld-builder --target builder .` 僅構建包含 JDK 環境的 builder 階段。
使用 `docker images` 命令檢視映象大小差異
$ docker images
您將獲得類似於以下的輸出
spring-helloworld-builder latest c5c76cb815c0 24 minutes ago 428MB spring-helloworld latest ff708d5ee194 About an hour ago 880MB
您的最終映象只有 428 MB,而原始構建大小為 880 MB。
透過最佳化每個階段並僅包含必要的內容,您可以顯著減小
總體映象大小,同時仍保持相同的功能。這不僅提高了效能,還
使您的 Docker 映象更輕量級、更安全、更易於管理。