許多人使用容器來(lái)包裝他們的 Spring Boot 應(yīng)用程序,而構(gòu)建容器并不是一件簡(jiǎn)單的事情。這是針對(duì) Spring Boot 應(yīng)用程序開(kāi)發(fā)人員的指南,容器對(duì)于開(kāi)發(fā)人員來(lái)說(shuō)并不總是一個(gè)好的抽象。它們迫使你去了解和思考低層次的問(wèn)題。但是,有時(shí)可能會(huì)要求您創(chuàng)建或使用容器,因此了解構(gòu)建塊是值得的。在本指南中,我們旨在向您展示如果您面臨需要?jiǎng)?chuàng)建自己的容器的前景,您可以做出的一些選擇。
我們假設(shè)您知道如何創(chuàng)建和構(gòu)建基本的 Spring Boot 應(yīng)用程序。如果沒(méi)有,請(qǐng)轉(zhuǎn)到入門指南之一?——例如,關(guān)于構(gòu)建REST 服務(wù)的指南。從那里復(fù)制代碼并練習(xí)本指南中包含的一些想法。
還有一個(gè)關(guān)于Docker的入門指南,這也是一個(gè)很好的起點(diǎn),但它沒(méi)有涵蓋我們?cè)诖颂幗榻B的選擇范圍或詳細(xì)介紹它們。
一個(gè)基本的 Dockerfile
Spring Boot 應(yīng)用程序很容易轉(zhuǎn)換為可執(zhí)行的 JAR 文件。所有的入門指南都是這樣做的,你從Spring Initializr下載的每個(gè)應(yīng)用程序都有一個(gè)構(gòu)建步驟來(lái)創(chuàng)建一個(gè)可執(zhí)行的 JAR。使用 Maven,你運(yùn)行./mvnw install,使用 Gradle,你運(yùn)行./gradlew build。運(yùn)行該 JAR 的基本 Dockerfile 將如下所示,位于項(xiàng)目的頂層:
Dockerfile
FROM openjdk:8-jdk-alpine
VOLUME /tmp
ARG JAR_FILE
COPY ${JAR_FILE} app.jar
ENTRYPOINT ["java","-jar","/app.jar"]復(fù)制
JAR_FILE您可以作為命令的一部分傳入docker(Maven 和 Gradle 不同)。對(duì)于 Maven,以下命令有效:
docker build --build-arg JAR_FILE=target/*.jar -t myorg/myapp .復(fù)制
對(duì)于 Gradle,以下命令有效:
docker build --build-arg JAR_FILE=build/libs/*.jar -t myorg/myapp .復(fù)制
一旦你選擇了一個(gè)構(gòu)建系統(tǒng),你就不需要ARG. 您可以對(duì) JAR 位置進(jìn)行硬編碼。對(duì)于 Maven,如下所示:
Dockerfile
FROM openjdk:8-jdk-alpine
VOLUME /tmp
COPY target/*.jar app.jar
ENTRYPOINT ["java","-jar","/app.jar"]復(fù)制
然后我們可以使用以下命令構(gòu)建鏡像:
docker build -t myorg/myapp .復(fù)制
然后我們可以通過(guò)運(yùn)行以下命令來(lái)運(yùn)行它:
docker run -p 8080:8080 myorg/myapp復(fù)制
輸出類似于以下示例輸出:
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.0.2.RELEASE)
Nov 06, 2018 2:45:16 PM org.springframework.boot.StartupInfoLogger logStarting
INFO: Starting Application v0.1.0 on b8469cdc9b87 with PID 1 (/app.jar started by root in /)
Nov 06, 2018 2:45:16 PM org.springframework.boot.SpringApplication logStartupProfileInfo
...復(fù)制
如果你想在鏡像內(nèi)部四處尋找,你可以通過(guò)運(yùn)行以下命令在其中打開(kāi)一個(gè) shell(注意基礎(chǔ)鏡像沒(méi)有bash):
docker run -ti --entrypoint /bin/sh myorg/myapp復(fù)制
輸出類似于以下示例輸出:
/ # ls
app.jar dev home media proc run srv tmp var
bin etc lib mnt root sbin sys usr
/ #
我們?cè)谑纠惺褂玫?alpine 基礎(chǔ)容器沒(méi)有bash,所以這是一個(gè)ashshell。它具有一些但不是全部的特性bash。
如果你有一個(gè)正在運(yùn)行的容器并且你想查看它,你可以通過(guò)運(yùn)行docker exec:
docker run --name myapp -ti --entrypoint /bin/sh myorg/myapp
docker exec -ti myapp /bin/sh
/ #復(fù)制
傳遞給命令myapp的位置在哪里。如果您沒(méi)有使用,docker 會(huì)分配一個(gè)助記名稱,您可以從. 您還可以使用容器的 SHA 標(biāo)識(shí)符而不是名稱。SHA 標(biāo)識(shí)符在輸出中也可見(jiàn)。--namedocker run--namedocker psdocker ps
入口點(diǎn)
使用Dockerfile的exec 形式ENTRYPOINT,以便沒(méi)有外殼包裝 Java 進(jìn)程。優(yōu)點(diǎn)是java進(jìn)程響應(yīng)KILL發(fā)送到容器的信號(hào)。實(shí)際上,這意味著(例如)如果您docker run在本地使用圖像,則可以使用CTRL-C. 如果命令行有點(diǎn)長(zhǎng),您可以COPY在運(yùn)行之前將其提取到 shell 腳本中并放入映像中。以下示例顯示了如何執(zhí)行此操作:
Dockerfile
FROM openjdk:8-jdk-alpine
VOLUME /tmp
COPY run.sh .
COPY target/*.jar app.jar
ENTRYPOINT ["run.sh"]復(fù)制
請(qǐng)記住使用exec java …啟動(dòng) java 進(jìn)程(以便它可以處理KILL信號(hào)):
run.sh
#!/bin/sh
exec java -jar /app.jar復(fù)制
入口點(diǎn)的另一個(gè)有趣方面是您是否可以在運(yùn)行時(shí)將環(huán)境變量注入 Java 進(jìn)程。例如,假設(shè)您想要在運(yùn)行時(shí)添加 Java 命令行選項(xiàng)。您可以嘗試這樣做:
Dockerfile
FROM openjdk:8-jdk-alpine
VOLUME /tmp
ARG JAR_FILE=target/*.jar
COPY ${JAR_FILE} app.jar
ENTRYPOINT ["java","${JAVA_OPTS}","-jar","/app.jar"]復(fù)制
然后您可以嘗試以下命令:
docker build -t myorg/myapp .
docker run -p 9000:9000 -e JAVA_OPTS=-Dserver.port=9000 myorg/myapp復(fù)制
這失敗了,因?yàn)?/span>${}替換需要一個(gè)外殼。exec 表單不使用 shell 來(lái)啟動(dòng)進(jìn)程,因此不應(yīng)用選項(xiàng)。您可以通過(guò)將入口點(diǎn)移動(dòng)到腳本(如run.sh前面顯示的示例)或在入口點(diǎn)顯式創(chuàng)建 shell 來(lái)解決此問(wèn)題。以下示例顯示了如何在入口點(diǎn)中創(chuàng)建 shell:
Dockerfile
FROM openjdk:8-jdk-alpine
VOLUME /tmp
ARG JAR_FILE=target/*.jar
COPY ${JAR_FILE} app.jar
ENTRYPOINT ["sh", "-c", "java ${JAVA_OPTS} -jar /app.jar"]復(fù)制
然后,您可以通過(guò)運(yùn)行以下命令來(lái)啟動(dòng)此應(yīng)用程序:
docker run -p 8080:8080 -e "JAVA_OPTS=-Ddebug -Xmx128m" myorg/myapp復(fù)制
該命令產(chǎn)生類似于以下的輸出:
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.2.0.RELEASE)
...
2019-10-29 09:12:12.169 DEBUG 1 --- [ main] ConditionEvaluationReportLoggingListener :
============================
CONDITIONS EVALUATION REPORT
============================
...復(fù)制
(前面的輸出顯示了 Spring BootDEBUG生成的完整輸出的一部分。)-Ddebug
將 anENTRYPOINT與顯式 shell 一起使用(如前面的示例所做的那樣)意味著您可以將環(huán)境變量傳遞給 Java 命令。但是,到目前為止,您還不能為 Spring Boot 應(yīng)用程序提供命令行參數(shù)。以下命令不會(huì)在端口 9000 上運(yùn)行應(yīng)用程序:
docker run -p 9000:9000 myorg/myapp --server.port=9000復(fù)制
該命令產(chǎn)生以下輸出,將端口顯示為 8080 而不是 9000:
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.2.0.RELEASE)
...
2019-10-29 09:20:19.718 INFO 1 --- [ main] o.s.b.web.embedded.netty.NettyWebServer : Netty started on port(s): 8080復(fù)制
它不起作用,因?yàn)?docker 命令(該--server.port=9000部分)被傳遞到入口點(diǎn) ( sh),而不是它啟動(dòng)的 Java 進(jìn)程。要解決此問(wèn)題,您需要將命令行從以下添加CMD到ENTRYPOINT:
Dockerfile
FROM openjdk:8-jdk-alpine
VOLUME /tmp
ARG JAR_FILE=target/*.jar
COPY ${JAR_FILE} app.jar
ENTRYPOINT ["sh", "-c", "java ${JAVA_OPTS} -jar /app.jar ${0} ${@}"]復(fù)制
然后您可以運(yùn)行相同的命令并將端口設(shè)置為 9000:
$ docker run -p 9000:9000 myorg/myapp --server.port=9000復(fù)制
如以下輸出示例所示,端口確實(shí)設(shè)置為 9000:
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.2.0.RELEASE)
...
2019-10-29 09:30:19.751 INFO 1 --- [ main] o.s.b.web.embedded.netty.NettyWebServer : Netty started on port(s): 9000復(fù)制
注意${0}“命令”(在這種情況下是第一個(gè)程序參數(shù))和${@}“命令參數(shù)”(程序參數(shù)的其余部分)的使用。如果您使用腳本作為入口點(diǎn),那么您不需要${0}(/app/run.sh在前面的示例中)。以下列表顯示了腳本文件中的正確命令:
run.sh
#!/bin/sh
exec java ${JAVA_OPTS} -jar /app.jar ${@}復(fù)制
docker配置到現(xiàn)在都非常簡(jiǎn)單,生成的鏡像效率不是很高。docker 鏡像有一個(gè)文件系統(tǒng)層,其中包含 fat JAR,我們對(duì)應(yīng)用程序代碼所做的每一次更改都會(huì)更改該層,這可能是 10MB 或更多(對(duì)于某些應(yīng)用程序甚至高達(dá) 50MB)。我們可以通過(guò)將 JAR 拆分為多個(gè)層來(lái)改進(jìn)這一點(diǎn)。
較小的圖像
請(qǐng)注意,前面示例中的基本映像是openjdk:8-jdk-alpine. 這些alpine圖像小于Dockerhubopenjdk的標(biāo)準(zhǔn)庫(kù)圖像。您還可以通過(guò)使用標(biāo)簽而不是. 并非所有應(yīng)用程序都使用 JRE(與 JDK 相對(duì)),但大多數(shù)應(yīng)用程序都可以。一些組織強(qiáng)制執(zhí)行一個(gè)規(guī)則,即每個(gè)應(yīng)用程序都必須使用 JRE,因?yàn)榇嬖跒E用某些 JDK 功能(例如編譯)的風(fēng)險(xiǎn)。jrejdk
另一個(gè)可以讓您獲得更小的映像的技巧是使用JLink,它與 OpenJDK 11 捆綁在一起。JLink 允許您從完整 JDK 中的模塊子集構(gòu)建自定義 JRE 分發(fā),因此您不需要 JRE 或 JDK基礎(chǔ)圖像。原則上,這將使您獲得比使用openjdk官方 docker 圖像更小的總圖像大小。在實(shí)踐中,您(還)不能將alpine基礎(chǔ)鏡像與 JDK 11 一起使用,因此您對(duì)基礎(chǔ)鏡像的選擇是有限的,并且可能會(huì)導(dǎo)致最終鏡像的大小更大。此外,您自己的基本映像中的自定義 JRE 不能在其他應(yīng)用程序之間共享,因?yàn)樗鼈冃枰煌淖远x。因此,您的所有應(yīng)用程序可能都有較小的圖像,但它們?nèi)匀恍枰L(zhǎng)的時(shí)間才能啟動(dòng),因?yàn)樗鼈儧](méi)有從緩存 JRE 層中受益。
最后一點(diǎn)突出了圖像構(gòu)建者的一個(gè)非常重要的問(wèn)題:目標(biāo)不一定總是盡可能地構(gòu)建最小的圖像。較小的圖像通常是一個(gè)好主意,因?yàn)樗鼈冃枰俚臅r(shí)間來(lái)上傳和下載,但前提是它們中的所有圖層都沒(méi)有被緩存。如今,圖像注冊(cè)非常復(fù)雜,您很容易通過(guò)嘗試巧妙地構(gòu)建圖像而失去這些功能的好處。如果您使用通用基礎(chǔ)層,圖像的總大小就不再那么重要了,而且隨著注冊(cè)中心和平臺(tái)的發(fā)展,它可能變得更不重要。話雖如此,嘗試優(yōu)化應(yīng)用程序映像中的層仍然很重要且有用。然而,
更好的 Dockerfile
由于 JAR 本身的打包方式,Spring Boot fat JAR 自然有“層”。如果我們先解包,它已經(jīng)分為外部依賴和內(nèi)部依賴。要在 docker 構(gòu)建中一步完成此操作,我們需要先解壓縮 JAR。以下命令(堅(jiān)持使用 Maven,但 Gradle 版本非常相似)解壓縮 Spring Boot fat JAR:
mkdir target/dependency
(cd target/dependency; jar -xf ../*.jar)
docker build -t myorg/myapp .復(fù)制
然后我們可以使用下面的Dockerfile
Dockerfile
FROM openjdk:8-jdk-alpine
VOLUME /tmp
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"]復(fù)制
現(xiàn)在有三層,所有應(yīng)用程序資源都在后面兩層。如果應(yīng)用程序依賴沒(méi)有改變,第一層(from BOOT-INF/lib)不需要改變,所以構(gòu)建更快,并且容器在運(yùn)行時(shí)的啟動(dòng)也更快,只要基礎(chǔ)層已經(jīng)被緩存。
我們使用了一個(gè)硬編碼的主應(yīng)用程序類:hello.Application. 這對(duì)于您的應(yīng)用程序可能有所不同。如果你愿意,你可以用另一個(gè)參數(shù)化它ARG。您還可以將 Spring Boot fat 復(fù)制JarLauncher到映像中并使用它來(lái)運(yùn)行應(yīng)用程序。它可以工作,您不需要指定主類,但啟動(dòng)時(shí)會(huì)慢一些。
Spring Boot 層索引
從 Spring Boot 2.3.0 開(kāi)始,使用 Spring Boot Maven 或 Gradle 插件構(gòu)建的 JAR 文件在 JAR 文件中包含層信息。該層信息根據(jù)應(yīng)用程序構(gòu)建之間更改的可能性來(lái)分離應(yīng)用程序的各個(gè)部分。這可以用來(lái)使 Docker 鏡像層更加高效。
層信息可用于將 JAR 內(nèi)容提取到每個(gè)層的目錄中:
mkdir target/extracted
java -Djarmode=layertools -jar target/*.jar extract --destination target/extracted
docker build -t myorg/myapp .復(fù)制
然后我們可以使用以下內(nèi)容Dockerfile:
Dockerfile
FROM openjdk:8-jdk-alpine
VOLUME /tmp
ARG EXTRACTED=/workspace/app/target/extracted
COPY ${EXTRACTED}/dependencies/ ./
COPY ${EXTRACTED}/spring-boot-loader/ ./
COPY ${EXTRACTED}/snapshot-dependencies/ ./
COPY ${EXTRACTED}/application/ ./
ENTRYPOINT ["java","org.springframework.boot.loader.JarLauncher"]
Spring Boot fatJarLauncher是從 JAR 中提取到鏡像中的,因此它可以用于啟動(dòng)應(yīng)用程序,而無(wú)需對(duì)主應(yīng)用程序類進(jìn)行硬編碼。
有關(guān)使用分層功能的更多信息,請(qǐng)參閱Spring Boot 文檔。
調(diào)整
如果您想盡快啟動(dòng)您的應(yīng)用程序(大多數(shù)人都這樣做),您可能會(huì)考慮一些調(diào)整:
- 使用spring-context-indexer(鏈接到文檔)。它不會(huì)為小型應(yīng)用程序增加太多,但每一點(diǎn)都有幫助。
- 如果您負(fù)擔(dān)得起,請(qǐng)不要使用執(zhí)行器。
- 使用 Spring Boot 2.1(或更高版本)和 Spring 5.1(或更高版本)。
- 使用(通過(guò)命令行參數(shù)、系統(tǒng)屬性或其他方法)修復(fù)Spring Boot 配置文件的位置。spring.config.location
- 通過(guò)設(shè)置來(lái)關(guān)閉 JMX(您可能不需要在容器中使用它)spring.jmx.enabled=false。
- 使用-noverify. 還要考慮-XX:TieredStopAtLevel=1(這會(huì)在以后減慢 JIT 但會(huì)縮短啟動(dòng)時(shí)間)。
- 使用 Java 8 的容器內(nèi)存提示:-XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap. 在 Java 11 中,默認(rèn)情況下這是自動(dòng)的。
您的應(yīng)用程序在運(yùn)行時(shí)可能不需要完整的 CPU,但它確實(shí)需要多個(gè) CPU 才能盡快啟動(dòng)(至少兩個(gè),四個(gè)更好)。如果您不介意啟動(dòng)速度較慢,則可以將 CPU 限制在四個(gè)以下。如果您被迫從少于四個(gè) CPU 開(kāi)始,設(shè)置 可能會(huì)有所幫助
-Dspring.backgroundpreinitializer.ignore=true,因?yàn)樗梢苑乐?Spring Boot 創(chuàng)建一個(gè)它可能無(wú)法使用的新線程(這適用于 Spring Boot 2.1.0 及更高版本)。
多階段構(gòu)建
A Better Dockerfile中Dockerfile所示的假設(shè)假設(shè)胖 JAR 已經(jīng)在命令行上構(gòu)建。您還可以通過(guò)使用多階段構(gòu)建并將結(jié)果從一個(gè)圖像復(fù)制到另一個(gè)圖像來(lái)在 docker 中執(zhí)行該步驟。以下示例通過(guò)使用 Maven 來(lái)實(shí)現(xiàn):
Dockerfile
FROM openjdk:8-jdk-alpine as build
WORKDIR /workspace/app
COPY mvnw .
COPY .mvn .mvn
COPY pom.xml .
COPY src src
RUN ./mvnw install -DskipTests
RUN mkdir -p target/dependency && (cd target/dependency; jar -xf ../*.jar)
FROM openjdk:8-jdk-alpine
VOLUME /tmp
ARG DEPENDENCY=/workspace/app/target/dependency
COPY --from=build ${DEPENDENCY}/BOOT-INF/lib /app/lib
COPY --from=build ${DEPENDENCY}/META-INF /app/META-INF
COPY --from=build ${DEPENDENCY}/BOOT-INF/classes /app
ENTRYPOINT ["java","-cp","app:app/lib/*","hello.Application"]復(fù)制
第一個(gè)圖像標(biāo)記為build,它用于運(yùn)行 Maven、構(gòu)建胖 JAR 并解壓縮它。解包也可以由 Maven 或 Gradle 完成(這是入門指南中采用的方法)。沒(méi)有太大區(qū)別,只是必須編輯構(gòu)建配置并添加插件。
請(qǐng)注意,源代碼已分為四層。后面的層包含構(gòu)建配置和應(yīng)用程序的源代碼,前面的層包含構(gòu)建系統(tǒng)本身(Maven 包裝器)。這是一個(gè)小的優(yōu)化,也意味著我們不必將target目錄復(fù)制到 docker 鏡像,即使是用于構(gòu)建的臨時(shí)鏡像。
RUN每個(gè)源代碼更改的構(gòu)建都很慢,因?yàn)楸仨氃诘谝徊糠种匦聞?chuàng)建 Maven 緩存。但是你有一個(gè)完全獨(dú)立的構(gòu)建,只要他們有 docker,任何人都可以運(yùn)行它來(lái)運(yùn)行你的應(yīng)用程序。這在某些環(huán)境中可能非常有用——例如,您需要與不了解 Java 的人共享您的代碼。
實(shí)驗(yàn)功能
Docker 18.06 帶有一些“實(shí)驗(yàn)性”特性,包括緩存構(gòu)建依賴項(xiàng)的方法。要打開(kāi)它們,您需要在守護(hù)進(jìn)程 ( dockerd) 中有一個(gè)標(biāo)志,并在運(yùn)行客戶端時(shí)需要一個(gè)環(huán)境變量。然后你可以添加一個(gè)“神奇”的第一行到你的Dockerfile:
Dockerfile
# syntax=docker/dockerfile:experimental復(fù)制
然后該RUN指令接受一個(gè)新標(biāo)志:--mount. 以下清單顯示了一個(gè)完整示例:
Dockerfile
# syntax=docker/dockerfile:experimental
FROM openjdk:8-jdk-alpine as build
WORKDIR /workspace/app
COPY mvnw .
COPY .mvn .mvn
COPY pom.xml .
COPY src src
RUN --mount=type=cache,target=/root/.m2 ./mvnw install -DskipTests
RUN mkdir -p target/dependency && (cd target/dependency; jar -xf ../*.jar)
FROM openjdk:8-jdk-alpine
VOLUME /tmp
ARG DEPENDENCY=/workspace/app/target/dependency
COPY --from=build ${DEPENDENCY}/BOOT-INF/lib /app/lib
COPY --from=build ${DEPENDENCY}/META-INF /app/META-INF
COPY --from=build ${DEPENDENCY}/BOOT-INF/classes /app
ENTRYPOINT ["java","-cp","app:app/lib/*","hello.Application"]復(fù)制
然后你可以運(yùn)行它:
DOCKER_BUILDKIT=1 docker build -t myorg/myapp .復(fù)制
以下清單顯示了示例輸出:
...
=> /bin/sh -c ./mvnw install -DskipTests 5.7s
=> exporting to image 0.0s
=> => exporting layers 0.0s
=> => writing image sha256:3defa...
=> => naming to docker.io/myorg/myapp復(fù)制
使用實(shí)驗(yàn)性功能,您會(huì)在控制臺(tái)上獲得不同的輸出,但您可以看到,如果緩存是熱的,現(xiàn)在 Maven 構(gòu)建只需幾秒鐘而不是幾分鐘。
這個(gè)Dockerfile配置的 Gradle 版本非常相似:
Dockerfile
# syntax=docker/dockerfile:experimental
FROM openjdk:8-jdk-alpine AS build
WORKDIR /workspace/app
COPY . /workspace/app
RUN --mount=type=cache,target=/root/.gradle ./gradlew clean build
RUN mkdir -p build/dependency && (cd build/dependency; jar -xf ../libs/*.jar)
FROM openjdk:8-jdk-alpine
VOLUME /tmp
ARG DEPENDENCY=/workspace/app/build/dependency
COPY --from=build ${DEPENDENCY}/BOOT-INF/lib /app/lib
COPY --from=build ${DEPENDENCY}/META-INF /app/META-INF
COPY --from=build ${DEPENDENCY}/BOOT-INF/classes /app
ENTRYPOINT ["java","-cp","app:app/lib/*","hello.Application"]
雖然這些功能處于實(shí)驗(yàn)階段,但打開(kāi)和關(guān)閉 buildkit 的選項(xiàng)取決于docker您使用的版本。檢查您擁有的版本的文檔(前面顯示的示例對(duì)于docker18.0.6 是正確的)。
安全方面
就像在經(jīng)典 VM 部署中一樣,進(jìn)程不應(yīng)以 root 權(quán)限運(yùn)行。相反,映像應(yīng)包含運(yùn)行應(yīng)用程序的非 root 用戶。
在 aDockerfile中,您可以通過(guò)添加另一個(gè)添加(系統(tǒng))用戶和組并將其設(shè)置為當(dāng)前用戶(而不是默認(rèn)的 root)的層來(lái)實(shí)現(xiàn)此目的:
Dockerfile
FROM openjdk:8-jdk-alpine
RUN addgroup -S demo && adduser -S demo -G demo
USER demo
...復(fù)制
如果有人設(shè)法突破您的應(yīng)用程序并在容器內(nèi)運(yùn)行系統(tǒng)命令,這種預(yù)防措施會(huì)限制他們的能力(遵循最小權(quán)限原則)。
一些進(jìn)一步的Dockerfile命令只能以 root 身份運(yùn)行,因此您可能必須將 USER 命令進(jìn)一步向下移動(dòng)(例如,如果您計(jì)劃在容器中安裝更多包,它只能以 root 身份運(yùn)行)。
對(duì)于其他方法,不使用 aDockerfile可能更適合。例如,在后面描述的 buildpack 方法中,大多數(shù)實(shí)現(xiàn)默認(rèn)使用非 root 用戶。
另一個(gè)考慮因素是大多數(shù)應(yīng)用程序在運(yùn)行時(shí)可能不需要完整的 JDK,因此一旦我們進(jìn)行了多階段構(gòu)建,我們就可以安全地切換到 JRE 基礎(chǔ)映像。因此,在前面顯示的多階段構(gòu)建中,我們可以將其用于最終的可運(yùn)行映像:
Dockerfile
FROM openjdk:8-jre-alpine
...復(fù)制
如前所述,這也節(jié)省了映像中的一些空間,這些空間將被運(yùn)行時(shí)不需要的工具占用。
審核編輯:湯梓紅
-
容器
+關(guān)注
關(guān)注
0文章
511瀏覽量
22458 -
應(yīng)用程序
+關(guān)注
關(guān)注
38文章
3337瀏覽量
59045 -
spring
+關(guān)注
關(guān)注
0文章
340瀏覽量
15088 -
Docker
+關(guān)注
關(guān)注
0文章
515瀏覽量
12974
發(fā)布評(píng)論請(qǐng)先 登錄
Spring Boot中Docker的入門指南(二)
Spring Boot嵌入式Web容器原理是什么
Docker入門指南
使用Spring Cloud與Docker實(shí)戰(zhàn)微服務(wù)

spring boot入門篇
Spring Boot從零入門1 詳述
Spring Boot特有的實(shí)踐
強(qiáng)大的Spring Boot 3.0要來(lái)了
Spring Boot Web相關(guān)的基礎(chǔ)知識(shí)
在Spring Boot中如何使用定時(shí)任務(wù)
Spring Boot Actuator快速入門
Spring Boot啟動(dòng) Eureka流程

Spring Boot的啟動(dòng)原理

評(píng)論