【问题标题】:Java 11 application as lightweight docker imageJava 11 应用程序作为轻量级 docker 映像
【发布时间】:2019-05-09 04:44:38
【问题描述】:

Why is the Java 11 base Docker image so large? (openjdk:11-jre-slim)问题启发,发现Java世界的这个话题还没有定论。

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

由于这些问题,即使是 slim Oracle Java 11 基础映像也非常繁重并被认为不稳定:https://hub.docker.com/_/openjdk/

所以问题是:

有哪些优化推荐方法来构建和交付 Java 11 应用程序作为 Docker 映像

【问题讨论】:

标签: java docker alpine java-11


【解决方案1】:

jdk 11 images by size列表

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

【讨论】:

  • 这太棒了!
【解决方案2】:

根据 radistao 的回答(很酷的东西!)我创建了一个Amazon Corretto JDK11 based image。它也可以在DockerHub 上找到。

最小的 ma​​slick/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

【讨论】:

    【解决方案3】:

    您也可以查看 bellsoft 的 liberica openjdk11。很抱歉引用了很多,但无论如何,这里是

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

    他们的开箱即用 lite 版本需要大约 100MB。它没有 javafx 模块,并且其模块被压缩(jlink --compress=2their Dockerfile)。除此之外,bellsoft Docker Hub account 有各种 repos,有不同的 OS/glibc/arch 选项。例如。在liberica-openjdk-alpine-musl 他们说:

    用于 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 命令创建自己的运行时

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

    如果您准备为静态占用而牺牲性能,请考虑使用最小虚拟机而不是服务器虚拟机或客户端虚拟机。有了它,就可以创建一个小于 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
    

    【讨论】:

      【解决方案4】:

      从 07.2019 开始更新https://stackoverflow.com/a/57145029/907576

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

      1. Jedi 路径如果我们想在稳定的 slim Linux 版本上使用官方 Oracle OpenJDK 发行版(目前为Debian 9 "Stretch"):

        • 使用debian:stretch-slim(最新稳定版)基础镜像
        • 使用Docker multi-stage build

          1. 第一个 Docker 构建阶段:

            • 在第一个 Docker 构建阶段下载并安装 Oracle OpenJDK 存档
            • 使用 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's Zulu JDK 11 支持Alpine port 并有各自的基础Docker image

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

      生成的图像为 73 MB,与剥离的 Alpine 分布一致。

      【讨论】:

      • 很好的分析,很好的问题/答案
      • 11.0.3 的任何更新?我在哪里可以找到 JDK_HASH?尝试使用oracle.com/webfolder/s/digest/11-0-3-checksum.html,但使用“d50908ea53c2ad154a797aa0930eafb7813247dae13d9d891116df889814ebf3”失败:“sha256sum:警告:1 个计算的校验和不匹配”
      • 感谢@radistao 在构建服务器上创建 docker 镜像时有什么方法可以添加 CA?
      • 因为stackoverflow.com/questions/56523042/…,我基本上是在“找朋友”
      • 那是另一个话题,所以请不要阻塞这个线程。您能否删除从“Any way to add CAs...”开始的消息?谢谢。
      【解决方案5】:

      截至 2019 年 7 月

      (注意:第一阶段图像可以像您希望的那样:可以使用 debian/ubuntu/whatever 并包括 git/gradle/whatever - 这不会' t 影响最终生成的图像大小,这完全基于最后(第二)阶段)

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

      【讨论】:

      • 这是我找到的最小的解决方案~55Mb。请保持联系更新。
      • 这基本上是在专门基于musl 的发行版中安装和使用glibc。看起来像一个黑客。
      • 这可能是一个 hack,但我想我可以接受。虽然我建议在 CMD 行中添加 jvm arg -XX:+UseContainerSupport。这是向 java 发出的一个信号,以纠正它在容器内计算内存和 CPU 的方式。
      • 老兄你的容器,srsly 踢驴!按照建议添加了-Xmx128m -Xms128m -XX:+UseContainerSupport,将容器内存使用限制为 192MB,瞧,即使是贪婪的 spring-boot 容器现在总共吃掉了大约 155MB。从 705MB 减少。
      • 太好了,谢谢。不明白为什么 Java 8 和 13 如此简单,而 Java 11 却如此痛苦。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2022-01-19
      • 1970-01-01
      • 2021-10-26
      • 2017-01-29
      • 2010-10-02
      • 2012-12-24
      • 1970-01-01
      相关资源
      最近更新 更多