博客 / 詳情

返回

Java 應用容器化與部署

如何開始打包、分發並將 Java 交付至生產環境

應用程序的容器化 提供了一種方法,可以將所有必需的應用程序資源——包括程序和配置文件、環境變量、網絡設置等——組合到一個標準化、易於管理的包中。

從單個容器鏡像可以啓動、運行、管理和終止多個功能相同的容器,確保從鏡像創建點開始的一致性。容器可以在截然不同的操作平台上運行,從本地機器到全球可擴展的雲環境,以及介於兩者之間的一切。可以構建流水線,輕鬆地在它們之間過渡。
雖然應用程序容器化有許多好處,但許多都可以歸結為一個詞:一致性

為何要對 JAVA 應用進行容器化?

Java 早期的一個承諾是"一次編寫,到處運行",即"WORA"。儘管 Java 通過其 Java 虛擬機(JVM)實現了某種形式的目標,但在實現真正無縫的體驗方面仍然存在相當多的外部障礙。
容器化解決了幾乎所有這些外部障礙。雖然 100% 在任何追求中都可能是一個難以實現的目標,但將 Java 應用程序的可執行文件及其所有必需的依賴項和支持屬性(配置等)打包的能力,使我們達到了有效的 100% 可移植性和一致性水平。

為 JAVA 應用程序創建 DOCKERFILE

許多開發人員通過仔細閲讀官方的 Dockerfile 參考文檔來開始他們的容器化工作。為了立即獲得良好效果,讓我們介紹關鍵點,創建一些鏡像,並在此基礎上進行構建。

為容器化選擇操作系統和 JDK 構建

對此有各種不同的觀點,但如果您剛開始接觸容器化,從一個較小但完整的操作系統(OS)開始是一個很好的第一步。我們稍後將討論其他選項(例如,無發行版)。
一般來説,您在操作系統層包含的內容越多,容器鏡像就越大,安全漏洞的攻擊面也就越大。可信來源也是一個關鍵的考慮因素。如果使用完整的操作系統構建,強烈推薦使用 eclipse-temurin(基於 Ubuntu)或 Alpine 基礎層。
任何 OpenJDK 的構建都能運行您基於 JVM 的 Java 應用程序,而 Eclipse Temurin 是眾多良好選項之一。但是,如果您希望對可能發現的任何 Java 問題獲得專門的生產支持,那麼選擇商業支持的構建可以提供這種支持。

JAVA 應用的基本 DOCKERFILE 結構

一個基本 Java 應用程序的最低可行 Dockerfile 看起來像這樣:

FROM eclipse-temurin:latest
COPY java-in-the-can-0.0.1-SNAPSHOT.jar /app.jar
EXPOSE 8080
CMD ["java", "-jar", "/app.jar"]

將上述文本(在 COPY 指令中使用您應用程序的名稱)保存在一個名為 Dockerfile 的文件中,該文件與您的 Java 應用程序(.jar)文件位於同一目錄。
在上面的 Dockerfile 中,我們提供了構建容器鏡像的基本信息:

  • 構建應用程序容器鏡像所基於的更高層基礎鏡像FROM
  • 將 .jar 文件複製COPY)到鏡像中(在此示例中,還進行了重命名)的命令
  • 為應用程序監聽連接請求而需要暴露EXPOSE)的任何特定端口(如有必要)
  • 在容器啓動時運行應用程序的命令CMD
    在包含您的 Dockerfile 和 .jar 文件的目錄中執行以下命令:
docker build -t <app-image-name> .

請注意,在運行鏡像創建和其他容器命令之前,docker 守護進程(或 Mac/Windows 上的 Docker Desktop、Podman 等)必須正在運行。另外,不要忘記命令末尾的 .;它指的是可以找到 Dockerfile 的當前目錄。
以這種方式運行生成的應用程序容器,用您上面創建的容器鏡像名稱替換 <app-image-name>

