镜像尽可能最小原则
FROM maven:3.6.3-jdk-11-slim AS buildRUN mkdir /projectCOPY . /projectWORKDIR /projectRUN mvn clean package -DskipTestsFROM adoptopenjdk/openjdk11:jre-11.0.9.1_1-alpineRUN mkdir /appCOPY --from=build /project/target/java-application.jar /app/java-application.jarWORKDIR /appCMD "java" "-jar" "java-application.jar"
为了应用的安全,不要使用 root 账户运行应用
# Add app userARG APPLICATION_USER=appuserRUN adduser --no-create-home -u 1000 -D $APPLICATION_USER# Configure working directoryRUN mkdir /app && \chown -R $APPLICATION_USER /appUSER 1000COPY --chown=1000:1000 ./app.jar /app/app.jarWORKDIR /appEXPOSE 8080ENTRYPOINT [ "exec","/jre/bin/java", "-jar", "/app/app.jar" ]
推荐使用exec
命令
ENTRYPOINT [ "exec","/jre/bin/java", "-jar", "/app/app.jar" ]
推荐处理应用终止回调方法
Runtime.getRuntime().addShutdownHook(yourShutdownThread);
使用.dockerignore
排除不需要的文件
使用Java 8 update 191+
和jdk10+
版本,其他版本无法感知 docker 内存和 cpu 限制。
在云环境下,精简镜像有极大的资源利用率的提升,节省磁盘和网络带宽,有着极大的收益。
jdk1.8 以下推荐使用alpine
镜像
docker pull openjdk:8u212-jre-alpine3.9
jdk11+,由于不再提供 jre 运行时,推进使用alpine
+jlink 剪裁出最佳运行时。
首先使用jdeps
查看依赖的 module
$ jdeps --print-module-deps --ignore-missing-deps --recursive --multi-release 17 --module-path="./app/BOOT-INF/lib/*" --class-path ./app/BOOT-INF/lib/* app.jarjava.base,java.compiler,java.desktop,java.instrument,java.management,java.naming,java.prefs,java.scripting
对于 spring 应用推荐增加--ignore-missing-deps
,可以忽略 spring 的一些 module 缺失,一般情况下我们的 jar 会包含 spring 所有依赖,对于这种 fat.jar 我们可以先解压该包,然后增加--module-path="./app/BOOT-INF/lib/*" --class-path ./app/BOOT-INF/lib/*
参数进行分析。
最后使用jlink
输出自定义 jre
$ jlink \--add-modules java.base,java.compiler,java.desktop,java.instrument,java.management,java.naming,java.prefs,java.scripting \--strip-debug \--no-man-pages \--no-header-files \--compress=2 \--output ./javaruntime
以下为最佳实践案例
# base image to build a JREFROM amazoncorretto:17.0.3-alpine as corretto-jdk# required for strip-debug to workRUN apk add --no-cache binutils# Build small JRE imageRUN $JAVA_HOME/bin/jlink \--verbose \--add-modules java.base,java.compiler,java.desktop,java.instrument,java.management,java.naming,java.prefs,java.scripting \--strip-debug \--no-man-pages \--no-header-files \--compress=2 \--output /customjre# main app imageFROM alpine:latestENV JAVA_HOME=/jreENV PATH="${JAVA_HOME}/bin:${PATH}"# copy JRE from the base imageCOPY --from=corretto-jdk /customjre $JAVA_HOME# Add app userARG APPLICATION_USER=appuserRUN adduser --no-create-home -u 1000 -D $APPLICATION_USER# Configure working directoryRUN mkdir /app && \chown -R $APPLICATION_USER /appUSER 1000COPY --chown=1000:1000 ./app.jar /app/app.jarWORKDIR /appEXPOSE 8080ENTRYPOINT [ "exec","/jre/bin/java", "-jar", "/app/app.jar" ]
使用 native image 可以极大的减少镜像大小,启动时间,占用内存,因此如果项目可以接入 native image,建议优先接入。
具体文档可参考:java 云原生实践
java -Djarmode=tools -jar application.jar extract --layers
该命令可以将jar按分层结构输出,这样可以将依赖和代码打入不同的docker 层。通过jarmode 创建的,对 CDS 友好。案例如下:
FROM bellsoft/liberica-runtime-container:jre-17-cds-slim-glibc as builderWORKDIR /builderARG JAR_FILE=target/*.jarCOPY ${JAR_FILE} application.jarRUN java -Djarmode=tools -jar application.jar extract --layers --destination extractedFROM bellsoft/liberica-runtime-container:jre-17-cds-slim-glibcWORKDIR /applicationCOPY --from=builder /builder/extracted/dependencies/ ./COPY --from=builder /builder/extracted/spring-boot-loader/ ./COPY --from=builder /builder/extracted/snapshot-dependencies/ ./COPY --from=builder /builder/extracted/application/ ./ENTRYPOINT ["java", "-jar", "application.jar"]