Spring Boot 與 Docker

本指南將引導您完成建置 Docker 映像檔以執行 Spring Boot 應用程式的過程。我們先從基本的 Dockerfile 開始,並進行一些調整。然後,我們展示幾個使用建置外掛程式(適用於 Maven 和 Gradle)而不是 docker 的選項。這是一份「入門」指南,因此範圍僅限於一些基本需求。如果您正在為生產環境建置容器映像檔,則需要考慮許多事項,並且不可能在一份簡短的指南中涵蓋所有內容。

另有一份 Docker 主題指南,其中更廣泛且更詳細地介紹了我們在此處提供的一些選擇。

您將建置的內容

Docker 是一個 Linux 容器管理工具組,具有「社交」層面,可讓使用者發佈容器映像檔並使用其他人發佈的映像檔。Docker 映像檔是用於執行容器化程序的配方。在本指南中,我們將為簡單的 Spring Boot 應用程式建置一個。

您需要的東西

如果您未使用 Linux 機器,則需要虛擬化伺服器。如果您安裝 VirtualBox,則其他工具(例如 Mac 的 boot2docker)可以為您無縫管理它。請造訪 VirtualBox 的下載網站,並選擇適合您機器的版本。下載並安裝。不用擔心實際執行它。

您還需要 Docker,它僅在 64 位元機器上執行。請參閱 https://docker-docs.dev.org.tw/installation/#installation,以瞭解在您的機器上設定 Docker 的詳細資訊。在繼續之前,請驗證您可以從 shell 執行 docker 命令。如果您使用 boot2docker,則需要執行它。

從 Spring Initializr 開始

您可以使用這個 預先初始化的專案,然後按一下 Generate 以下載 ZIP 檔案。此專案已設定為符合本教學課程中的範例。

手動初始化專案

  1. 瀏覽至 https://start.spring.io。此服務會提取應用程式所需的所有相依性,並為您完成大部分設定。

  2. 選擇 Gradle 或 Maven 以及您想要使用的語言。本指南假設您選擇 Java。

  3. 按一下Dependencies,然後選取 Spring Web

  4. 按一下Generate

  5. 下載產生的 ZIP 檔案,這是根據您的選擇設定的網路應用程式的封存檔。

如果您的 IDE 具有 Spring Initializr 整合,您可以從您的 IDE 完成此程序。
您也可以從 Github 分叉專案,並在您的 IDE 或其他編輯器中開啟它。

設定 Spring Boot 應用程式

現在您可以建立一個簡單的應用程式

src/main/java/hello/Application.java

package hello;

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;

@SpringBootApplication
@RestController
public class Application {

  @RequestMapping("/")
  public String home() {
    return "Hello Docker World";
  }

  public static void main(String[] args) {
    SpringApplication.run(Application.class, args);
  }

}

此類別標記為 @SpringBootApplication@RestController,表示它已準備好供 Spring MVC 使用以處理網路請求。@RequestMapping/ 對應到 home() 方法,該方法會傳送 Hello World 回應。main() 方法使用 Spring Boot 的 SpringApplication.run() 方法來啟動應用程式。

現在我們可以在沒有 Docker 容器的情況下(也就是在主機作業系統中)執行應用程式

如果您使用 Gradle,請執行以下命令

./gradlew build && java -jar build/libs/gs-spring-boot-docker-0.1.0.jar

如果您使用 Maven,請執行以下命令

./mvnw package && java -jar target/gs-spring-boot-docker-0.1.0.jar

然後前往 localhost:8080 以查看您的「Hello Docker World」訊息。

容器化

Docker 有一個簡單的 "Dockerfile" 檔案格式,用於指定映像檔的「層」。在您的 Spring Boot 專案中建立以下 Dockerfile