docker run -p 8080:8080 <app-image-name>

選擇無發行版操作系統+JDK 基礎鏡像

對於大多數用例,在大小和攻擊面方面可實現的最佳優化可能由"無發行版"基礎鏡像提供。雖然無發行版基礎鏡像中確實包含一個 Linux 發行版,但它已被剝離了任何非當前目的 specifically 不需要的文件,留下一個完全精簡的操作系統,對於無發行版 Java 鏡像而言,還包括 JVM。以下是一個使用無發行版 Java 基礎鏡像的 Dockerfile 示例:

FROM mcr.microsoft.com/openjdk/jdk:21-distroless
COPY java-in-the-can-0.0.1-SNAPSHOT.jar /app.jar
EXPOSE 8080
CMD ["-Xmx256m", "-jar", "/app.jar"]

請注意,這個針對 Java 優化的基礎鏡像預先配置了 java 命令的 ENTRYPOINT,因此 CMD 指令用於為 JVM 啓動器進程提供命令行參數。

使用多階段構建來減小鏡像大小

如果您有構建所需但最終輸出不需要的文件,多階段構建提供了減小容器鏡像大小的方法。就本文參考而言,情況並非如此,因為 JVM 以及應用程序的 .jar 文件和依賴項已為鏡像創建預先配置好了。
正如您可能想象的那樣,在某些非常常見的情況下,這變得有利。通常,應用程序通過配置好的構建流水線部署到生產環境,這些流水線基於源倉庫上的觸發器來創建構件。這是多階段構建的最佳用例之一:構建流水線創建一個帶有適當工具的構建容器,使用它來創建構件(例如 .jar 文件、配置文件),然後將這些構件複製到一個新的容器鏡像中,該鏡像不包含生產環境不需要的額外工具。這一系列操作大致類似於我們之前手動完成的操作,但實現了自動化,以獲得一致和最優的結果。

管理環境變量

有多種方法可以向容器和應用程序提供輸入值,用於啓動或執行。一個應該採用的良好實踐是,儘可能使用 ENVENTRYPOINTCMD 指令在 Dockerfile 本身中指定所有可能的值。所有這些值都可以在容器初始化時根據需要覆蓋。
請注意,覆蓋現有環境變量時應謹慎,因為這可能會以意外和不良的方式改變應用程序行為。
使用 ENV 配置 Java 特定選項的示例:

ENV JAVA_OPTS="-Xmx512m -Xms256m"

相同的概念也適用於應用程序特定變量:

ENV APP_GREETING="Greetings, Friend!"

使用 ENTRYPOINT 配置應用程序特定值的示例:

ENTRYPOINT ["java", "$JAVA_OPTS", "-jar", "your-app.jar"]

使用 CMD 的示例:

CMD ["java", "-Xmx256m", "-jar", "/app.jar"]

您可能已經注意到,ENTRYPOINTCMD 都可以用來執行 Java 應用程序。像所有其他技術(和非技術)選項一樣,這兩種指令各有優缺點。如果操作得當,兩者都會使您的 Java 應用程序運行。
一般來説,Java 應用程序使用 CMD 指令,以便應用程序可以處理操作系統信號,用於支持的鈎子機制(例如,SIGTERM 對應 java.lang.Runtime.addShutdownHook)。當然,這並非絕對必要,並且可以(也經常)使用 ENTRYPOINTCMD 來促進運行時參數傳遞,以提供/覆蓋特定行為。這兩者並不互斥。

使用 SPRING BOOT 插件進行容器化

