Java 11 应用程序作为轻量级 docker 镜像

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/53669151/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-11 00:51:49  来源:igfitidea点击:

Java 11 application as lightweight docker image

javadockeralpinejava-11

提问by radistao

Inspired by question Why is the Java 11 base Docker image so large? (openjdk:11-jre-slim)I found that this topic in Java world is still not settled.

受问题启发,为什么 Java 11 基础 Docker 镜像如此之大?(openjdk:11-jre-slim)我发现这个话题在 Java 世界中仍然没有解决。

As for 07 Dec 2018there are common issues/pitfalls (discussed in the ticket above):

至于07 Dec 2018存在常见问题/陷阱(在上面的票证中讨论过):

As a result of these issues even slimOracle Java 11 base images are quite heavy and considered to be unstable: https://hub.docker.com/_/openjdk/

由于这些问题,即使是纤薄的Oracle Java 11 基础镜像也很重并且被认为是不稳定的:https: //hub.docker.com/_/openjdk/

So the question is:

所以问题是:

what are optimizedor recommendedways to build and deliver Java 11 applications as docker images?

什么是优化的推荐的方式来构建和交付的Java应用11作为搬运工的图像

采纳答案by radistao

UPD from 07.2019: https://stackoverflow.com/a/57145029/907576

2019 年 7 月起更新https: //stackoverflow.com/a/57145029/907576

Taking as an example of simple spring boot application (with only one REST endpoint) so far i was able to figure out the following solutions (considering application jar is located at build/libs/spring-boot-demo.jarbefore Docker build:

以简单的 Spring Boot 应用程序(只有一个 REST 端点)为例,到目前为止我能够找出以下解决方案(考虑到应用程序 jar 位于build/libs/spring-boot-demo.jarDocker 构建之前:

  1. Jedi pathif we want to use official Oracle OpenJDK distribution on stable slim Linux version(Debian 9 "Stretch"for now):

    • use debian:stretch-slim(latest stable) base image
    • use Docker multi-stage build

      1. First Docker build stage:

        • download and install Oracle OpenJDKarchive on the first Docker build stage
        • compile Java minimal distribution for your project (aka JRE) using jlinktool
      2. Second Docker build stage:

        • copy compiled minimal Java distribution from stage 1 to the new image
        • configure path to access Java
        • copy application jar to the image

    So, final Dockerfilelooks smth like this

    (actualize JDK VERSION, URLand HASHvalue):

    # 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" ]
    

    Note:

    • there are 5 java modules included to the minimal JRE example (java.base,java.sql,java.naming,java.desktop,java.management,java.security.jgss,java.instrument). I found them "manually" running the application and fixing ClassNotFoundException. Waiting for some further Spring Boot developers recommendations/guides which Java modules to include and when, as same as removing some redundant dependencies, like java.desktop, which seems to be used only for PropertyEditorSupport
    • if you are afraid to miss some modules - they are quite lightweight and all of them together give about 2 MB size increasing. Get a full list of java.*and jdk.*11 modules:

      java --list-modules | grep -E "^java\.[^@]*" | cut -d @ -f 1
      java --list-modules | grep -E "^jdk\.[^@]*" | cut -d @ -f 1

    The resulting image size in my case was 123 MBwith minimal 7 Spring Boot modules and 125 MBwith all java.*modules

    As an optional improvement of this build workflow:

    • Pre-build an image with downloaded and extracted JDK and use it as a base image for first stage
    • if you know which modules to include every time - pre-build a base image with compiled minimal JRE and included modules
  2. Easy way with vendor's Open JDK distributions:

    Opposite to Oracle Azul's Zulu JDK 11supports Alpine portand has respective base Docker image.

  1. 如果我们想在稳定的超薄 Linux 版本Debian 9 "Stretch"目前)使用官方 Oracle OpenJDK 发行版,绝地路径

    • 使用debian:stretch-slim(最新的稳定版)基础镜像
    • 使用Docker 多阶段构建

      1. 第一个 Docker 构建阶段:

        • Oracle OpenJDK在第一个 Docker 构建阶段下载并安装存档
        • 使用jlink工具为您的项目(又名 JRE)编译 Java 最小发行版
      2. 第二个 Docker 构建阶段:

        • 将编译的最小 Java 发行版从阶段 1 复制到新映像
        • 配置访问Java的路径
        • 将应用程序 jar 复制到图像

    所以,最终Dockerfile看起来像这样

    具体化JDK VERSIONURLHASH):

    # 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 模块。我发现他们“手动”运行应用程序并修复ClassNotFoundException. 等待一些进一步的 Spring Boot 开发人员建议/指南包含哪些 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 Boot 模块,125 MB包含所有java.*模块

    作为此构建工作流程的可选改进

    • 使用下载和提取的 JDK 预构建映像,并将其用作第一阶段的基础映像
    • 如果您知道每次要包含哪些模块 - 使用已编译的最小 JRE 和包含的模块预先构建基本映像
  2. 使用供应商的 Open JDK 发行版的简单方法:

    与 Oracle Azul相对,Zulu JDK 11支持Alpine 端口并具有各自的基础Docker 镜像