範例 1. Dockerfile
FROM openjdk:8-jdk-alpine
ARG JAR_FILE=target/*.jar
COPY ${JAR_FILE} app.jar
ENTRYPOINT ["java","-jar","/app.jar"]

如果您使用 Gradle,您可以使用以下命令執行它

docker build --build-arg JAR_FILE=build/libs/\*.jar -t springio/gs-spring-boot-docker .

如果您使用 Maven,您可以使用以下命令執行它

docker build -t springio/gs-spring-boot-docker .

此命令會建置映像檔並將其標記為 springio/gs-spring-boot-docker

這個 Dockerfile 非常簡單,但它是執行沒有任何額外功能的 Spring Boot 應用程式所需的全部:僅限 Java 和 JAR 檔案。建置會建立一個 spring 使用者和一個 spring 群組來執行應用程式。然後,它會將專案 JAR 檔案(透過 COPY 命令)複製到容器中作為 app.jar,並在 ENTRYPOINT 中執行。Dockerfile ENTRYPOINT 的陣列形式用於避免 shell 包裝 Java 程序。Docker 主題指南 更詳細地介紹了這個主題。

為了縮短 Tomcat 啟動時間,我們過去常新增一個系統屬性,將 /dev/urandom 指向作為熵的來源。使用 JDK 8 或更新版本 後,這已不再必要。

以使用者權限執行應用程式有助於降低某些風險(例如,請參閱 StackExchange 上的一個討論串)。因此,對 Dockerfile 的一項重要改進是以非 root 使用者身分執行應用程式

範例 2. Dockerfile
FROM openjdk:8-jdk-alpine
RUN addgroup -S spring && adduser -S spring -G spring
USER spring:spring
ARG JAR_FILE=target/*.jar
COPY ${JAR_FILE} app.jar
ENTRYPOINT ["java","-jar","/app.jar"]

當您建置並執行應用程式時,您可以在應用程式啟動記錄中看到使用者名稱

docker build -t springio/gs-spring-boot-docker .
docker run -p 8080:8080 springio/gs-spring-boot-docker

請注意第一個 INFO 記錄項目中的 started by

 :: Spring Boot ::        (v2.2.1.RELEASE)

2020-04-23 07:29:41.729  INFO 1 --- [           main] hello.Application                        : Starting Application on b94c86e91cf9 with PID 1 (/app started by spring in /)
...

此外,Spring Boot fat JAR 檔案中的相依性和應用程式資源之間有明確的分隔,我們可以利用這個事實來提高效能。關鍵是在容器檔案系統中建立層。這些層在建置時和執行時(在大多數執行時環境中)都會快取,因此我們希望變更最頻繁的資源(通常是應用程式本身的類別和靜態資源)在變更較慢的資源之後分層。因此,我們使用略有不同的 Dockerfile 實作方式

範例 3. Dockerfile
FROM openjdk:8-jdk-alpine
RUN addgroup -S spring && adduser -S spring -G spring
USER spring:spring
ARG DEPENDENCY=target/dependency
COPY ${DEPENDENCY}/BOOT-INF/lib /app/lib
COPY ${DEPENDENCY}/META-INF /app/META-INF
COPY ${DEPENDENCY}/BOOT-INF/classes /app
ENTRYPOINT ["java","-cp","app:app/lib/*","hello.Application"]

此 Dockerfile 有一個 DEPENDENCY 參數,指向我們解壓縮 fat JAR 的目錄。若要搭配 Gradle 使用 DEPENDENCY 參數,請執行以下命令

mkdir -p build/dependency && (cd build/dependency; jar -xf ../libs/*.jar)

若要搭配 Maven 使用 DEPENDENCY 參數,請執行以下命令

mkdir -p target/dependency && (cd target/dependency; jar -xf ../*.jar)

如果我們做得正確,它應該已經包含一個 BOOT-INF/lib 目錄,其中包含相依性 JAR,以及一個 BOOT-INF/classes 目錄,其中包含應用程式類別。請注意,我們使用應用程式自己的主類別:hello.Application。(這比使用 fat JAR 啟動器提供的間接方式更快。)

解壓縮 JAR 檔案可能會導致類別路徑順序在 執行時有所不同。行為良好且編寫完善的應用程式不應在意這一點,但如果相依性未仔細管理,您可能會看到行為變更。
如果您使用 boot2docker,您需要執行它,然後才能對 Docker 命令列或建置工具執行任何操作(它會執行一個常駐程序,在虛擬機器中為您處理工作)。

從 Gradle 建置,您需要在 Docker 命令列中新增明確的建置引數

docker build --build-arg DEPENDENCY=build/dependency -t springio/gs-spring-boot-docker .

若要在 Maven 中建置映像檔,您可以使用更簡單的 Docker 命令列

docker build -t springio/gs-spring-boot-docker .
如果您僅使用 Gradle,您可以變更 Dockerfile,使 DEPENDENCY 的預設值符合解壓縮封存檔的位置。

除了使用 Docker 命令列建置之外,您可能還想使用建置外掛程式。Spring Boot 支援使用其自身的建置外掛程式從 Maven 或 Gradle 建置容器。Google 也有一個名為 Jib 的開放原始碼工具,其中包含 Maven 和 Gradle 外掛程式。這種方法最有趣的地方可能是您不需要 Dockerfile。您可以使用與從 docker build 取得的相同標準容器格式來建置映像檔。此外,它可以在未安裝 docker 的環境中運作(在建置伺服器中並不少見)。

預設情況下,預設建置套件產生的映像檔不會以 root 身分執行您的應用程式。請查看 GradleMaven 的設定指南,以瞭解如何變更預設設定。

使用 Gradle 建置 Docker 映像檔

您可以使用一個命令透過 Gradle 建置已標記的 docker 映像檔

./gradlew bootBuildImage --imageName=springio/gs-spring-boot-docker

使用 Maven 建置 Docker 映像檔

若要快速入門,您可以執行 Spring Boot 映像檔產生器,甚至無需變更您的 pom.xml(請記住,Dockerfile(如果仍然存在)會被忽略)

./mvnw spring-boot:build-image -Dspring-boot.build-image.imageName=springio/gs-spring-boot-docker

若要推送至 Docker 登錄檔,您需要擁有推送權限,而您預設沒有該權限。將映像檔前置詞變更為您自己的 Dockerhub ID,並執行 docker login 以確保在執行 Docker 之前已通過驗證。

推送之後

範例中的 docker push 會失敗(除非您是 Dockerhub 中「springio」組織的成員)。但是,如果您變更組態以符合您自己的 docker ID,則應該會成功。然後,您會有一個新的已標記、已部署的映像檔。

需要向 docker 註冊或發佈任何內容即可執行在本機建置的 docker 映像檔。如果您使用 Docker(從命令列或從 Spring Boot)建置,您仍然會有一個本機標記的映像檔,您可以像這樣執行它

$ docker run -p 8080:8080 -t springio/gs-spring-boot-docker
Container memory limit unset. Configuring JVM for 1G container.
Calculated JVM Memory Configuration: -XX:MaxDirectMemorySize=10M -XX:MaxMetaspaceSize=86381K -XX:ReservedCodeCacheSize=240M -Xss1M -Xmx450194K (Head Room: 0%, Loaded Class Count: 12837, Thread Count: 250, Total Memory: 1073741824)
....
2015-03-31 13:25:48.035  INFO 1 --- [           main] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat started on port(s): 8080 (http)
2015-03-31 13:25:48.037  INFO 1 --- [           main] hello.Application                        : Started Application in 5.613 seconds (JVM running for 7.293)
建置套件會在執行時使用記憶體計算器來調整 JVM 大小以符合容器。

應用程式隨後可在 https://127.0.0.1:8080 上使用(造訪該網址,它會顯示「Hello Docker World」)。

當在 Mac 上使用 boot2docker 時,您通常會在啟動時看到類似這樣的內容

Docker client to the Docker daemon, please set:
    export DOCKER_CERT_PATH=/Users/gturnquist/.boot2docker/certs/boot2docker-vm
    export DOCKER_TLS_VERIFY=1
    export DOCKER_HOST=tcp://192.168.59.103:2376

若要查看應用程式,您必須造訪 DOCKER_HOST 中的 IP 位址,而不是 localhost — 在本例中,https://192.168.59.103:8080,VM 的面向公眾的 IP。

當它執行時,您可以在容器列表中看到類似於以下範例的內容

$ docker ps
CONTAINER ID        IMAGE                                   COMMAND                  CREATED             STATUS              PORTS                    NAMES
81c723d22865        springio/gs-spring-boot-docker:latest   "java -Djava.secur..."   34 seconds ago      Up 33 seconds       0.0.0.0:8080->8080/tcp   goofy_brown

若要再次關閉它,您可以使用先前清單中的容器 ID 執行 docker stop(您的 ID 會有所不同)

docker stop goofy_brown
81c723d22865

如果您願意,您也可以在完成容器後刪除它(它會持續儲存在您檔案系統中的 /var/lib/docker 下的某個位置)

docker rm goofy_brown

使用 Spring Profile

使用 Spring Profile 執行您新建立的 Docker 映像檔就像將環境變數傳遞至 Docker run 命令一樣簡單(適用於 prod Profile)

docker run -e "SPRING_PROFILES_ACTIVE=prod" -p 8080:8080 -t springio/gs-spring-boot-docker

您可以對 dev Profile 執行相同的操作

docker run -e "SPRING_PROFILES_ACTIVE=dev" -p 8080:8080 -t springio/gs-spring-boot-docker

在 Docker 容器中偵錯應用程式

若要偵錯應用程式,您可以使用 JPDA Transport。我們將容器視為遠端伺服器。若要啟用此功能,請在 JAVA_OPTS 變數中傳遞 Java 代理程式設定,並在容器執行期間將代理程式的埠對應至 localhost。使用 Docker for Mac,存在一個限制,因為我們無法在沒有 黑魔法用法 的情況下透過 IP 存取容器。

docker run -e "JAVA_TOOL_OPTIONS=-agentlib:jdwp=transport=dt_socket,address=5005,server=y,suspend=n" -p 8080:8080 -p 5005:5005 -t springio/gs-spring-boot-docker

摘要

恭喜!您已為 Spring Boot 應用程式建立 Docker 容器!預設情況下,Spring Boot 應用程式在容器內部的 8080 埠上執行,我們使用命令列上的 -p 將其對應到主機上的相同埠。

另請參閱

以下指南也可能有所幫助

想要撰寫新的指南或為現有指南貢獻一份心力嗎?請查看我們的貢獻指南

所有指南均以 ASLv2 授權發佈程式碼,並以 姓名標示-禁止改作創用 CC 授權 發佈寫作內容。

取得程式碼