如果您使用 Spring Boot 開發 Java 應用程序,容器化會簡單得多。無論使用 Maven 還是 Gradle 作為項目構建工具,創建容器鏡像都像執行一個預定義的目標一樣簡單。

  • 如果使用 Maven 作為構建工具,您可以通過調用 build-image 目標來創建包含應用程序的容器鏡像:
    ./mvnw spring-boot:build-image
  • 如果使用 Gradle 作為構建工具,您可以通過調用 bootBuildImage 目標來創建包含應用程序的容器鏡像:
    ./gradlew bootBuildImage
    在大多數情況下,自定義鏡像創建(例如,鏡像層定義)既不必要也不可取,但如果需要這樣做,請參閲 Spring Boot Maven 或 Gradle 插件文檔中的"打包 OCI 鏡像"部分。

為原生應用程序構建容器鏡像

開發人員可以選擇使用 JVM 或作為原生的、操作系統特定的可執行文件來交付 Java 應用程序。以下部分提供了一些關於選擇的考慮因素,以及如果您決定使用原生應用程序構建容器鏡像,如何以最小的代價實現。

使用 GRAALVM 的 JAVA 原生可執行文件

GraalVM 支持創建原生可執行文件/二進制 Java 應用程序,在構建時執行所有編譯和優化,而不是利用 JVM 在運行應用程序字節碼時進行一些優化。
與所有選擇一樣,需要權衡利弊。編譯為字節碼與原生可執行文件是秒與分鐘的問題,並且 JVM 執行的運行時優化在原生可執行文件中消失了,因為代碼無法在運行時動態重寫(這是 JVM 啓用的一個特性)。
原生可執行文件優於基於 JVM 的 Java 應用程序的地方在於文件大小、內存需求和啓動時間。原生應用程序要小得多,需要的資源更少,不需要 JVM 存在,並且啓動速度顯著更快。這在許多生產環境中是非常重要的考慮因素,因為較小的應用程序(及其容器)會降低平台資源需求,並且以毫秒而不是幾秒衡量的啓動時間可以增加可用性、可擴展性以及系統設計和部署的選項,從而可以顯著節省成本。
根據您的框架和工具選擇,有幾種構建完全可執行的、操作系統原生的 Java 應用程序的選項。然而,一旦您有了原生可執行文件/二進制應用程序,您可以創建一個類似以下的 Dockerfile 作為您的原生應用程序容器鏡像的模板:

FROM alpine:latest
WORKDIR /app
COPY java-in-the-can /app/
EXPOSE 8080
CMD ["/app/java-in-the-can"]

如果您使用 Spring Boot,您可以使用 GraalVM Maven 或 Gradle 插件,通過一條命令將您的應用程序編譯為操作系統原生應用程序並創建容器鏡像。

MAVEN
首先,將此依賴項添加到您的 pom.xml<build><plugins> 部分並保存文件:

<plugin>
    <groupId>org.graalvm.buildtools</groupId>
    <artifactId>native-maven-plugin</artifactId>
</plugin>

要構建原生應用程序和容器鏡像,從您的項目根目錄運行此命令:
./mvnw -Pnative spring-boot:build-image

GRADLE
類似地,將此依賴項添加到您的 build.gradle 文件的 plugins {} 部分並保存:
id 'org.graalvm.buildtools.native'

要構建原生應用程序和容器鏡像,從您的項目根目錄運行此命令:
./gradlew bootBuildImage

關於減小鏡像大小和加快啓動時間的考慮因素

您可能已經注意到,上述各節的順序總體上趨向於生產更精簡、啓動更快的容器鏡像。許多決策可能涉及組織標準或選擇(例如,部署標準),這些標準或選擇會使天平傾向於或反對某些選擇,但一般來説,容器鏡像優化的路徑遵循以下順序:

  1. 選擇更小的基礎鏡像(操作系統發行版和 JVM)
  2. 選擇帶有 JVM 的無發行版鏡像
  3. 如果您的工具鏈(例如 Spring Boot)允許,利用專門構建的工具
  4. 利用帶有原生可執行應用程序的精簡發行版或無發行版鏡像

容器中 JAVA 應用程序的部署策略