Thus, if Zulu JVM/JDK is respected, Docker build is much simpler:

因此,如果尊重 Zulu 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" ]

The resulting image is 73 MB, as expected with stripped Alpine distributions.

生成的图像是73 MB,正如预期的剥离 Alpine 分布。

回答by maslick

Based on the answer by radistao(cool stuff!) I created an Amazon Corretto JDK11 based image. It's also available on DockerHub.

根据radistao的回答(很酷的东西!),我创建了一个基于 Amazon Corretto JDK11 的图像。它也可以在DockerHub使用

The minimal maslick/minimalka:jdk11Corretto image is ~108MB(55MB compressed on Dockerhub).

最小的maslick/minimalka:jdk11Corretto 映像大约为108MB(在 Dockerhub 上压缩为 55MB)。

If you add a simple Springboot jar to it, the resulting image would be ~125MB(71MB compressed on Dockerhub):

如果向其中添加一个简单的 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

回答by radistao

As of 07.2019

截至 07.2019

(Note: first stage image could be as fatas you wish: one can use debian/ubuntu/whatever and include git/gradle/whatever - this won't influence the final resulting image size, which is completely based on the last (second) stage)

:第一阶段图像可能是因为脂肪如你所愿:可以使用于Debian / Ubuntu /不管和包括混帐/ gradle产出/什么-这不会影响最终结果的图像大小,这是完全基于上次(第二) 阶段)

Using Alpine community repository

使用Alpine 社区存储库

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"]

Using AdoptOpenJDK

使用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"]

Also read https://blog.gilliard.lol/2018/11/05/alpine-jdk11-images.html

另请阅读https://blog.gilliard.lol/2018/11/05/alpine-jdk11-images.html

回答by Dmitry Khamitov

You can also look at liberica openjdk11by bellsoft. Sorry for lots of quotations but anyway, here it is

您还可以通过 bellsoft查看libericaopenjdk11。抱歉有很多引用,但无论如何,这里是

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...

Liberica 是 100% 开源的 Java 11 实现。它由 BellSoft 贡献的 OpenJDK 构建,经过全面测试并通过了在 OpenJDK 许可下提供的 JCK...

Their out of the box liteversion takes as much as ~100MB. It does not have javafx modules and its modules are compressed (jlink --compress=2at their Dockerfile). Apart from that, there are various repos at bellsoft Docker Hub accountwith different options of OS/glibc/arch. E.g. at liberica-openjdk-alpine-muslthey say:

他们开箱即用的精简版需要大约 100MB。它没有 javafx 模块并且它的模块被压缩(jlink --compress=2它们的 Dockerfile 中)。除此之外,在 bellsoft Docker Hub 帐户中还有各种 repos,其中有不同的 OS/glibc/arch 选项。例如在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

用于 Alpine Linux(musl 变体)的 Dockerfile 支持三个开箱即用的目标图像:

base:最小运行时映像,压缩 java.base 模块,剥离服务器 VM 和可选文件,约 37 MB 与 Alpine 基础

lite:Liberica JDK lite 映像,占用空间最少,服务器 VM,约 100 MB(默认)

full:Liberica JDK 完整映像,带有服务器 VM 和 jmods,可用于创建任意模块集,~180 MB

为了节省空间,鼓励用户使用足以运行目标应用程序的 jmod 命令创建自己的运行时

And you can go even further at the expense of performance:

您可以以牺牲性能为代价走得更远:

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

如果您准备为静态占用而牺牲性能,请考虑使用最小虚拟机而不是服务器虚拟机或客户端虚拟机。有了它,就可以创建一个小于 20 Mb 的运行时

Some examples from my machine:

我机器上的一些例子:

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