Java 11 应用作为轻量级 docker 图片
Java 11 application as lightweight docker image
受到问题的启发,发现这个话题在Java世界还没有定论
至于07 Dec 2018
有常见的issues/pitfalls(在上面的票中讨论):
JRE 未作为单独的 "package" 分发。应该使用来自 JDK 的模块
Oracle OpenJDK 11 doesn't support Linux Alpine,所以轻量级图像不容易创建
- 同时当前稳定的 Debian 版本在 openjdk-11 包下仍然没有 Java 11 packages (Ubuntu has Java 10 installed),这就是为什么不稳定的 sid 版本用于基础 docker 图像
当前可用的 Oracle openjdk-11 映像构建未剥离的 libjvm.so
模块,该模块有数百兆,必须单独剥离:
由于这些问题,甚至 slim Oracle Java 11 个基本映像非常重,被认为是不稳定的:https://hub.docker.com/_/openjdk/
所以问题是:
优化或推荐构建和交付Java11个应用程序的方法是docker 图片?
2019 年 7 月更新:
以简单的 spring 引导应用程序(只有一个 REST 端点)为例,到目前为止我能够找出以下解决方案(考虑到应用程序 jar 位于 build/libs/spring-boot-demo.jar
之前 Docker 建造:
Jedi path 如果我们想在 stable slim Linux 版本上使用 official Oracle OpenJDK distribution (目前为Debian 9 "Stretch"
):
- 使用
debian:stretch-slim
(最新稳定版)基础镜像
-
第一个 Docker 构建阶段:
- 在第一个 Docker 构建阶段下载并安装
Oracle OpenJDK
存档
- 使用
jlink
工具 为您的项目(又名 JRE)编译 Java 最小分发版
第二个 Docker 构建阶段:
- 复制从第 1 阶段编译的最小 Java 分发到新映像
- 配置访问路径Java
- 将应用程序 jar 复制到图像
所以,最终 Dockerfile
看起来像这样
(实现 JDK VERSION
, URL
和 HASH
值):
# First stage: JDK 11 with modules required for Spring Boot
FROM debian:stretch-slim as packager
# source JDK distribution names
# update from https://jdk.java.net/java-se-ri/11
ENV JDK_VERSION="11.0.1"
ENV JDK_URL="https://download.java.net/java/GA/jdk11/13/GPL/openjdk-${JDK_VERSION}_linux-x64_bin.tar.gz"
ENV JDK_HASH="7a6bb980b9c91c478421f865087ad2d69086a0583aeeb9e69204785e8e97dcfd"
ENV JDK_HASH_FILE="${JDK_ARJ_FILE}.sha2"
ENV JDK_ARJ_FILE="openjdk-${JDK_VERSION}.tar.gz"
# target JDK installation names
ENV OPT="/opt"
ENV JKD_DIR_NAME="jdk-${JDK_VERSION}"
ENV JAVA_HOME="${OPT}/${JKD_DIR_NAME}"
ENV JAVA_MINIMAL="${OPT}/java-minimal"
# downlodad JDK to the local file
ADD "$JDK_URL" "$JDK_ARJ_FILE"
# verify downloaded file hashsum
RUN { \
echo "Verify downloaded JDK file $JDK_ARJ_FILE:" && \
echo "$JDK_HASH $JDK_ARJ_FILE" > "$JDK_HASH_FILE" && \
sha256sum -c "$JDK_HASH_FILE" ; \
}
# extract JDK and add to PATH
RUN { \
echo "Unpack downloaded JDK to ${JAVA_HOME}/:" && \
mkdir -p "$OPT" && \
tar xf "$JDK_ARJ_FILE" -C "$OPT" ; \
}
ENV PATH="$PATH:$JAVA_HOME/bin"
RUN { \
java --version ; \
echo "jlink version:" && \
jlink --version ; \
}
# build modules distribution
RUN jlink \
--verbose \
--add-modules \
java.base,java.sql,java.naming,java.desktop,java.management,java.security.jgss,java.instrument \
# java.naming - javax/naming/NamingException
# java.desktop - java/beans/PropertyEditorSupport
# java.management - javax/management/MBeanServer
# java.security.jgss - org/ietf/jgss/GSSException
# java.instrument - java/lang/instrument/IllegalClassFormatException
--compress 2 \
--strip-debug \
--no-header-files \
--no-man-pages \
--output "$JAVA_MINIMAL"
# Second stage, add only our minimal "JRE" distr and our app
FROM debian:stretch-slim
ENV JAVA_HOME=/opt/java-minimal
ENV PATH="$PATH:$JAVA_HOME/bin"
COPY --from=packager "$JAVA_HOME" "$JAVA_HOME"
COPY "build/libs/spring-boot-demo.jar" "/app.jar"
EXPOSE 8080
CMD [ "-jar", "/app.jar" ]
ENTRYPOINT [ "java" ]
注意:
- 最小 JRE 示例 (
java.base,java.sql,java.naming,java.desktop,java.management,java.security.jgss,java.instrument
) 中包含 5 个 java 模块。我找到了他们 "manually" 运行 应用程序并修复了 ClassNotFoundException
。等待进一步 Spring 引导开发人员 recommendations/guides 包含哪些 Java 模块以及何时包含,就像删除一些冗余依赖项一样,例如 java.desktop
,这似乎仅用于PropertyEditorSupport
如果您害怕错过某些模块 - 它们非常轻巧,所有这些模块加在一起大约增加了 2 MB 的大小。获取 java.*
和 jdk.*
11 个模块的完整列表:
java --list-modules | grep -E "^java\.[^@]*" | cut -d @ -f 1
java --list-modules | grep -E "^jdk\.[^@]*" | cut -d @ -f 1
在我的案例中,生成的图像大小为 123 MB,最少 7 个 Spring 引导模块和 125 MB,所有java.*
个模块
作为此构建工作流程的可选改进:
- 使用下载和提取的图像预构建 JDK 并将其用作第一阶段的基础图像
- 如果您知道每次要包含哪些模块 - 使用已编译的最小 JRE 和包含的模块预构建基础映像
vendor's Open JDK 发行版的简单方法:
与 Oracle 相反 Azul's Zulu JDK 11 supports Alpine port and has respective base Docker image.
因此,如果尊重祖鲁 JVM/JDK,Docker 构建就简单得多:
FROM azul/zulu-openjdk-alpine:11 as packager
RUN { \
java --version ; \
echo "jlink version:" && \
jlink --version ; \
}
ENV JAVA_MINIMAL=/opt/jre
# build modules distribution
RUN jlink \
--verbose \
--add-modules \
java.base,java.sql,java.naming,java.desktop,java.management,java.security.jgss,java.instrument \
# java.naming - javax/naming/NamingException
# java.desktop - java/beans/PropertyEditorSupport
# java.management - javax/management/MBeanServer
# java.security.jgss - org/ietf/jgss/GSSException
# java.instrument - java/lang/instrument/IllegalClassFormatException
--compress 2 \
--strip-debug \
--no-header-files \
--no-man-pages \
--output "$JAVA_MINIMAL"
# Second stage, add only our minimal "JRE" distr and our app
FROM alpine
ENV JAVA_MINIMAL=/opt/jre
ENV PATH="$PATH:$JAVA_MINIMAL/bin"
COPY --from=packager "$JAVA_MINIMAL" "$JAVA_MINIMAL"
COPY "build/libs/spring-boot-demo.jar" "/app.jar"
EXPOSE 8080
CMD [ "-jar", "/app.jar" ]
ENTRYPOINT [ "java" ]
生成的图像是 73 MB,正如剥离的 Alpine 分布所预期的那样。
根据 radistao 的回答(很酷!)我创建了一个 Amazon Corretto JDK11 based image. It's also available on DockerHub.
最小的 maslick/minimalka:jdk11 Corretto 镜像是 ~108MB(在 Dockerhub 上压缩了 55MB)。
如果向其中添加一个简单的 Springboot jar,生成的图像将是 ~125MB(在 Dockerhub 上压缩为 71MB):
FROM maslick/minimalka:jdk11
WORKDIR /app
EXPOSE 8080
COPY my-cool-app.jar ./app.jar
CMD java $JAVA_OPTIONS -jar app.jar
docker build -t my-cool-app:latest .
docker run -d my-cool-app
截至 2019 年 7 月
(注意:第一阶段图像可以 fat 如您所愿:可以使用 debian/ubuntu/whatever 并包含 git/gradle/whatever - 这不会影响最终生成的图像大小,它完全基于最后(第二)阶段)
使用Alpine community repository
FROM alpine:latest as packager
RUN apk --no-cache add openjdk11-jdk openjdk11-jmods
ENV JAVA_MINIMAL="/opt/java-minimal"
# build minimal JRE
RUN /usr/lib/jvm/java-11-openjdk/bin/jlink \
--verbose \
--add-modules \
java.base,java.sql,java.naming,java.desktop,java.management,java.security.jgss,java.instrument \
--compress 2 --strip-debug --no-header-files --no-man-pages \
--release-info="add:IMPLEMENTOR=radistao:IMPLEMENTOR_VERSION=radistao_JRE" \
--output "$JAVA_MINIMAL"
FROM alpine:latest
ENV JAVA_HOME=/opt/java-minimal
ENV PATH="$PATH:$JAVA_HOME/bin"
COPY --from=packager "$JAVA_HOME" "$JAVA_HOME"
COPY build/libs/application.jar app.jar
ENTRYPOINT ["java","-jar","/app.jar"]
使用AdoptOpenJDK
FROM adoptopenjdk/openjdk11:x86_64-alpine-jdk-11.0.4_11 as packager
ENV JAVA_MINIMAL="/opt/java-minimal"
# build minimal JRE
RUN jlink \
--verbose \
--add-modules \
java.base,java.sql,java.naming,java.desktop,java.management,java.security.jgss,java.instrument \
--compress 2 --strip-debug --no-header-files --no-man-pages \
--output "$JAVA_MINIMAL"
FROM alpine:latest
# magic to make Java binaries work in Alpine
# https://github.com/AdoptOpenJDK/openjdk-docker/blob/master/11/jdk/alpine/Dockerfile.hotspot.releases.slim#L24-L54
RUN apk add --no-cache --virtual .build-deps curl binutils \
&& GLIBC_VER="2.29-r0" \
&& ALPINE_GLIBC_REPO="https://github.com/sgerrand/alpine-pkg-glibc/releases/download" \
&& GCC_LIBS_URL="https://archive.archlinux.org/packages/g/gcc-libs/gcc-libs-9.1.0-2-x86_64.pkg.tar.xz" \
&& GCC_LIBS_SHA256="91dba90f3c20d32fcf7f1dbe91523653018aa0b8d2230b00f822f6722804cf08" \
&& ZLIB_URL="https://archive.archlinux.org/packages/z/zlib/zlib-1%3A1.2.11-3-x86_64.pkg.tar.xz" \
&& ZLIB_SHA256=17aede0b9f8baa789c5aa3f358fbf8c68a5f1228c5e6cba1a5dd34102ef4d4e5 \
&& curl -LfsS https://alpine-pkgs.sgerrand.com/sgerrand.rsa.pub -o /etc/apk/keys/sgerrand.rsa.pub \
&& SGERRAND_RSA_SHA256="823b54589c93b02497f1ba4dc622eaef9c813e6b0f0ebbb2f771e32adf9f4ef2" \
&& echo "${SGERRAND_RSA_SHA256} */etc/apk/keys/sgerrand.rsa.pub" | sha256sum -c - \
&& curl -LfsS ${ALPINE_GLIBC_REPO}/${GLIBC_VER}/glibc-${GLIBC_VER}.apk > /tmp/glibc-${GLIBC_VER}.apk \
&& apk add /tmp/glibc-${GLIBC_VER}.apk \
&& curl -LfsS ${ALPINE_GLIBC_REPO}/${GLIBC_VER}/glibc-bin-${GLIBC_VER}.apk > /tmp/glibc-bin-${GLIBC_VER}.apk \
&& apk add /tmp/glibc-bin-${GLIBC_VER}.apk \
&& curl -Ls ${ALPINE_GLIBC_REPO}/${GLIBC_VER}/glibc-i18n-${GLIBC_VER}.apk > /tmp/glibc-i18n-${GLIBC_VER}.apk \
&& apk add /tmp/glibc-i18n-${GLIBC_VER}.apk \
&& /usr/glibc-compat/bin/localedef --force --inputfile POSIX --charmap UTF-8 "$LANG" || true \
&& echo "export LANG=$LANG" > /etc/profile.d/locale.sh \
&& curl -LfsS ${GCC_LIBS_URL} -o /tmp/gcc-libs.tar.xz \
&& echo "${GCC_LIBS_SHA256} */tmp/gcc-libs.tar.xz" | sha256sum -c - \
&& mkdir /tmp/gcc \
&& tar -xf /tmp/gcc-libs.tar.xz -C /tmp/gcc \
&& mv /tmp/gcc/usr/lib/libgcc* /tmp/gcc/usr/lib/libstdc++* /usr/glibc-compat/lib \
&& strip /usr/glibc-compat/lib/libgcc_s.so.* /usr/glibc-compat/lib/libstdc++.so* \
&& curl -LfsS ${ZLIB_URL} -o /tmp/libz.tar.xz \
&& echo "${ZLIB_SHA256} */tmp/libz.tar.xz" | sha256sum -c - \
&& mkdir /tmp/libz \
&& tar -xf /tmp/libz.tar.xz -C /tmp/libz \
&& mv /tmp/libz/usr/lib/libz.so* /usr/glibc-compat/lib \
&& apk del --purge .build-deps glibc-i18n \
&& rm -rf /tmp/*.apk /tmp/gcc /tmp/gcc-libs.tar.xz /tmp/libz /tmp/libz.tar.xz /var/cache/apk/*
ENV JAVA_HOME=/opt/java-minimal
ENV PATH="$PATH:$JAVA_HOME/bin"
COPY --from=packager "$JAVA_HOME" "$JAVA_HOME"
COPY build/libs/application.jar app.jar
ENTRYPOINT ["java","-jar","/app.jar"]
另读https://blog.gilliard.lol/2018/11/05/alpine-jdk11-images.html
你也可以看看bellsoft的liberica openjdk11。抱歉引用了很多,但无论如何,就在这里
Liberica is a 100% open-source Java 11 implementation. It is built from OpenJDK which BellSoft contributes to, is thoroughly tested and passed the JCK provided under the license from OpenJDK...
他们开箱即用的 lite 版本占用大约 100MB。它没有 javafx 模块并且它的模块被压缩(jlink --compress=2
在 their Dockerfile). Apart from that, there are various repos at bellsoft Docker Hub account with different options of OS/glibc/arch. E.g. at liberica-openjdk-alpine-musl 他们说:
Dockerfile for Alpine Linux (musl variant) supports three target images out of the box:
base: minimal runtime image with compressed java.base module, Server VM and optional files stripped, ~37 MB with Alpine base
lite: Liberica JDK lite image with minimal footprint and Server VM, ~ 100 MB (default)
full: Liberica JDK full image with Server VM and jmods, can be used to create arbitrary module set, ~180 MB
To save space, users are encouraged to create their own runtimes using jmod command sufficient to run the target application
而且您可以以牺牲性能为代价走得更远:
If you are ready to sacrifice performance for static footprint, please consider using Minimal VM instead of Server VM or Client VM. With that, it's possible to create a runtime as small as < 20 Mb
我机器上的一些例子:
docker images 'bellsoft/liberica-openjdk-*' --format "table {{.Repository}}\t{{.Tag}}\t{{.Size}}"
REPOSITORY TAG SIZE
bellsoft/liberica-openjdk-alpine-musl 11.0.4-x86_64 102MB
bellsoft/liberica-openjdk-alpine 11.0.4 127MB
bellsoft/liberica-openjdk-centos latest 307MB
openjdk:11.0.6-jre-buster
openjdk:11.0.6-jre
openjdk:11.0.6-jre-slim-buster
openjdk:11.0.6-jre-slim
openjdk:11.0.6-jre-stretch
adoptopenjdk:11.0.6_10-jre-openj9-0.18.1
adoptopenjdk:11.0.6_10-jre-hotspot
adoptopenjdk:11.0.6_10-jre-openj9-0.18.1-bionic
adoptopenjdk:11.0.6_10-jre-hotspot-bionic
adoptopenjdk/openjdk11:jre-11.0.6_10-ubuntu
adoptopenjdk/openjdk11:jre-11.0.6_10
adoptopenjdk/openjdk11:jre-11.0.6_10-ubi-minimal
adoptopenjdk/openjdk11:jre-11.0.6_10-ubi
adoptopenjdk/openjdk11:jre-11.0.6_10-debianslim
adoptopenjdk/openjdk11:jre-11.0.6_10-debian
adoptopenjdk/openjdk11:jre-11.0.6_10-centos
adoptopenjdk/openjdk11:jre-11.0.6_10-alpine
adoptopenjdk/openjdk11:x86_64-alpine-jre-11.0.6_10
adoptopenjdk/openjdk11:x86_64-debian-jre-11.0.6_10
adoptopenjdk/openjdk11:x86_64-debianslim-jre-11.0.6_10
adoptopenjdk/openjdk11:x86_64-ubi-jre-11.0.6_10
adoptopenjdk/openjdk11:x86_64-ubi-minimal-jre-11.0.6_10
adoptopenjdk/openjdk11:x86_64-centos-jre-11.0.6_10
adoptopenjdk/openjdk11:x86_64-ubuntu-jre-11.0.6_10
mcr.microsoft.com/java/jre:11u6-zulu-alpine
mcr.microsoft.com/java/jre:11u6-zulu-centos
mcr.microsoft.com/java/jre:11u6-zulu-debian8
mcr.microsoft.com/java/jre:11u6-zulu-debian9
mcr.microsoft.com/java/jre:11u6-zulu-debian10
mcr.microsoft.com/java/jre:11u6-zulu-ubuntu
azul/zulu-openjdk-alpine:11.0.6-jre
受到问题
至于07 Dec 2018
有常见的issues/pitfalls(在上面的票中讨论):
JRE 未作为单独的 "package" 分发。应该使用来自 JDK 的模块
Oracle OpenJDK 11 doesn't support Linux Alpine,所以轻量级图像不容易创建
- 同时当前稳定的 Debian 版本在 openjdk-11 包下仍然没有 Java 11 packages (Ubuntu has Java 10 installed),这就是为什么不稳定的 sid 版本用于基础 docker 图像
当前可用的 Oracle openjdk-11 映像构建未剥离的
libjvm.so
模块,该模块有数百兆,必须单独剥离:
由于这些问题,甚至 slim Oracle Java 11 个基本映像非常重,被认为是不稳定的:https://hub.docker.com/_/openjdk/
所以问题是:
优化或推荐构建和交付Java11个应用程序的方法是docker 图片?
2019 年 7 月更新:
以简单的 spring 引导应用程序(只有一个 REST 端点)为例,到目前为止我能够找出以下解决方案(考虑到应用程序 jar 位于 build/libs/spring-boot-demo.jar
之前 Docker 建造:
Jedi path 如果我们想在 stable slim Linux 版本上使用 official Oracle OpenJDK distribution (目前为
Debian 9 "Stretch"
):- 使用
debian:stretch-slim
(最新稳定版)基础镜像 -
第一个 Docker 构建阶段:
- 在第一个 Docker 构建阶段下载并安装
Oracle OpenJDK
存档 - 使用
jlink
工具 为您的项目(又名 JRE)编译 Java 最小分发版
- 在第一个 Docker 构建阶段下载并安装
第二个 Docker 构建阶段:
- 复制从第 1 阶段编译的最小 Java 分发到新映像
- 配置访问路径Java
- 将应用程序 jar 复制到图像
所以,最终
Dockerfile
看起来像这样(实现 JDK
VERSION
,URL
和HASH
值):# First stage: JDK 11 with modules required for Spring Boot FROM debian:stretch-slim as packager # source JDK distribution names # update from https://jdk.java.net/java-se-ri/11 ENV JDK_VERSION="11.0.1" ENV JDK_URL="https://download.java.net/java/GA/jdk11/13/GPL/openjdk-${JDK_VERSION}_linux-x64_bin.tar.gz" ENV JDK_HASH="7a6bb980b9c91c478421f865087ad2d69086a0583aeeb9e69204785e8e97dcfd" ENV JDK_HASH_FILE="${JDK_ARJ_FILE}.sha2" ENV JDK_ARJ_FILE="openjdk-${JDK_VERSION}.tar.gz" # target JDK installation names ENV OPT="/opt" ENV JKD_DIR_NAME="jdk-${JDK_VERSION}" ENV JAVA_HOME="${OPT}/${JKD_DIR_NAME}" ENV JAVA_MINIMAL="${OPT}/java-minimal" # downlodad JDK to the local file ADD "$JDK_URL" "$JDK_ARJ_FILE" # verify downloaded file hashsum RUN { \ echo "Verify downloaded JDK file $JDK_ARJ_FILE:" && \ echo "$JDK_HASH $JDK_ARJ_FILE" > "$JDK_HASH_FILE" && \ sha256sum -c "$JDK_HASH_FILE" ; \ } # extract JDK and add to PATH RUN { \ echo "Unpack downloaded JDK to ${JAVA_HOME}/:" && \ mkdir -p "$OPT" && \ tar xf "$JDK_ARJ_FILE" -C "$OPT" ; \ } ENV PATH="$PATH:$JAVA_HOME/bin" RUN { \ java --version ; \ echo "jlink version:" && \ jlink --version ; \ } # build modules distribution RUN jlink \ --verbose \ --add-modules \ java.base,java.sql,java.naming,java.desktop,java.management,java.security.jgss,java.instrument \ # java.naming - javax/naming/NamingException # java.desktop - java/beans/PropertyEditorSupport # java.management - javax/management/MBeanServer # java.security.jgss - org/ietf/jgss/GSSException # java.instrument - java/lang/instrument/IllegalClassFormatException --compress 2 \ --strip-debug \ --no-header-files \ --no-man-pages \ --output "$JAVA_MINIMAL" # Second stage, add only our minimal "JRE" distr and our app FROM debian:stretch-slim ENV JAVA_HOME=/opt/java-minimal ENV PATH="$PATH:$JAVA_HOME/bin" COPY --from=packager "$JAVA_HOME" "$JAVA_HOME" COPY "build/libs/spring-boot-demo.jar" "/app.jar" EXPOSE 8080 CMD [ "-jar", "/app.jar" ] ENTRYPOINT [ "java" ]
注意:
- 最小 JRE 示例 (
java.base,java.sql,java.naming,java.desktop,java.management,java.security.jgss,java.instrument
) 中包含 5 个 java 模块。我找到了他们 "manually" 运行 应用程序并修复了ClassNotFoundException
。等待进一步 Spring 引导开发人员 recommendations/guides 包含哪些 Java 模块以及何时包含,就像删除一些冗余依赖项一样,例如java.desktop
,这似乎仅用于PropertyEditorSupport
如果您害怕错过某些模块 - 它们非常轻巧,所有这些模块加在一起大约增加了 2 MB 的大小。获取
java.*
和jdk.*
11 个模块的完整列表:java --list-modules | grep -E "^java\.[^@]*" | cut -d @ -f 1
java --list-modules | grep -E "^jdk\.[^@]*" | cut -d @ -f 1
在我的案例中,生成的图像大小为 123 MB,最少 7 个 Spring 引导模块和 125 MB,所有
java.*
个模块作为此构建工作流程的可选改进:
- 使用下载和提取的图像预构建 JDK 并将其用作第一阶段的基础图像
- 如果您知道每次要包含哪些模块 - 使用已编译的最小 JRE 和包含的模块预构建基础映像
- 使用
vendor's Open JDK 发行版的简单方法:
与 Oracle 相反 Azul's Zulu JDK 11 supports Alpine port and has respective base Docker image.
因此,如果尊重祖鲁 JVM/JDK,Docker 构建就简单得多:
FROM azul/zulu-openjdk-alpine:11 as packager
RUN { \
java --version ; \
echo "jlink version:" && \
jlink --version ; \
}
ENV JAVA_MINIMAL=/opt/jre
# build modules distribution
RUN jlink \
--verbose \
--add-modules \
java.base,java.sql,java.naming,java.desktop,java.management,java.security.jgss,java.instrument \
# java.naming - javax/naming/NamingException
# java.desktop - java/beans/PropertyEditorSupport
# java.management - javax/management/MBeanServer
# java.security.jgss - org/ietf/jgss/GSSException
# java.instrument - java/lang/instrument/IllegalClassFormatException
--compress 2 \
--strip-debug \
--no-header-files \
--no-man-pages \
--output "$JAVA_MINIMAL"
# Second stage, add only our minimal "JRE" distr and our app
FROM alpine
ENV JAVA_MINIMAL=/opt/jre
ENV PATH="$PATH:$JAVA_MINIMAL/bin"
COPY --from=packager "$JAVA_MINIMAL" "$JAVA_MINIMAL"
COPY "build/libs/spring-boot-demo.jar" "/app.jar"
EXPOSE 8080
CMD [ "-jar", "/app.jar" ]
ENTRYPOINT [ "java" ]
生成的图像是 73 MB,正如剥离的 Alpine 分布所预期的那样。
根据 radistao 的回答(很酷!)我创建了一个 Amazon Corretto JDK11 based image. It's also available on DockerHub.
最小的 maslick/minimalka:jdk11 Corretto 镜像是 ~108MB(在 Dockerhub 上压缩了 55MB)。
如果向其中添加一个简单的 Springboot jar,生成的图像将是 ~125MB(在 Dockerhub 上压缩为 71MB):
FROM maslick/minimalka:jdk11
WORKDIR /app
EXPOSE 8080
COPY my-cool-app.jar ./app.jar
CMD java $JAVA_OPTIONS -jar app.jar
docker build -t my-cool-app:latest .
docker run -d my-cool-app
截至 2019 年 7 月
(注意:第一阶段图像可以 fat 如您所愿:可以使用 debian/ubuntu/whatever 并包含 git/gradle/whatever - 这不会影响最终生成的图像大小,它完全基于最后(第二)阶段)
使用Alpine community repository
FROM alpine:latest as packager
RUN apk --no-cache add openjdk11-jdk openjdk11-jmods
ENV JAVA_MINIMAL="/opt/java-minimal"
# build minimal JRE
RUN /usr/lib/jvm/java-11-openjdk/bin/jlink \
--verbose \
--add-modules \
java.base,java.sql,java.naming,java.desktop,java.management,java.security.jgss,java.instrument \
--compress 2 --strip-debug --no-header-files --no-man-pages \
--release-info="add:IMPLEMENTOR=radistao:IMPLEMENTOR_VERSION=radistao_JRE" \
--output "$JAVA_MINIMAL"
FROM alpine:latest
ENV JAVA_HOME=/opt/java-minimal
ENV PATH="$PATH:$JAVA_HOME/bin"
COPY --from=packager "$JAVA_HOME" "$JAVA_HOME"
COPY build/libs/application.jar app.jar
ENTRYPOINT ["java","-jar","/app.jar"]
使用AdoptOpenJDK
FROM adoptopenjdk/openjdk11:x86_64-alpine-jdk-11.0.4_11 as packager
ENV JAVA_MINIMAL="/opt/java-minimal"
# build minimal JRE
RUN jlink \
--verbose \
--add-modules \
java.base,java.sql,java.naming,java.desktop,java.management,java.security.jgss,java.instrument \
--compress 2 --strip-debug --no-header-files --no-man-pages \
--output "$JAVA_MINIMAL"
FROM alpine:latest
# magic to make Java binaries work in Alpine
# https://github.com/AdoptOpenJDK/openjdk-docker/blob/master/11/jdk/alpine/Dockerfile.hotspot.releases.slim#L24-L54
RUN apk add --no-cache --virtual .build-deps curl binutils \
&& GLIBC_VER="2.29-r0" \
&& ALPINE_GLIBC_REPO="https://github.com/sgerrand/alpine-pkg-glibc/releases/download" \
&& GCC_LIBS_URL="https://archive.archlinux.org/packages/g/gcc-libs/gcc-libs-9.1.0-2-x86_64.pkg.tar.xz" \
&& GCC_LIBS_SHA256="91dba90f3c20d32fcf7f1dbe91523653018aa0b8d2230b00f822f6722804cf08" \
&& ZLIB_URL="https://archive.archlinux.org/packages/z/zlib/zlib-1%3A1.2.11-3-x86_64.pkg.tar.xz" \
&& ZLIB_SHA256=17aede0b9f8baa789c5aa3f358fbf8c68a5f1228c5e6cba1a5dd34102ef4d4e5 \
&& curl -LfsS https://alpine-pkgs.sgerrand.com/sgerrand.rsa.pub -o /etc/apk/keys/sgerrand.rsa.pub \
&& SGERRAND_RSA_SHA256="823b54589c93b02497f1ba4dc622eaef9c813e6b0f0ebbb2f771e32adf9f4ef2" \
&& echo "${SGERRAND_RSA_SHA256} */etc/apk/keys/sgerrand.rsa.pub" | sha256sum -c - \
&& curl -LfsS ${ALPINE_GLIBC_REPO}/${GLIBC_VER}/glibc-${GLIBC_VER}.apk > /tmp/glibc-${GLIBC_VER}.apk \
&& apk add /tmp/glibc-${GLIBC_VER}.apk \
&& curl -LfsS ${ALPINE_GLIBC_REPO}/${GLIBC_VER}/glibc-bin-${GLIBC_VER}.apk > /tmp/glibc-bin-${GLIBC_VER}.apk \
&& apk add /tmp/glibc-bin-${GLIBC_VER}.apk \
&& curl -Ls ${ALPINE_GLIBC_REPO}/${GLIBC_VER}/glibc-i18n-${GLIBC_VER}.apk > /tmp/glibc-i18n-${GLIBC_VER}.apk \
&& apk add /tmp/glibc-i18n-${GLIBC_VER}.apk \
&& /usr/glibc-compat/bin/localedef --force --inputfile POSIX --charmap UTF-8 "$LANG" || true \
&& echo "export LANG=$LANG" > /etc/profile.d/locale.sh \
&& curl -LfsS ${GCC_LIBS_URL} -o /tmp/gcc-libs.tar.xz \
&& echo "${GCC_LIBS_SHA256} */tmp/gcc-libs.tar.xz" | sha256sum -c - \
&& mkdir /tmp/gcc \
&& tar -xf /tmp/gcc-libs.tar.xz -C /tmp/gcc \
&& mv /tmp/gcc/usr/lib/libgcc* /tmp/gcc/usr/lib/libstdc++* /usr/glibc-compat/lib \
&& strip /usr/glibc-compat/lib/libgcc_s.so.* /usr/glibc-compat/lib/libstdc++.so* \
&& curl -LfsS ${ZLIB_URL} -o /tmp/libz.tar.xz \
&& echo "${ZLIB_SHA256} */tmp/libz.tar.xz" | sha256sum -c - \
&& mkdir /tmp/libz \
&& tar -xf /tmp/libz.tar.xz -C /tmp/libz \
&& mv /tmp/libz/usr/lib/libz.so* /usr/glibc-compat/lib \
&& apk del --purge .build-deps glibc-i18n \
&& rm -rf /tmp/*.apk /tmp/gcc /tmp/gcc-libs.tar.xz /tmp/libz /tmp/libz.tar.xz /var/cache/apk/*
ENV JAVA_HOME=/opt/java-minimal
ENV PATH="$PATH:$JAVA_HOME/bin"
COPY --from=packager "$JAVA_HOME" "$JAVA_HOME"
COPY build/libs/application.jar app.jar
ENTRYPOINT ["java","-jar","/app.jar"]
另读https://blog.gilliard.lol/2018/11/05/alpine-jdk11-images.html
你也可以看看bellsoft的liberica openjdk11。抱歉引用了很多,但无论如何,就在这里
Liberica is a 100% open-source Java 11 implementation. It is built from OpenJDK which BellSoft contributes to, is thoroughly tested and passed the JCK provided under the license from OpenJDK...
他们开箱即用的 lite 版本占用大约 100MB。它没有 javafx 模块并且它的模块被压缩(jlink --compress=2
在 their Dockerfile). Apart from that, there are various repos at bellsoft Docker Hub account with different options of OS/glibc/arch. E.g. at liberica-openjdk-alpine-musl 他们说:
Dockerfile for Alpine Linux (musl variant) supports three target images out of the box:
base: minimal runtime image with compressed java.base module, Server VM and optional files stripped, ~37 MB with Alpine base
lite: Liberica JDK lite image with minimal footprint and Server VM, ~ 100 MB (default)
full: Liberica JDK full image with Server VM and jmods, can be used to create arbitrary module set, ~180 MB
To save space, users are encouraged to create their own runtimes using jmod command sufficient to run the target application
而且您可以以牺牲性能为代价走得更远:
If you are ready to sacrifice performance for static footprint, please consider using Minimal VM instead of Server VM or Client VM. With that, it's possible to create a runtime as small as < 20 Mb
我机器上的一些例子:
docker images 'bellsoft/liberica-openjdk-*' --format "table {{.Repository}}\t{{.Tag}}\t{{.Size}}"
REPOSITORY TAG SIZE
bellsoft/liberica-openjdk-alpine-musl 11.0.4-x86_64 102MB
bellsoft/liberica-openjdk-alpine 11.0.4 127MB
bellsoft/liberica-openjdk-centos latest 307MB
openjdk:11.0.6-jre-buster
openjdk:11.0.6-jre
openjdk:11.0.6-jre-slim-buster
openjdk:11.0.6-jre-slim
openjdk:11.0.6-jre-stretch
adoptopenjdk:11.0.6_10-jre-openj9-0.18.1
adoptopenjdk:11.0.6_10-jre-hotspot
adoptopenjdk:11.0.6_10-jre-openj9-0.18.1-bionic
adoptopenjdk:11.0.6_10-jre-hotspot-bionic
adoptopenjdk/openjdk11:jre-11.0.6_10-ubuntu
adoptopenjdk/openjdk11:jre-11.0.6_10
adoptopenjdk/openjdk11:jre-11.0.6_10-ubi-minimal
adoptopenjdk/openjdk11:jre-11.0.6_10-ubi
adoptopenjdk/openjdk11:jre-11.0.6_10-debianslim
adoptopenjdk/openjdk11:jre-11.0.6_10-debian
adoptopenjdk/openjdk11:jre-11.0.6_10-centos
adoptopenjdk/openjdk11:jre-11.0.6_10-alpine
adoptopenjdk/openjdk11:x86_64-alpine-jre-11.0.6_10
adoptopenjdk/openjdk11:x86_64-debian-jre-11.0.6_10
adoptopenjdk/openjdk11:x86_64-debianslim-jre-11.0.6_10
adoptopenjdk/openjdk11:x86_64-ubi-jre-11.0.6_10
adoptopenjdk/openjdk11:x86_64-ubi-minimal-jre-11.0.6_10
adoptopenjdk/openjdk11:x86_64-centos-jre-11.0.6_10
adoptopenjdk/openjdk11:x86_64-ubuntu-jre-11.0.6_10
mcr.microsoft.com/java/jre:11u6-zulu-alpine
mcr.microsoft.com/java/jre:11u6-zulu-centos
mcr.microsoft.com/java/jre:11u6-zulu-debian8
mcr.microsoft.com/java/jre:11u6-zulu-debian9
mcr.microsoft.com/java/jre:11u6-zulu-debian10
mcr.microsoft.com/java/jre:11u6-zulu-ubuntu
azul/zulu-openjdk-alpine:11.0.6-jre