您的應用程序的重要考慮因素超出了將其打包成應用程序容器鏡像的範圍。接下來是部署和維護決策,這些決策對於您的應用程序進入並保持在生產環境至關重要。

單容器部署

對於基本上是自包含的應用程序,部署到生產環境可以像單個命令一樣簡單,前提是部署目標已準備好接受容器化應用程序。即使在應用程序部署之前必須創建支持資源的情況下,這通常也意味着通過命令行或 Web 門户發出少量指令。當應用程序包含多個容器部署時,進程間依賴關係可能要求按特定順序部署容器,以確保可用性或最小化波動或 chatter。為了實現這些目標,需要進行編排部署。

編排部署

編排部署可能比單容器部署複雜得多,並相應地提供更多功能。由於這兩個特點,與單容器部署相比,編排部署可能有更多平台層級值得考慮。這些層級範圍從提供廣泛靈活性並相應需要開發人員付出更高水平努力的較低層級 Kubernetes 平台,到完成大量繁重工作以安全配置和集成多個容器和/或服務的完整平台。
您選擇的目標平台將決定您對部署工具(例如,腳本、門户、基礎設施配置工具)的選擇。非常籠統地説,您選擇的平台目標應該是能夠部署和維護您的應用程序及其相關服務的最簡單的平台。其他重要的考慮因素包括應用程序所有必需容器/服務的部署目標之間的比較成本、您組織已建立的實踐/流水線等。

構建支持持續打補丁的 JAVA 應用程序

生產部署在應用程序上線後並未完成;開發人員必須確保應用程序保持安全、最新和可用。關鍵的補丁管理考慮因素包括:

  • 定期補丁 – 為常規補丁(例如,每月或每季度)建立一個無中斷、可預測的頻率,以更新庫和依賴項
  • 緊急補丁 – 提供關於何時需要緊急補丁的指導,通常是為了應對關鍵漏洞或緊急安全更新
    容器鏡像中需要打補丁的組件包括:
  • 基礎操作系統容器鏡像
  • 附加的操作系統包(如果適用)
  • 應用程序運行時(例如,JVM 版本,如果未包含在基礎鏡像中)
  • 應用程序依賴項/庫
  • 應用程序性能監控(APM)代理二進制文件
    由開發人員及其組織來決定並嚴格維護一個保護應用程序、系統基礎設施和數據的補丁策略。請參考此指南來幫助制定您的具體策略。

結論

容器化使開發人員能夠將所有必需的應用程序資源和支持服務組合到一個或多個容器鏡像中,並更輕鬆地部署、運行和管理它們。如果操作得當,容器化可以從鏡像創建點開始實現安全性和一致性。容器可以在截然不同的操作平台上運行,從本地機器到全球可擴展的雲環境。可以構建流水線,輕鬆地在它們之間過渡。因此,開發人員構建和運行支持生產工作負載的相同構件,減少了衝突並簡化了調優和故障排除。
如果您是容器新手,請從小處着手,在本地構建以獲得知識和穩定的基礎,然後通過納入更多容器最佳實踐、構建流水線和合適的雲平台來"擴展構建",逐步走向強大的應用程序部署生產模型。

其他考慮因素和資源:

  • 工具 – Docker, Podman, Minikube, Skaffold
  • 基於 JVM OSS 的可觀測性工具 – Java Flight Recorder, Java Mission Control, Visual VM, Jitwatch
作者:MARK A. HECKLER,
微軟首席雲技術推廣專家(Java/JVM 語言)
Mark Heckler 是一名軟件開發人員,微軟的 Java/JVM 語言首席雲技術推廣專家,會議演講者,Java Champion 和 Kotlin 開發專家,專注於為雲和邊緣計算平台快速開發生產軟件。Mark 是開源貢獻者,也是《Spring Boot: Up and Running》的作者,可以在 X @mkheck 上找到他。

【注】本文譯自:Java Application Containerization and Deployment

user avatar
0 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.