Spring Boot 與 Docker

本指南將引導您完成為執行 Spring Boot 應用程式構建 Docker 映象的過程。我們將從一個基本的 Dockerfile 開始,然後進行一些調整。之後,我們將介紹兩種不使用 docker 命令而使用構建外掛(適用於 Maven 和 Gradle)的選項。這是一個“入門”指南,因此範圍僅限於一些基本需求。如果您正在為生產環境構建容器映象,有許多事項需要考慮,而一本簡短的指南不可能涵蓋所有內容。

還有一份關於 Docker 的 專題指南,它涵蓋了比本指南更廣泛的選擇,並且更為詳細。

您將構建什麼

Docker 是一個 Linux 容器管理工具包,具有“社交”特性,允許使用者釋出容器映象並使用其他人釋出的映象。Docker 映象是一個執行容器化程序的“配方”。在本指南中,我們將為一個簡單的 Spring Boot 應用程式構建一個映象。

您需要準備什麼

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

您還需要 Docker,它只在 64 位機器上執行。有關為您的機器設定 Docker 的詳細資訊,請參閱 https://dockerdocs.tw/installation/#installation。在繼續之前,請驗證您可以在 shell 中執行 docker 命令。如果您使用 boot2docker,您需要 **首先** 執行它。

從 Spring Initializr 開始

您可以使用這個 預先初始化好的專案,然後點選“生成”下載 ZIP 檔案。這個專案已配置為適應本教程中的示例。

手動初始化專案

  1. 導航到 https://start.spring.io。此服務會為您拉取應用程式所需的所有依賴項,併為您完成大部分設定。

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

  3. 點選 Dependencies 並選擇 Spring Web

  4. 單擊生成

  5. 下載生成的 ZIP 檔案,這是一個已根據您的選擇配置好的 Web 應用程式存檔。

如果您的 IDE 集成了 Spring Initializr,您可以從 IDE 中完成此過程。
您還可以從 Github fork 該專案並在您的 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 已準備好使用它來處理 Web 請求。@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://:8080 訪問該應用程式(訪問它會顯示“Hello Docker World”)。

在使用帶有 boot2docker 的 Mac 時,您通常會在啟動時看到類似以下內容:

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 Profiles

使用 Spring Profiles 執行您新建立的 Docker 映象,就像將環境變數傳遞給 Docker 執行命令一樣簡單(用於 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 傳輸。我們將容器視為遠端伺服器。要啟用此功能,請在 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 許可,文字內容採用署名-禁止演繹知識共享許可

獲取程式碼