Docker 镜像最佳实践

5 条最佳建议

1.仅安装产线需要依赖与软件

镜像尽可能最小原则

  • 仅复制 jar/war
  • 使用自定义 JRE(Java Runtime Environment)

2.使用多阶段构建

FROM maven:3.6.3-jdk-11-slim AS build
RUN mkdir /project
COPY . /project
WORKDIR /project
RUN mvn clean package -DskipTests
FROM adoptopenjdk/openjdk11:jre-11.0.9.1_1-alpine
RUN mkdir /app
COPY --from=build /project/target/java-application.jar /app/java-application.jar
WORKDIR /app
CMD "java" "-jar" "java-application.jar"

3.不要使用 root 运行应用

为了应用的安全,不要使用 root 账户运行应用

# Add app user
ARG APPLICATION_USER=appuser
RUN adduser --no-create-home -u 1000 -D $APPLICATION_USER
# Configure working directory
RUN mkdir /app && \
chown -R $APPLICATION_USER /app
USER 1000
COPY --chown=1000:1000 ./app.jar /app/app.jar
WORKDIR /app
EXPOSE 8080
ENTRYPOINT [ "exec","/jre/bin/java", "-jar", "/app/app.jar" ]

4.尽可能处理终止信号量

推荐使用exec命令

ENTRYPOINT [ "exec","/jre/bin/java", "-jar", "/app/app.jar" ]

推荐处理应用终止回调方法

Runtime.getRuntime().addShutdownHook(yourShutdownThread);

5.使用.dockerigore,使用新版 jdk

使用.dockerignore排除不需要的文件

使用Java 8 update 191+ jdk10+版本,其他版本无法感知 docker 内存和 cpu 限制。

最小镜像实践

在云环境下,精简镜像有极大的资源利用率的提升,节省磁盘和网络带宽,有着极大的收益。

jdk1.8 以下推荐使用alpine镜像

docker pull openjdk:8u212-jre-alpine3.9

jdk11+,由于不再提供 jre 运行时,推进使用alpine+jlink 剪裁出最佳运行时。

自定义 jre

首先使用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.jar
java.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 JRE
FROM amazoncorretto:17.0.3-alpine as corretto-jdk
# required for strip-debug to work
RUN apk add --no-cache binutils
# Build small JRE image
RUN $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 image
FROM alpine:latest
ENV JAVA_HOME=/jre
ENV PATH="${JAVA_HOME}/bin:${PATH}"
# copy JRE from the base image
COPY --from=corretto-jdk /customjre $JAVA_HOME
# Add app user
ARG APPLICATION_USER=appuser
RUN adduser --no-create-home -u 1000 -D $APPLICATION_USER
# Configure working directory
RUN mkdir /app && \
chown -R $APPLICATION_USER /app
USER 1000
COPY --chown=1000:1000 ./app.jar /app/app.jar
WORKDIR /app
EXPOSE 8080
ENTRYPOINT [ "exec","/jre/bin/java", "-jar", "/app/app.jar" ]

Native Image

使用 native image 可以极大的减少镜像大小,启动时间,占用内存,因此如果项目可以接入 native image,建议优先接入。

具体文档可参考:NativeImage

参考

  1. How to reduce JVM docker image size
  2. 10 best practices to build a Java container with Docker
  3. 使用 jlink 的 Java 运行时